Search code examples
clrwindbghistorical-debugging

Play back to the moment of creation of some managed object in WinDbg TTD


Using WinDbg with TTD enabled, how to play back to the moment of creation of some managed object? Lets say I do have its address obtained using !clrstack -a or !dso


Solution

  • There are several ways of doing this. If you have the address of the object and want to go back to its creation you can use a ba (break on access). When an object is created the Method Table address is written into the first word (4 bytes for 32-bit, 8 bytes for 64-bit). So adding a breakpoint per write access and going backwards will stop at the object creation. Another way is to add breakpoints pointing to all constructors of the object and also going backwards. Also notice that all of this can also be done by a 'dx' command filtering by breakpoint address or constructor call.

    Imagine you have this output:

    0:016> !dso
    OS Thread Id: 0x2f54 (16)
    RSP/REG          Object           Name
    000000A2CD5FC770 000001e9849cd4b8 ConceptNetConsole1.SampleClass1
    (...)
    
    0:016> !DumpObj /d 000001e9849cd4b8
    Name:        ConceptNetConsole1.SampleClass1
    MethodTable: 00007ffb85545ef8 <<< This is the method table
    EEClass:     00007ffb85542d68
    Size:        136(0x88) bytes
    File:        C:\Projects\ConceptNetConsole1\ConceptNetConsole1\bin\x64\Release\ConceptNetConsole1.exe
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    00007ffbe38e70b0  40005a8        8        System.Object  0 instance 000001e9849cd718 __identity
    (...)
    

    Approach 1: And you want to stop when this object is created you can use (for 32-bit use w4):

    ba w8 000001e9849cd4b8
    g-
    

    Using 'dx' (notice that the address must be in C++ format, starting wit '0x'):

    dx -g @$cursession.TTD.Memory(0x00001e9849cd4b8,0x00001e9849cd4b8+8,"w")
    

    Again, for 32-bit use address+4 on the second parameter. The option -g will show in a grid format.

    Approach 2:

    Get the address(es) of the constructors by listing the methods table of the class:

    0:016> !dumpmt -md 00007ffb85545ef8
    EEClass:         00007ffb85542d68
    Module:          00007ffb85545408
    Name:            ConceptNetConsole1.SampleClass1
    mdToken:         0000000002000005
    File:            C:\Projects\ConceptNetConsole1\ConceptNetConsole1\bin\x64\Release\ConceptNetConsole1.exe
    BaseSize:        0x88
    ComponentSize:   0x0
    Slots in VTable: 23
    Number of IFaces in IFaceMap: 0
    --------------------------------------
    MethodDesc Table
               Entry       MethodDesc    JIT Name
    00007ffbe36fb1f0 00007ffbe3257538 PreJIT System.Object.ToString()
    00007ffbe36ffd90 00007ffbe3257540 PreJIT System.Object.Equals(System.Object)
    00007ffbe3721dc0 00007ffbe3257568 PreJIT System.Object.GetHashCode()
    00007ffbe36fce50 00007ffbe3257580 PreJIT System.Object.Finalize()
    00007ffbe37d8f40 00007ffbe333cfd0 PreJIT System.MarshalByRefObject.GetLifetimeService()
    00007ffbe36f8b10 00007ffbe333cfd8 PreJIT System.MarshalByRefObject.InitializeLifetimeService()
    00007ffbe37cbd80 00007ffbe333cfe0 PreJIT System.MarshalByRefObject.CreateObjRef(System.Type)
    00007ffb85560090 00007ffb85545d58    JIT ConceptNetConsole1.SampleClass1..ctor()  <<< This is the constructor
    (...)
    

    You may simply set the breakpoint at the 'Entry' address (or use !sos.bpmd) and go backwards:

    bp 00007ffb85560090
    g-
    

    Or use 'dx' to show all occasions where the code was called (note that the code was again adjusted to look like C++ '0x' and also that is in quotes):

    dx -g @$cursession.TTD.Calls("0x00007ffb85560090")
    

    Hope it works for you.