//---------------------------------------------------------------------------- // // C++ dbgeng extension framework. // // Copyright (C) Microsoft Corporation, 2005-2009. // //---------------------------------------------------------------------------- #ifndef DBG_ENGEXTCPP_SKIP_HEADERS #include #include #include #endif // DBG_ENGEXTCPP_SKIP_HEADERS #if defined(_PREFAST_) || defined(_PREFIX_) #define PRE_ASSUME(_Cond) _Analysis_assume_(_Cond) #else #define PRE_ASSUME(_Cond) #endif #define IsSpace(_Char) isspace((UCHAR)(_Char)) PEXT_DLL_MAIN g_ExtDllMain; WINDBG_EXTENSION_APIS64 ExtensionApis; ExtCheckedPointer g_Ext("g_Ext not set, used outside of a command"); //---------------------------------------------------------------------------- // // ExtException family. // //---------------------------------------------------------------------------- void WINAPI ExtException::PrintMessageVa(_In_reads_(BufferChars) PSTR Buffer, _In_ ULONG BufferChars, _In_ PCSTR Format, _In_ va_list Args) { StringCchVPrintfA(Buffer, BufferChars, Format, Args); m_Message = Buffer; } void WINAPIV ExtException::PrintMessage(_In_reads_(BufferChars) PSTR Buffer, _In_ ULONG BufferChars, _In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); PrintMessageVa(Buffer, BufferChars, Format, Args); va_end(Args); } //---------------------------------------------------------------------------- // // Holders. // //---------------------------------------------------------------------------- void WINAPI ExtCurrentThreadHolder::Refresh(void) { HRESULT Status; if ((Status = g_Ext->m_System-> GetCurrentThreadId(&m_ThreadId)) != S_OK) { throw ExtStatusException(Status, "ExtCurrentThreadHolder::Refresh failed"); } } void WINAPI ExtCurrentThreadHolder::Restore(void) { if (m_ThreadId != DEBUG_ANY_ID) { PRE_ASSUME(g_Ext.IsSet()); if (g_Ext.IsSet()) { // Ensure that g_Ext-> operator will not throw exception. g_Ext->m_System->SetCurrentThreadId(m_ThreadId); } m_ThreadId = DEBUG_ANY_ID; } } void WINAPI ExtCurrentProcessHolder::Refresh(void) { HRESULT Status; if ((Status = g_Ext->m_System-> GetCurrentProcessId(&m_ProcessId)) != S_OK) { throw ExtStatusException(Status, "ExtCurrentProcessHolder::Refresh failed"); } } void WINAPI ExtCurrentProcessHolder::Restore(void) { if (m_ProcessId != DEBUG_ANY_ID) { PRE_ASSUME(g_Ext.IsSet()); if (g_Ext.IsSet()) { // Ensure that g_Ext-> operator will not throw exception. g_Ext->m_System->SetCurrentProcessId(m_ProcessId); } m_ProcessId = DEBUG_ANY_ID; } } void WINAPI ExtEffectiveProcessorTypeHolder::Refresh(void) { HRESULT Status; if ((Status = g_Ext->m_Control-> GetEffectiveProcessorType(&m_ProcType)) != S_OK) { throw ExtStatusException(Status, "ExtEffectiveProcessorTypeHolder::" "Refresh failed"); } } void WINAPI ExtEffectiveProcessorTypeHolder::Restore(void) { if (m_ProcType != DEBUG_ANY_ID) { PRE_ASSUME(g_Ext.IsSet()); if (g_Ext.IsSet()) { // Ensure that g_Ext-> operator will not throw exception. g_Ext->SetEffectiveProcessor(m_ProcType); } m_ProcType = DEBUG_ANY_ID; } } void WINAPI ExtRadixHolder::Refresh(void) { HRESULT Status; if ((Status = g_Ext->m_Control-> GetRadix(&m_Radix)) != S_OK) { throw ExtStatusException(Status, "ExtRadixHolder::Refresh failed"); } } void WINAPI ExtRadixHolder::Restore(void) { if (m_Radix != DEBUG_ANY_ID) { PRE_ASSUME(g_Ext.IsSet()); if (g_Ext.IsSet()) { // Ensure that g_Ext-> operator will not throw exception. g_Ext->m_Control->SetRadix(m_Radix); } m_Radix = DEBUG_ANY_ID; } } //---------------------------------------------------------------------------- // // ExtCommandDesc. // //---------------------------------------------------------------------------- ExtCommandDesc* ExtCommandDesc::s_Commands; ULONG ExtCommandDesc::s_LongestCommandName; WINAPI ExtCommandDesc::ExtCommandDesc(_In_ PCSTR Name, _In_ ExtCommandMethod Method, _In_ PCSTR Desc, _In_opt_ PCSTR Args) { m_Name = Name; m_Method = Method; m_Desc = Desc; m_ArgDescStr = Args; ClearArgs(); // // Add into command list sorted by name. // ExtCommandDesc* Cur, *Prev; Prev = NULL; for (Cur = s_Commands; Cur; Cur = Cur->m_Next) { if (strcmp(Name, Cur->m_Name) < 0) { break; } Prev = Cur; } if (Prev) { Prev->m_Next = this; } else { s_Commands = this; } m_Next = Cur; if (strlen(Name) > s_LongestCommandName) { s_LongestCommandName = static_cast(strlen(Name)); } } WINAPI ExtCommandDesc::~ExtCommandDesc(void) { DeleteArgs(); } void WINAPI ExtCommandDesc::ClearArgs(void) { m_ArgsInitialized = false; m_CustomArgParsing = false; m_CustomArgDescLong = NULL; m_CustomArgDescShort = NULL; m_OptionChars = "/-"; m_ArgStrings = NULL; m_NumArgs = 0; m_NumUnnamedArgs = 0; m_Args = NULL; } void WINAPI ExtCommandDesc::DeleteArgs(void) { free(m_ArgStrings); delete [] m_Args; ClearArgs(); } PSTR WINAPI ExtCommandDesc::ParseDirective(_In_ PSTR Scan) { // // Scan to collect the directive name. // PSTR Name = Scan; while (*Scan != ':' && *Scan != '}') { if (!*Scan) { m_Ext->ThrowInvalidArg("ArgDesc: Improper directive " "name termination"); } Scan++; } // // Scan to collect the directive value. // PSTR Value = ""; if (*Scan == ':') { *Scan++ = 0; Value = Scan; while (*Scan != '}' || *(Scan + 1) != '}') { if (!*Scan) { m_Ext->ThrowInvalidArg("ArgDesc: Improper directive " "value termination"); } Scan++; } } else if (*(Scan + 1) != '}') { m_Ext->ThrowInvalidArg("ArgDesc: Improper directive }} closure"); } // Terminate name or value. *Scan = 0; Scan += 2; // // Process directive. // bool NoValue = false; bool NeedValue = false; if (!strcmp(Name, "custom")) { m_CustomArgParsing = true; NoValue = true; } else if (!strcmp(Name, "l")) { m_CustomArgDescLong = Value; NeedValue = true; } else if (!strcmp(Name, "opt")) { m_OptionChars = Value; } else if (!strcmp(Name, "s")) { m_CustomArgDescShort = Value; NeedValue = true; } else { m_Ext->ThrowInvalidArg("ArgDesc: Unknown directive '%s'", Name); } if (!Value[0] && NeedValue) { m_Ext->ThrowInvalidArg("ArgDesc: {{%s}} requires an argument", Name); } if (Value[0] && NoValue) { m_Ext->ThrowInvalidArg("ArgDesc: {{%s}} does not have an argument", Name); } return Scan; } void WINAPI ExtCommandDesc::ParseArgDesc(void) { // // Parse the argument description. // if (!m_ArgDescStr || !m_ArgDescStr[0]) { // No arguments. return; } // First copy the string so we can chop it up. m_ArgStrings = _strdup(m_ArgDescStr); if (!m_ArgStrings) { m_Ext->ThrowOutOfMemory(); } // // Each argument description is // {;;;} // ArgDesc* pArgs = (ArgDesc*)malloc(sizeof(ArgDesc) * ExtExtension::s_MaxArgs); if (pArgs == nullptr) { return; } ArgDesc* Arg = pArgs - 1; ULONG NumUnOptArgs = 0; bool RemainderUsed = false; PSTR Scan = m_ArgStrings; while (*Scan) { if (*Scan != '{') { m_Ext->ThrowInvalidArg("ArgDesc: Missing { at '%s'", Scan); } Scan++; if (*Scan == '{') { // This is a {{directive}} and not an argument. Scan = ParseDirective(++Scan); continue; } if (m_NumArgs >= ExtExtension::s_MaxArgs) { m_Ext->ThrowInvalidArg("ArgDesc: Argument count " "overflow at '%s'", Scan); } m_NumArgs++; Arg++; // // Check for an argument name. // Arguments can be unnamed. // if (*Scan == '}' || *Scan == ';') { Arg->Name = NULL; m_NumUnnamedArgs++; if (*Scan == ';') { Scan++; } } else { Arg->Name = Scan; while (*Scan != '}' && *Scan != ';') { if (!*Scan) { m_Ext->ThrowInvalidArg("ArgDesc: Improper argument " "name termination for '%s'", Arg->Name); } Scan++; } if (*Scan != '}') { *Scan++ = 0; } if (Arg->Name[0] == '?' && !Arg->Name[1]) { m_Ext->ThrowInvalidArg("ArgDesc: /? is automatically " "provided by the framework"); } } // // Check for a type. // Type defaults to string. // PCSTR TypeName = "ERROR"; Arg->Boolean = false; Arg->Expression = false; Arg->String = false; Arg->StringRemainder = false; switch(*Scan) { case 'x': Arg->StringRemainder = true; __fallthrough; case 's': Scan++; __fallthrough; case '}': case ';': case ',': TypeName = "string"; Arg->String = true; break; case 'b': Scan++; Arg->Boolean = true; break; case 'e': Scan++; TypeName = "expr"; Arg->Expression = true; Arg->ExpressionBits = 64; Arg->ExpressionSigned = false; Arg->ExpressionDelimited = false; Arg->ExpressionEvaluator = NULL; Arg->ExpressionRadix = 0; for (;;) { if (*Scan == 'd') { Arg->ExpressionDelimited = true; } else if (*Scan == 'n') { if (Scan[1] != '=' || Scan[2] != '(') { m_Ext->ThrowInvalidArg("ArgDesc: " "Invalid input radix argument"); } Scan += 3; Arg->ExpressionRadix = strtoul(Scan, &Scan, 0); if (Arg->ExpressionRadix < 1 || Arg->ExpressionRadix > 36) { m_Ext->ThrowInvalidArg("ArgDesc: " "Invalid input radix %u", Arg->ExpressionRadix); } if (*Scan != ')') { m_Ext->ThrowInvalidArg("ArgDesc: " "Invalid input radix argument"); } } else if (*Scan == 's') { Arg->ExpressionSigned = true; } else if (*Scan == 'v') { if (Scan[1] != '=' || Scan[2] != '(') { m_Ext->ThrowInvalidArg("ArgDesc: " "Invalid evaluator argument"); } Scan += 3; Arg->ExpressionEvaluator = Scan; while (*Scan && *Scan != ')') { Scan++; } if (Scan == Arg->ExpressionEvaluator || !*Scan) { m_Ext->ThrowInvalidArg("ArgDesc: " "Invalid evaluator argument"); } *Scan = 0; } else { break; } Scan++; } if (*Scan >= '0' && *Scan <= '9') { Arg->ExpressionBits = strtoul(Scan, &Scan, 10); if (Arg->ExpressionBits < 1 || Arg->ExpressionBits > 64) { m_Ext->ThrowInvalidArg("ArgDesc: " "Invalid expression bit count %u", Arg->ExpressionBits); } } break; default: m_Ext->ThrowInvalidArg("ArgDesc: Unknown argument type at '%s'", Scan); break; } // // Check for flags. // PSTR NeedTerm = NULL; Arg->Default = NULL; Arg->DefaultSilent = false; // Unnamed arguments default to // required as a required argument // tail is a very common pattern. Arg->Required = Arg->Name == NULL; while (*Scan == ',') { if (NeedTerm) { *NeedTerm = 0; NeedTerm = NULL; } Scan++; switch(*Scan) { case 'd': Scan++; switch(*Scan) { case '=': if (Arg->Boolean) { m_Ext->ThrowInvalidArg("ArgDesc: boolean arguments " "cannot have defaults"); } Arg->Default = ++Scan; while (*Scan && *Scan != ',' && *Scan != ';' && *Scan != '}') { Scan++; } if (*Scan != '}') { NeedTerm = Scan; } break; case 's': Scan++; Arg->DefaultSilent = true; break; default: m_Ext->ThrowInvalidArg("ArgDesc: " "Unknown 'd' argument flag at '%s'", Scan); } break; case 'o': Scan++; Arg->Required = false; break; case 'r': Scan++; Arg->Required = true; break; default: m_Ext->ThrowInvalidArg("ArgDesc: " "Unknown argument flag at '%s'", Scan); } } if (*Scan == ';') { Scan++; } else if (*Scan != '}') { m_Ext->ThrowInvalidArg("ArgDesc: Improper argument " "type/flags termination at '%s'", Scan); } if (NeedTerm) { *NeedTerm = 0; NeedTerm = NULL; } if (!Arg->Name) { if (Arg->Boolean) { // Not possible to have an unnamed flag // since the presence/absence of the flag // is what a boolean is for. m_Ext->ThrowInvalidArg("ArgDesc: Boolean arguments " "must be named"); } // Given the lack of placement identification (a name), // unnamed arguments are filled in the // order they appear in the argument string. // That means that a required argument cannot // follow an optional argument since there's // no way of knowing that the optional argument // should be skipped. if (!Arg->Required) { NumUnOptArgs++; } else { if (NumUnOptArgs > 0) { m_Ext->ThrowInvalidArg("ArgDesc: " "Required unnamed arguments " "cannot follow optional " "unnamed arguments"); } } if (RemainderUsed) { m_Ext->ThrowInvalidArg("ArgDesc: " "Unnamed arguments " "cannot follow remainder usage"); } if (Arg->StringRemainder) { RemainderUsed = true; } } // // Check for a short descriptive argument name. // if (*Scan == '}' || *Scan == ';') { // Use a default name so there's always // some short description. Arg->DescShort = TypeName; if (*Scan == ';') { Scan++; } } else { Arg->DescShort = Scan; while (*Scan != '}' && *Scan != ';') { if (!*Scan) { m_Ext->ThrowInvalidArg("ArgDesc: " "Improper short description " "termination for '%s'", Arg->Name ? Arg->Name : ""); } Scan++; } if (*Scan != '}') { *Scan++ = 0; } } // // Check for a long argument description. // if (*Scan == '}') { Arg->DescLong = NULL; } else { Arg->DescLong = Scan; while (*Scan != '}') { if (!*Scan) { m_Ext->ThrowInvalidArg("ArgDesc: " "Improper long description " "termination for '%s'", Arg->Name ? Arg->Name : ""); } Scan++; } } // // Finished. // Terminate whatever was the last string // in the description. // if (*Scan != '}') { m_Ext->ThrowInvalidArg("ArgDesc: Expecting } at '%s'", Scan); } *Scan++ = 0; } // Copy temporary array to permanent storage. if (m_NumArgs) { m_Args = new ArgDesc[m_NumArgs]; if (! m_Args) { m_Ext->ThrowOutOfMemory(); } memcpy(m_Args, pArgs, m_NumArgs * sizeof(m_Args[0])); } m_ArgsInitialized = true; free(pArgs); } void WINAPI ExtCommandDesc::ExInitialize(_In_ ExtExtension* Ext) { m_Ext = Ext; if (!m_ArgsInitialized) { try { ParseArgDesc(); } catch(...) { DeleteArgs(); throw; } } } ExtCommandDesc::ArgDesc* WINAPI ExtCommandDesc::FindArg(_In_ PCSTR Name) { ArgDesc* Check = m_Args; for (ULONG i = 0; i < m_NumArgs; i++, Check++) { if (Check->Name && !strcmp(Name, Check->Name)) { return Check; } } return NULL; } ExtCommandDesc::ArgDesc* WINAPI ExtCommandDesc::FindUnnamedArg(_In_ ULONG Index) { ArgDesc* Check = m_Args; for (ULONG i = 0; i < m_NumArgs; i++, Check++) { if (!Check->Name && Index-- == 0) { return Check; } } return NULL; } void WINAPI ExtCommandDesc::Transfer(_Out_ ExtCommandDesc** Commands, _Out_ PULONG LongestName) { *Commands = s_Commands; s_Commands = NULL; *LongestName = ExtCommandDesc::s_LongestCommandName; s_LongestCommandName = 0; } //---------------------------------------------------------------------------- // // ExtExtension. // //---------------------------------------------------------------------------- HMODULE ExtExtension::s_Module; char ExtExtension::s_String[2000]; char ExtExtension::s_CircleStringBuffer[2000]; char* ExtExtension::s_CircleString = s_CircleStringBuffer; WINAPI ExtExtension::ExtExtension(void) : m_Advanced("The extension did not initialize properly."), m_Client("The extension did not initialize properly."), m_Control("The extension did not initialize properly."), m_Data("The extension did not initialize properly."), m_Registers("The extension did not initialize properly."), m_Symbols("The extension did not initialize properly."), m_System("The extension did not initialize properly."), m_Advanced2("The extension requires IDebugAdvanced2."), m_Advanced3("The extension requires IDebugAdvanced3."), m_Client2("The extension requires IDebugClient2."), m_Client3("The extension requires IDebugClient3."), m_Client4("The extension requires IDebugClient4."), m_Client5("The extension requires IDebugClient5."), m_Control2("The extension requires IDebugControl2."), m_Control3("The extension requires IDebugControl3."), m_Control4("The extension requires IDebugControl4."), m_Control5("The extension requires IDebugControl5."), m_Control6("The extension requires IDebugControl6."), m_Data2("The extension requires IDebugDataSpaces2."), m_Data3("The extension requires IDebugDataSpaces3."), m_Data4("The extension requires IDebugDataSpaces4."), m_Registers2("The extension requires IDebugRegisters2."), m_Symbols2("The extension requires IDebugSymbols2."), m_Symbols3("The extension requires IDebugSymbols3."), m_System2("The extension requires IDebugSystemObjects2."), m_System3("The extension requires IDebugSystemObjects3."), m_System4("The extension requires IDebugSystemObjects4.") { m_ExtMajorVersion = 1; m_ExtMinorVersion = 0; m_ExtInitFlags = DEBUG_EXTINIT_HAS_COMMAND_HELP; m_KnownStructs = NULL; m_ProvidedValues = NULL; m_ExInitialized = false; m_OutMask = DEBUG_OUTPUT_NORMAL; m_CurChar = 0; m_LeftIndent = 0; m_AllowWrap = true; m_TestWrap = 0; m_CurCommand = NULL; m_AppendBuffer = NULL; m_AppendBufferChars = 0; m_AppendAt = NULL; m_DbgHelp = NULL; m_SymMatchStringA = NULL; } HRESULT WINAPI ExtExtension::BaseInitialize(_In_ HMODULE ExtDllModule, _Out_ PULONG Version, _Out_ PULONG Flags) { HRESULT Status; // Set up our global state. s_Module = ExtDllModule; g_ExtInstancePtr = this; g_Ext = this; // Pass registered commands to the extension // so that further references are confined to // extension class data. ExtCommandDesc::Transfer(&m_Commands, &m_LongestCommandName); if ((Status = Initialize()) != S_OK) { return Status; } *Version = DEBUG_EXTENSION_VERSION(m_ExtMajorVersion, m_ExtMinorVersion); *Flags = m_ExtInitFlags; return S_OK; } HRESULT ExtExtension::Initialize(void) { return S_OK; } void ExtExtension::Uninitialize(void) { if (m_DbgHelp) { FreeLibrary(m_DbgHelp); m_DbgHelp = NULL; m_SymMatchStringA = NULL; } } void ExtExtension::OnSessionActive(_In_ ULONG64 Argument) { UNREFERENCED_PARAMETER(Argument); // Empty. } void ExtExtension::OnSessionInactive(_In_ ULONG64 Argument) { UNREFERENCED_PARAMETER(Argument); // Empty. } void ExtExtension::OnSessionAccessible(_In_ ULONG64 Argument) { UNREFERENCED_PARAMETER(Argument); // Empty. } void ExtExtension::OnSessionInaccessible(_In_ ULONG64 Argument) { UNREFERENCED_PARAMETER(Argument); // Empty. } void WINAPIV ExtExtension::Out(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->OutputVaList(m_OutMask, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Warn(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->OutputVaList(DEBUG_OUTPUT_WARNING, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Err(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->OutputVaList(DEBUG_OUTPUT_ERROR, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Verb(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->OutputVaList(DEBUG_OUTPUT_VERBOSE, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Out(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->OutputVaListWide(m_OutMask, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Warn(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->OutputVaListWide(DEBUG_OUTPUT_WARNING, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Err(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->OutputVaListWide(DEBUG_OUTPUT_ERROR, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Verb(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->OutputVaListWide(DEBUG_OUTPUT_VERBOSE, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Dml(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, m_OutMask, Format, Args); va_end(Args); } void WINAPIV ExtExtension::DmlWarn(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_WARNING, Format, Args); va_end(Args); } void WINAPIV ExtExtension::DmlErr(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_ERROR, Format, Args); va_end(Args); } void WINAPIV ExtExtension::DmlVerb(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control->ControlledOutputVaList(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_VERBOSE, Format, Args); va_end(Args); } void WINAPIV ExtExtension::Dml(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->ControlledOutputVaListWide(DEBUG_OUTCTL_AMBIENT_DML, m_OutMask, Format, Args); va_end(Args); } void WINAPIV ExtExtension::DmlWarn(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->ControlledOutputVaListWide(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_WARNING, Format, Args); va_end(Args); } void WINAPIV ExtExtension::DmlErr(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->ControlledOutputVaListWide(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_ERROR, Format, Args); va_end(Args); } void WINAPIV ExtExtension::DmlVerb(_In_ PCWSTR Format, ...) { va_list Args; va_start(Args, Format); m_Control4->ControlledOutputVaListWide(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_VERBOSE, Format, Args); va_end(Args); } void WINAPI ExtExtension::WrapLine(void) { if (m_LeftIndent) { m_Control->Output(m_OutMask, "\n%*c", m_LeftIndent, ' '); } else { m_Control->Output(m_OutMask, "\n"); } m_CurChar = m_LeftIndent; } void WINAPI ExtExtension::OutWrapStr(_In_ PCSTR String) { if (m_TestWrap) { m_TestWrapChars += static_cast(strlen(String)); return; } while (*String) { // // Collect characters until the end or // until we run out of output width. // PCSTR Scan = String; PCSTR LastSpace = NULL; while (*Scan && *Scan != '\n' && (!m_AllowWrap || !LastSpace || m_CurChar < m_OutputWidth)) { if (*Scan == ' ') { LastSpace = Scan; } m_CurChar++; Scan++; } if (m_AllowWrap && LastSpace && ((*Scan && *Scan != '\n') || m_CurChar >= m_OutputWidth)) { // We ran out of room, so dump output up // to the last space. Scan = LastSpace; } m_Control->Output(m_OutMask, "%.*s", (int)(Scan - String), String); if (!*Scan) { break; } // // Wrap to the next line. // WrapLine(); String = Scan + 1; while (*String == ' ') { String++; } } } void WINAPIV ExtExtension::OutWrapVa(_In_ PCSTR Format, _In_ va_list Args) { StringCbVPrintfA(s_String, sizeof(s_String), Format, Args); OutWrapStr(s_String); } void WINAPIV ExtExtension::OutWrap(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); OutWrapVa(Format, Args); va_end(Args); } PSTR WINAPI ExtExtension::RequestCircleString(_In_ ULONG Chars) { if (Chars > EXT_DIMA(s_CircleStringBuffer)) { ThrowInvalidArg("Circle string buffer overflow, %u chars", Chars); } if ((ULONG_PTR)(s_CircleString - s_CircleStringBuffer) > EXT_DIMA(s_CircleStringBuffer) - Chars) { // String is too long to fit in the remainder, wrap around. s_CircleString = s_CircleStringBuffer; } PSTR Str = s_CircleString; s_CircleString += Chars; return Str; } PSTR WINAPI ExtExtension::CopyCircleString(_In_ PCSTR Str) { PSTR Buf; ULONG Chars; Chars = static_cast(strlen(Str) + 1); Buf = RequestCircleString(Chars); memcpy(Buf, Str, Chars * sizeof(*Str)); return Buf; } PSTR WINAPI ExtExtension::PrintCircleStringVa(_In_ PCSTR Format, _In_ va_list Args) { StringCbVPrintfA(s_String, sizeof(s_String), Format, Args); return CopyCircleString(s_String); } PSTR WINAPIV ExtExtension::PrintCircleString(_In_ PCSTR Format, ...) { PSTR Str; va_list Args; va_start(Args, Format); Str = PrintCircleStringVa(Format, Args); va_end(Args); return Str; } void WINAPI ExtExtension::SetAppendBuffer(_In_reads_(BufferChars) PSTR Buffer, _In_ ULONG BufferChars) { m_AppendBuffer = Buffer; m_AppendBufferChars = BufferChars; m_AppendAt = Buffer; } void WINAPI ExtExtension::AppendBufferString(_In_ PCSTR Str) { size_t Chars; Chars = strlen(Str) + 1; if (Chars > m_AppendBufferChars || (ULONG_PTR)(m_AppendAt - m_AppendBuffer) > m_AppendBufferChars - Chars) { ThrowStatus(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), "Append string overflowed"); } memcpy(m_AppendAt, Str, Chars * sizeof(*Str)); // Position next append where it will overwrite the terminator // to continue the existing string. m_AppendAt += static_cast(Chars - 1); } void WINAPI ExtExtension::AppendStringVa(_In_ PCSTR Format, _In_ va_list Args) { if (m_AppendBuffer >= s_String && m_AppendBuffer <= s_String + (EXT_DIMA(s_String) - 1)) { ThrowInvalidArg("Append string buffer cannot use s_String"); } StringCbVPrintfA(s_String, sizeof(s_String), Format, Args); AppendBufferString(s_String); } void WINAPIV ExtExtension::AppendString(_In_ PCSTR Format, ...) { va_list Args; va_start(Args, Format); AppendStringVa(Format, Args); va_end(Args); } void WINAPI ExtExtension::SetCallStatus(_In_ HRESULT Status) { // If an error has already been saved don't override it. if (!FAILED(m_CallStatus)) { m_CallStatus = Status; } } ULONG WINAPI ExtExtension::GetEffectiveProcessor(void) { ULONG CurType; EXT_STATUS(m_Control->GetEffectiveProcessorType(&CurType)); return CurType; } void WINAPI ExtExtension::SetEffectiveProcessor(_In_ ULONG ProcType, _Inout_opt_ ExtEffectiveProcessorTypeHolder* Holder) { if (Holder && !Holder->IsHolding()) { Holder->Refresh(); } EXT_STATUS(m_Control->SetEffectiveProcessorType(ProcType)); EXT_STATUS(QueryMachineInfo()); } ULONG WINAPI ExtExtension::GetCachedSymbolTypeId(_Inout_ PULONG64 Cookie, _In_ PCSTR Symbol, _Out_ PULONG64 ModBase) { HRESULT Status; DEBUG_CACHED_SYMBOL_INFO Info; // // Check for an existing cache entry. // if ((Status = m_Advanced2-> Request(DEBUG_REQUEST_GET_CACHED_SYMBOL_INFO, Cookie, sizeof(*Cookie), &Info, sizeof(Info), NULL)) == S_OK) { *ModBase = Info.ModBase; return Info.Id; } // // No entry in cache, find the data the hard way. // ZeroMemory(&Info, sizeof(Info)); if ((Status = m_Symbols-> GetSymbolTypeId(Symbol, &Info.Id, &Info.ModBase)) != S_OK) { ThrowStatus(Status, "Unable to get type ID of '%s'", Symbol); } *ModBase = Info.ModBase; // // Add recovered info to cache. // We don't care if this fails as // cache addition is not required, // we just zero the cookie. // if (m_Advanced2-> Request(DEBUG_REQUEST_ADD_CACHED_SYMBOL_INFO, &Info, sizeof(Info), Cookie, sizeof(*Cookie), NULL) != S_OK) { *Cookie = 0; } return Info.Id; } ULONG WINAPI ExtExtension::GetCachedFieldOffset(_Inout_ PULONG64 Cookie, _In_ PCSTR Type, _In_ PCSTR Field, _Out_opt_ PULONG64 TypeModBase, _Out_opt_ PULONG TypeId) { HRESULT Status; DEBUG_CACHED_SYMBOL_INFO Info; // // Check for an existing cache entry. // if ((Status = m_Advanced2-> Request(DEBUG_REQUEST_GET_CACHED_SYMBOL_INFO, Cookie, sizeof(*Cookie), &Info, sizeof(Info), NULL)) == S_OK) { if (TypeModBase) { *TypeModBase = Info.ModBase; } if (TypeId) { *TypeId = Info.Id; } return Info.Arg3; } // // No entry in cache, find the data the hard way. // ZeroMemory(&Info, sizeof(Info)); if ((Status = m_Symbols-> GetSymbolTypeId(Type, &Info.Id, &Info.ModBase)) != S_OK) { ThrowStatus(Status, "Unable to get type ID of '%s'", Type); } if ((Status = m_Symbols-> GetFieldOffset(Info.ModBase, Info.Id, Field, &Info.Arg3)) != S_OK) { ThrowStatus(Status, "Unable to get field '%s.%s'", Type, Field); } if (TypeModBase) { *TypeModBase = Info.ModBase; } if (TypeId) { *TypeId = Info.Id; } // // Add recovered info to cache. // We don't care if this fails as // cache addition is not required, // we just zero the cookie. // if (m_Advanced2-> Request(DEBUG_REQUEST_ADD_CACHED_SYMBOL_INFO, &Info, sizeof(Info), Cookie, sizeof(*Cookie), NULL) != S_OK) { *Cookie = 0; } return Info.Arg3; } bool WINAPI ExtExtension::GetCachedSymbolInfo(_In_ ULONG64 Cookie, _Out_ PDEBUG_CACHED_SYMBOL_INFO Info) { HRESULT Status; if ((Status = m_Advanced2-> Request(DEBUG_REQUEST_GET_CACHED_SYMBOL_INFO, &Cookie, sizeof(Cookie), Info, sizeof(*Info), NULL)) == S_OK) { return true; } return false; } bool WINAPI ExtExtension::AddCachedSymbolInfo(_In_ PDEBUG_CACHED_SYMBOL_INFO Info, _In_ bool ThrowFailure, _Out_ PULONG64 Cookie) { HRESULT Status; if ((Status = m_Advanced2-> Request(DEBUG_REQUEST_ADD_CACHED_SYMBOL_INFO, Info, sizeof(*Info), Cookie, sizeof(*Cookie), NULL)) == S_OK) { return true; } if (ThrowFailure) { ThrowStatus(Status, "Unable to cache symbol info"); } return false; } void WINAPI ExtExtension::FindSymMatchStringA(void) { m_DbgHelp = LoadLibraryExA("dbghelp.dll", NULL, 0); if (!m_DbgHelp) { ThrowLastError("Unable to load dbghelp.dll"); } m_SymMatchStringA = (PFN_SymMatchStringA) GetProcAddress(m_DbgHelp, "SymMatchStringA"); if (!m_SymMatchStringA) { HRESULT Status = HRESULT_FROM_WIN32(GetLastError()); FreeLibrary(m_DbgHelp); m_DbgHelp = NULL; ThrowStatus(Status, "Unable to find SymMatchStringA in dbghelp.dll"); } } bool WINAPI ExtExtension::GetOffsetSymbol(_In_ ULONG64 Offs, _Inout_ ExtBuffer* Name, _Out_opt_ PULONG64 Displacement, _In_ bool AddDisp) throw(...) { HRESULT Status; ULONG Need; ULONG64 LocalDisp; for (UINT i = 0; i < 2; i++) { Status = m_Symbols->GetNameByOffset(Offs, Name->GetRawBuffer(), Name->GetEltsAlloc(), &Need, &LocalDisp); if (Status == E_NOINTERFACE) { return false; } else if (Status == S_OK && Name->GetRawBuffer()) { Name->SetEltsUsed(Need); if (Displacement) { *Displacement = LocalDisp; } if (AddDisp) { const ULONG DispChars = 19; Name->Require(Need, DispChars); StringCchPrintfA(Name->GetBuffer() + (Need - 1), DispChars, "+0x%I64x", LocalDisp); } return true; } else if (FAILED(Status)) { ThrowStatus(Status, "Failed during symbol resolution for 0x%p", Offs); } Name->Require(Need); } ThrowStatus(E_FAIL, "Invalid loop when resolving 0x%p"); } ULONG WINAPI ExtExtension::FindFirstModule(_In_ PCSTR Pattern, _Inout_opt_ ExtBuffer* Name, _In_ ULONG StartIndex) throw(...) { HRESULT Status; ULONG Need; ExtDeclBuffer LocalName; for (;;) { Status = m_Symbols->GetModuleNames(StartIndex, 0, NULL, 0, NULL, LocalName.GetRawBuffer(), LocalName.GetEltsAlloc(), &Need, NULL, 0, NULL); if (Status == S_OK) { if (!MatchPattern(LocalName, Pattern)) { StartIndex++; continue; } if (Name) { LocalName.SetEltsUsed(Need); Name->Copy(&LocalName); } return StartIndex; } else if (Status != S_FALSE) { ThrowStatus(Status, "Unable to find any module matches for '%s'", Pattern); } LocalName.RequireRounded(Need, 20); } } void WINAPI ExtExtension::GetModuleImagehlpInfo(_In_ ULONG64 ModBase, _Out_ struct _IMAGEHLP_MODULEW64* Info) { HRESULT Status; ZeroMemory(Info, sizeof(*Info)); Info->SizeOfStruct = sizeof(*Info); if ((Status = m_Advanced2-> GetSymbolInformation(DEBUG_SYMINFO_IMAGEHLP_MODULEW64, ModBase, 0, Info, Info->SizeOfStruct, NULL, NULL, 0, NULL)) != S_OK) { ThrowStatus(Status, "Unable to retrieve module info"); } } bool WINAPI ExtExtension::ModuleHasGlobalSymbols(_In_ ULONG64 ModBase) { IMAGEHLP_MODULEW64* pInfo = (IMAGEHLP_MODULEW64*)malloc(sizeof(IMAGEHLP_MODULEW64)); if (pInfo == nullptr) { return FALSE; } GetModuleImagehlpInfo(ModBase, pInfo); bool const hasGlobalSymbols = pInfo->GlobalSymbols != FALSE; free(pInfo); return hasGlobalSymbols; } bool WINAPI ExtExtension::ModuleHasTypeInfo(_In_ ULONG64 ModBase) { IMAGEHLP_MODULEW64* pInfo = (IMAGEHLP_MODULEW64*)malloc(sizeof(IMAGEHLP_MODULEW64)); if (pInfo == nullptr) { return FALSE; } GetModuleImagehlpInfo(ModBase, pInfo); bool const hasTypeInfo = pInfo->TypeInfo != FALSE; free(pInfo); return hasTypeInfo; } ULONG64 WINAPI ExtExtension::CallDebuggeeBase(_In_ PCSTR CommandString, _In_ ULONG TimeoutMilliseconds) { HRESULT Status; ExtDeclBuffer Cmd; ExtCaptureOutputA IgnoreOut; Cmd.Copy(".call ", 6); Cmd.Append(CommandString, static_cast(strlen(CommandString) + 1)); if (FAILED(Status = m_Control-> Execute(DEBUG_OUTCTL_IGNORE, Cmd, DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT))) { ThrowStatus(Status, "Unable to execute '%s'", Cmd); } // Capture output just so we can throw away the // automatic retval output from .call when execution completes. // This isn't ideal since it won't hide output to // other clients but it's the best we can do. // Eventually .call will get a quiet mode and then // at least the return value output can be hidden. IgnoreOut.Start(); if ((Status = m_Control->SetExecutionStatus(DEBUG_STATUS_GO)) == S_OK) { Status = m_Control->WaitForEvent(DEBUG_WAIT_DEFAULT, TimeoutMilliseconds); } IgnoreOut.Delete(); if (FAILED(Status)) { // Try and revert our .call setup. m_Control->Execute(DEBUG_OUTCTL_IGNORE, ".call -c", DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT); ThrowStatus(Status, "Unable to wait for debuggee to run"); } else if (Status != S_OK) { // Try and get control back. m_Control->SetInterrupt(DEBUG_INTERRUPT_ACTIVE); ThrowStatus(E_FAIL, "DANGEROUS FAILURE: " "Debuggee took longer than %g seconds to run " "and is still running.\n" "A break-in request has been made but the debuggee " "may be dead.\n" "If the debuggee does come back validate that its " "state has not be corrupted.", (double)TimeoutMilliseconds / 1000); } ULONG64 RetVal; GetExprU64("@$callret", (ULONG64)-1, &RetVal); return RetVal; } ULONG WINAPI ExtExtension::FindRegister(_In_ PCSTR Name, _Inout_opt_ PULONG IndexCache) { HRESULT Status; ULONG Index; if (IndexCache != NULL && *IndexCache != DEBUG_ANY_ID) { return *IndexCache; } if ((Status = m_Registers->GetIndexByName(Name, &Index)) != S_OK) { ThrowStatus(Status, "Unable to find register '%s'", Name); } if (IndexCache != NULL) { *IndexCache = Index; } return Index; } ULONG64 WINAPI ExtExtension::GetRegisterU64(_In_ PCSTR Name, _Inout_opt_ PULONG IndexCache) { HRESULT Status; ULONG Index = FindRegister(Name, IndexCache); DEBUG_VALUE RegVal, RegVal64; if ((Status = m_Registers->GetValue(Index, &RegVal)) != S_OK || (Status = m_Control->CoerceValue(&RegVal, DEBUG_VALUE_INT64, &RegVal64)) != S_OK) { ThrowStatus(Status, "Unable to get value for '%s'", Name); } return RegVal64.I64; } void WINAPI ExtExtension::SetRegisterU64(_In_ PCSTR Name, _In_ ULONG64 Val, _Inout_opt_ PULONG IndexCache) { HRESULT Status; ULONG Index = FindRegister(Name, IndexCache); DEBUG_VALUE RegVal64; RegVal64.Type = DEBUG_VALUE_INT64; RegVal64.I64 = Val; if ((Status = m_Registers->SetValue(Index, &RegVal64)) != S_OK) { ThrowStatus(Status, "Unable to set value for '%s'", Name); } } ULONG WINAPI ExtExtension::FindPseudoRegister(_In_ PCSTR Name, _Inout_opt_ PULONG IndexCache) { HRESULT Status; ULONG Index; if (IndexCache != NULL && *IndexCache != DEBUG_ANY_ID) { return *IndexCache; } if ((Status = m_Registers2->GetPseudoIndexByName(Name, &Index)) != S_OK) { ThrowStatus(Status, "Unable to find pseudo-register '%s'", Name); } if (IndexCache != NULL) { *IndexCache = Index; } return Index; } ULONG64 WINAPI ExtExtension::GetPseudoRegisterU64(_In_ PCSTR Name, _Inout_opt_ PULONG IndexCache) { HRESULT Status; ULONG Index = FindPseudoRegister(Name, IndexCache); DEBUG_VALUE RegVal, RegVal64; if ((Status = m_Registers2->GetPseudoValues(DEBUG_REGSRC_DEBUGGEE, 1, &Index, 0, &RegVal)) != S_OK || (Status = m_Control->CoerceValue(&RegVal, DEBUG_VALUE_INT64, &RegVal64)) != S_OK) { ThrowStatus(Status, "Unable to get value for '%s'", Name); } return RegVal64.I64; } void WINAPI ExtExtension::SetPseudoRegisterU64(_In_ PCSTR Name, _In_ ULONG64 Val, _Inout_opt_ PULONG IndexCache) { HRESULT Status; ULONG Index = FindPseudoRegister(Name, IndexCache); DEBUG_VALUE RegVal64; RegVal64.Type = DEBUG_VALUE_INT64; RegVal64.I64 = Val; if ((Status = m_Registers2->SetPseudoValues(DEBUG_REGSRC_DEBUGGEE, 1, &Index, 0, &RegVal64)) != S_OK) { ThrowStatus(Status, "Unable to set value for '%s'", Name); } } PCSTR WINAPI ExtExtension::GetUnnamedArgStr(_In_ ULONG Index) { if (Index >= m_NumUnnamedArgs) { ThrowInvalidArg("Invalid unnamed argument index %u, only given %u", Index + 1, m_NumUnnamedArgs); } if (!m_Args[Index].StrVal) { ThrowInvalidArg("Unnamed argument index %u is not a string", Index + 1); } return m_Args[Index].StrVal; } ULONG64 WINAPI ExtExtension::GetUnnamedArgU64(_In_ ULONG Index) { if (Index >= m_NumUnnamedArgs) { ThrowInvalidArg("Invalid unnamed argument index %u, only given %u", Index + 1, m_NumUnnamedArgs); } if (m_Args[Index].StrVal) { ThrowInvalidArg("Unnamed argument index %u is not a number", Index + 1); } return m_Args[Index].NumVal; } PCSTR WINAPI ExtExtension::GetArgStr(_In_ PCSTR Name, _In_ bool Required) { ArgVal* Arg = FindArg(Name, Required); if (!Arg) { return NULL; } if (!Arg->StrVal) { ThrowInvalidArg("Argument /%s is not a string", Name); } return Arg->StrVal; } ULONG64 WINAPI ExtExtension::GetArgU64(_In_ PCSTR Name, _In_ bool Required) { ArgVal* Arg = FindArg(Name, Required); if (!Arg) { return 0; } if (Arg->StrVal) { ThrowInvalidArg("Argument /%s is not a number", Name); } return Arg->NumVal; } bool WINAPI ExtExtension::SetUnnamedArg(_In_ ULONG Index, _In_opt_ PCSTR StrArg, _In_ ULONG64 NumArg, _In_ bool OnlyIfUnset) { ExtCommandDesc::ArgDesc* Check = m_CurCommand->FindUnnamedArg(Index); if (!Check) { ThrowInvalidArg("Unnamed argument index %u too large", Index); } ArgVal* Val = NULL; if (HasUnnamedArg(Index)) { if (OnlyIfUnset) { return false; } Val = &m_Args[Index]; } SetRawArgVal(Check, Val, true, StrArg, false, NumArg); return true; } bool WINAPI ExtExtension::SetArg(_In_ PCSTR Name, _In_opt_ PCSTR StrArg, _In_ ULONG64 NumArg, _In_ bool OnlyIfUnset) { ExtCommandDesc::ArgDesc* Check = m_CurCommand->FindArg(Name); if (!Check) { ThrowInvalidArg("No argument named '%s'", Name); } ArgVal* Val = FindArg(Name, false); if (Val) { if (OnlyIfUnset) { return false; } } SetRawArgVal(Check, Val, true, StrArg, false, NumArg); return true; } PCSTR WINAPI ExtExtension::GetExpr64(_In_ PCSTR Str, _In_ bool Signed, _In_ ULONG64 Limit, _Out_ PULONG64 Val) { HRESULT Status; DEBUG_VALUE FullVal; ULONG EndIdx; if ((Status = m_Control-> Evaluate(Str, DEBUG_VALUE_INT64, &FullVal, &EndIdx)) != S_OK) { ExtStatusException Ex(Status); Ex.PrintMessage(s_String, EXT_DIMA(s_String), "Unable to evaluate expression '%s'", Str); throw Ex; } if ((!Signed && FullVal.I64 > Limit) || (Signed && ((LONG64)FullVal.I64 < -(LONG64)Limit || (LONG64)FullVal.I64 > (LONG64)Limit))) { ThrowInvalidArg("Result overflow in expression '%s'", Str); } *Val = FullVal.I64; Str += EndIdx; while (IsSpace(*Str)) { Str++; } return Str; } void WINAPIV ExtExtension::ThrowInvalidArg(_In_ PCSTR Format, ...) { ExtInvalidArgumentException Ex(""); va_list Args; va_start(Args, Format); Ex.PrintMessageVa(s_String, EXT_DIMA(s_String), Format, Args); va_end(Args); throw Ex; } void WINAPIV ExtExtension::ThrowRemote(_In_ HRESULT Status, _In_ PCSTR Format, ...) { ExtRemoteException Ex(Status, ""); va_list Args; va_start(Args, Format); Ex.PrintMessageVa(s_String, EXT_DIMA(s_String), Format, Args); va_end(Args); throw Ex; } void WINAPIV ExtExtension::ThrowStatus(_In_ HRESULT Status, _In_ PCSTR Format, ...) { ExtStatusException Ex(Status); va_list Args; va_start(Args, Format); Ex.PrintMessageVa(s_String, EXT_DIMA(s_String), Format, Args); va_end(Args); throw Ex; } void WINAPI ExtExtension::ExInitialize(void) { if (m_ExInitialized) { return; } m_ExInitialized = true; // // Special initialization pass that // is done when output can be produced // and exceptions thrown. // This pass allows verbose feedback on // errors, as opposed to the DLL-load Initialize(). // } HRESULT WINAPI ExtExtension::QueryMachineInfo(void) { HRESULT Status; if ((Status = m_Control-> GetEffectiveProcessorType(&m_Machine)) != S_OK || (Status = m_Control-> GetPageSize(&m_PageSize)) != S_OK || // IsPointer64Bit check must be last as Status // is used to compute the pointer size below. FAILED(Status = m_Control-> IsPointer64Bit())) { return Status; } if (Status == S_OK) { m_PtrSize = 8; m_OffsetMask = 0xffffffffffffffffUI64; } else { m_PtrSize = 4; m_OffsetMask = 0xffffffffUI64; } m_ExtRetIndex = DEBUG_ANY_ID; for (ULONG i = 0; i < EXT_DIMA(m_TempRegIndex); i++) { m_TempRegIndex[i] = DEBUG_ANY_ID; } return S_OK; } #define REQ_IF(_If, _Member) \ if ((Status = Start->QueryInterface(__uuidof(_If), \ (PVOID*)&_Member)) != S_OK) \ { \ goto Exit; \ } #define OPT_IF(_If, _Member) \ if ((Status = Start->QueryInterface(__uuidof(_If), \ (PVOID*)&_Member)) != S_OK) \ { \ _Member.Set(NULL); \ } HRESULT WINAPI ExtExtension::Query(_In_ PDEBUG_CLIENT Start) { HRESULT Status; // We don't support nested queries. if (*&m_Advanced != NULL) { return E_UNEXPECTED; } m_ArgCopy = NULL; REQ_IF(IDebugAdvanced, m_Advanced); REQ_IF(IDebugClient, m_Client); REQ_IF(IDebugControl, m_Control); REQ_IF(IDebugDataSpaces, m_Data); REQ_IF(IDebugRegisters, m_Registers); REQ_IF(IDebugSymbols, m_Symbols); REQ_IF(IDebugSystemObjects, m_System); OPT_IF(IDebugAdvanced2, m_Advanced2); OPT_IF(IDebugAdvanced3, m_Advanced3); OPT_IF(IDebugClient2, m_Client2); OPT_IF(IDebugClient3, m_Client3); OPT_IF(IDebugClient4, m_Client4); OPT_IF(IDebugClient5, m_Client5); OPT_IF(IDebugControl2, m_Control2); OPT_IF(IDebugControl3, m_Control3); OPT_IF(IDebugControl4, m_Control4); OPT_IF(IDebugControl5, m_Control5); OPT_IF(IDebugControl6, m_Control6); OPT_IF(IDebugDataSpaces2, m_Data2); OPT_IF(IDebugDataSpaces3, m_Data3); OPT_IF(IDebugDataSpaces4, m_Data4); OPT_IF(IDebugRegisters2, m_Registers2); OPT_IF(IDebugSymbols2, m_Symbols2); OPT_IF(IDebugSymbols3, m_Symbols3); OPT_IF(IDebugSystemObjects2, m_System2); OPT_IF(IDebugSystemObjects3, m_System3); OPT_IF(IDebugSystemObjects4, m_System4); // If this isn't a dump target GetDumpFormatFlags // will fail, so just zero the flags. People // checking should check the class and qualifier // first so having them zeroed is not a problem. if (!m_Control2.IsSet() || m_Control2->GetDumpFormatFlags(&m_DumpFormatFlags) != S_OK) { m_DumpFormatFlags = 0; } if ((Status = m_Control-> GetDebuggeeType(&m_DebuggeeClass, &m_DebuggeeQual)) != S_OK || (Status = m_Client-> GetOutputWidth(&m_OutputWidth)) != S_OK || (Status = m_Control-> GetActualProcessorType(&m_ActualMachine)) != S_OK || (Status = QueryMachineInfo()) != S_OK) { goto Exit; } // User targets may fail a processor count request. if (m_Control->GetNumberProcessors(&m_NumProcessors) != S_OK) { m_NumProcessors = 0; } ExtensionApis.nSize = sizeof(ExtensionApis); Status = m_Control->GetWindbgExtensionApis64(&ExtensionApis); if (Status == RPC_E_CALL_REJECTED) { // GetWindbgExtensionApis64 is not remotable, // and this particular failure means we // are running remotely. Go on without any // wdbgexts support. ZeroMemory(&ExtensionApis, sizeof(ExtensionApis)); m_IsRemote = true; Status = S_OK; } else { m_IsRemote = false; } RefreshOutputCallbackFlags(); Exit: if (Status != S_OK) { if (*&m_Control != NULL) { m_Control->Output(DEBUG_OUTPUT_ERROR, "ERROR: Unable to query interfaces, 0x%08x\n", Status); } Release(); } return Status; } void WINAPI ExtExtension::Release(void) { EXT_RELEASE(m_Advanced); EXT_RELEASE(m_Client); EXT_RELEASE(m_Control); EXT_RELEASE(m_Data); EXT_RELEASE(m_Registers); EXT_RELEASE(m_Symbols); EXT_RELEASE(m_System); EXT_RELEASE(m_Advanced2); EXT_RELEASE(m_Advanced3); EXT_RELEASE(m_Client2); EXT_RELEASE(m_Client3); EXT_RELEASE(m_Client4); EXT_RELEASE(m_Client5); EXT_RELEASE(m_Control2); EXT_RELEASE(m_Control3); EXT_RELEASE(m_Control4); EXT_RELEASE(m_Control5); EXT_RELEASE(m_Control6); EXT_RELEASE(m_Data2); EXT_RELEASE(m_Data3); EXT_RELEASE(m_Data4); EXT_RELEASE(m_Registers2); EXT_RELEASE(m_Symbols2); EXT_RELEASE(m_Symbols3); EXT_RELEASE(m_System2); EXT_RELEASE(m_System3); EXT_RELEASE(m_System4); ZeroMemory(&ExtensionApis, sizeof(ExtensionApis)); free(m_ArgCopy); m_ArgCopy = NULL; m_CurCommand = NULL; } HRESULT WINAPI ExtExtension::CallExtCodeCEH(_In_opt_ ExtCommandDesc* Desc, _In_opt_ PCSTR Args, _In_opt_ ExtRawMethod RawMethod, _In_opt_ ExtRawFunction RawFunction, _In_opt_ PVOID Context, _In_opt_ PCSTR RawName) { HRESULT Status; PCSTR PreName; PCSTR Name; PreName = ""; if (RawName) { Name = RawName; } else if (Desc) { PreName = "!"; Name = Desc->m_Name; } else { Name = NULL; } try { ExInitialize(); if (Desc) { Desc->ExInitialize(this); ParseArgs(Desc, Args); } m_CallStatus = S_OK; // Release NULLs this out. m_CurCommand = Desc; if (RawFunction) { Status = RawFunction(Context); } else if (RawMethod) { Status = (this->*RawMethod)(Context); } else if (Desc) { (this->*Desc->m_Method)(); Status = m_CallStatus; } else { // This should never happen. Status = E_INVALIDARG; } } catch(ExtInterruptException Ex) { if (Name) { m_Control->Output(DEBUG_OUTPUT_ERROR, "%s%s: %s.\n", PreName, Name, Ex.GetMessage()); } Status = Ex.GetStatus(); } catch(ExtException Ex) { if (Name && Ex.GetMessage()) { if (FAILED(Ex.GetStatus())) { m_Control-> Output(DEBUG_OUTPUT_ERROR, "ERROR: %s%s: extension exception " "0x%08x.\n \"%s\"\n", PreName, Name, Ex.GetStatus(), Ex.GetMessage()); } else { m_Control->Output(DEBUG_OUTPUT_NORMAL, "%s%s: %s\n", PreName, Name, Ex.GetMessage()); } } else if (Name && Ex.GetStatus() != DEBUG_EXTENSION_CONTINUE_SEARCH && Ex.GetStatus() != DEBUG_EXTENSION_RELOAD_EXTENSION && FAILED(Ex.GetStatus())) { m_Control-> Output(DEBUG_OUTPUT_ERROR, "ERROR: %s%s: extension exception 0x%08x.\n", PreName, Name, Ex.GetStatus()); } Status = Ex.GetStatus(); } return Status; } HRESULT WINAPI ExtExtension::CallExtCodeSEH(_In_opt_ ExtCommandDesc* Desc, _In_ PDEBUG_CLIENT Client, _In_opt_ PCSTR Args, _In_opt_ ExtRawMethod RawMethod, _In_opt_ ExtRawFunction RawFunction, _In_opt_ PVOID Context, _In_opt_ PCSTR RawName) { HRESULT Status = Query(Client); if (Status != S_OK) { return Status; } // Use a hard SEH try/finally to guarantee that // Release always occurs. __try { Status = CallExtCodeCEH(Desc, Args, RawMethod, RawFunction, Context, RawName); } __finally { Release(); } return Status; } HRESULT WINAPI ExtExtension::CallKnownStructMethod(_In_ ExtKnownStruct* Struct, _In_ ULONG Flags, _In_ ULONG64 Offset, _Out_writes_(*BufferChars) PSTR Buffer, _Inout_ PULONG BufferChars) { HRESULT Status; try { ExInitialize(); SetAppendBuffer(Buffer, *BufferChars); m_CallStatus = S_OK; (this->*Struct->Method)(Struct->TypeName, Flags, Offset); Status = m_CallStatus; } catch(ExtException Ex) { Status = Ex.GetStatus(); } return Status; } HRESULT WINAPI ExtExtension::CallKnownStruct(_In_ PDEBUG_CLIENT Client, _In_ ExtKnownStruct* Struct, _In_ ULONG Flags, _In_ ULONG64 Offset, _Out_writes_(*BufferChars) PSTR Buffer, _Inout_ PULONG BufferChars) { HRESULT Status = Query(Client); if (Status != S_OK) { return Status; } // Use a hard SEH try/finally to guarantee that // Release always occurs. __try { Status = CallKnownStructMethod(Struct, Flags, Offset, Buffer, BufferChars); } __finally { Release(); } return Status; } HRESULT WINAPI ExtExtension::HandleKnownStruct(_In_ PDEBUG_CLIENT Client, _In_ ULONG Flags, _In_ ULONG64 Offset, _In_opt_ PCSTR TypeName, _Out_writes_opt_(*BufferChars) PSTR Buffer, _Inout_opt_ PULONG BufferChars) { HRESULT Status; ExtKnownStruct* Struct = m_KnownStructs; if (Flags == DEBUG_KNOWN_STRUCT_GET_NAMES && Buffer != NULL && *BufferChars > 0) { ULONG CharsNeeded; // // Return names of known structs packed in // the output buffer. // // Save a character for the double terminator. (*BufferChars)--; CharsNeeded = 1; Status = S_OK; while (Struct && Struct->TypeName) { ULONG Chars = static_cast(strlen(Struct->TypeName) + 1); CharsNeeded += Chars; if (Status != S_OK || *BufferChars < Chars) { Status = S_FALSE; } else { memcpy(Buffer, Struct->TypeName, Chars * sizeof(*Buffer)); Buffer += Chars; (*BufferChars) -= Chars; } Struct++; } *Buffer = 0; *BufferChars = CharsNeeded; } else if (Flags == DEBUG_KNOWN_STRUCT_GET_SINGLE_LINE_OUTPUT && Buffer != NULL && BufferChars > 0) { // // Dispatch request to method. // Status = E_NOINTERFACE; while (Struct && Struct->TypeName) { if (!strcmp(TypeName, Struct->TypeName)) { Status = CallKnownStruct(Client, Struct, Flags, Offset, Buffer, BufferChars); break; } Struct++; } } else if (Flags == DEBUG_KNOWN_STRUCT_SUPPRESS_TYPE_NAME) { // // Determine if formatting method suppresses the type name. // Status = E_NOINTERFACE; while (Struct && Struct->TypeName) { if (!strcmp(TypeName, Struct->TypeName)) { Status = Struct->SuppressesTypeName ? S_OK : S_FALSE; break; } Struct++; } } else { Status = E_INVALIDARG; } return Status; } HRESULT WINAPI ExtExtension::HandleQueryValueNames(_In_ PDEBUG_CLIENT Client, _In_ ULONG Flags, _Out_writes_(BufferChars) PWSTR Buffer, _In_ ULONG BufferChars, _Out_ PULONG BufferNeeded) { HRESULT Status; UNREFERENCED_PARAMETER(Client); UNREFERENCED_PARAMETER(Flags); if (Buffer == NULL || BufferChars < 1) { return E_INVALIDARG; } ExtProvidedValue* ExtVal = m_ProvidedValues; ULONG CharsNeeded; // // Return names of values packed in // the output buffer. // // Save a character for the double terminator. BufferChars--; CharsNeeded = 1; Status = S_OK; while (ExtVal && ExtVal->ValueName) { ULONG Chars = static_cast(wcslen(ExtVal->ValueName) + 1); CharsNeeded += Chars; if (Status != S_OK || BufferChars < Chars) { Status = S_FALSE; } else { memcpy(Buffer, ExtVal->ValueName, Chars * sizeof(*Buffer)); Buffer += Chars; BufferChars -= Chars; } ExtVal++; } *Buffer = 0; *BufferNeeded = CharsNeeded; return Status; } HRESULT WINAPI ExtExtension::CallProvideValueMethod(_In_ ExtProvidedValue* ExtVal, _In_ ULONG Flags, _Out_ PULONG64 Value, _Out_ PULONG64 TypeModBase, _Out_ PULONG TypeId, _Out_ PULONG TypeFlags) { HRESULT Status; try { ExInitialize(); m_CallStatus = S_OK; (this->*ExtVal->Method)(Flags, ExtVal->ValueName, Value, TypeModBase, TypeId, TypeFlags); Status = m_CallStatus; } catch(ExtException Ex) { Status = Ex.GetStatus(); } return Status; } HRESULT WINAPI ExtExtension::HandleProvideValue(_In_ PDEBUG_CLIENT Client, _In_ ULONG Flags, _In_ PCWSTR Name, _Out_ PULONG64 Value, _Out_ PULONG64 TypeModBase, _Out_ PULONG TypeId, _Out_ PULONG TypeFlags) { HRESULT Status = Query(Client); if (Status != S_OK) { return Status; } // Use a hard SEH try/finally to guarantee that // Release always occurs. __try { ExtProvidedValue* ExtVal = m_ProvidedValues; while (ExtVal && ExtVal->ValueName) { if (wcscmp(Name, ExtVal->ValueName) == 0) { break; } ExtVal++; } if (!ExtVal) { Status = E_UNEXPECTED; } else { Status = CallProvideValueMethod(ExtVal, Flags, Value, TypeModBase, TypeId, TypeFlags); } } __finally { Release(); } return Status; } ExtExtension::ArgVal* WINAPI ExtExtension::FindArg(_In_ PCSTR Name, _In_ bool Required) { ULONG i; for (i = m_FirstNamedArg; i < m_FirstNamedArg + m_NumNamedArgs; i++) { if (!strcmp(Name, m_Args[i].Name)) { return &m_Args[i]; } } if (Required) { ThrowInvalidArg("No argument /%s was provided", Name); } return NULL; } PCSTR WINAPI ExtExtension::SetRawArgVal(_In_ ExtCommandDesc::ArgDesc* Check, _In_opt_ ArgVal* Val, _In_ bool ExplicitVal, _In_opt_ PCSTR StrVal, _In_ bool StrWritable, _In_ ULONG64 NumVal) { if (!Val) { if (Check->Name) { if (m_NumNamedArgs + m_FirstNamedArg >= EXT_DIMA(m_Args)) { ThrowInvalidArg("Argument overflow on '%s'", Check->Name); } Val = &m_Args[m_NumNamedArgs + m_FirstNamedArg]; m_NumArgs++; m_NumNamedArgs++; } else { Val = &m_Args[m_NumUnnamedArgs]; m_NumArgs++; m_NumUnnamedArgs++; } } Check->Present = true; Val->Name = Check->Name; Val->StrVal = NULL; Val->NumVal = 0; if (Check->Boolean) { return StrVal; } if (StrVal) { while (IsSpace(*StrVal)) { StrVal++; } if (!*StrVal && !ExplicitVal) { ThrowInvalidArg("Missing value for argument '%s'", Check->Name); } if (Check->String) { Val->StrVal = StrVal; if (Check->StringRemainder) { StrVal += strlen(StrVal); } else { while (*StrVal && !IsSpace(*StrVal)) { StrVal++; } } } else if (Check->Expression) { PSTR StrEnd = NULL; char StrEndChar = 0; if (Check->ExpressionDelimited) { StrEnd = (PSTR)StrVal; while (*StrEnd && !IsSpace(*StrEnd)) { StrEnd++; } if (IsSpace(*StrEnd)) { // // We found some trailing text so we need // to force a terminator to delimit the // expression. We can only do this if // we make a copy of the string or have // a writable string. As any case where a // non-writable string is passed in involves // a caller setting an argument explicitly they // can provide a properly-terminated expression, // so don't support copying. // if (!StrWritable) { ThrowInvalidArg("Delimited expressions can " "only be parsed from extension " "command arguments"); } StrEndChar = *StrEnd; *StrEnd = 0; } else { // No trailing text so no need to force // termination. StrEnd = NULL; } } ExtRadixHolder HoldRadix; if (Check->ExpressionRadix != 0) { HoldRadix.Refresh(); EXT_STATUS(m_Control->SetRadix(Check->ExpressionRadix)); } if (Check->ExpressionEvaluator != NULL) { StrVal = PrintCircleString("@@%s(%s)", Check->ExpressionEvaluator, StrVal); } StrVal = GetExpr64(StrVal, Check->ExpressionSigned != 0, (0xffffffffffffffffUI64 >> (64 - Check->ExpressionBits)), &Val->NumVal); if (Check->ExpressionEvaluator != NULL && StrEnd != NULL) { StrVal = StrEnd; } if (StrEnd) { *StrEnd = StrEndChar; } } } else if (Check->String) { ThrowInvalidArg("Missing value for argument '%s'", Check->Name); } else { Val->NumVal = NumVal; } return StrVal; } void WINAPI ExtExtension::ParseArgs(_In_ ExtCommandDesc* Desc, _In_opt_ PCSTR Args) { if (!Args) { Args = ""; } m_RawArgStr = Args; m_NumArgs = 0; m_NumNamedArgs = 0; m_NumUnnamedArgs = 0; m_FirstNamedArg = Desc->m_NumUnnamedArgs; // // First make a copy of the argument string as // we will need to chop it up when parsing. // Release() automatically cleans this up. // m_ArgCopy = _strdup(Args); if (!m_ArgCopy) { ThrowOutOfMemory(); } if (Desc->m_CustomArgParsing) { return; } PSTR Scan = m_ArgCopy; bool ImplicitNamedArg = false; ULONG i; ExtCommandDesc::ArgDesc* Check; Check = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Check++) { Check->Present = false; } for (;;) { while (IsSpace(*Scan)) { ImplicitNamedArg = false; Scan++; } if (!*Scan) { break; } if (ImplicitNamedArg || strchr(Desc->m_OptionChars, *Scan) != NULL) { // // Named argument. Collect name and // see if this is a valid argument. // if (!ImplicitNamedArg) { Scan++; // If /? is given at any point immediately // go help for the command and exit. if (*Scan == '?' && (!*(Scan + 1) || IsSpace(*(Scan + 1)))) { HelpCommand(Desc); throw ExtStatusException(S_OK); } } PSTR Start = Scan++; while (*Scan && !IsSpace(*Scan)) { Scan++; } char Save = *Scan; *Scan = 0; // // First check for a full name match. // if (!ImplicitNamedArg) { Check = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Check++) { if (!Check->Name) { continue; } if (!strcmp(Start, Check->Name)) { break; } } } else { i = Desc->m_NumArgs; } if (i >= Desc->m_NumArgs) { // // Didn't find it with a full name match, // so check for a single-character match. // This is only allowed for single-character // boolean options. // ImplicitNamedArg = false; Check = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Check++) { if (!Check->Name || !Check->Boolean) { continue; } if (*Start == Check->Name[0] && !Check->Name[1]) { // Multiple single-character options // can be combined with a single slash, // so the next character should be // checked as a named option. ImplicitNamedArg = true; break; } } } if (i >= Desc->m_NumArgs) { ThrowInvalidArg("Unrecognized argument '%s'", Start); } // // Found the argument. Validate it. // if (Check->Present) { ThrowInvalidArg("Duplicate argument '%s'", Start); } // // Argument is valid, fix up the scan string // and move to value processing. // *Scan = Save; if (ImplicitNamedArg) { Scan = Start + 1; } } else { // // Unnamed argument. // Find the n'th unnamed argument description // and use it. // Check = Desc->FindUnnamedArg(m_NumUnnamedArgs); if (! Check) { ThrowInvalidArg("Extra unnamed argument at '%s'", Scan); } } // // We have an argument description, so // look for any appropriate value. // Scan = (PSTR)SetRawArgVal(Check, NULL, false, Scan, true, 0); if (Check->String && *Scan) { *Scan++ = 0; } } // // Fill in default values where needed. // Check = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Check++) { if (!Check->Present && Check->Default) { SetRawArgVal(Check, NULL, true, Check->Default, false, 0); } } // // Verify that all required arguments are present. // ULONG NumUnPresent = 0; Check = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Check++) { if (!Check->Name) { NumUnPresent++; } if (Check->Required && !Check->Present) { if (Check->Name) { ThrowInvalidArg("Missing required argument '%s'", Check->Name); } else if (Check->DescShort) { ThrowInvalidArg("Missing required argument '<%s>'", Check->DescShort); } else { ThrowInvalidArg("Missing unnamed argument %u", NumUnPresent); } } } } void WINAPI ExtExtension::OutCommandArg(_In_ ExtCommandDesc::ArgDesc* Arg, _In_ bool Separate) { if (Arg->Name) { if (Separate) { OutWrapStr("/"); } OutWrapStr(Arg->Name); if (!Arg->Boolean) { OutWrapStr(" "); } } if (!Arg->Boolean) { OutWrap("<%s>", Arg->DescShort); } } void WINAPI ExtExtension::HelpCommandArgsSummary(_In_ ExtCommandDesc* Desc) { ULONG i; ExtCommandDesc::ArgDesc* Arg; bool Hit; if (Desc->m_CustomArgDescShort) { OutWrapStr(Desc->m_CustomArgDescShort); return; } // // In order to try and make things pretty we make // several passes over the arguments. // // // Display all optional single-char booleans as a collection. // Hit = false; Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Arg++) { if (Arg->Boolean && !Arg->Required && !Arg->Name[1]) { if (!Hit) { OutWrapStr(" [/"); Hit = true; AllowWrap(false); } OutWrapStr(Arg->Name); } } if (Hit) { OutWrapStr("]"); AllowWrap(true); } // // Display all optional multi-char booleans. // Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Arg++) { if (Arg->Boolean && !Arg->Required && Arg->Name[1]) { OutWrap(" [/%s]", Arg->Name); } } // // Display all required single-char booleans as a collection. // Hit = false; Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Arg++) { if (Arg->Boolean && Arg->Required && !Arg->Name[1]) { if (!Hit) { OutWrapStr(" /"); Hit = true; AllowWrap(false); } OutWrapStr(Arg->Name); } } AllowWrap(true); // // Display all required multi-char booleans. // Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Arg++) { if (Arg->Boolean && Arg->Required && Arg->Name[1]) { OutWrap(" /%s", Arg->Name); } } // // Display all optional named non-booleans. // Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Arg++) { if (!Arg->Boolean && !Arg->Required && Arg->Name) { TestWrap(true); OutCommandArg(Arg, true); TestWrap(false); if (!DemandWrap(m_TestWrapChars + 3)) { OutWrapStr(" "); } OutWrapStr("["); AllowWrap(false); OutCommandArg(Arg, true); OutWrapStr("]"); AllowWrap(true); } } // // Display all required named non-booleans. // Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Arg++) { if (!Arg->Boolean && Arg->Required && Arg->Name) { TestWrap(true); OutCommandArg(Arg, true); TestWrap(false); if (!DemandWrap(m_TestWrapChars + 1)) { OutWrapStr(" "); } AllowWrap(false); OutCommandArg(Arg, true); AllowWrap(true); } } // // Display all unnamed arguments. As any optional // unnamed argument must be last we can handle both // optional and required in a single pass. // Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++, Arg++) { if (!Arg->Boolean && !Arg->Name) { TestWrap(true); OutCommandArg(Arg, true); TestWrap(false); if (!Arg->Required) { m_TestWrapChars += 2; } if (!DemandWrap(m_TestWrapChars + 1)) { OutWrapStr(" "); } if (!Arg->Required) { OutWrapStr("["); } AllowWrap(false); OutCommandArg(Arg, true); if (!Arg->Required) { OutWrapStr("]"); } AllowWrap(true); } } } void WINAPI ExtExtension::OutArgDescOptions(_In_ ExtCommandDesc::ArgDesc* Arg) { bool First = true; if (Arg->Default && !Arg->DefaultSilent) { OutWrapStr("defaults to "); OutWrapStr(Arg->Default); First = false; } if (Arg->Expression) { if (Arg->ExpressionSigned) { if (!First) { OutWrapStr(", "); } OutWrapStr("signed"); First = false; } if (Arg->ExpressionDelimited) { if (!First) { OutWrapStr(", "); } OutWrapStr("space-delimited"); First = false; } if (Arg->ExpressionBits != 64) { if (!First) { OutWrapStr(", "); } OutWrap("%u-bit max", Arg->ExpressionBits); First = false; } if (Arg->ExpressionRadix) { if (!First) { OutWrapStr(", "); } OutWrap("base %u", Arg->ExpressionRadix); First = false; } if (Arg->ExpressionEvaluator) { if (!First) { OutWrapStr(", "); } OutWrapStr(Arg->ExpressionEvaluator); OutWrapStr(" syntax"); First = false; } } if (Arg->String) { if (Arg->StringRemainder) { if (!First) { OutWrapStr(", "); } OutWrapStr("consumes remainder of input string"); First = false; } } } void WINAPI ExtExtension::HelpCommand(_In_ ExtCommandDesc* Desc) { ULONG i; Desc->ExInitialize(this); m_CurChar = 0; OutWrap("!%s", Desc->m_Name); m_LeftIndent = m_CurChar + 1; HelpCommandArgsSummary(Desc); m_LeftIndent = 0; OutWrapStr("\n"); if (Desc->m_CustomArgDescLong) { OutWrapStr(" "); m_LeftIndent = m_CurChar; OutWrapStr(Desc->m_CustomArgDescLong); m_LeftIndent = 0; OutWrapStr("\n"); } else { ExtCommandDesc::ArgDesc* Arg = Desc->m_Args; for (i = 0; i < Desc->m_NumArgs; i++) { OutWrapStr(" "); OutCommandArg(Arg, true); if (Arg->DescLong) { OutWrapStr(" - "); m_LeftIndent = m_CurChar; OutWrapStr(Arg->DescLong); if (Arg->NeedsOptionsOutput()) { OutWrapStr(" ("); OutArgDescOptions(Arg); OutWrapStr(")"); } } else if (Arg->NeedsOptionsOutput()) { OutWrapStr(" - "); m_LeftIndent = m_CurChar; OutArgDescOptions(Arg); } m_LeftIndent = 0; OutWrapStr("\n"); Arg++; } } OutWrapStr(Desc->m_Desc); Out("\n"); } void WINAPI ExtExtension::HelpCommandName(_In_ PCSTR Name) { ExtCommandDesc* Desc = m_Commands; while (Desc) { if (!strcmp(Name, Desc->m_Name)) { break; } Desc = Desc->m_Next; } if (!Desc) { ThrowInvalidArg("No command named '%s'", Name); } HelpCommand(Desc); } void WINAPI ExtExtension::HelpAll(void) { char ModName[2 * MAX_PATH]; if (!GetModuleFileNameA(s_Module, ModName, EXT_DIMA(ModName))) { StringCbCopyA(ModName, sizeof(ModName), ""); } Out("Commands for %s:\n", ModName); m_CurChar = 0; ExtCommandDesc* Desc = m_Commands; while (Desc) { ULONG NameLen = static_cast(strlen(Desc->m_Name)); OutWrap(" !%s%*c- ", Desc->m_Name, m_LongestCommandName - NameLen + 1, ' '); m_LeftIndent = m_CurChar; OutWrapStr(Desc->m_Desc); m_LeftIndent = 0; OutWrapStr("\n"); Desc = Desc->m_Next; } Out("!help will give more information for a particular command\n"); } EXT_CLASS_COMMAND(ExtExtension, help, "Displays information on available extension commands", "{;s,o;command;Command to get information on}") { if (HasUnnamedArg(0)) { HelpCommandName(GetUnnamedArgStr(0)); } else { HelpAll(); SetCallStatus(DEBUG_EXTENSION_CONTINUE_SEARCH); } } //---------------------------------------------------------------------------- // // Global forwarders for common methods. // //---------------------------------------------------------------------------- void WINAPIV ExtOut(_In_ PCSTR Format, ...) { g_Ext.Throw(); va_list Args; va_start(Args, Format); g_Ext->m_Control-> OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args); va_end(Args); } void WINAPIV ExtWarn(_In_ PCSTR Format, ...) { g_Ext.Throw(); va_list Args; va_start(Args, Format); g_Ext->m_Control-> OutputVaList(DEBUG_OUTPUT_WARNING, Format, Args); va_end(Args); } void WINAPIV ExtErr(_In_ PCSTR Format, ...) { g_Ext.Throw(); va_list Args; va_start(Args, Format); g_Ext->m_Control-> OutputVaList(DEBUG_OUTPUT_ERROR, Format, Args); va_end(Args); } void WINAPIV ExtVerb(_In_ PCSTR Format, ...) { g_Ext.Throw(); va_list Args; va_start(Args, Format); g_Ext->m_Control-> OutputVaList(DEBUG_OUTPUT_VERBOSE, Format, Args); va_end(Args); } //---------------------------------------------------------------------------- // // ExtRemoteData. // //---------------------------------------------------------------------------- void WINAPI ExtRemoteData::Set(_In_ const DEBUG_TYPED_DATA* Typed) { m_Offset = Typed->Offset; m_ValidOffset = (Typed->Flags & DEBUG_TYPED_DATA_IS_IN_MEMORY) != 0; m_Bytes = Typed->Size; m_Data = Typed->Data; m_ValidData = Typed->Size > 0 && Typed->Size <= sizeof(m_Data); } void WINAPI ExtRemoteData::Read(void) { g_Ext->ThrowInterrupt(); // Zero data so that unread bytes have a known state. ULONG64 NewData = 0; #pragma prefast(suppress:__WARNING_REDUNDANTTEST, "valid redundancy") if (m_Bytes > sizeof(m_Data) || m_Bytes > sizeof(NewData)) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData::Read too large"); } ReadBuffer(&NewData, m_Bytes); m_Data = NewData; m_ValidData = true; } void WINAPI ExtRemoteData::Write(void) { g_Ext->ThrowInterrupt(); if (m_Bytes > sizeof(m_Data)) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData::Write too large"); } if (!m_ValidData) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData does not have valid data"); } WriteBuffer(&m_Data, m_Bytes); } ULONG64 WINAPI ExtRemoteData::GetData(_In_ ULONG Request) { g_Ext->ThrowInterrupt(); if (m_Bytes != Request) { g_Ext->ThrowRemote(E_INVALIDARG, "Invalid ExtRemoteData size"); } if (!m_ValidData) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData does not have valid data"); } return m_Data; } void WINAPI ExtRemoteData::SetData(_In_ ULONG64 Data, _In_ ULONG Request, _In_ bool NoWrite) throw(...) { g_Ext->ThrowInterrupt(); if (m_Bytes != Request) { g_Ext->ThrowRemote(E_INVALIDARG, "Invalid ExtRemoteData size"); } m_Data = Data; m_ValidData = true; if (!NoWrite) { Write(); } } ULONG WINAPI ExtRemoteData::ReadBuffer(_Out_writes_bytes_(Bytes) PVOID Buffer, _In_ ULONG Bytes, _In_ bool MustReadAll) { HRESULT Status; ULONG Done; g_Ext->ThrowInterrupt(); if (!Bytes) { g_Ext->ThrowRemote(E_INVALIDARG, "Zero-sized ExtRemoteData"); } if (!m_ValidOffset) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData does not have a valid address"); } if (m_Physical) { Status = g_Ext->m_Data4-> ReadPhysical2(m_Offset, m_SpaceFlags, Buffer, Bytes, &Done); } else { Status = g_Ext->m_Data-> ReadVirtual(m_Offset, Buffer, Bytes, &Done); } if (Status == S_OK && Done != Bytes && MustReadAll) { Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT); } if (Status != S_OK) { if (m_Name) { g_Ext->ThrowRemote(Status, "Unable to read %s at %p", m_Name, m_Offset); } else { g_Ext->ThrowRemote(Status, "Unable to read 0x%x bytes at %p", Bytes, m_Offset); } } return Done; } ULONG WINAPI ExtRemoteData::WriteBuffer(_In_reads_bytes_(Bytes) PVOID Buffer, _In_ ULONG Bytes, _In_ bool MustWriteAll) { HRESULT Status; ULONG Done; UNREFERENCED_PARAMETER(Buffer); g_Ext->ThrowInterrupt(); if (!Bytes) { g_Ext->ThrowRemote(E_INVALIDARG, "Zero-sized ExtRemoteData"); } if (!m_ValidOffset) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData does not have a valid address"); } if (m_Physical) { Status = g_Ext->m_Data4-> WritePhysical2(m_Offset, m_SpaceFlags, Buffer, Bytes, &Done); } else { Status = g_Ext->m_Data-> WriteVirtual(m_Offset, Buffer, Bytes, &Done); } if (Status == S_OK && Done != Bytes && MustWriteAll) { Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT); } if (Status != S_OK) { if (m_Name) { g_Ext->ThrowRemote(Status, "Unable to write %s at %p", m_Name, m_Offset); } else { g_Ext->ThrowRemote(Status, "Unable to write 0x%x bytes at %p", Bytes, m_Offset); } } return Done; } PSTR WINAPI ExtRemoteData::GetString(_Out_writes_opt_(BufferChars) PSTR Buffer, _In_ ULONG BufferChars, _In_ ULONG MaxChars, _In_ bool MustFit, _Out_opt_ PULONG NeedChars) { HRESULT Status; g_Ext->ThrowInterrupt(); if (!m_ValidOffset) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData does not have a valid address"); } if (m_Physical) { g_Ext->ThrowRemote(E_NOTIMPL, "ExtRemoteData cannot read strings " "from physical memory"); } ULONG Need; if (FAILED(Status = g_Ext->m_Data4-> ReadMultiByteStringVirtual(m_Offset, MaxChars * sizeof(*Buffer), Buffer, BufferChars, &Need))) { g_Ext->ThrowRemote(Status, "Unable to read string at %p", m_Offset); } if (Status != S_OK && MustFit) { g_Ext->ThrowRemote(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), "String at %p overflows buffer, need 0x%x chars", m_Offset, Need); } if (NeedChars) { *NeedChars = Need; } return Buffer; } PWSTR WINAPI ExtRemoteData::GetString(_Out_writes_opt_(BufferChars) PWSTR Buffer, _In_ ULONG BufferChars, _In_ ULONG MaxChars, _In_ bool MustFit, _Out_opt_ PULONG NeedChars) { HRESULT Status; g_Ext->ThrowInterrupt(); if (!m_ValidOffset) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteData does not have a valid address"); } if (m_Physical) { g_Ext->ThrowRemote(E_NOTIMPL, "ExtRemoteData cannot read strings " "from physical memory"); } ULONG Need; if (FAILED(Status = g_Ext->m_Data4-> ReadUnicodeStringVirtualWide(m_Offset, MaxChars * sizeof(*Buffer), Buffer, BufferChars, &Need))) { g_Ext->ThrowRemote(Status, "Unable to read string at %p", m_Offset); } if (Status != S_OK && MustFit) { g_Ext->ThrowRemote(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), "String at %p overflows buffer, need 0x%x chars", m_Offset, Need); } if (NeedChars) { *NeedChars = Need; } return Buffer; } PSTR WINAPI ExtRemoteData::GetString(_Inout_ ExtBuffer* Buffer, _In_ ULONG MaxChars) { ULONG Need; for (ULONG i = 0; i < 2; i++) { GetString(Buffer->GetRawBuffer(), Buffer->GetEltsAlloc(), MaxChars, false, &Need); if (Need <= Buffer->GetEltsAlloc()) { Buffer->SetEltsUsed(Need); return Buffer->GetBuffer(); } Buffer->Require(Need); } g_Ext->ThrowRemote(E_INVALIDARG, "Unable to read string at %p", m_Offset); } PWSTR WINAPI ExtRemoteData::GetString(_Inout_ ExtBuffer* Buffer, _In_ ULONG MaxChars) { ULONG Need; for (ULONG i = 0; i < 2; i++) { GetString(Buffer->GetRawBuffer(), Buffer->GetEltsAlloc(), MaxChars, false, &Need); if (Need <= Buffer->GetEltsAlloc()) { Buffer->SetEltsUsed(Need); return Buffer->GetBuffer(); } Buffer->Require(Need); } g_Ext->ThrowRemote(E_INVALIDARG, "Unable to read string at %p", m_Offset); } //---------------------------------------------------------------------------- // // ExtRemoteTyped. // //---------------------------------------------------------------------------- void WINAPI ExtRemoteTyped::Copy(_In_ const DEBUG_TYPED_DATA* Source) { m_Typed = *Source; ErtIoctl("Copy", EXT_TDOP_COPY, ErtUncheckedIn | ErtOut); } void WINAPI ExtRemoteTyped::Set(_In_ PCSTR Expr) { EXT_TDOP Op; ULONG Flags = ErtOut; // If we have a valid value let it be used // in the expression if desired. if (m_Release) { Op = EXT_TDOP_EVALUATE; Flags |= ErtIn; } else { Op = EXT_TDOP_SET_FROM_EXPR; } PSTR Msg = g_Ext-> PrintCircleString("Set: unable to evaluate '%s'", Expr); ErtIoctl(Msg, Op, Flags, Expr); } void WINAPI ExtRemoteTyped::Set(_In_ PCSTR Expr, _In_ ULONG64 Offset) { m_Typed.Offset = Offset; PSTR Msg = g_Ext-> PrintCircleString("Set: unable to evaluate '%s' for 0x%I64x", Expr, Offset); ErtIoctl(Msg, EXT_TDOP_SET_FROM_U64_EXPR, ErtUncheckedIn | ErtOut, Expr); } void WINAPI ExtRemoteTyped::Set(_In_ bool PtrTo, _In_ ULONG64 TypeModBase, _In_ ULONG TypeId, _In_ ULONG64 Offset) { HRESULT Status; EXT_TYPED_DATA ExtData; g_Ext->ThrowInterrupt(); ZeroMemory(&ExtData, sizeof(ExtData)); ExtData.Operation = PtrTo ? EXT_TDOP_SET_PTR_FROM_TYPE_ID_AND_U64 : EXT_TDOP_SET_FROM_TYPE_ID_AND_U64; if (m_Physical) { ExtData.Flags |= (m_SpaceFlags + 1) << 1; } ExtData.InData.ModBase = TypeModBase; ExtData.InData.TypeId = TypeId; ExtData.InData.Offset = Offset; Status = g_Ext->m_Advanced2-> Request(DEBUG_REQUEST_EXT_TYPED_DATA_ANSI, &ExtData, sizeof(ExtData), &ExtData, sizeof(ExtData), NULL); if (SUCCEEDED(Status)) { Status = ExtData.Status; } if (FAILED(Status)) { g_Ext->ThrowRemote(Status, "ExtRemoteTyped::Set from type and offset"); } Release(); m_Typed = ExtData.OutData; ExtRemoteData::Set(&m_Typed); m_Release = true; } void WINAPI ExtRemoteTyped::Set(_In_ PCSTR Type, _In_ ULONG64 Offset, _In_ bool PtrTo, _Inout_opt_ PULONG64 CacheCookie, _In_opt_ PCSTR LinkField) { HRESULT Status; ULONG64 TypeModBase; ULONG TypeId; if (!CacheCookie) { if ((Status = g_Ext->m_Symbols-> GetSymbolTypeId(Type, &TypeId, &TypeModBase)) != S_OK) { g_Ext->ThrowStatus(Status, "Unable to get type ID of '%s'", Type); } } else { if (LinkField) { // We don't really need the field offset // here but it allows us to use cache // entries that were created for list // usage and so do have it. g_Ext->GetCachedFieldOffset(CacheCookie, Type, LinkField, &TypeModBase, &TypeId); } else { TypeId = g_Ext->GetCachedSymbolTypeId(CacheCookie, Type, &TypeModBase); } } Set(PtrTo, TypeModBase, TypeId, Offset); } void WINAPIV ExtRemoteTyped::SetPrint(_In_ PCSTR Format, ...) { HRESULT Status; va_list Args; va_start(Args, Format); Status = StringCbVPrintfA(ExtExtension::s_String, sizeof(ExtExtension::s_String), Format, Args); va_end(Args); if (Status != S_OK) { g_Ext->ThrowRemote(Status, "ExtRemoteTyped::SetPrint: overflow on '%s'", Format); } Set(g_Ext->CopyCircleString(g_Ext->s_String)); } ULONG WINAPI ExtRemoteTyped::GetFieldOffset(_In_ PCSTR Field) throw(...) { ULONG Offset; PSTR Msg = g_Ext-> PrintCircleString("GetFieldOffset: no field '%s'", Field); ErtIoctl(Msg, EXT_TDOP_GET_FIELD_OFFSET, ErtIn, Field, 0, NULL, NULL, 0, &Offset); return Offset; } ExtRemoteTyped WINAPI ExtRemoteTyped::Field(_In_ PCSTR Field) { ExtRemoteTyped Ret; PSTR Msg = g_Ext-> PrintCircleString("Field: unable to retrieve field '%s' at %I64x", Field, m_Offset); ErtIoctl(Msg, EXT_TDOP_GET_FIELD, ErtIn | ErtOut, Field, 0, &Ret); return Ret; } ExtRemoteTyped WINAPI ExtRemoteTyped::ArrayElement(_In_ LONG64 Index) { ExtRemoteTyped Ret; PSTR Msg = g_Ext-> PrintCircleString("ArrayElement: unable to retrieve element %I64d", Index); ErtIoctl(Msg, EXT_TDOP_GET_ARRAY_ELEMENT, ErtIn | ErtOut, NULL, Index, &Ret); return Ret; } ExtRemoteTyped WINAPI ExtRemoteTyped::Dereference(void) { ExtRemoteTyped Ret; ErtIoctl("Dereference", EXT_TDOP_GET_DEREFERENCE, ErtIn | ErtOut, NULL, 0, &Ret); return Ret; } ExtRemoteTyped WINAPI ExtRemoteTyped::GetPointerTo(void) { ExtRemoteTyped Ret; ErtIoctl("GetPointerTo", EXT_TDOP_GET_POINTER_TO, ErtIn | ErtOut, NULL, 0, &Ret); return Ret; } ExtRemoteTyped WINAPI ExtRemoteTyped::Eval(_In_ PCSTR Expr) { ExtRemoteTyped Ret; PSTR Msg = g_Ext-> PrintCircleString("Eval: unable to evaluate '%s'", Expr); ErtIoctl(Msg, EXT_TDOP_EVALUATE, ErtIn | ErtOut, Expr, 0, &Ret); return Ret; } PSTR WINAPI ExtRemoteTyped::GetTypeName(void) { ErtIoctl("GetTypeName", EXT_TDOP_GET_TYPE_NAME, ErtIn, NULL, 0, NULL, ExtExtension::s_String, EXT_DIMA(ExtExtension::s_String)); return g_Ext->CopyCircleString(ExtExtension::s_String); } PSTR WINAPI ExtRemoteTyped::GetSimpleValue(void) { ExtCaptureOutputA Capture; Capture.Start(); OutSimpleValue(); Capture.Stop(); return g_Ext->CopyCircleString(Capture.GetTextNonNull()); } ULONG WINAPI ExtRemoteTyped::GetTypeFieldOffset(_In_ PCSTR Type, _In_ PCSTR Field) { HRESULT Status; DEBUG_VALUE Data; PSTR Expr; Expr = g_Ext->PrintCircleString("@@c++(#FIELD_OFFSET(%s, %s))", Type, Field); if (FAILED(Status = g_Ext->m_Control-> Evaluate(Expr, DEBUG_VALUE_INT64, &Data, NULL))) { g_Ext->ThrowRemote(Status, "Could not find type field %s.%s", Type, Field); } return (ULONG)Data.I64; } HRESULT WINAPI ExtRemoteTyped::ErtIoctl(_In_ PCSTR Message, _In_ EXT_TDOP Op, _In_ ULONG Flags, _In_opt_ PCSTR InStr, _In_ ULONG64 In64, _Out_opt_ ExtRemoteTyped* Ret, _Out_writes_opt_(StrBufferChars) PSTR StrBuffer, _In_ ULONG StrBufferChars, _Out_opt_ PULONG Out32) { HRESULT Status; ExtDeclAlignedBuffer DataHolder; EXT_TYPED_DATA* ExtData; ULONG ExtDataBytes; PBYTE ExtraData; C_ASSERT(EXT_TDF_PHYSICAL_MEMORY == DEBUG_TYPED_DATA_PHYSICAL_MEMORY); // Check for a user interrupt, but don't do that // when we're in a cleanup path since we don't // want to prevent orderly shutdown of objects. if (Op != EXT_TDOP_RELEASE) { g_Ext->ThrowInterrupt(); } ExtDataBytes = sizeof(*ExtData) + StrBufferChars * sizeof(*StrBuffer); if (InStr) { ExtDataBytes += static_cast((strlen(InStr) + 1) * sizeof(*InStr)); } ExtData = (EXT_TYPED_DATA*)DataHolder.Get(ExtDataBytes); ExtraData = (PBYTE)(ExtData + 1); ZeroMemory(ExtData, sizeof(*ExtData)); ExtData->Operation = Op; if (m_Physical) { ExtData->Flags |= (m_SpaceFlags + 1) << 1; } if (InStr) { ExtData->InStrIndex = (ULONG)(ExtraData - (PBYTE)ExtData); memcpy(ExtraData, InStr, (strlen(InStr) + 1) * sizeof(*InStr)); ExtraData += (strlen(InStr) + 1) * sizeof(*InStr); } ExtData->In64 = In64; if (StrBuffer) { ExtData->StrBufferIndex = (ULONG)(ExtraData - (PBYTE)ExtData); ExtData->StrBufferChars = StrBufferChars; ExtraData += StrBufferChars * sizeof(*StrBuffer); } if ((Flags & (ErtIn | ErtUncheckedIn)) != 0) { if ((Flags & ErtIn) != 0 && !m_Release) { g_Ext->ThrowRemote(E_INVALIDARG, "ExtRemoteTyped::%s", Message); } ExtData->InData = m_Typed; } Status = g_Ext->m_Advanced2-> Request(DEBUG_REQUEST_EXT_TYPED_DATA_ANSI, ExtData, ExtDataBytes, ExtData, ExtDataBytes, NULL); if (SUCCEEDED(Status)) { Status = ExtData->Status; } if ((Flags & ErtIgnoreError) == 0 && FAILED(Status)) { g_Ext->ThrowRemote(Status, "ExtRemoteTyped::%s", Message); } if ((Flags & ErtOut) != 0) { if (!Ret) { Ret = this; } Ret->Release(); Ret->m_Typed = ExtData->OutData; Ret->ExtRemoteData::Set(&Ret->m_Typed); Ret->m_Release = true; } if (StrBuffer) { memcpy(StrBuffer, (PBYTE)ExtData + ExtData->StrBufferIndex, StrBufferChars * sizeof(*StrBuffer)); } if (Out32) { *Out32 = ExtData->Out32; } return Status; } void WINAPI ExtRemoteTyped::Clear(void) { ZeroMemory(&m_Typed, sizeof(m_Typed)); m_Release = false; ExtRemoteData::Clear(); } //---------------------------------------------------------------------------- // // Helpers for handling well-known NT data and types. // //---------------------------------------------------------------------------- ULONG64 ExtNtOsInformation::s_KernelLoadedModuleBaseInfoCookie; ULONG64 ExtNtOsInformation::s_KernelProcessBaseInfoCookie; ULONG64 ExtNtOsInformation::s_KernelThreadBaseInfoCookie; ULONG64 ExtNtOsInformation::s_KernelProcessThreadListFieldCookie; ULONG64 ExtNtOsInformation::s_UserOsLoadedModuleBaseInfoCookie; ULONG64 ExtNtOsInformation::s_UserAltLoadedModuleBaseInfoCookie; ULONG64 ExtNtOsInformation::s_OsPebBaseInfoCookie; ULONG64 ExtNtOsInformation::s_AltPebBaseInfoCookie; ULONG64 ExtNtOsInformation::s_OsTebBaseInfoCookie; ULONG64 ExtNtOsInformation::s_AltTebBaseInfoCookie; ULONG64 WINAPI ExtNtOsInformation::GetKernelLoadedModuleListHead(void) { return GetNtDebuggerData(DEBUG_DATA_PsLoadedModuleListAddr, "nt!PsLoadedModuleList", 0); } ExtRemoteTypedList WINAPI ExtNtOsInformation::GetKernelLoadedModuleList(void) { ExtRemoteTypedList List(GetKernelLoadedModuleListHead(), "nt!_KLDR_DATA_TABLE_ENTRY", "InLoadOrderLinks", 0, 0, &s_KernelLoadedModuleBaseInfoCookie, true); List.m_MaxIter = 1000; return List; } ExtRemoteTyped WINAPI ExtNtOsInformation::GetKernelLoadedModule(_In_ ULONG64 Offset) { // We are caching both type and link information // so provide a link field here to keep the // cache properly filled out. return ExtRemoteTyped("nt!_KLDR_DATA_TABLE_ENTRY", Offset, true, &s_KernelLoadedModuleBaseInfoCookie, "InLoadOrderLinks"); } ULONG64 WINAPI ExtNtOsInformation::GetKernelProcessListHead(void) { return GetNtDebuggerData(DEBUG_DATA_PsActiveProcessHeadAddr, "nt!PsActiveProcessHead", 0); } ExtRemoteTypedList WINAPI ExtNtOsInformation::GetKernelProcessList(void) { ExtRemoteTypedList List(GetKernelProcessListHead(), "nt!_EPROCESS", "ActiveProcessLinks", 0, 0, &s_KernelProcessBaseInfoCookie, true); List.m_MaxIter = 4000; return List; } ExtRemoteTyped WINAPI ExtNtOsInformation::GetKernelProcess(_In_ ULONG64 Offset) { // We are caching both type and link information // so provide a link field here to keep the // cache properly filled out. return ExtRemoteTyped("nt!_EPROCESS", Offset, true, &s_KernelProcessBaseInfoCookie, "ActiveProcessLinks"); } ULONG64 WINAPI ExtNtOsInformation::GetKernelProcessThreadListHead(_In_ ULONG64 Process) { return Process + g_Ext->GetCachedFieldOffset(&s_KernelProcessThreadListFieldCookie, "nt!_EPROCESS", "Pcb.ThreadListHead"); } ExtRemoteTypedList WINAPI ExtNtOsInformation::GetKernelProcessThreadList(_In_ ULONG64 Process) { ExtRemoteTypedList List(GetKernelProcessThreadListHead(Process), "nt!_ETHREAD", "Tcb.ThreadListEntry", 0, 0, &s_KernelThreadBaseInfoCookie, true); List.m_MaxIter = 15000; return List; } ExtRemoteTyped WINAPI ExtNtOsInformation::GetKernelThread(_In_ ULONG64 Offset) { // We are caching both type and link information // so provide a link field here to keep the // cache properly filled out. return ExtRemoteTyped("nt!_ETHREAD", Offset, true, &s_KernelThreadBaseInfoCookie, "Tcb.ThreadListEntry"); } ULONG64 WINAPI ExtNtOsInformation::GetUserLoadedModuleListHead(_In_ bool NativeOnly) { HRESULT Status; if (NativeOnly || !g_Ext->Is32On64()) { DEBUG_VALUE Data; if (FAILED(Status = g_Ext->m_Control-> Evaluate("@@c++(&@$peb->Ldr->InLoadOrderModuleList)", DEBUG_VALUE_INT64, &Data, NULL))) { g_Ext->ThrowRemote(Status, "Unable to get loader list head from PEB"); } return Data.I64; } else { // We're looking at a 32-bit structure so only // pull out a 32-bit pointer value. We do // not sign-extend as this is a UM pointer and // should not get sign-extended. return GetAltPeb(). Eval("&@$extin->Ldr->InLoadOrderModuleList").GetUlong(); } } ExtRemoteTypedList WINAPI ExtNtOsInformation::GetUserLoadedModuleList(_In_ bool NativeOnly) { if (NativeOnly || !g_Ext->Is32On64()) { ExtRemoteTypedList List(GetUserLoadedModuleListHead(NativeOnly), "${$ntnsym}!_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks", 0, 0, &s_UserOsLoadedModuleBaseInfoCookie, true); List.m_MaxIter = 1000; return List; } else { ExtRemoteTypedList List(GetUserLoadedModuleListHead(NativeOnly), "${$ntwsym}!_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks", 0, 0, &s_UserAltLoadedModuleBaseInfoCookie, true); List.m_MaxIter = 1000; return List; } } ExtRemoteTyped WINAPI ExtNtOsInformation::GetUserLoadedModule(_In_ ULONG64 Offset, _In_ bool NativeOnly) { // We are caching both type and link information // so provide a link field here to keep the // cache properly filled out. if (NativeOnly || !g_Ext->Is32On64()) { return ExtRemoteTyped("${$ntnsym}!_LDR_DATA_TABLE_ENTRY", Offset, true, &s_UserOsLoadedModuleBaseInfoCookie, "InLoadOrderLinks"); } else { return ExtRemoteTyped("${$ntwsym}!_LDR_DATA_TABLE_ENTRY", Offset, true, &s_UserAltLoadedModuleBaseInfoCookie, "InLoadOrderLinks"); } } ULONG64 WINAPI ExtNtOsInformation::GetOsPebPtr(void) { HRESULT Status; ULONG64 Offset; if ((Status = g_Ext->m_System-> GetCurrentProcessPeb(&Offset)) != S_OK) { g_Ext->ThrowRemote(Status, "Unable to get OS PEB pointer"); } return Offset; } ExtRemoteTyped WINAPI ExtNtOsInformation::GetOsPeb(_In_ ULONG64 Offset) { return ExtRemoteTyped("${$ntnsym}!_PEB", Offset, true, &s_OsPebBaseInfoCookie); } ULONG64 WINAPI ExtNtOsInformation::GetOsTebPtr(void) { HRESULT Status; ULONG64 Offset; if ((Status = g_Ext->m_System-> GetCurrentThreadTeb(&Offset)) != S_OK) { g_Ext->ThrowRemote(Status, "Unable to get OS TEB pointer"); } return Offset; } ExtRemoteTyped WINAPI ExtNtOsInformation::GetOsTeb(_In_ ULONG64 Offset) { return ExtRemoteTyped("${$ntnsym}!_TEB", Offset, true, &s_OsTebBaseInfoCookie); } ULONG64 WINAPI ExtNtOsInformation::GetAltPebPtr(void) { ExtRemoteTyped AltTeb = GetAltTeb(); return AltTeb.Field("ProcessEnvironmentBlock").GetUlong(); } ExtRemoteTyped WINAPI ExtNtOsInformation::GetAltPeb(_In_ ULONG64 Offset) { return ExtRemoteTyped("${$ntwsym}!_PEB", Offset, true, &s_AltPebBaseInfoCookie); } ULONG64 WINAPI ExtNtOsInformation::GetAltTebPtr(void) { // If this is a 32-bit machine there's no // WOW64 TEB. if (!g_Ext->IsMachine64(g_Ext->m_ActualMachine)) { g_Ext->ThrowRemote(E_INVALIDARG, "No alternate TEB available"); } // // The pointer to the WOW64 TEB is the first pointer of // the 64-bit TEB. // ExtRemoteData OsTeb(GetOsTebPtr(), sizeof(ULONG64)); return OsTeb.GetUlong64(); } ExtRemoteTyped WINAPI ExtNtOsInformation::GetAltTeb(_In_ ULONG64 Offset) { return ExtRemoteTyped("${$ntwsym}!_TEB", Offset, true, &s_AltTebBaseInfoCookie); } ULONG64 WINAPI ExtNtOsInformation::GetCurPebPtr(void) { return g_Ext->Is32On64() ? GetAltPebPtr() : GetOsPebPtr(); } ExtRemoteTyped WINAPI ExtNtOsInformation::GetCurPeb(_In_ ULONG64 Offset) { return g_Ext->Is32On64() ? GetAltPeb(Offset) : GetOsPeb(Offset); } ULONG64 WINAPI ExtNtOsInformation::GetCurTebPtr(void) { return g_Ext->Is32On64() ? GetAltTebPtr() : GetOsTebPtr(); } ExtRemoteTyped WINAPI ExtNtOsInformation::GetCurTeb(_In_ ULONG64 Offset) { return g_Ext->Is32On64() ? GetAltTeb(Offset) : GetOsTeb(Offset); } ULONG64 WINAPI ExtNtOsInformation::GetNtDebuggerData(_In_ ULONG DataOffset, _In_ PCSTR Symbol, _In_ ULONG Flags) { ULONG64 Data; UNREFERENCED_PARAMETER(Flags); // // First check the kernel's data block. // if (g_Ext->m_Data-> ReadDebuggerData(DataOffset, &Data, sizeof(Data), NULL) == S_OK) { return Data; } // // Fall back on symbols. // if (g_Ext->m_Symbols-> GetOffsetByName(Symbol, &Data) != S_OK) { g_Ext->ThrowRemote(E_INVALIDARG, "Unable to find '%s', check your NT kernel symbols", Symbol); } return Data; } //---------------------------------------------------------------------------- // // Number-to-string helpers for things like #define translations. // //---------------------------------------------------------------------------- ExtDefine* WINAPI ExtDefineMap::Map(_In_ ULONG64 Value) { if ((m_Flags & Bitwise) != 0) { for (ExtDefine* Define = m_Defines; Define->Name; Define++) { if ((Define->Value & Value) == Define->Value) { return Define; } } } else { for (ExtDefine* Define = m_Defines; Define->Name; Define++) { if (Define->Value == Value) { return Define; } } } return NULL; } PCSTR WINAPI ExtDefineMap::MapStr(_In_ ULONG64 Value, _In_opt_ PCSTR InvalidStr) { ExtDefine* Define = Map(Value); if (Define) { return Define->Name; } if (InvalidStr) { return InvalidStr; } else { return g_Ext->PrintCircleString("<0x%I64x>", Value); } } void WINAPI ExtDefineMap::Out(_In_ ULONG64 Value, _In_ ULONG Flags, _In_opt_ PCSTR InvalidStr) { ULONG OldIndent = g_Ext->m_LeftIndent; g_Ext->m_LeftIndent = g_Ext->m_CurChar; if ((Flags & OutValue) != 0) { g_Ext->OutWrap("%I64x", Value); } else if ((Flags & OutValue32) != 0) { g_Ext->OutWrap("%08I64x", Value); } else if ((Flags & OutValue64) != 0) { g_Ext->OutWrap("%016I64x", Value); } if ((m_Flags & Bitwise) != 0) { if (!Value) { if ((Flags & ValueAny) == 0) { g_Ext->OutWrapStr(""); } } else { bool First = true; while (Value) { ExtDefine* Define = Map(Value); if (!Define && (Flags & ValueAny) != 0 && !InvalidStr) { // Value already displayed. break; } if (!First) { g_Ext->OutWrapStr(" | "); } else { if ((Flags & OutValueAny) != 0) { g_Ext->OutWrapStr(" "); } First = false; } if (Define) { g_Ext->OutWrapStr(Define->Name); Value &= ~Define->Value; } else { if (InvalidStr) { g_Ext->OutWrapStr(InvalidStr); } else { g_Ext->OutWrap("<0x%I64x>", Value); } break; } } } } else { if ((Flags & ValueAny) == 0 || InvalidStr) { if ((Flags & OutValueAny) != 0) { g_Ext->OutWrapStr(" "); } g_Ext->OutWrapStr(MapStr(Value, InvalidStr)); } else { ExtDefine* Define = Map(Value); if (Define) { InvalidStr = Define->Name; } if (InvalidStr) { if ((Flags & OutValueAny) != 0) { g_Ext->OutWrapStr(" "); } g_Ext->OutWrapStr(InvalidStr); } } } g_Ext->m_LeftIndent = OldIndent; } //---------------------------------------------------------------------------- // // Extension DLL exports. // //---------------------------------------------------------------------------- EXTERN_C BOOL WINAPI DllMain(HANDLE Instance, ULONG Reason, PVOID Reserved) { switch(Reason) { case DLL_PROCESS_ATTACH: ExtExtension::s_Module = (HMODULE)Instance; break; } if (g_ExtDllMain) { return g_ExtDllMain(Instance, Reason, Reserved); } return TRUE; } EXTERN_C HRESULT CALLBACK DebugExtensionInitialize(_Out_ PULONG Version, _Out_ PULONG Flags) { return g_ExtInstancePtr->BaseInitialize(ExtExtension::s_Module, Version, Flags); } EXTERN_C void CALLBACK DebugExtensionUninitialize(void) { if (!g_Ext.IsSet()) { return; } g_Ext->Uninitialize(); } EXTERN_C void CALLBACK DebugExtensionNotify(_In_ ULONG Notify, _In_ ULONG64 Argument) { if (!g_Ext.IsSet()) { return; } ExtExtension* Inst = g_Ext; switch(Notify) { case DEBUG_NOTIFY_SESSION_ACTIVE: Inst->OnSessionActive(Argument); break; case DEBUG_NOTIFY_SESSION_INACTIVE: Inst->OnSessionInactive(Argument); break; case DEBUG_NOTIFY_SESSION_ACCESSIBLE: Inst->OnSessionAccessible(Argument); break; case DEBUG_NOTIFY_SESSION_INACCESSIBLE: Inst->OnSessionInaccessible(Argument); break; } } EXTERN_C HRESULT CALLBACK KnownStructOutputEx(_In_ PDEBUG_CLIENT Client, _In_ ULONG Flags, _In_ ULONG64 Offset, _In_opt_ PCSTR TypeName, _Out_writes_opt_(*BufferChars) PSTR Buffer, _Inout_opt_ PULONG BufferChars) { if (!g_Ext.IsSet()) { return E_UNEXPECTED; } return g_Ext->HandleKnownStruct(Client, Flags, Offset, TypeName, Buffer, BufferChars); } EXTERN_C HRESULT CALLBACK DebugExtensionQueryValueNames(_In_ PDEBUG_CLIENT Client, _In_ ULONG Flags, _Out_writes_(BufferChars) PWSTR Buffer, _In_ ULONG BufferChars, _Out_ PULONG BufferNeeded) { if (!g_Ext.IsSet()) { return E_UNEXPECTED; } return g_Ext->HandleQueryValueNames(Client, Flags, Buffer, BufferChars, BufferNeeded); } EXTERN_C HRESULT CALLBACK DebugExtensionProvideValue(_In_ PDEBUG_CLIENT Client, _In_ ULONG Flags, _In_ PCWSTR Name, _Out_ PULONG64 Value, _Out_ PULONG64 TypeModBase, _Out_ PULONG TypeId, _Out_ PULONG TypeFlags) { if (!g_Ext.IsSet()) { return E_UNEXPECTED; } return g_Ext->HandleProvideValue(Client, Flags, Name, Value, TypeModBase, TypeId, TypeFlags); }