Search code examples
debuggingwindbgdumpdebug-symbolspykd

How to get variable names out of a dump and a symbol file?


I'm debugging dump files, while I have access to the symbol files.

I'm using a script, which combines the results of following windbg commands:

x /2 *!*  // which types are present in the symbol files?
!heap -h 0 // which memory address ranges are used in the dump?

(At least, that's how I understand it. In case I'm wrong don't hesitate to correct me)

The result of the script (called heap_stat.py, found under this list of Windbg extensions) is a list of memory addresses, followed by their type. By taking statistics of those I can derive if there is a memory leak.

In top of this, using the WinDbg command dt CStringArray m_nSize of the mentioned memory address (in the specific case of a CStringArray, of course), I can see the total amount of entries of the used CStringArray objects, and see if there are CStringArray objects with lots of entries.

However there is a drawback to this system:
When I find such a CStringArray object with a lot of entries, I'm stuck there because out of all the CStringArray objects in my application, I have no idea which one I'm dealing with.

One thing which might help is the name of the local variable the memory address is about, but there's the catch: I don't know if this information is present and in case yes, can I find it in the symbol file or in the dump, and (obviously) which command do I need to run in order to get this information (I didn't find a flag that makes dt <flag> <memory address> return the local variable name, occupied by <memory address>)?

Can anybody help me?

For clarification purposes, this is how the result of my script currently looks like:

0x0065a4d0  mfc110u!CStringArray  Size:[1]
0x0065a4e4  mfc110u!CStringArray  Size:[0]
0x0065a4f8  mfc110u!CStringArray  Size:[295926]
0x0065a520  mfc110u!CStringArray  Size:[0]

As you can see, I can see the memory address where my variable is stored, I can see the type (retrieved from the symbols files), and I can see the amount of entries (retrieved from the dt Windbg command), but I'd like to have an output like the following:

0x0065a4d0  mfc110u!CStringArray  Size:[1]      var1
0x0065a4e4  mfc110u!CStringArray  Size:[0]      var2
0x0065a4f8  mfc110u!CStringArray  Size:[295926] var3
0x0065a520  mfc110u!CStringArray  Size:[0]      var3

or:

0x0065a4d0  mfc110u!CStringArray  Size:[1]      obj1.prop1
0x0065a4e4  mfc110u!CStringArray  Size:[0]      obj2.prop1
0x0065a4f8  mfc110u!CStringArray  Size:[295926] obj1.prop2
0x0065a520  mfc110u!CStringArray  Size:[0]      obj1.prop2

Such an output would indicate me that I need to verify what is happening to var3 or obj1.prop2 in the source code.


Solution

  • I wrote the following MFC application (partial source):

    CStringArray stringarray;
    void* anotherarray = new CStringArray();
    
    void CLocalVariableNameApp::AnotherMethod(void* a)
    {
        CStringArray* temp = static_cast<CStringArray*>(a);
        temp->Add(L"Something else");
    }
    
    CLocalVariableNameApp::CLocalVariableNameApp()
    {
        stringarray.Add(L"Something");
        AnotherMethod(anotherarray);
        CStringArray* holycow = static_cast<CStringArray*>(anotherarray);
        holycow->Add(L"Yet something else");
        DebugBreak();
    }
    

    As you can see, the second CStringArray has multiple names: anotherarray, a, temp and holycow.

    Observation 1: there will not be an easy 1:1 mapping on memory addresses and variable names.

    Local variable names are available from the PDB files:

    0:000> k
     # ChildEBP RetAddr  
    00 0022fa40 00fa2a30 KERNELBASE!DebugBreak+0x2
    01 0022fb3c 00f97958 LocalVariableName!CLocalVariableNameApp::CLocalVariableNameApp+0x90 [c:\...\localvariablename.cpp @ 38] 
    02 0022fc10 014b628a LocalVariableName!`dynamic initializer for 'theApp''+0x28 [c:\...\localvariablename.cpp @ 43] 
    03 0022fc18 014b5e8c LocalVariableName!_initterm+0x1a [f:\...\crt0dat.c @ 955] 
    04 0022fc2c 014a9263 LocalVariableName!_cinit+0x6c [f:\...\crt0dat.c @ 308] 
    05 0022fc78 014a947d LocalVariableName!__tmainCRTStartup+0xf3 [f:\...\crt0.c @ 237] 
    06 0022fc80 7558336a LocalVariableName!wWinMainCRTStartup+0xd [f:\...\crt0.c @ 165] 
    07 0022fc8c 771198f2 kernel32!BaseThreadInitThunk+0xe
    08 0022fccc 771198c5 ntdll!__RtlUserThreadStart+0x70
    09 0022fce4 00000000 ntdll!_RtlUserThreadStart+0x1b
    0:000> .frame 1
    01 0022fb3c 00f97958 LocalVariableName!CLocalVariableNameApp::CLocalVariableNameApp+0x90 [c:\...\localvariablename.cpp @ 38] 
    0:000> dv
               this = 0x01637118
            holycow = 0x00445820
    

    Note that the names a and temp are not visible.

    Observation 2: variable names will be limited to their scope. If you want to remember all variable names, you would need to track all functions.

    Above was a debug build. The release build is different:

    0:000> k
     # ChildEBP RetAddr  
    00 005efe04 002a1c18 KERNELBASE!DebugBreak+0x2
    01 005efe20 002a1095 LocalVariableName!CLocalVariableNameApp::CLocalVariableNameApp+0x88 [c:\...\localvariablename.cpp @ 39] 
    02 005efe24 003c0289 LocalVariableName!`dynamic initializer for 'theApp''+0x5 [c:\...\localvariablename.cpp @ 43] 
    03 005efe38 003c01ea LocalVariableName!_initterm+0x29 [f:\...\crt0dat.c @ 954] 
    04 005efe48 003bd280 LocalVariableName!_cinit+0x5a [f:\...\crt0dat.c @ 321] 
    05 005efe88 7558336a LocalVariableName!__tmainCRTStartup+0xde [f:\...\crt0.c @ 237] 
    06 005efe94 771198f2 kernel32!BaseThreadInitThunk+0xe
    07 005efed4 771198c5 ntdll!__RtlUserThreadStart+0x70
    08 005efeec 00000000 ntdll!_RtlUserThreadStart+0x1b
    0:000> .frame 1
    01 005efe20 002a1095 LocalVariableName!CLocalVariableNameApp::CLocalVariableNameApp+0x88 [c:\...\localvariablename.cpp @ 39] 
    0:000> dv
               this = 0x0043f420
    

    Note that holycow is missing.

    Observation 3: in release builds, variables might not be needed (optimized) and therefore not present.

    Overall conclusion: it's not possible to map memory addresses to variable names.