【发布时间】: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 查询看看