3

I'm implementing DebugExtensionProvideValue in my extension so I can provide custom pseudo-registers. It works perfectly in CDB and it works fine initially in WinDbg but after stopping debugging and opening a new executable something happens and WinDbg ends up in a weird unusable state.

WinDbg prints this message to the command window when you trigger the problem:

Unable to deliver callback, 3131

and after this happens WinDbg seems to print all output twice in the command window!

My extension code is very simple:

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;
}

Reproducing the issue looks something like this:

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"

I was able to come up with a workaround that does seem to work but it just does not feel right because I have to Release a interface I never QI'ed nor AddRef'ed. In my minimal amount of testing this hack never seemed to crash and by peeking at IDebugClients refcount it does seem correct over multiple calls.

As far as I can tell you cannot stop debugging and open a new .exe like this in CDB so it seems the problem can only happen WinDbg.

Am I doing something wrong or is there a bug in DbgEng?

Anders
  • 97,548
  • 12
  • 110
  • 164
  • Are you using WinDbg 6.3.9600? I stepped back to 6.2.9200 because I had that duplicate output too often. – Thomas Weller May 24 '16 at 19:28
  • 1
    @ThomasWeller I have tested 6.12.0002.633 (2010.02.01) (32&64-bit), 6.2.9200.16384 (2012.07.26) and 10.0.10586.0 (2015.10.30) (both 32-bit) with the same result in all of them. No problems with the hack and the callback error without it. It does not help that there seems to be no examples on the internet of anyone using DebugExtensionProvideValue so it could be possible that nobody has actually done this before? Either way, it makes sense that you get double output when the previous IDebugClient has not been released, it somehow still has access to the output callback and the WinDbg UI. – Anders May 24 '16 at 21:17
  • take a look at engextcpp.cpp ExtExtension::HandleProvideValue which is the default implementation of this call back which you can bypass by defining your own in .def file it uses _try_finally to guarentee release(); // Use a hard SEH try/finally to guarantee that // Release always occurs. __try { ExtProvidedValue* ExtVal = m_ProvidedValues; while (ExtVal && ExtVal->ValueName) xxxxxxxxxxxxxxxxxxxxxx __finally { Release(); } return Status; } – blabb May 24 '16 at 22:32
  • @blabb No that is not the same thing, at the start of that function it calls Query() to initialize itself and that does QueryInterface for a lot of things and those are the ones it releases at the end (including the IDebugClient it got from REQ_IF(IDebugClient, m_Client);). The Release you are talking about is a member function, I'm talking about the IDebugClient passed as a parameter and I'm never incrementing the reference count so I should not really be releasing it... – Anders May 24 '16 at 22:52
  • @Anders the bug seems to exist in the commented code path too also i added an answer to address the typeid and TypemodBase query take a look – blabb Jun 10 '16 at 08:37

1 Answers1

0

@Anders

I found some time to check it in a different code-path and it appears there is a bug in dbgeng ClientListCapture / Find / fill / list / FindExt
an incremented Reference count doesnt seem to be decremented resulting in a ref accumulation adding up several callbacks on each close and open

the code-path i used was to set the m_ProvidedValue member of the Extension Class

Note I also added code to address your other two queries TypeId and TypeModBase below

#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 file contains

EXPORTS
    DebugExtensionInitialize
    DebugExtensionQueryValueNames
    DebugExtensionProvideValue
    help

compiled and linked with enterprise wdk as follows

@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"\.

loading the extension into windbg and result of invocation

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

and as posted by you if i do Shift+f5 and ctrl+e windbg complains that it cannot deliver the callback
if the procedure of shift +f5 / ctrl+e / load / invoke is repeated
the number of complaints keeps on adding to the point that windbg hangs
trying to ProcessPendingMessages in hung callstack

blabb
  • 8,674
  • 1
  • 18
  • 27
  • My value type does not exist in any module that I have symbols for which is why I wanted to set them directly if possible but MSDN is not exactly helpful in its description of these out parameters. If we trust MSDN then your code is wrong because TypeModBase is supposed to be "The base starting address for Client" but that of course makes no sense since Client is a interface it already knows everything about. – Anders Jun 10 '16 at 13:48
  • Well not to argue with your reasoning but there are other apis that seem to interpret TypeModBase as the ModuleBase of the Module that contains the type And TypeId is an index in the modules associated pdb file – blabb Jun 11 '16 at 12:30