【问题标题】:Implementing DebugExtensionProvideValue corrupts WinDbg internal state?实现 DebugExtensionProvideValue 会破坏 WinDbg 内部状态?
【发布时间】:2016-09-21 21:20:16
【问题描述】:

我正在我的扩展中实现DebugExtensionProvideValue,以便我可以提供自定义伪寄存器。它在 CDB 中运行良好,最初在 WinDbg 中运行良好,但在停止调试并打开新的可执行文件后,发生了一些事情,WinDbg 最终处于奇怪的不可用状态。

当您触发问题时,WinDbg 会将此消息打印到命令窗口:

无法传递回调,3131

发生这种情况后,WinDbg 似乎在命令窗口中打印 all 两次输出!

我的扩展代码很简单:

EXTERN_C HRESULT CALLBACK DebugExtensionProvideValue(PDEBUG_CLIENT Client, ULONG Flags, IN PCWSTR Name, OUT PULONG64 Value, OUT PULONG64 TypeModBase, OUT PULONG TypeId, OUT PULONG TypeFlags)
{
    HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
    if (!Name || !Value || !TypeFlags) 
    {
        hr = E_INVALIDARG;
    }
    else if (0 == lstrcmpiW(Name, L"$$test"))
    {
        *Value = 0xDeadCafeBabeBeefULL;
        *TypeFlags = DEBUG_EXT_PVTYPE_IS_VALUE;
        if (TypeId) *TypeId = 0; // Where are these types defined?
        if (TypeModBase) *TypeModBase = 0;
        hr = S_OK;
    }
#if 0 // <-- ** Setting this to != 0 fixes the problem **
    Client->Release(); // This does not feel right but it does seem to be required!
#endif
    return hr;
}

EXTERN_C HRESULT CALLBACK DebugExtensionQueryValueNames(PDEBUG_CLIENT Client, ULONG Flags, OUT PWSTR Buffer, ULONG BufferChars, OUT PULONG BufferNeeded)
{
    static const WCHAR pseregs[] = L"$$test\0";
    if (BufferNeeded) *BufferNeeded = ARRAYSIZE(pseregs);
    memcpy(Buffer, pseregs, min(sizeof(pseregs), BufferChars*sizeof(*Buffer)));
    return ARRAYSIZE(pseregs) > BufferChars ? S_FALSE : S_OK;
}

EXTERN_C HRESULT CALLBACK DebugExtensionInitialize(OUT PULONG Version, OUT PULONG Flags)
{
    *Version = DEBUG_EXTENSION_VERSION(1, 0), *Flags = 0;
    return S_OK;
}

重现问题如下所示:

0:000> $$ Press Ctrl+E to open a executable, I'm going to open WinVer.exe
0:000> .load c:\test\myext.dll
0:000> ?@$$test
Evaluate expression: -2401039830915039505 = deadcafe`babebeef
0:000> $$ Press Shift+F5 to stop debugging
0:000> $$ Press Ctrl+E and open a executable again, WinDbg will now print "Unable to deliver callback, 3131"

我能够想出一个似乎确实有效但感觉不对的解决方法,因为我必须发布一个我从未 QI'ed 或 AddRef'ed 的接口。在我最少量的测试中,这个 hack 似乎从来没有崩溃过,通过查看 IDebugClients refcount,它在多次调用中似乎是正确的。

据我所知,您无法停止调试并在 CDB 中打开这样的新 .exe,因此问题似乎只能发生在 WinDbg 中。

是我做错了什么还是 DbgEng 中存在错误?

【问题讨论】:

  • 您使用的是 WinDbg 6.3.9600 吗?我退回到 6.2.9200,因为我经常重复输出。
  • @ThomasWeller 我已经测试了 6.12.0002.633 (2010.02.01) (32&64-bit)、6.2.9200.16384 (2012.07.26) 和 10.0.10586.0 (2015.10.30) (都是 32-bit)在所有这些中都有相同的结果。没有它,黑客和回调错误没有问题。互联网上似乎没有任何人使用 DebugExtensionProvideValue 的例子也无济于事,所以以前可能没有人真正这样做过?无论哪种方式,当先前的 IDebugClient 尚未释放时,您获得双重输出是有意义的,它仍然可以访问输出回调和 WinDbg UI。
  • 看看 engextcpp.cpp ExtExtension::HandleProvideValue 这是这个回调的默认实现,你可以通过在它使用 _try_finally 的 .def 文件中定义你自己的来绕过它来保证 release(); // 使用硬 SEH try/finally 来保证 // Release 总是发生。 __try { ExtProvidedValue* ExtVal = m_ProvidedValues; while (ExtVal && ExtVal->ValueName) xxxxxxxxxxxxxxxxxxxxxx __finally { Release(); } 返回状态; }
  • @blabb 不,这不是一回事,在该函数的开头,它调用 Query() 来初始化自己,并且为很多事情执行 QueryInterface,这些就是它在结束(包括它从 REQ_IF(IDebugClient, m_Client); 获得的 IDebugClient)。你所说的 Release 是一个成员函数,我说的是作为参数传递的 IDebugClient,我从不增加引用计数,所以我不应该真正释放它......
  • @Anders 这个错误似乎也存在于注释的代码路径中,我还添加了一个答案来解决 typeid 和 TypemodBase 查询看看

标签: windows com windbg dbgeng


【解决方案1】:

@安德斯

我找到了一些时间在不同的代码路径中检查它,似乎 dbgeng ClientListCapture / Find / fill / list / FindExt 中存在错误
增加的引用计数似乎没有减少,导致引用累积在每次关闭和打开时增加了几个回调

我使用的代码路径是设置扩展类的 m_ProvidedValue 成员

注意,我还添加了代码来解决下面的其他两个查询 TypeId 和 TypeModBase

#include <engextcpp.cpp>
ExtProvidedValue pval[];
class EXT_CLASS : public ExtExtension {
public:
    void
    handler (
    _In_ ULONG Flags, _In_ PCWSTR ValueName, _Out_ PULONG64 Value,
    _Out_ PULONG64 TypeModBase, _Out_ PULONG TypeId, _Out_ PULONG TypeFlags
    )   {

        DEBUG_CACHED_SYMBOL_INFO Info;
        ZeroMemory(&Info, sizeof(Info));
        PCSTR Type = "ntdll!_LDR_DATA_TABLE_ENTRY";
        HRESULT Status = E_FAIL;
        if((Status = m_Symbols->GetSymbolTypeId(Type,&Info.Id,&Info.ModBase)) != S_OK) {
            ThrowStatus(Status, "Unable to get type ID of '%s'", Type);
        }
        if (0 == lstrcmpiW(ValueName, L"$$test")) {
            if(Value)       { *Value        = 0xDeadCafeBabeBeefULL; }
            if(TypeModBase) { *TypeModBase  = Info.ModBase; }
            if(TypeId)      { *TypeId       = Info.Id; }
            if(TypeFlags)   { *TypeFlags    = DEBUG_EXT_PVTYPE_IS_POINTER; }
        }
    };
    HRESULT Initialize(void) {
        this->m_ProvidedValues = pval;
        return S_OK;
    }
};
EXT_DECLARE_GLOBALS();
ExtProvideValueMethod mymethod = (ExtProvideValueMethod)&Extension::handler;
ExtProvidedValue pval[] = {L"$$test\0",mymethod,NULL,NULL};

def 文件包含

EXPORTS
    DebugExtensionInitialize
    DebugExtensionQueryValueNames
    DebugExtensionProvideValue
    help

与企业级wdk编译链接如下

@echo off
IF "%donesetup%" == "" ( pushd .. )
IF "%donesetup%" == "" ( cd /d E:\ewdk )
IF "%donesetup%" == "" ( @call launchbuildenv.cmd )
IF "%donesetup%" == "" ( popd )
IF "%donesetup%" == "" ( set  "donesetup=donesetup" )

IF "%INCLUDE%"   == "" ( set "INCLUDE=%vcinstalldir%include;%windowssdkdir%Include\10.0.10586.0\ucrt;%windowssdkdir%Include\10.0.10586.0\um;%windowssdkdir%Include\10.0.10586.0\shared;%windowssdkdir%Debuggers\inc;" )
IF "%LIB%"       == "" ( set "LIB=%vcinstalldir%\LIB;%WINDOWSSDKDIR%Lib\10.0.10586.0\ucrt\x86;%WINDOWSSDKDIR%Lib\10.0.10586.0\um\x86;%windowssdkdir%Debuggers\lib\x86")
IF "%LINKLIBS%"  == "" ( set "LINKLIBS=user32.lib kernel32.lib dbgeng.lib dbghelp.lib" )



cl /LD /nologo /W3 /Zi  /EHsc pstest.cpp /link /DEF:pstest.def /RELEASE %linklibs%

move /y pstest.dll "e:\ewdk\Program Files\Windows Kits\10\Debuggers\x86\winext"\.

将扩展加载到windbg和调用结果

Microsoft (R) Windows Debugger Version 10.0.10586.567 X86

0:000> .load pstest
0:000> ? @$$test   (masm Evaluate)

Evaluate expression: -2401039830915039505 = deadcafe`babebeef
0:000> ?? @$$test   (c++ Evaluate note the use of TypeId and TypeModbase   
 results in a type Display instead of a Int64 value)

struct _LDR_DATA_TABLE_ENTRY * 0xbabebeef
   +0x000 InLoadOrderLinks : _LIST_ENTRY
   +0x008 InMemoryOrderLinks : _LIST_ENTRY
   +0x010 InInitializationOrderLinks : _LIST_ENTRY
   +0x018 DllBase          : ???? 
   +0x01c EntryPoint       : ???? 
   +0x020 SizeOfImage      : ??
   +0x024 FullDllName      : _UNICODE_STRING 
   +0x02c BaseDllName      : _UNICODE_STRING 
   +0x034 Flags            : ??
   +0x038 LoadCount        : ??
   +0x03a TlsIndex         : ??
   +0x03c HashLinks        : _LIST_ENTRY
   +0x03c SectionPointer   : ???? 
   +0x040 CheckSum         : ??
   +0x044 TimeDateStamp    : ??
   +0x044 LoadedImports    : ???? 
   +0x048 EntryPointActivationContext : ???? 
   +0x04c PatchInformation : ???? 
   +0x050 ForwarderLinks   : _LIST_ENTRY
   +0x058 ServiceTagLinks  : _LIST_ENTRY
   +0x060 StaticLinks      : _LIST_ENTRY
   +0x068 ContextInformation : ???? 
   +0x06c OriginalBase     : ??
   +0x070 LoadTime         : _LARGE_INTEGER

如果我执行 Shift+f5 并且 ctrl+e windbg 抱怨它无法提供回调
如果重复 shift +f5 / ctrl+e / load / invoke 的过程
投诉的数量不断增加,以至于 windbg 挂起
尝试在挂起的调用堆栈中处理 PendingMessages

【讨论】:

  • 我的值类型不存在于我有符号的任何模块中,这就是为什么我想尽可能直接设置它们,但 MSDN 对这些输出参数的描述并不完全有帮助。如果我们信任 MSDN,那么您的代码是错误的,因为 TypeModBase 应该是“客户端的基本起始地址”,但这当然没有意义,因为客户端是一个它已经知道一切的接口。
  • 好吧,不要与你的推理争论,但还有其他 api 似乎将 TypeModBase 解释为包含类型的模块的 ModuleBase 并且 TypeId 是与模块关联的 pdb 文件中的索引
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-06
  • 1970-01-01
  • 2023-02-07
  • 2019-09-09
  • 1970-01-01
  • 2016-07-25
相关资源
最近更新 更多