You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2824 lines
69 KiB
2824 lines
69 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// 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);
|
|
}
|