WinDbg the easy way (Part 2)

Part 1

Saving dumps

When debugging a problem that is not easy to reproduce, I sometimes want to make a snapshot of the application's state (memory contents, the list of open handles, and so on) and save it in a file for further analysis. It can be useful when, for example, I suspect that the current state can contain the key to the problem I am trying to solve, but want to continue running the application to see how the situation develops. Sometimes I make a series of snapshots, one after another, so that I could compare them later and see how some data structures change while the application is running. And I always create a snapshot when I have finally managed to reproduce the problem, to make sure that I don't lose valueable information if, for example, I close the debugging session by mistake. Probably, it is not difficult to guess that when I say “snapshot” I actually mean “minidump”, because minidumps proved to be very convenient for saving the application state at any moment of time.

Here is the command line that can be used to create a minidump:

  cdb -pv -pn myapp.exe -c ".dump /m c:\myapp.dmp;q"

Let's take a closer look at .dump command. In the example above, this command receives only one option (/m) and is followed by the name of the minidump file. /m option is used to specify what kinds of information should be included into the minidump. The most important (in my opinion) variants of /m option are listed in the following table:

Option Description Example
/m This option is used by default. It creates a standard minidump, equivalent to MiniDumpNormal minidump type. The resulting minidump is usually very small, and therefore this option is useful if you want to transfer the minidump over a slow network. But unfortunately, small size of the minidump also means that in most cases it does not contain enough information for serious analysis (you can find more information about minidump contents in this article) .dump /m c:\myapp.dmp
/ma Minidump with all possible options (complete memory contents, handles, unloaded modules, etc.), and the resulting minidump can be huge. This option is the best for local debugging, if disk space is not limited. .dump /ma c:\myapp.dmp
/mFhutwd Minidump with data sections, non-shared read/write memory pages and other useful information. This option can be used if you want to collect as much information as possible, but still need to keep the minidump relatively small (and compressible). .dump /mFhutwd c:\myapp.dmp

The following command creates a minidump that includes all possible kinds of information:

  cdb -pv -pn myapp.exe -c ".dump /ma c:\myapp.dmp;q"

What if we want to create a new minidump and overwrite the existing one? By default, .dump command does not allow to do it – it complains that the file with the given name already exists. To change the default behavior and overwrite the existing minidump file, we can use /o option:

  cdb -pv -pn myapp.exe -c ".dump /ma /o c:\myapp.dmp;q"

If we want to create a series of minidumps, one after another, it can be handy to name the minidump files so that the names reflect the time when the minidumps were created. Good news are that .dump command can do it automatically, if we specify /u option. For example, the following command could produce a minidump called myapp_02CC_2006-01-28_04-11-18-171_0158.dmp (0158 is the process id):

  cdb -pv -pn myapp.exe -c ".dump /m /u c:\myapp.dmp;q"

.dump command also supports other interesting options (you can find them in the documentation).

If you want to create a minidump of a process that is running under Visual Studio debugger, I would recommend to temporarily disable all breakpoints in Visual Studio before creating the dump. If breakpoints are not disabled, the minidump will contain breakpoint instructions (int 3) inserted by Visual Studio debugger into the code of the target process.

Crash dump analysis

CDB can also be used to automate crash dump analysis. The automation is possible because we usually have to perform the same set of operations when we start analysing a crash dump. What operations? It depends on the kind of the crash dump. I would separate all crash dumps into two main categories:

  • crash dumps with exception information
  • crash dumps without exception information

Crash dumps with exception information are usually created when the application raises an unhandled exception and invokes a just-in-time debugger (Dr. Watson, NTSD, or other) or creates a minidump in the custom filter for unhandled exceptions. Exception information is written into the crash dump so that we could determine the type of the exception and the place in the code where it occurred. Crash dumps without exception information are usually created manually, when we want to create a snapshot of the process for further analysis (for example, with the help of techniques described in the previous chapter of this article).

When we start debugging a crash dump with exception information, we usually want to know the following:

  • the place in the code where the exception occurred (address, source file and line number)
  • call stack at the moment when the exception was raised
  • values of function parameters and local variables, for some or all functions on the call stack

WinDbg and CDB support a very useful command for crash dump debugging - !analyze. This command analyzes exception information in the crash dump, determines the place where the exception occurred, the call stack, and displays detailed report. Here is how to use this command:

  cdb -z c:\myapp.dmp -logo out.txt -lines -c "!analyze -v;q"

(-v option asks !analyze to display verbose output)

CrashDemo.cpp sample demonstrates how to use a custom filter to catch unhandled exceptions and create minidumps. If you compile it and run, and then use the abovementioned CDB command to analyze the resulting minidump, you will get an output similar to the following:

0:001> !analyze -v
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************

FAULTING_IP: 
CrashDemo!TestFunc+2e [c:\tests\crashdemo\crashdemo.cpp @ 124]
004309de c70000000000     mov     dword ptr [eax],0x0

EXCEPTION_RECORD:  ffffffff -- (.exr ffffffffffffffff)
.exr ffffffffffffffff
ExceptionAddress: 004309de (CrashDemo!TestFunc+0x0000002e)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 00000000
Attempt to write to address 00000000

DEFAULT_BUCKET_ID:  APPLICATION_FAULT

PROCESS_NAME:  CrashDemo.exe

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referenced memory 
  at "0x%08lx". The memory could not be "%s".

WRITE_ADDRESS:  00000000 

BUGCHECK_STR:  ACCESS_VIOLATION

LAST_CONTROL_TRANSFER:  from 0043096e to 004309de

STACK_TEXT:
006afe88 0043096e 00000000 00354130 00350001 CrashDemo!TestFunc+0x2e 
  [c:\tests\crashdemo\crashdemo.cpp @ 124]
006aff6c 00430f31 00000000 52319518 00354130 CrashDemo!WorkerThread+0x5e 
  [c:\tests\crashdemo\crashdemo.cpp @ 115]
006affa8 00430ea2 00000000 006affec 7c80b50b CrashDemo!_callthreadstartex+0x51 
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 348]
006affb4 7c80b50b 00355188 00354130 00350001 CrashDemo!_threadstartex+0xa2 
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 331]
006affec 00000000 00430e00 00355188 00000000 kernel32!BaseThreadStart+0x37


FOLLOWUP_IP: 
CrashDemo!TestFunc+2e [c:\tests\crashdemo\crashdemo.cpp @ 124]
004309de c70000000000     mov     dword ptr [eax],0x0

SYMBOL_STACK_INDEX:  0

FOLLOWUP_NAME:  MachineOwner

SYMBOL_NAME:  CrashDemo!TestFunc+2e

MODULE_NAME:  CrashDemo

IMAGE_NAME:  CrashDemo.exe

DEBUG_FLR_IMAGE_TIMESTAMP:  43dc6ee7

STACK_COMMAND:  .ecxr ; kb

FAILURE_BUCKET_ID:  ACCESS_VIOLATION_CrashDemo!TestFunc+2e

BUCKET_ID:  ACCESS_VIOLATION_CrashDemo!TestFunc+2e

Followup: MachineOwner
---------

Pay attention to the blocks of the text shown in bold. The first block reports the address and the type of the exception. The second block reports the call stack. And the third block gives us additional information on how to access the exception information stored in the crash dump.

Now we know the place where the exception occurred, and can even see the call stack. It's time to get the values of function parameters and local variables. Before we start, let's pay attention to the third selected block of information reported by !analyze. To repeat, the block contains the following:

STACK_COMMAND:  .ecxr ; kb

We already know 'kb' command (it displays the call stack). But what is .ecxr? This command asks the debugger to switch the current context to the one stored in the crash dump's exception information. After we have executed .ecxr, and only after that, we can reliably get access to the call stack and the values of local variables at the moment when the exception was raised.

After we have asked the debugger to use the exception context, we can use 'dv' command to display the values of function parameters and local variables. Since we usually want to see this information for every function on the call stack, we should actually use '!for_each_frame dv /t' command (/t option asks 'dv' to show type information, which is also useful). (And of course, we have to remember that in optimized builds some local variables can be optimized away, enregistered, or reused to store other data throughout the lifetime of the function, and as a result the values reported by 'dv' command can be incorrect).

Here is the final command line for analysis of crash dumps with exception information:

  cdb -z c:\myapp.dmp -logo out.txt -lines -c "!analyze -v;.ecxr;!for_each_frame dv /t;q"

And here is the sample output of '!for_each_frame dv /t' command:

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
00 006afe88 0043096e CrashDemo!TestFunc+0x2e [c:\tests\crashdemo\crashdemo.cpp @ 124]
int * pParam = 0x00000000
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
01 006aff6c 00430f31 CrashDemo!WorkerThread+0x5e [c:\tests\crashdemo\crashdemo.cpp @ 115]
void * lpParam = 0x00000000
int * TempPtr = 0x00000000
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
02 006affa8 00430ea2 CrashDemo!_callthreadstartex+0x51 
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 348]
struct _tiddata * ptd = 0x00355188
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
03 006affb4 7c80b50b CrashDemo!_threadstartex+0xa2 
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 331]
void * ptd = 0x00355188
struct _tiddata * _ptd = 0x00000000
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
04 006affec 00000000 kernel32!BaseThreadStart+0x37
Unable to enumerate locals, HRESULT 0x80004005
Private symbols (symbols.pri) are required for locals.
Type ".hh dbgerr005" for details.
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
00 006afe88 0043096e CrashDemo!TestFunc+0x2e [c:\tests\crashdemo\crashdemo.cpp @ 124]

If the minidump does not include the complete contents of the target process' memory, the debugger will be able to analyze the dump only if it can find exactly the same versions of executable modules that were loaded by the target process. In some cases, you will have to help the debugger to locate those modules – by specifying the module search path. Detailed information about module search path and related issues can be found in this article.

Now let's proceed to crash dumps without exception information. When we are starting to analyze such a dump, we usually want to know the call stacks of all threads. Here is how to get this information:

  cdb -z c:\myapp.dmp -logo out.txt -lines -c "~*kb;q"

What to do if we don't know whether the crash dump contains exception information or not? For minidumps, it is possible to use MiniDumpView to print the contents of the dump and see if it contains exception information. For old-style 'full user dumps', probably the only option is to start as if the dump contains exception information, and see if !analyze reports something meaningful.

There is an interesting special case – it is possible that the crash dump was created because of an unhandled exception, but does not contain exception information by some reason. It is still possible to find out the place where the exception occurred, with the help of the following procedure:

  1. Print the call stacks of all threads (using CDB command shown above).
  2. Find out the thread whose call stack contains kernel32!UnhandledExceptionFilter function.
  3. Use the fact that the first parameter of UnhandledExceptionFilter function contains a pointer to EXCEPTION_POINTERS structure.

Here is the declaration of EXCEPTION_POINTERS structure:

typedef struct _EXCEPTION_POINTERS 
{  
    PEXCEPTION_RECORD ExceptionRecord;  
    PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

If we know the address of this structure, we can take the pointer to the exception context (stored in ContextRecord field), pass it to .cxr command and thus switch the debugger context to the place where the exception occurred. After .cxr command has been executed, we can use, for example, 'kb' command to get the call stack at the moment of the exception. Here is an example:

1. Print call stacks of all threads.

  cdb -z c:\myapp.dmp -logo out.txt -c "~*kb;q"

0:000> ~*kb

.  0  Id: 6c4.73c Suspend: 1 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr  Args to Child              
0012fdf8 7c90d85c 7c8023ed 00000000 0012fe2c ntdll!KiFastSystemCallRet
0012fdfc 7c8023ed 00000000 0012fe2c 0012ff54 ntdll!NtDelayExecution+0xc
0012fe54 7c802451 0036ee80 00000000 0012ff54 kernel32!SleepEx+0x61
0012fe64 00430856 0036ee80 00330033 00300037 kernel32!Sleep+0xf
0012ff54 00431702 00000001 00352ed0 00352fb0 CrashDemo!wmain+0x96
0012ffb8 004314bd 0012fff0 7c816d4f 00330033 CrashDemo!__tmainCRTStartup+0x232
0012ffc0 7c816d4f 00330033 00300037 7ffd9000 CrashDemo!wmainCRTStartup+0xd
0012fff0 00000000 0042e5a5 00000000 00000000 kernel32!BaseProcessStart+0x23

   1  Id: 6c4.5cc Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr  Args to Child              
006af6e4 7c90e273 7c863130 d0000144 00000004 ntdll!KiFastSystemCallRet
006af6e8 7c863130 d0000144 00000004 00000000 ntdll!NtRaiseHardError+0xc
006af96c 00438951 006af9e0 5d343834 00000000 kernel32!UnhandledExceptionFilter+0x59c
006af990 00430f2a c0000005 006af9e0 0044ad30 CrashDemo!_XcptFilter+0x61
006af99c 0044ad30 00000000 00000000 00000000 CrashDemo!_callthreadstartex+0x7a
006af9b0 00438c67 00430f13 0049a230 00000000 CrashDemo!_EH4_CallFilterFunc+0x12
006af9e8 7c9037bf 006afad4 006aff98 006afaf0 CrashDemo!_except_handler4+0xb7
006afa0c 7c90378b 006afad4 006aff98 006afaf0 ntdll!ExecuteHandler2+0x26
006afabc 7c90eafa 00000000 006afaf0 006afad4 ntdll!ExecuteHandler+0x24
006afabc 004309be 00000000 006afaf0 006afad4 ntdll!KiUserExceptionDispatcher+0xe
006afe88 0043094e 00000000 00354130 00350001 CrashDemo!TestFunc+0x2e
006aff6c 00430f01 00000000 647bff58 00354130 CrashDemo!WorkerThread+0x5e
006affa8 00430e72 00000000 006affec 7c80b50b CrashDemo!_callthreadstartex+0x51
006affb4 7c80b50b 00355188 00354130 00350001 CrashDemo!_threadstartex+0xa2
006affec 00000000 00430dd0 00355188 00000000 kernel32!BaseThreadStart+0x37

2. Change debugger context and get the call stack for the exception.

  cdb -z c:\myapp.dmp -logo out.txt -lines -c ".cxr dwo(0x006af9e0+4);kb;q"

('dwo' operator returns the double word stored at the specified address and passes it to .cxr command)

The batch files presented later in this article (DumpStackCtx.bat in particular) will simplify this task significantly.

There is an alternative approach that also allows to solve this problem – you can find more information about it here.

Virtual memory analysis

Another situation where CDB can significantly complement Visual Studio debugger is when we want to inspect virtual memory layout of the debuggee process. The following command will display the complete virtual memory map of the process:

  cdb -pv -pn myapp.exe -logo out.txt -c "!vadump -v;q"

(!vadump command is responsible for printing the virtual memory map, and -v option, as usual, asks it to show detailed output)

Here is an example of !vadump output:

BaseAddress:       00040000
AllocationBase:    00040000
AllocationProtect: 00000004  PAGE_READWRITE
RegionSize:        0002e000
State:             00002000  MEM_RESERVE
Type:              00020000  MEM_PRIVATE

BaseAddress:       0006e000
AllocationBase:    00040000
AllocationProtect: 00000004  PAGE_READWRITE
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000104  PAGE_READWRITE + PAGE_GUARD
Type:              00020000  MEM_PRIVATE

BaseAddress:       0006f000
AllocationBase:    00040000
AllocationProtect: 00000004  PAGE_READWRITE
RegionSize:        00011000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00020000  MEM_PRIVATE

On Windows XP and Windows Server 2003, CDB offers a better command for inspecting virtual memory layout - !address. This command allows to perform the following tasks:

  • Display virtual memory map of the process (in my opinion, in a more readable format than !vadump)
  • Display useful statistics about virtual memory usage
  • Determine the kind of virtual memory region the specified address belongs to (for example, does it belong to a stack, heap or an executable image?)

Here is how to use !address to report the virtual memory map:

  cdb -pv -pn myapp.exe -logo out.txt -c "!address;q"

Here is a sample output that shows a memory region occupied by a thread's stack:

    00040000 : 00040000 - 0002e000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000000 
                    State    00002000 MEM_RESERVE
                    Usage    RegionUsageStack
                    Pid.Tid  658.644
               0006e000 - 00001000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000104 PAGE_READWRITE | PAGE_GUARD
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  658.644
               0006f000 - 00011000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  658.644

Note that !address is smart enough to report the thread id of the thread the stack belongs to.

After !address has finished reporting virtual memory regions, it also reports interesting statistics about virtual memory usage:

-------------------- Usage SUMMARY --------------------------
    TotSize   Pct(Tots) Pct(Busy)   Usage
   00838000 : 0.40%       27.96%      : RegionUsageIsVAD
   7e28c000 : 98.56%      0.00%       : RegionUsageFree
   01348000 : 0.94%       65.60%      : RegionUsageImage
   00040000 : 0.01%       0.85%       : RegionUsageStack
   00001000 : 0.00%       0.01%       : RegionUsageTeb
   001a0000 : 0.08%       5.53%       : RegionUsageHeap
   00000000 : 0.00%       0.00%       : RegionUsagePageHeap
   00001000 : 0.00%       0.01%       : RegionUsagePeb
   00001000 : 0.00%       0.01%       : RegionUsageProcessParametrs
   00001000 : 0.00%       0.01%       : RegionUsageEnvironmentBlock
       Tot: 7fff0000 Busy: 01d64000

-------------------- Type SUMMARY --------------------------
    TotSize   Pct(Tots) Usage
   7e28c000 : 98.56%     : <free>
   01348000 : 0.94%      : MEM_IMAGE
   007b6000 : 0.38%      : MEM_MAPPED
   00266000 : 0.12%      : MEM_PRIVATE

-------------------- State SUMMARY --------------------------
    TotSize   Pct(Tots) Usage
   01647000 : 1.09%      : MEM_COMMIT
   7e28c000 : 98.56%     : MEM_FREE
   0071d000 : 0.35%      : MEM_RESERVE

Largest free region: Base 01014000 - Size 59d5c000

The statistics can be especially useful when we are debugging a memory leak and want to determine what kind of memory is leaked (heap, stack, raw virtual memory, and so on). The last item allows to determine the size of the largest free region of virtual memory, which can be helpful when we have to design an application with high memory demands.

If you only want to see the statistics and do not need the virtual memory map, you can use -summary parameter:

  cdb -pv -pn myapp.exe -logo out.txt -c "!address -summary;q"

If we need to determine what kind of virtual memory the given address belongs to, we can pass this address as a parameter to !address command. Here is an example:

0:000> !address 0x000a2480;q
    000a0000 : 000a0000 - 000d7000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageHeap
                    Handle   000a0000

Searching for symbols

Sometimes we might need to determine the address of a symbol (function or variable). If we know the exact name of the symbol, we can enter it into Disassembly window of Visual Studio debugger and get the address. But what if we don't remember the exact name? Or want to find the addresses of a set of symbols with the same pattern in the name (for example, all member functions of a class)? CDB can easily solve this problem – it offers 'x' command, which can list all symbols whose names match the specified mask:

  x Module!Symbol

The following command tries to locate the address of UnhandledExceptionFilter function, located in kernel32.dll:

  cdb -pv -pn notepad.exe -logo out.txt -c "x kernel32!UnhandledExceptionFilter;q"

Here is the output:

0:000> x kernel32!UnhandledExceptionFilter;q
7c862b8a kernel32!UnhandledExceptionFilter = <no type information>

'x' command accepts a large number of possible wildcards, and offers some useful options for sorting the output and for additional information about symbols - you can find more information about it in WinDbg documentation. For example, the following command lists all member functions and static data members of CMainFrame class defined in our application's main executable:

0:000> x myapp!*CMainFrame*
004542f8 MyApp!CMainFrame::classCMainFrame = struct CRuntimeClass
00401100 MyApp!CMainFrame::`scalar deleting destructor' (void)
004011a0 MyApp!CMainFrame::OnCreate (struct tagCREATESTRUCTW *)
00401000 MyApp!CMainFrame::CreateObject (void)
00401280 MyApp!CMainFrame::PreCreateWindow (struct tagCREATESTRUCTW *)
00401070 MyApp!CMainFrame::GetRuntimeClass (void)
00401120 MyApp!CMainFrame::~CMainFrame (void)
00401090 MyApp!CMainFrame::CMainFrame (void)
00401080 MyApp!CMainFrame::GetMessageMap (void)
004578ec MyApp!CMainFrame::`RTTI Base Class Array' = <no type information>
004578dc MyApp!CMainFrame::`RTTI Class Hierarchy Descriptor' = <no type information>
004578c8 MyApp!CMainFrame::`RTTI Complete Object Locator' = <no type information>
004579ec MyApp!CMainFrame::`RTTI Base Class Descriptor at (0,-1,0,64)' = <no type information>
00461e94 MyApp!CMainFrame `RTTI Type Descriptor' = <no type information>
00454354 MyApp!CMainFrame::`vftable' = <no type information>

CDB can also do just the opposite – find symbol by address, using 'ln' command:

  ln Address

Here is how to use it:

  cdb -pv -pn notepad.exe -logo out.txt -c "ln 0x77d491c8;q"

Here is the output:

0:000> ln 0x77d491c8;q
(77d491c6)   USER32!GetMessageW+0x2   |  (77d49216)   USER32!CharUpperBuffW

Note that we do not have to specify the start address of the symbol (a function in this case), but can use any address inside the address range occupied by the symbol. 'ln' will find the symbol, report its address, and in addition report the address and the name of the symbol that follows the specified one.

Displaying data structures

If we want to explore the contents of a data structure, we usually use Visual Studio's Watch, QuickWatch or other similar window. These windows allow us to see the types and values of the structure's member variables. But what if we also need to know the exact layout of the structure, including the offsets of its members? Visual Studio does not offer an easy-to-use solution, but fortunately CDB does. With the help of 'dt' command, we can display the exact layout of a data structure or a class.

If we simply want to know the layout of a data type, we can use this command as follows:

  dt -b TypeName

(-b option enables recursive display of embedded data structures for members whose type is also a structure or a class).

Here is a sample CDB command line:

  cdb -pv -pn myapp.exe -logo out.txt -c "dt -b CSymbolInfoPackage;q"

Here is the output (obtained while running SymFromAddr application):

0:000> dt /b CSymbolInfoPackage;q
   +0x000 si               : _SYMBOL_INFO
      +0x000 SizeOfStruct     : Uint4B
      +0x004 TypeIndex        : Uint4B
      +0x008 Reserved         : Uint8B
      +0x018 Index            : Uint4B
      +0x01c Size             : Uint4B
      +0x020 ModBase          : Uint8B
      +0x028 Flags            : Uint4B
      +0x030 Value            : Uint8B
      +0x038 Address          : Uint8B
      +0x040 Register         : Uint4B
      +0x044 Scope            : Uint4B
      +0x048 Tag              : Uint4B
      +0x04c NameLen          : Uint4B
      +0x050 MaxNameLen       : Uint4B
      +0x054 Name             : Char
   +0x058 name             : Char

If you want to display the layout of a particular variable, you can pass its address to 'dt' command:

  dt -b TypeName Address

Here is a sample:

  cdb -pv -pn myapp.exe -logo out.txt -c "dt -b CSymbolInfoPackage 0x0012f6d0;q"

0:000> dt /b CSymbolInfoPackage 0x0012f6d0;q
   +0x000 si               : _SYMBOL_INFO
      +0x000 SizeOfStruct     : 0x58
      +0x004 TypeIndex        : 2
      +0x008 Reserved         : 
       [00] 0
       [01] 0
      +0x018 Index            : 1
      +0x01c Size             : 0x428
      +0x020 ModBase          : 0x400000
      +0x028 Flags            : 0
      +0x030 Value            : 0
      +0x038 Address          : 0x411d30
      +0x040 Register         : 0
      +0x044 Scope            : 0
      +0x048 Tag              : 5
      +0x04c NameLen          : 0xe
      +0x050 MaxNameLen       : 0x7d1
      +0x054 Name             :  "S"
       [00] 83 'S'
   +0x058 name             :  "SymbolInfo"
    [00] 83 'S'
    [01] 121 'y'
    [02] 109 'm'
    [03] 98 'b'
    [04] 111 'o'
    [05] 108 'l'
    [06] 73 'I'
    [07] 110 'n'
    [08] 102 'f'
    [09] 111 'o'
    [10] 0 ''
    [11] 0 ''
    [12] 0 ''
    [13] 0 ''
    [14] 0 ''
    [15] 0 ''
    [16] 0 ''
    [17] 0 ''
    ... part of the output omitted
    [1990] 0 ''
    [1991] 0 ''
    [1992] 0 ''
    [1993] 0 ''
    [1994] 0 ''
    [1995] 0 ''
    [1996] 0 ''
    [1997] -52 ''
    [1998] -52 ''
    [1999] -52 ''
    [2000] -52 ''

Note that now 'dt' also shows the values of the structure's member variables.

Batch files

Now we know how to use CDB to solve some interesting debugging problems. It's time to solve one more problem – replace long CDB command lines with easy-to-use batch files. Consider the command we used as a sample at the beginning of the article:

  cdb -pv -pn myapp.exe -logo out.txt -c "lm;q"

Most parts of this command are static and cannot change. The only variable part is the target information (-pn myapp.exe), where we might need to use another executable name, or even another way of attaching (e.g., by process id).

Here is how this command can be represented in a batch file:

  ; lm.bat
  cdb -pv %1 %2 -logo out.txt -c "lm;q"

If we want to run this batch file to get the list of modules loaded by a process, we can use either one of the following commands:

Attach by executable name:

  lm -pn myapp.exe

Attach by process id:

  lm -p 1234

Attach by service name:

  lm -psn MyService

Open a crash dump file:

  lm -z c:\myapp.dmp

Regardless of the target specified, the command still does the same – prints the list of loaded modules.

If we need to specify additional parameters to a CDB command, we can do it using the same approach. Consider the following command, which can be used to display the layout of a data structure:

  cdb -pv -pn myapp.exe -logo out.txt -c "dt /b MyStruct;q"

Of course, we want to use this command with any data type, not only with MyStruct. Here is how we can do it:

  ; dt.bat
  cdb -pv %1 %2 -logo out.txt -c "dt /b %3;q"

Now we can run the command like this:

  dt -pn myapp.exe CTestClass

Or like this:

  dt -p 1234 SYMBOL_INFO

Or, for example, like this:

  dt -z c:\myapp.dmp EXCEPTION_POINTERS

We can use the same approach with many other commands. Here you can find the list of batch files that wrap the commands discussed in this article. In the future, I am going to extend it with other useful commands.

Contact

Have questions or comments? Free free to contact Oleg Starodumov at firstname@debuginfo.com.