|
|
//----------------------------------------------------------------------------
//
// General utility functions.
//
// Copyright (C) Microsoft Corporation, 1997-2002.
//
//----------------------------------------------------------------------------
#include "ntsdp.hpp"
#include <sys\types.h>
#include <sys\stat.h>
PCSTR g_DefaultLogFileName = ENGINE_MOD_NAME ".log"; char g_OpenLogFileName[MAX_PATH + 1]; BOOL g_OpenLogFileAppended; int g_LogFile = -1; ULONG g_DisableErrorPrint;
ULONG CheckUserInterrupt(void) { if (g_EngStatus & ENG_STATUS_USER_INTERRUPT) { g_EngStatus &= ~ENG_STATUS_USER_INTERRUPT; return TRUE; } return FALSE; }
BOOL PollUserInterrupt(BOOL AllowPendingBreak) { // Check for a simple user interrupt.
if (g_EngStatus & ENG_STATUS_USER_INTERRUPT) { return TRUE; } // If we're running and we're supposed to be breaking in,
// we also consider this an interrupt to prevent long
// operations from delaying the break-in.
if (AllowPendingBreak && IS_RUNNING(g_CmdState) && (g_EngStatus & ENG_STATUS_PENDING_BREAK_IN)) { return TRUE; } return FALSE; }
LONG MappingExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo) { static ULONG s_InPageErrors = 0; ULONG Code = ExceptionInfo->ExceptionRecord->ExceptionCode; if (Code == STATUS_IN_PAGE_ERROR) { if (++s_InPageErrors < 10) { if (s_InPageErrors % 2) { WarnOut("Ignore in-page I/O error\n"); FlushCallbacks(); } Sleep(500); return EXCEPTION_CONTINUE_EXECUTION; } }
s_InPageErrors = 0; ErrOut("Exception 0x%08x while accessing mapping\n", Code); FlushCallbacks(); return EXCEPTION_EXECUTE_HANDLER; }
void RemoveDelChar(PSTR Buffer) { PSTR BufferOld = Buffer; PSTR BufferNew = Buffer;
while (*BufferOld) { if (*BufferOld == 0x7f) { if (BufferNew > Buffer) { BufferNew--; } } else if (*BufferOld == '\r' || *BufferOld == '\n') { *BufferNew++ = ' '; } else { *BufferNew++ = *BufferOld; }
BufferOld++; }
*BufferNew = '\0'; }
PCSTR ErrorString(ULONG Error) { switch(Error) { case OVERFLOW: return "Overflow"; case SYNTAX: return "Syntax"; case BADRANGE: return "Range"; case VARDEF: return "Couldn't resolve"; case EXTRACHARS: return "Extra character"; case LISTSIZE: return "List size"; case STRINGSIZE: return "String size"; case MEMORY: return "Memory access"; case BADREG: return "Bad register"; case BADOPCODE: return "Bad opcode"; case SUFFIX: return "Opcode suffix"; case OPERAND: return "Operand"; case ALIGNMENT: return "Alignment"; case PREFIX: return "Opcode prefix"; case DISPLACEMENT: return "Displacement"; case BPLISTFULL: return "No breakpoint available"; case BPDUPLICATE: return "Duplicate breakpoint"; case UNIMPLEMENT: return "Unimplemented"; case AMBIGUOUS: return "Ambiguous symbol"; case BADTHREAD: return "Illegal thread"; case BADPROCESS: return "Illegal process"; case FILEREAD: return "File read"; case LINENUMBER: return "Line number"; case BADSEL: return "Bad selector"; case BADSEG: return "Bad segment"; case SYMTOOSMALL: return "Symbol only 1 character"; case BPIONOTSUP: return "I/O breakpoints not supported"; case NOTFOUND: return "No information found"; case SESSIONNOTSUP: return "Operation not supported in current debug session"; case BADSYSTEM: return "Illegal system"; case NOMEMORY: return "Out of memory"; case TYPECONFLICT: return "Type conflict"; case TYPEDATA: return "Type information missing"; case NOTMEMBER: return "Type does not have given member"; case IMPLERR: return "Internal implementation"; case ENGBUSY: return "Engine is busy"; case TARGETNOTSUP: return "Operation not supported by current debuggee"; case NORUNNABLE: return "No runnable debuggees"; case NOTSECURE: return "SECURE: Operation disallowed"; default: return "Unknown"; } }
/*** error - error reporting and recovery
* * Purpose: * To output an error message with a location indicator * of the problem. Once output, the command line is reset * and the command processor is restarted. * * Input: * errorcode - number of error to output * * Output: * None. * * Exceptions: * Return is made via exception to start of command processor. * *************************************************************************/
char g_Blanks[] = " " " " " " " ^ ";
void ReportError( ULONG Error, PCSTR* DescPtr ) { ULONG Count = g_PromptLength + 1; PSTR Temp = g_CommandStart; PCSTR Desc;
// Reset the expression evaluators in case we're
// hitting an error during evaluation which would
// otherwise leave the evaluator in an inconsistent state.
ReleaseEvaluators(); if (DescPtr != NULL) { // Clear out description so it doesn't get reused.
Desc = *DescPtr; *DescPtr = NULL; } else { Desc = NULL; } if (g_DisableErrorPrint || (g_CommandStart > g_CurCmd) || (g_CommandStart + MAX_COMMAND < g_CurCmd)) { goto SkipErrorPrint; }
while (Temp < g_CurCmd) { if (*Temp++ == '\t') { Count = (Count + 7) & ~7; } else { Count++; } }
ErrOut(&g_Blanks[sizeof(g_Blanks) - (Count + 1)]);
if (Desc != NULL) { ErrOut("%s '%s'\n", Desc, g_CommandStart); } else if (Error != VARDEF && Error != SESSIONNOTSUP) { ErrOut("%s error in '%s'\n", ErrorString(Error), g_CommandStart); } else { ErrOut("%s '%s'\n", ErrorString(Error), g_CommandStart); } SkipErrorPrint: RaiseException(COMMAND_EXCEPTION_BASE + Error, 0, 0, NULL); }
ULONG64 HexValue(ULONG Size) { ULONG64 Value;
Value = GetExpression();
// Reverse sign extension done by expression evaluator.
if (Size == 4 && !NeedUpper(Value)) { Value = (ULONG)Value; }
if (Value > (0xffffffffffffffffUI64 >> (8 * (8 - Size)))) { error(OVERFLOW); }
return Value; }
void HexList(PUCHAR Buffer, ULONG BufferSize, ULONG EltSize, PULONG CountRet) { CHAR Ch; ULONG64 Value; ULONG Count = 0; ULONG i;
while ((Ch = PeekChar()) != '\0' && Ch != ';') { if (Count >= BufferSize) { error(LISTSIZE); } Value = HexValue(EltSize); for (i = 0; i < EltSize; i++) { *Buffer++ = (UCHAR)Value; Value >>= 8; } Count += EltSize; } *CountRet = Count; }
ULONG64 FloatValue(ULONG Size) { int Scanned; double Value; ULONG64 RawValue;
if (sscanf(g_CurCmd, "%lf%n", &Value, &Scanned) != 1) { error(SYNTAX); }
g_CurCmd += Scanned;
if (Size == 4) { float FloatVal = (float)Value; RawValue = *(PULONG)&FloatVal; } else { RawValue = *(PULONG64)&Value; }
return RawValue; }
void FloatList(PUCHAR Buffer, ULONG BufferSize, ULONG EltSize, PULONG CountRet) { CHAR Ch; ULONG64 Value; ULONG Count = 0; ULONG i;
while ((Ch = PeekChar()) != '\0' && Ch != ';') { if (Count >= BufferSize) { error(LISTSIZE); } Value = FloatValue(EltSize); for (i = 0; i < EltSize; i++) { *Buffer++ = (UCHAR)Value; Value >>= 8; } Count += EltSize; } *CountRet = Count; }
void AsciiList(PSTR Buffer, ULONG BufferSize, PULONG CountRet) { CHAR Ch; ULONG Count = 0;
if (PeekChar() != '"') { error(SYNTAX); } g_CurCmd++;
do { Ch = *g_CurCmd++;
if (Ch == '"') { Count++; *Buffer++ = 0; break; }
if (Ch == '\0' || Ch == ';') { g_CurCmd--; break; }
if (Count >= BufferSize) { error(STRINGSIZE); }
Count++; *Buffer++ = Ch;
} while (1);
*CountRet = Count; }
PSTR GetEscapedChar(PSTR Str, PCHAR Raw) { switch(*Str) { case 0: error(SYNTAX); case '0': // Octal char value.
*Raw = (char)strtoul(Str + 1, &Str, 8); break; case 'b': *Raw = '\b'; Str++; break; case 'n': *Raw = '\n'; Str++; break; case 'r': *Raw = '\r'; Str++; break; case 't': *Raw = '\t'; Str++; break; case 'x': // Hex char value.
*Raw = (char)strtoul(Str + 1, &Str, 16); break; default: // Verbatim escape.
*Raw = *Str; Str++; break; }
return Str; }
PSTR BufferStringValue(PSTR* Buf, ULONG Flags, PULONG Len, PCHAR Save) { BOOL Quoted; PSTR Str; BOOL Escapes = FALSE;
while (isspace(*(*Buf))) { (*Buf)++; } if (*(*Buf) == '"') { Quoted = TRUE; Str = ++(*Buf); // If the string is quoted it can always contain spaces.
Flags &= ~STRV_SPACE_IS_SEPARATOR; } else if (!*(*Buf) && !(Flags & STRV_ALLOW_EMPTY_STRING)) { // No string at all.
return NULL; } else { Quoted = FALSE; Str = *Buf; // Escaped characters can only be present in quoted strings.
Flags &= ~STRV_ALLOW_ESCAPED_CHARACTERS; } while (*(*Buf) && (!(Flags & STRV_SPACE_IS_SEPARATOR) || !isspace(*(*Buf))) && (Quoted || *(*Buf) != ';') && (!Quoted || *(*Buf) != '"')) { if (Flags & STRV_ALLOW_ESCAPED_CHARACTERS) { if (*(*Buf) == '\\') { char Raw; *Buf = GetEscapedChar((*Buf) + 1, &Raw); Escapes = TRUE; } else { (*Buf)++; } } else { (*Buf)++; } }
if (Quoted && *(*Buf) != '"') { return NULL; }
if ((Flags & (STRV_TRIM_TRAILING_SPACE | STRV_NO_MODIFICATION)) == STRV_TRIM_TRAILING_SPACE) { PSTR Trim = *Buf;
while (Trim > Str) { if (isspace(*--Trim)) { *Trim = 0; } else { break; } } } if (Quoted && *(*Buf) == '"') { if (!(Flags & STRV_ALLOW_EMPTY_STRING) && *Buf == Str) { return NULL; }
// Require some kind of separator after the
// string to keep things symmetric with the
// non-quoted case.
if (!isspace(*(*Buf + 1)) && *(*Buf + 1) != ';' && *(*Buf + 1) != 0) { return NULL; } // Null the quote and advance beyond it
// so that the buffer pointer is always pointing
// beyond the string on exit.
if (!(Flags & STRV_NO_MODIFICATION)) { *(*Buf) = 0; }
if (Len) { *Len = (ULONG)(*Buf - Str); }
(*Buf)++; } else if (Len) { *Len = (ULONG)(*Buf - Str); } if (Flags & STRV_NO_MODIFICATION) { return Str; } *Save = *(*Buf); *(*Buf) = 0;
if (Escapes && (Flags & STRV_COMPRESS_ESCAPED_CHARACTERS)) { CompressEscapes(Str); } return Str; }
PSTR StringValue(ULONG Flags, PCHAR Save) { ULONG Len; PSTR Str = BufferStringValue((PSTR*)&g_CurCmd, Flags, &Len, Save); if (Str == NULL) { error(SYNTAX); } return Str; }
void CompressEscapes(PSTR Str) { // Scan through a string for character escapes and
// convert them to their escape value, packing
// the rest of the string down. This allows for
// in-place conversion of strings with escapes
// inside the command buffer.
while (*Str) { if (*Str == '\\') { char Raw; PSTR Slash = Str; Str = GetEscapedChar(Slash + 1, &Raw);
// Copy raw value over backslash and pack down
// trailing characters.
*Slash = Raw; ULONG Len = strlen(Str) + 1; memmove(Slash + 1, Str, Len); Str = Slash + 1; } else { Str++; } } }
void OpenLogFile(PCSTR File, BOOL Append) { // Close any open log file.
CloseLogFile();
if (Append) { g_LogFile = _open(File, O_APPEND | O_CREAT | O_RDWR, S_IREAD | S_IWRITE); } else { g_LogFile = _open(File, O_APPEND | O_CREAT | O_TRUNC | O_RDWR, S_IREAD | S_IWRITE); }
if (g_LogFile != -1) { dprintf("Opened log file '%s'\n", File); CopyString(g_OpenLogFileName, File, DIMA(g_OpenLogFileName)); g_OpenLogFileAppended = Append;
NotifyChangeEngineState(DEBUG_CES_LOG_FILE, TRUE, TRUE); } else { ErrOut("log file could not be opened\n"); } }
void CloseLogFile(void) { if (g_LogFile != -1) { dprintf("Closing open log file %s\n", g_OpenLogFileName); _close(g_LogFile); g_LogFile = -1; g_OpenLogFileName[0] = 0; g_OpenLogFileAppended = FALSE;
NotifyChangeEngineState(DEBUG_CES_LOG_FILE, FALSE, TRUE); } }
void ParseLogOpen(BOOL Append) { PSTR FileName; char Save; char UniqueName[MAX_PATH]; BOOL AppendTime = FALSE;
// Don't look for '- as that's a reasonable filename character.
while (PeekChar() == '/') { switch(*++g_CurCmd) { case 't': AppendTime = TRUE; break; default: ErrOut("Unknown option '%c'\n", *g_CurCmd); break; }
g_CurCmd++; }
if (PeekChar() && *g_CurCmd != ';') { FileName = StringValue(STRV_SPACE_IS_SEPARATOR | STRV_TRIM_TRAILING_SPACE, &Save); } else { FileName = (PSTR)g_DefaultLogFileName; Save = 0; }
if (AppendTime) { if (!MakeFileNameUnique(FileName, UniqueName, DIMA(UniqueName), TRUE, NULL)) { error(OVERFLOW); }
FileName = UniqueName; } OpenLogFile(FileName, Append);
if (Save) { *g_CurCmd = Save; } }
void lprintf(PCSTR String) { if (g_LogFile != -1) { _write(g_LogFile, String, strlen(String)); } }
void OutputSymAddr(ULONG64 Offset, ULONG Flags, PCSTR Prefix) { CHAR AddrBuffer[MAX_SYMBOL_LEN]; ULONG64 Displacement;
GetSymbol(Offset, AddrBuffer, sizeof(AddrBuffer), &Displacement); if ((!Displacement || (Flags & SYMADDR_FORCE)) && AddrBuffer[0]) { if (Prefix) { dprintf("%s", Prefix); } dprintf("%s", AddrBuffer); if (Displacement) { dprintf("+%s", FormatDisp64(Displacement)); } if (Flags & SYMADDR_OFFSET) { dprintf(" (%s)", FormatAddr64(Offset)); } if (Flags & SYMADDR_SOURCE) { OutputLineAddr(Offset, " [%s @ %d]"); } if (Flags & SYMADDR_LABEL) { dprintf(":\n"); } else { dprintf(" "); } } else if (Flags & SYMADDR_OFFSET) { if (Prefix) { dprintf("%s", Prefix); } dprintf("%s", FormatAddr64(Offset)); } }
BOOL OutputLineAddr(ULONG64 Offset, PCSTR Format) { if ((g_SymOptions & SYMOPT_LOAD_LINES) == 0 || !g_Process) { return FALSE; } IMAGEHLP_LINE Line; DWORD Disp; if (GetLineFromAddr(g_Process, Offset, &Line, &Disp)) { char DispStr[64];
if (Disp) { PrintString(DispStr, DIMA(DispStr), "+0x%x", Disp); } else { DispStr[0] = 0; } dprintf(Format, Line.FileName, Line.LineNumber, DispStr); return TRUE; }
return FALSE; }
/*** OutCurInfo - Display selected information about the current register
* state. * * Purpose: * Source file lines may be shown. * Source line information may be shown. * Symbol information may be shown. * The current register set may be shown. * The instruction at the current program current may be disassembled * with any effective address displayed. * * Input: * None. * * Output: * None. * * Notes: * If the disassembly is of a delayed control instruction, the * delay slot instruction is also output. * *************************************************************************/
void OutCurInfo(ULONG Flags, ULONG AllMask, ULONG RegMask) { ADDR PcValue; ADDR DisasmAddr; CHAR Buffer[MAX_DISASM_LEN]; BOOL EA;
if (g_Process == NULL || g_Thread == NULL) { WarnOut("WARNING: The debugger does not have a current " "process or thread\n"); WarnOut("WARNING: Many commands will not work\n"); } if (!IS_MACHINE_SET(g_Target) || g_Process == NULL || g_Thread == NULL || IS_LOCAL_KERNEL_TARGET(g_Target) || ((Flags & OCI_IGNORE_STATE) == 0 && IS_RUNNING(g_CmdState)) || ((IS_KERNEL_FULL_DUMP(g_Target) || IS_KERNEL_SUMMARY_DUMP(g_Target)) && g_Target->m_KdDebuggerData.KiProcessorBlock == 0)) { // State is not available right now.
return; }
if (g_Thread == g_EventThread) { g_Thread->OutputEventStrings(); } g_Machine->GetPC(&PcValue);
if ((Flags & (OCI_FORCE_ALL | OCI_FORCE_REG)) || ((g_SrcOptions & SRCOPT_LIST_SOURCE_ONLY) == 0 && (Flags & OCI_ALLOW_REG) && g_OciOutputRegs)) { g_Machine->OutputAll(AllMask, RegMask); }
// Output g_PrevRelatedPc address
if (Flat(g_PrevRelatedPc) && !AddrEqu(g_PrevRelatedPc, PcValue)) { if (Flags & (OCI_FORCE_ALL | OCI_SYMBOL)) { OutputSymAddr(Flat(g_PrevRelatedPc), SYMADDR_FORCE, NULL); dprintf("(%s)", FormatAddr64(Flat(g_PrevRelatedPc))); } else { dprintf("%s", FormatAddr64(Flat(g_PrevRelatedPc))); } dprintf("\n -> "); } // Deliberately does not force source with force-all so that source line
// support has no effect on default operation.
if (Flags & (OCI_FORCE_ALL | OCI_FORCE_SOURCE | OCI_ALLOW_SOURCE)) { if (g_SrcOptions & SRCOPT_LIST_SOURCE) { if (OutputSrcLinesAroundAddr(Flat(PcValue), g_OciSrcBefore, g_OciSrcAfter) && (Flags & OCI_FORCE_ALL) == 0 && (g_SrcOptions & SRCOPT_LIST_SOURCE_ONLY)) { return; } } else if ((g_SrcOptions & SRCOPT_LIST_LINE) || (Flags & OCI_FORCE_SOURCE)) { OutputLineAddr(Flat(PcValue), "%s(%d)%s\n"); } }
if (Flags & (OCI_FORCE_ALL | OCI_SYMBOL)) { OutputSymAddr(Flat(PcValue), SYMADDR_FORCE | SYMADDR_LABEL, NULL); }
if (Flags & (OCI_FORCE_ALL | OCI_DISASM)) { if (Flags & (OCI_FORCE_ALL | OCI_FORCE_EA)) { EA = TRUE; } else if (Flags & OCI_ALLOW_EA) { if (IS_DUMP_TARGET(g_Target) || IS_USER_TARGET(g_Target)) { // Always show the EA info.
EA = TRUE; } else { // Only show the EA information if registers were shown.
EA = g_OciOutputRegs; } } else { EA = FALSE; }
DisasmAddr = PcValue; g_Machine->Disassemble(g_Process, &DisasmAddr, Buffer, EA); dprintf("%s", Buffer); if (g_Machine->IsDelayInstruction(&PcValue)) { g_Machine->Disassemble(g_Process, &DisasmAddr, Buffer, EA); dprintf("%s", Buffer); } } }
#define MAX_FORMAT_STRINGS 8
LPSTR FormatMachineAddr64( MachineInfo* Machine, ULONG64 Addr ) /*++
Routine Description:
Format a 64 bit address, showing the high bits or not according to various flags.
An array of static string buffers is used, returning a different buffer for each successive call so that it may be used multiple times in the same printf.
Arguments:
Addr - Supplies the value to format
Return Value:
A pointer to the string buffer containing the formatted number
--*/ { static CHAR s_Strings[MAX_FORMAT_STRINGS][22]; static int s_Next = 0; LPSTR String;
String = s_Strings[s_Next]; ++s_Next; if (s_Next >= MAX_FORMAT_STRINGS) { s_Next = 0; } if (!Machine) { sprintf(String, "?%08x`%08x?", (ULONG)(Addr >> 32), (ULONG)Addr); } else if (Machine->m_Ptr64) { sprintf(String, "%08x`%08x", (ULONG)(Addr >> 32), (ULONG)Addr); } else { sprintf(String, "%08x", (ULONG)Addr); } return String; }
LPSTR FormatDisp64( ULONG64 addr ) /*++
Routine Description:
Format a 64 bit address, showing the high bits or not according to various flags. This version does not print leading 0's.
An array of static string buffers is used, returning a different buffer for each successive call so that it may be used multiple times in the same printf.
Arguments:
addr - Supplies the value to format
Return Value:
A pointer to the string buffer containing the formatted number
--*/ { static CHAR strings[MAX_FORMAT_STRINGS][20]; static int next = 0; LPSTR string;
string = strings[next]; ++next; if (next >= MAX_FORMAT_STRINGS) { next = 0; } if ((addr >> 32) != 0) { sprintf(string, "%x`%08x", (ULONG)(addr>>32), (ULONG)addr); } else { sprintf(string, "%x", (ULONG)addr); } return string; }
DWORD NetworkPathCheck( LPCSTR PathList )
/*++
Routine Description:
Checks if any members of the PathList are network paths.
Arguments:
PathList - A list of paths separated by ';' characters.
Return Values:
ERROR_SUCCESS - The path list contained no network or invalid paths.
ERROR_BAD_PATHNAME - The path list contained one or more invalid paths, but no network paths.
ERROR_FILE_OFFLINE - The path list contained one or more network paths.
Bugs:
Any path containing the ';' character will totally confuse this function.
--*/
{ CHAR EndPath0; CHAR EndPath1; LPSTR EndPath; LPSTR StartPath; DWORD DriveType; LPSTR Buffer = NULL; DWORD ret = ERROR_SUCCESS; BOOL AddedTrailingSlash = FALSE;
if (PathList == NULL || *PathList == '\000') { return FALSE; }
Buffer = (LPSTR) malloc ( strlen (PathList) + 3); if (!Buffer) { return ERROR_BAD_PATHNAME; } strcpy (Buffer, PathList); StartPath = Buffer;
do { if (StartPath [0] == '\\' && StartPath [1] == '\\') { ret = ERROR_FILE_OFFLINE; break; }
EndPath = strchr (StartPath, ';');
if (EndPath == NULL) { EndPath = StartPath + strlen (StartPath); EndPath0 = *EndPath; } else { EndPath0 = *EndPath; *EndPath = '\000'; }
if (EndPath [-1] != '\\') { EndPath [0] = '\\'; EndPath1 = EndPath [1]; EndPath [1] = '\000'; AddedTrailingSlash = TRUE; }
DriveType = GetDriveType (StartPath);
if (DriveType == DRIVE_REMOTE) { ret = ERROR_FILE_OFFLINE; break; } else if (DriveType == DRIVE_UNKNOWN || DriveType == DRIVE_NO_ROOT_DIR) { //
// This is not necessarily an error, but it may merit
// investigation.
//
if (ret == ERROR_SUCCESS) { ret = ERROR_BAD_PATHNAME; } }
EndPath [0] = EndPath0; if (AddedTrailingSlash) { EndPath [1] = EndPath1; } AddedTrailingSlash = FALSE;
if (EndPath [ 0 ] == '\000') { StartPath = NULL; } else { StartPath = &EndPath [ 1 ]; } } while (StartPath && *StartPath != '\000');
free ( Buffer ); return ret; }
//----------------------------------------------------------------------------
//
// Returns either an ID value or ALL_ID_LIST. In theory
// this routine could be expanded to pass back true intervals
// so a full list could be specified.
//
// Originally built up a mask for the multi-ID case but that
// was changed to return a real ID when 32 bits became
// constraining.
//
//----------------------------------------------------------------------------
ULONG GetIdList(BOOL AllowMulti) { ULONG Value = 0; CHAR ch; CHAR Digits[20]; int i;
//
// Change to allow more than 32 break points to be set. Use
// break point numbers instead of masks.
//
if ((ch = PeekChar()) == '*') { if (!AllowMulti) { error(SYNTAX); } Value = ALL_ID_LIST; g_CurCmd++; } else if (ch == '[') { Value = (ULONG)GetTermExpression("Breakpoint ID missing from"); } else { for (i = 0; i < sizeof(Digits) - 1; i++) { if (ch >= '0' && ch <= '9') { Digits[i] = ch; ch = *++g_CurCmd; } else { break; } }
Digits[i] = '\0';
if (ch == '\0' || ch == ';' || ch == ' ' || ch == '\t') { Value = strtoul(Digits, NULL, 10); } else { error(SYNTAX); } }
return Value; }
void AppendComponentsToPath(PSTR Path, PCSTR Components, BOOL Validate) { if (!Components || !Components[0]) { return; }
PSTR PathEnd; PCSTR Comp;
PathEnd = Path + strlen(Path); Comp = Components;
while (*Comp) { PCSTR CompEnd; int CompLen;
CompEnd = strchr(Comp, ';'); if (CompEnd) { CompLen = (int)(CompEnd - Comp); } else { CompLen = strlen(Comp); CompEnd = Comp + CompLen; }
//
// Check and see if this component is already in the path.
// If it is, don't add it again.
//
PCSTR Dup, DupEnd; int DupLen;
Dup = Path; while (*Dup) { DupEnd = strchr(Dup, ';'); if (DupEnd) { DupLen = (int)(DupEnd - Dup); } else { DupLen = strlen(Dup); DupEnd = Dup + DupLen; }
if (DupLen == CompLen && !_memicmp(Comp, Dup, CompLen)) { break; }
Dup = DupEnd + (*DupEnd ? 1 : 0); }
if (!*Dup) { PSTR OldPathEnd = PathEnd; PSTR NewStart; if (PathEnd > Path) { *PathEnd++ = ';'; } NewStart = PathEnd; memcpy(PathEnd, Comp, CompLen); PathEnd += CompLen; *PathEnd = 0; if (Validate && !ValidatePathComponent(NewStart)) { WarnOut("WARNING: %s is not accessible, ignoring\n", NewStart); PathEnd = OldPathEnd; *PathEnd = 0; } }
Comp = CompEnd + (*CompEnd ? 1 : 0); } }
//
// Sets or appends to a semicolon-delimited path.
//
HRESULT ChangePath(PSTR* Path, PCSTR New, BOOL Append, ULONG SymNotify) { ULONG NewLen, CurLen, TotLen; PSTR NewPath;
if (New != NULL && *New != 0) { NewLen = strlen(New) + 1; } else if (Append) { // Nothing to append.
return S_OK; } else { NewLen = 0; }
if (*Path == NULL || **Path == 0) { // Nothing to append to.
Append = FALSE; }
if (Append) { CurLen = strlen(*Path) + 1; } else { CurLen = 0; }
TotLen = CurLen + NewLen; if (TotLen > 0) { NewPath = (PSTR)malloc(TotLen); if (NewPath == NULL) { ErrOut("Unable to allocate memory for path\n"); return E_OUTOFMEMORY; } } else { NewPath = NULL; }
PSTR Cat = NewPath;
if (CurLen > 0) { memcpy(Cat, *Path, CurLen); Cat += CurLen - 1; }
if (NewLen > 0) { *Cat = 0; AppendComponentsToPath(NewPath, New, FALSE); }
if (*Path != NULL) { free(*Path); } *Path = NewPath;
if (SymNotify != 0) { NotifyChangeSymbolState(SymNotify, 0, g_Process); }
return S_OK; }
void CheckPath(PCSTR Path) { PCSTR EltStart; PCSTR Scan; BOOL Space;
if (!Path || !Path[0]) { return; }
for (;;) { BOOL Warned = FALSE; EltStart = Path;
Scan = EltStart; while (isspace(*Scan)) { Scan++; } if (Scan != EltStart) { WarnOut("WARNING: Whitespace at start of path element\n"); Warned = TRUE; }
// Find the end of the element.
Space = FALSE; while (*Scan && *Scan != ';') { Space = isspace(*Scan); Scan++; }
if (Space) { WarnOut("WARNING: Whitespace at end of path element\n"); Warned = TRUE; }
if (Scan - EltStart >= MAX_PATH) { WarnOut("WARNING: Path element is longer than MAX_PATH\n"); Warned = TRUE; }
if (Scan == EltStart) { WarnOut("WARNING: Path element is empty\n"); Warned = TRUE; } if (!Warned) { char Elt[MAX_PATH];
memcpy(Elt, EltStart, Scan - EltStart); Elt[Scan - EltStart] = 0; if (!ValidatePathComponent(Elt)) { WarnOut("WARNING: %s is not accessible\n", Elt); Warned = TRUE; } }
if (!*Scan) { break; }
Path = Scan + 1; } }
HRESULT ChangeString(PSTR* Str, PULONG StrLen, PCSTR New) { ULONG Len; PSTR Buf;
if (New != NULL) { Len = strlen(New) + 1; Buf = new char[Len]; if (Buf == NULL) { return E_OUTOFMEMORY; } } else { Buf = NULL; Len = 0; }
delete [] *Str;
*Str = Buf; if (New != NULL) { memcpy(Buf, New, Len); } if (StrLen != NULL) { *StrLen = Len; }
return S_OK; }
#if DBG
void DbgAssertionFailed(PCSTR File, int Line, PCSTR Str) { char Text[512];
_snprintf(Text, sizeof(Text), "Assertion failed: %s(%d)\n %s\n", File, Line, Str); Text[sizeof(Text) - 1] = 0; OutputDebugStringA(Text);
if (getenv("DBGENG_ASSERT_BREAK")) { DebugBreak(); } else { ErrOut("%s", Text); FlushCallbacks(); } }
#endif // #if DBG
void ExceptionRecordTo64(PEXCEPTION_RECORD Rec, PEXCEPTION_RECORD64 Rec64) { ULONG i;
Rec64->ExceptionCode = Rec->ExceptionCode; Rec64->ExceptionFlags = Rec->ExceptionFlags; Rec64->ExceptionRecord = (ULONG64)Rec->ExceptionRecord; Rec64->ExceptionAddress = (ULONG64)Rec->ExceptionAddress; Rec64->NumberParameters = Rec->NumberParameters; for (i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++) { Rec64->ExceptionInformation[i] = Rec->ExceptionInformation[i]; } }
void ExceptionRecord64To(PEXCEPTION_RECORD64 Rec64, PEXCEPTION_RECORD Rec) { ULONG i;
Rec->ExceptionCode = Rec64->ExceptionCode; Rec->ExceptionFlags = Rec64->ExceptionFlags; Rec->ExceptionRecord = (PEXCEPTION_RECORD)(ULONG_PTR) Rec64->ExceptionRecord; Rec->ExceptionAddress = (PVOID)(ULONG_PTR) Rec64->ExceptionAddress; Rec->NumberParameters = Rec64->NumberParameters; for (i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++) { Rec->ExceptionInformation[i] = (ULONG_PTR) Rec64->ExceptionInformation[i]; } }
void MemoryBasicInformationTo64(PMEMORY_BASIC_INFORMATION Mbi, PMEMORY_BASIC_INFORMATION64 Mbi64) { #ifdef _WIN64
memcpy(Mbi64, Mbi, sizeof(*Mbi64)); #else
Mbi64->BaseAddress = (ULONG64) Mbi->BaseAddress; Mbi64->AllocationBase = (ULONG64) Mbi->AllocationBase; Mbi64->AllocationProtect = Mbi->AllocationProtect; Mbi64->__alignment1 = 0; Mbi64->RegionSize = Mbi->RegionSize; Mbi64->State = Mbi->State; Mbi64->Protect = Mbi->Protect; Mbi64->Type = Mbi->Type; Mbi64->__alignment2 = 0; #endif
}
void MemoryBasicInformation32To64(PMEMORY_BASIC_INFORMATION32 Mbi32, PMEMORY_BASIC_INFORMATION64 Mbi64) { Mbi64->BaseAddress = EXTEND64(Mbi32->BaseAddress); Mbi64->AllocationBase = EXTEND64(Mbi32->AllocationBase); Mbi64->AllocationProtect = Mbi32->AllocationProtect; Mbi64->__alignment1 = 0; Mbi64->RegionSize = Mbi32->RegionSize; Mbi64->State = Mbi32->State; Mbi64->Protect = Mbi32->Protect; Mbi64->Type = Mbi32->Type; Mbi64->__alignment2 = 0; }
void DebugEvent32To64(LPDEBUG_EVENT32 Event32, LPDEBUG_EVENT64 Event64) { Event64->dwDebugEventCode = Event32->dwDebugEventCode; Event64->dwProcessId = Event32->dwProcessId; Event64->dwThreadId = Event32->dwThreadId; Event64->__alignment = 0; switch(Event32->dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: ExceptionRecord32To64(&Event32->u.Exception.ExceptionRecord, &Event64->u.Exception.ExceptionRecord); Event64->u.Exception.dwFirstChance = Event32->u.Exception.dwFirstChance; break; case CREATE_THREAD_DEBUG_EVENT: Event64->u.CreateThread.hThread = EXTEND64(Event32->u.CreateThread.hThread); Event64->u.CreateThread.lpThreadLocalBase = EXTEND64(Event32->u.CreateThread.lpThreadLocalBase); Event64->u.CreateThread.lpStartAddress = EXTEND64(Event32->u.CreateThread.lpStartAddress); break; case CREATE_PROCESS_DEBUG_EVENT: Event64->u.CreateProcessInfo.hFile = EXTEND64(Event32->u.CreateProcessInfo.hFile); Event64->u.CreateProcessInfo.hProcess = EXTEND64(Event32->u.CreateProcessInfo.hProcess); Event64->u.CreateProcessInfo.hThread = EXTEND64(Event32->u.CreateProcessInfo.hThread); Event64->u.CreateProcessInfo.lpBaseOfImage = EXTEND64(Event32->u.CreateProcessInfo.lpBaseOfImage); Event64->u.CreateProcessInfo.dwDebugInfoFileOffset = Event32->u.CreateProcessInfo.dwDebugInfoFileOffset; Event64->u.CreateProcessInfo.nDebugInfoSize = Event32->u.CreateProcessInfo.nDebugInfoSize; Event64->u.CreateProcessInfo.lpThreadLocalBase = EXTEND64(Event32->u.CreateProcessInfo.lpThreadLocalBase); Event64->u.CreateProcessInfo.lpStartAddress = EXTEND64(Event32->u.CreateProcessInfo.lpStartAddress); Event64->u.CreateProcessInfo.lpImageName = EXTEND64(Event32->u.CreateProcessInfo.lpImageName); Event64->u.CreateProcessInfo.fUnicode = Event32->u.CreateProcessInfo.fUnicode; break; case EXIT_THREAD_DEBUG_EVENT: Event64->u.ExitThread.dwExitCode = Event32->u.ExitThread.dwExitCode; break; case EXIT_PROCESS_DEBUG_EVENT: Event64->u.ExitProcess.dwExitCode = Event32->u.ExitProcess.dwExitCode; break; case LOAD_DLL_DEBUG_EVENT: Event64->u.LoadDll.hFile = EXTEND64(Event32->u.LoadDll.hFile); Event64->u.LoadDll.lpBaseOfDll = EXTEND64(Event32->u.LoadDll.lpBaseOfDll); Event64->u.LoadDll.dwDebugInfoFileOffset = Event32->u.LoadDll.dwDebugInfoFileOffset; Event64->u.LoadDll.nDebugInfoSize = Event32->u.LoadDll.nDebugInfoSize; Event64->u.LoadDll.lpImageName = EXTEND64(Event32->u.LoadDll.lpImageName); Event64->u.LoadDll.fUnicode = Event32->u.LoadDll.fUnicode; break; case UNLOAD_DLL_DEBUG_EVENT: Event64->u.UnloadDll.lpBaseOfDll = EXTEND64(Event32->u.UnloadDll.lpBaseOfDll); break; case OUTPUT_DEBUG_STRING_EVENT: Event64->u.DebugString.lpDebugStringData = EXTEND64(Event32->u.DebugString.lpDebugStringData); Event64->u.DebugString.fUnicode = Event32->u.DebugString.fUnicode; Event64->u.DebugString.nDebugStringLength = Event32->u.DebugString.nDebugStringLength; break; case RIP_EVENT: Event64->u.RipInfo.dwError = Event32->u.RipInfo.dwError; Event64->u.RipInfo.dwType = Event32->u.RipInfo.dwType; break; } }
#define COPYSE(p64, p32, f) p64->f = (ULONG64)(LONG64)(LONG)p32->f
void WaitStateChange32ToAny(IN PDBGKD_WAIT_STATE_CHANGE32 Ws32, IN ULONG ControlReportSize, OUT PDBGKD_ANY_WAIT_STATE_CHANGE WsAny) { WsAny->NewState = Ws32->NewState; WsAny->ProcessorLevel = Ws32->ProcessorLevel; WsAny->Processor = Ws32->Processor; WsAny->NumberProcessors = Ws32->NumberProcessors; COPYSE(WsAny, Ws32, Thread); COPYSE(WsAny, Ws32, ProgramCounter); memcpy(&WsAny->ControlReport, Ws32 + 1, ControlReportSize); if (Ws32->NewState == DbgKdLoadSymbolsStateChange) { DbgkdLoadSymbols32To64(&Ws32->u.LoadSymbols, &WsAny->u.LoadSymbols); } else { DbgkmException32To64(&Ws32->u.Exception, &WsAny->u.Exception); } }
#undef COPYSE
PSTR TimeToStr(ULONG TimeDateStamp) { LPSTR TimeDateStr;
// Handle invalid \ page out timestamps, since ctime blows up on
// this number
if ((TimeDateStamp == 0) || (TimeDateStamp == UNKNOWN_TIMESTAMP)) { return "unavailable"; } else if (IS_LIVE_KERNEL_TARGET(g_Target) && TimeDateStamp == 0x49ef6f00) { // At boot time the shared memory data area is not
// yet initialized. The above value seems to be
// the random garbage that's there so detect it and
// ignore it. This is highly fragile but people
// keep asking about the garbage value.
return "unavailable until booted"; } else { // TimeDateStamp is always a 32 bit quantity on the target,
// and we need to sign extend for 64 bit host since time_t
// has been extended to 64 bits.
time_t TDStamp = (time_t) (LONG) TimeDateStamp; TimeDateStr = ctime((time_t *)&TDStamp);
if (TimeDateStr) { TimeDateStr[strlen(TimeDateStr) - 1] = 0; } else { TimeDateStr = "***** Invalid"; } }
return TimeDateStr; }
PSTR LONG64FileTimeToStr(LONG64 UTCFileTimeStamp) { FILETIME FileTime;
FileTime.dwLowDateTime = (DWORD) UTCFileTimeStamp; FileTime.dwHighDateTime = (DWORD)(UTCFileTimeStamp >> 32);
return FileTimeToStr(FileTime); }
PSTR FileTimeToStr(FILETIME UTCFileTime) { //
// Need to be able to store time string in this format:
// Time: Wed Dec 31 16:00:05.000 1969 (GMT-8)
// Test value: .formats 1c100d2`1a18ff24
// Should display: Fri Jun 29 12:31:32.406 2001 (GMT-7)
//
static CHAR TimeDateBuffer[39]; PSTR TimeDateStr = TimeDateBuffer; FILETIME LocalFileTime; SYSTEMTIME UTCSysTime, LocalSysTime; SHORT GMTBias = 0;
//
// Note: month value is 1-based, day is 0-based
//
static LPSTR Months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static LPSTR Days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", };
FileTimeToLocalFileTime(&UTCFileTime, &LocalFileTime); FileTimeToSystemTime(&LocalFileTime, &LocalSysTime); FileTimeToSystemTime(&UTCFileTime, &UTCSysTime); GMTBias = LocalSysTime.wHour - UTCSysTime.wHour;
ZeroMemory(TimeDateBuffer, sizeof(TimeDateBuffer) / sizeof(TimeDateBuffer[0]));
//
// Ensure this looks like valid SYSTEMTIME
//
if ( (LocalSysTime.wYear > 1600) && (LocalSysTime.wYear < 30827) && (LocalSysTime.wMonth > 0) && (LocalSysTime.wMonth < 13) && (LocalSysTime.wDayOfWeek >= 0) && (LocalSysTime.wDayOfWeek < 7) && (LocalSysTime.wDay > 0) && (LocalSysTime.wDay < 32) && (LocalSysTime.wHour > 0) && (LocalSysTime.wHour < 24) && (LocalSysTime.wMilliseconds >= 0) && (LocalSysTime.wMilliseconds < 1000) ) { PrintString(TimeDateBuffer, DIMA(TimeDateBuffer), "%s %s %2d %02d:%02d:%02d.%03d %d (GMT%c%d)", Days[LocalSysTime.wDayOfWeek], Months[LocalSysTime.wMonth - 1], LocalSysTime.wDay, LocalSysTime.wHour, LocalSysTime.wMinute, LocalSysTime.wSecond, LocalSysTime.wMilliseconds, LocalSysTime.wYear, (GMTBias < 0) ? '-' : '+', abs(GMTBias) ); } else { PrintString(TimeDateBuffer, DIMA(TimeDateBuffer), "***** Invalid FILETIME"); }
return TimeDateStr; }
PSTR DurationToStr(ULONG64 Duration) { ULONG Seconds = FileTimeToTime(Duration); ULONG Millis = (ULONG)(Duration - TimeToFileTime(Seconds)) / 10000; ULONG Minutes = Seconds / 60; ULONG Hours = Minutes / 60; ULONG Days = Hours / 24; static char s_Buf[128]; PrintString(s_Buf, DIMA(s_Buf), "%d days %d:%02d:%02d.%03d", Days, Hours % 24, Minutes % 60, Seconds % 60, Millis); return s_Buf; }
PCSTR PathTail(PCSTR Path) { PCSTR Tail = Path + strlen(Path); while (--Tail >= Path) { if (*Tail == '\\' || *Tail == '/' || *Tail == ':') { break; } }
return Tail + 1; }
PCWSTR PathTailW(PCWSTR Path) { PCWSTR Tail = Path + wcslen(Path); while (--Tail >= Path) { if (*Tail == L'\\' || *Tail == L'/' || *Tail == L':') { break; } }
return Tail + 1; }
BOOL MatchPathTails(PCSTR Path1, PCSTR Path2, BOOL Wild) { PCSTR Tail1 = PathTail(Path1); PCSTR Tail2 = PathTail(Path2);
return (!Wild && !_stricmp(Tail1, Tail2)) || (Wild && MatchPattern((PSTR)Tail2, (PSTR)Tail1)); }
BOOL IsValidName(PSTR String) { while (*String) { if (*String < 0x20 || *String > 0x7e) { return FALSE; } if (isalnum(*String)) { return TRUE; } ++String; } return FALSE; }
BOOL MakeFileNameUnique(PSTR OriginalName, PSTR Buffer, ULONG BufferChars, BOOL AppendTime, ProcessInfo* Pid) { SYSTEMTIME Time; ULONG AppendAt; PSTR Dot; char Ext[8];
if (!CopyString(Buffer, OriginalName, BufferChars)) { return FALSE; } Dot = strrchr(Buffer, '.'); if (Dot && strlen(Dot) < sizeof(Ext) - 1) { strcpy(Ext, Dot); *Dot = 0; } else { Dot = NULL; }
if (AppendTime) { GetLocalTime(&Time); AppendAt = strlen(Buffer); if (!PrintString(Buffer + AppendAt, BufferChars - AppendAt, "_%04d-%02d-%02d_%02d-%02d-%02d-%03d", Time.wYear, Time.wMonth, Time.wDay, Time.wHour, Time.wMinute, Time.wSecond, Time.wMilliseconds)) { return FALSE; } }
if (Pid) { AppendAt = strlen(Buffer); if (!PrintString(Buffer + AppendAt, BufferChars - AppendAt, "_%04X", Pid->m_SystemId)) { return FALSE; } } if (Dot) { if (!CatString(Buffer, Ext, BufferChars)) { return FALSE; } }
return TRUE; }
BOOL GetEngineDirectory(PSTR Buffer, ULONG BufferChars) { DBG_ASSERT(BufferChars >= 16); if (!GetModuleFileName(GetModuleHandle(ENGINE_DLL_NAME), Buffer, BufferChars)) { // Error. Use the current directory.
strcpy(Buffer, "."); return FALSE; }
//
// Remove the image name.
//
PSTR Tmp = strrchr(Buffer, '\\'); if (!Tmp) { Tmp = strrchr(Buffer, '/'); if (!Tmp) { Tmp = strrchr(Buffer, ':'); if (!Tmp) { return TRUE; } Tmp++; } } *Tmp = 0; return TRUE; }
BOOL IsInternalPackage(void) { static HRESULT s_Result = E_NOINTERFACE; char EngPath[MAX_PATH]; HANDLE TriageFile; char TriageText[64]; ULONG Done;
if (SUCCEEDED(s_Result)) { return s_Result == S_OK; } //
// Determine if this is an internal Microsoft debugger
// package. Internal packages assume the existence of
// internal servers and so on, so conservatively assume
// this is an external package unless we're sure it's
// an internal package.
//
if (!GetEngineDirectory(EngPath, DIMA(EngPath))) { return FALSE; } if (!CatString(EngPath, "\\winxp\\triage.ini", DIMA(EngPath))) { return FALSE; } TriageFile = CreateFile(EngPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (TriageFile == INVALID_HANDLE_VALUE) { // Couldn't find triage.ini, may be a system ntsd or
// just some other problem. If it's file-not-found
// we don't want to keep hitting this case so
// mark things as external.
if (GetLastError() == ERROR_FILE_NOT_FOUND) { s_Result = S_FALSE; } return FALSE; }
s_Result = S_FALSE;
if (ReadFile(TriageFile, TriageText, sizeof(TriageText), &Done, NULL) && Done > 17 && !_strnicmp(TriageText, ";internal_package", 17)) { s_Result = S_OK; } CloseHandle(TriageFile);
return s_Result == S_OK; }
void TranslateNtPathName(PSTR Path) { if (Path[0] == '\\' && Path[1] == '?' && Path[2] == '?' && Path[3] == '\\') { ULONG Len = strlen(Path) + 1; if (Path[4] == 'U' && Path[5] == 'N' && Path[6] == 'C' && Path[7] == '\\') { // Compress \??\UNC\ to \\.
memmove(Path + 1, Path + 7, Len - 7); } else { // Remove \??\.
memmove(Path, Path + 4, Len - 4); } } } //----------------------------------------------------------------------------
//
// Shell process support.
//
//----------------------------------------------------------------------------
ULONG ShellProcess::s_PipeSerialNumber;
ShellProcess::ShellProcess(void) { m_IoIn = NULL; m_IoOut = NULL; m_ProcIn = NULL; m_ProcOut = NULL; m_ProcErr = NULL; m_IoSignal = NULL; m_ProcThread = NULL; m_Process = NULL; m_ReaderThread = NULL; m_DefaultTimeout = 1000; }
ShellProcess::~ShellProcess(void) { Close(); }
DWORD ShellProcess::ReaderThread(void) { OVERLAPPED Overlapped; HANDLE WaitHandles[2]; DWORD Error = NO_ERROR; UCHAR Buffer[_MAX_PATH]; DWORD BytesRead; DWORD WaitStatus;
ZeroMemory(&Overlapped, sizeof(Overlapped)); Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (Overlapped.hEvent == NULL) { return ERROR_NOT_ENOUGH_MEMORY; }
WaitHandles[0] = Overlapped.hEvent; WaitHandles[1] = m_Process;
//
// wait for data on handle 1.
// wait for signal on handle 2.
//
while (1) { //
// Initiate the read.
//
ResetEvent(Overlapped.hEvent);
if (ReadFile(m_IoOut, Buffer, sizeof(Buffer) - 1, &BytesRead, &Overlapped)) { //
// Read has successfully completed, print and repeat.
//
Buffer[BytesRead] = 0; dprintf("%s", Buffer);
// Notify the main thread that output was produced.
SetEvent(m_IoSignal); } else { Error = GetLastError(); if (Error != ERROR_IO_PENDING) { // The pipe can be broken if the user
// does .shell_quit to abandon the child process.
// There are also some other cases, but in general
// it means that the other end of the pipe has gone
// away so we can just stop reading.
if (Error != ERROR_BROKEN_PIPE) { dprintf(".shell: ReadFile failed, error == %d\n", Error); } break; } Error = NO_ERROR;
// Flush output before waiting.
FlushCallbacks();
WaitStatus = WaitForMultipleObjects(2, WaitHandles, FALSE, INFINITE); if (WaitStatus == WAIT_OBJECT_0) { if (GetOverlappedResult(m_IoOut, &Overlapped, &BytesRead, TRUE)) { //
// Read has successfully completed
//
Buffer[BytesRead] = 0; dprintf("%s", Buffer);
// Notify the main thread that output was produced.
SetEvent(m_IoSignal); } else { Error = GetLastError(); if (Error != ERROR_BROKEN_PIPE) { dprintf(".shell: GetOverlappedResult failed, " "error == %d\n", Error); } break; } } else if (WaitStatus == WAIT_OBJECT_0 + 1) { //
// process exited.
//
dprintf(".shell: Process exited\n"); break; } else { Error = GetLastError(); dprintf(".shell: WaitForMultipleObjects failed; error == %d\n", Error); break; } } }
CloseHandle(Overlapped.hEvent);
if (!Error && m_IoIn) { dprintf("Press ENTER to continue\n"); }
// Flush all remaining output.
FlushCallbacks();
// Notify the main thread that output was produced.
SetEvent(m_IoSignal);
return NO_ERROR; }
DWORD WINAPI ShellProcess::ReaderThreadCb(LPVOID Param) { return ((ShellProcess*)Param)->ReaderThread(); }
BOOL ShellProcess::CreateAsyncPipePair(OUT LPHANDLE ReadPipe, OUT LPHANDLE WritePipe, IN LPSECURITY_ATTRIBUTES SecAttr, IN DWORD Size, IN DWORD ReadMode, IN DWORD WriteMode) { HANDLE ReadPipeHandle, WritePipeHandle; CHAR PipeNameBuffer[MAX_PATH];
//
// Only one valid OpenMode flag - FILE_FLAG_OVERLAPPED
//
if ((ReadMode | WriteMode) & (~FILE_FLAG_OVERLAPPED)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if (Size == 0) { Size = 4096; }
sprintf(PipeNameBuffer, "\\\\.\\Pipe\\Win32PipesEx.%08x.%08x", GetCurrentProcessId(), s_PipeSerialNumber++);
//
// Set the default timeout to 120 seconds
//
ReadPipeHandle = CreateNamedPipeA(PipeNameBuffer, PIPE_ACCESS_INBOUND | ReadMode, PIPE_TYPE_BYTE | PIPE_WAIT, 1, // Number of pipes
Size, // Out buffer size
Size, // In buffer size
120 * 1000, // Timeout in ms
SecAttr); if (ReadPipeHandle == INVALID_HANDLE_VALUE) { return FALSE; }
WritePipeHandle = CreateFileA(PipeNameBuffer, GENERIC_WRITE, 0, // No sharing
SecAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | WriteMode, NULL); // Template file
if (WritePipeHandle == INVALID_HANDLE_VALUE) { DWORD Error = GetLastError(); CloseHandle(ReadPipeHandle); SetLastError(Error); return FALSE; }
*ReadPipe = ReadPipeHandle; *WritePipe = WritePipeHandle; return TRUE; }
HRESULT ShellProcess::Start(PCSTR CmdString, PCSTR InFile, PCSTR OutFile, PCSTR ErrFile) { SECURITY_ATTRIBUTES SecAttr;
// If output is going to a file input must
// come from a file since the user won't see
// any output to know whether input is necessary.
if (OutFile && !InFile) { ErrOut(".shell: Input must be redirected with output\n"); return E_INVALIDARG; }
SecAttr.nLength = sizeof(SecAttr); SecAttr.lpSecurityDescriptor = NULL; SecAttr.bInheritHandle = TRUE;
//
// If the debugger always ran through stdin/stdout, we
// could just run a shell and wait for it. However, in order
// to handle fDebugOutput, we have to open pipes and manage
// the i/o stream for the shell. Since we need to have that
// code anyway, always use it.
//
if (InFile) { //
// Open a file for the child process to use for input.
//
m_ProcIn = CreateFile(InFile, GENERIC_READ, FILE_SHARE_READ, &SecAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (m_ProcIn == INVALID_HANDLE_VALUE) { m_ProcIn = NULL; ErrOut(".shell: Unable to open %s\n", InFile); goto Exit; } } else { //
// Create stdin pipe for debugger->shell.
// Neither end needs to be overlapped.
//
if (!CreateAsyncPipePair(&m_ProcIn, &m_IoIn, &SecAttr, 0, 0, 0)) { ErrOut(".shell: Unable to create stdin pipe.\n"); goto Exit; }
//
// We don't want the shell to inherit our end of the pipe
// so duplicate it to a non-inheritable one.
//
if (!DuplicateHandle(GetCurrentProcess(), m_IoIn, GetCurrentProcess(), &m_IoIn, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { ErrOut(".shell: Unable to duplicate stdin handle.\n"); goto Exit; } }
if (OutFile) { //
// Open a file for the child process to use for output.
//
m_ProcOut = CreateFile(OutFile, GENERIC_WRITE, 0, &SecAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (m_ProcOut == INVALID_HANDLE_VALUE) { m_ProcOut = NULL; ErrOut(".shell: Unable to create %s\n", OutFile); goto Exit; } } else { //
// Create stdout shell->debugger pipe
//
if (!CreateAsyncPipePair(&m_IoOut, &m_ProcOut, &SecAttr, 0, FILE_FLAG_OVERLAPPED, 0)) { ErrOut(".shell: Unable to create stdout pipe.\n"); goto Exit; }
//
// We don't want the shell to inherit our end of the pipe
// so duplicate it to a non-inheritable one.
//
if (!DuplicateHandle(GetCurrentProcess(), m_IoOut, GetCurrentProcess(), &m_IoOut, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { ErrOut(".shell: Unable to duplicate local stdout handle.\n"); goto Exit; } }
if (ErrFile) { //
// Open a file for the child process to use for error output.
//
m_ProcErr = CreateFile(ErrFile, GENERIC_WRITE, 0, &SecAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (m_ProcErr == INVALID_HANDLE_VALUE) { m_ProcErr = NULL; ErrOut(".shell: Unable to create %s\n", ErrFile); goto Exit; } } else { //
// Duplicate shell's stdout to a new stderr.
//
if (!DuplicateHandle(GetCurrentProcess(), m_ProcOut, GetCurrentProcess(), &m_ProcErr, 0, TRUE, DUPLICATE_SAME_ACCESS)) { ErrOut(".shell: Unable to duplicate stdout handle for stderr.\n"); goto Exit; } }
//
// Create an event for output monitoring.
//
m_IoSignal = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_IoSignal == NULL) { ErrOut(".shell: Unable to allocate event.\n"); goto Exit; }
CHAR Shell[_MAX_PATH]; CHAR Command[2 * _MAX_PATH];
if (!GetEnvironmentVariable("SHELL", Shell, DIMA(Shell))) { if (!GetEnvironmentVariable("ComSpec", Shell, DIMA(Shell))) { strcpy(Shell, "cmd.exe"); } }
// Skip leading whitespace on the command string.
// Some commands, such as "net use", can't handle it.
if (CmdString != NULL) { while (isspace(*CmdString)) { CmdString++; } }
if (CmdString && *CmdString) { //
// If there was a command, use SHELL /c Command
//
if (!CopyString(Command, Shell, DIMA(Command)) || !CatString(Command, " /c \"", DIMA(Command)) || !CatString(Command, CmdString, DIMA(Command)) || !CatString(Command, "\"", DIMA(Command))) { ErrOut(".shell: Not enough room for command line\n"); SetLastError(ERROR_INVALID_PARAMETER); goto Exit; } } else { //
// If there was no command, just run the shell
//
strcpy(Command, Shell); }
STARTUPINFO StartInfo; PROCESS_INFORMATION ProcInfo;
ZeroMemory(&StartInfo, sizeof(StartInfo)); StartInfo.cb = sizeof(StartInfo); StartInfo.dwFlags = STARTF_USESTDHANDLES; StartInfo.hStdInput = m_ProcIn; StartInfo.hStdOutput = m_ProcOut; StartInfo.hStdError = m_ProcErr; StartInfo.wShowWindow = SW_SHOW;
ZeroMemory(&ProcInfo, sizeof(ProcInfo));
//
// Create Child Process
//
if (!CreateProcess(NULL, Command, NULL, NULL, TRUE, GetPriorityClass(GetCurrentProcess()), NULL, NULL, &StartInfo, &ProcInfo)) { if (GetLastError() == ERROR_FILE_NOT_FOUND) { ErrOut("%s not found\n", Shell); } else { HRESULT Status = WIN32_LAST_STATUS(); ErrOut("CreateProcess(%s) failed, %s.\n \"%s\"\n", Command, FormatStatusCode(Status), FormatStatus(Status)); } goto Exit; }
m_Process = ProcInfo.hProcess; m_ProcThread = ProcInfo.hThread;
if (m_IoOut) { DWORD ThreadId;
//
// Start reader thread to copy shell output
//
m_ReaderThread = CreateThread(NULL, 0, ReaderThreadCb, this, 0, &ThreadId); if (!m_ReaderThread) { ErrOut(".shell: Unable to create reader thread\n"); goto Exit; } }
WaitForProcessExit();
Close(); return S_OK;
Exit: Close(); return WIN32_LAST_STATUS(); }
void ShellProcess::WaitForProcessExit(void) { CHAR InputBuffer[MAX_PATH]; DWORD BytesWritten; ULONG Timeout; ULONG BaseTimeout; ULONG CheckTimeout; BOOL ProcessExited = FALSE;
//
// Feed input to shell if necessary; wait for it to exit.
//
BaseTimeout = m_IoIn ? m_DefaultTimeout : 10 * m_DefaultTimeout; CheckTimeout = BaseTimeout / 10; Timeout = BaseTimeout; while (1) { ULONG WaitStatus;
// Give the other process a little time to run.
// This is critical when output is being piped
// across kd as GetInput causes the machine to
// sit in the kernel debugger input routine and
// nobody gets any time to run.
if (m_IoOut && WaitForSingleObject(m_IoSignal, CheckTimeout) == WAIT_OBJECT_0) { // Reset the timeout since the process seems to
// be active.
Timeout = BaseTimeout;
// Some output was produced so let the child keep
// running to keep the output flowing. If this
// was the final output of the process, though,
// go to the last input request.
if (WaitForSingleObject(m_Process, 0) != WAIT_OBJECT_0) { continue; } else if (!m_IoIn) { ProcessExited = TRUE; break; } }
// We've run out of immediate output, so wait for a
// larger interval to give the process a reasonable
// amount of time to run. Show a message to keep
// users in the loop.
dprintf("<.shell waiting %d second(s) for process>\n", Timeout / 1000); FlushCallbacks();
if (m_IoOut) { WaitStatus = WaitForSingleObject(m_IoSignal, Timeout); if (WaitStatus == WAIT_OBJECT_0 && WaitForSingleObject(m_Process, 0) != WAIT_OBJECT_0) { // Reset the timeout since the process seems to
// be active.
Timeout = BaseTimeout; continue; } } else { if (WaitForSingleObject(m_Process, Timeout) == WAIT_OBJECT_0) { ProcessExited = TRUE; break; } }
GetInput(m_IoIn ? "<.shell process may need input>" : "<.shell running: .shell_quit to abandon, ENTER to wait>", InputBuffer, DIMA(InputBuffer) - 2, GETIN_LOG_INPUT_LINE);
// The user may not want to wait, so check for
// a magic input string that'll abandon the process.
if (!_strcmpi(InputBuffer, ".shell_quit")) { break; }
//
// see if client is still running
//
if (m_IoOut && WaitForSingleObject(m_Process, 0) == WAIT_OBJECT_0) { ProcessExited = TRUE; break; }
//
// GetInput always returns a string without a newline
//
if (m_IoIn) { strcat(InputBuffer, "\n"); if (!WriteFile(m_IoIn, InputBuffer, strlen(InputBuffer), &BytesWritten, NULL)) { //
// if the write fails, we're done...
//
break; } }
// The process has some input to chew on so
// increase the amount of time we'll wait for it.
Timeout *= 2; }
if (ProcessExited) { if (!m_IoOut) { dprintf(".shell: Process exited\n"); } else { // Give the reader thread time to finish up
// with any last input.
WaitForSingleObject(m_ReaderThread, INFINITE); } } }
#define HCLOSE(Handle) \
((Handle) ? (CloseHandle(Handle), (Handle) = NULL) : NULL)
void ShellProcess::Close(void) { // Close all of the I/O handles first.
// That will make the reader thread exit if it was running.
HCLOSE(m_IoIn); HCLOSE(m_IoOut); HCLOSE(m_ProcIn); HCLOSE(m_ProcOut); HCLOSE(m_ProcErr);
// Wait for the reader thread to exit.
if (m_ReaderThread) { WaitForSingleObject(m_ReaderThread, INFINITE); HCLOSE(m_ReaderThread); }
// Now close the child process handles.
HCLOSE(m_ProcThread); HCLOSE(m_Process);
// Close this handle after the reader thread has exited
// to avoid it using a bad handle.
HCLOSE(m_IoSignal); }
|