Viewing data structures is usually a critical part of any analysis. In WinDBG, there are several different ways in which you can inspect data structures. However, one of the most common ways is with the Display Type command, dt. On the surface dt is pretty simple, you specify a data structure name and WinDBG will show you the fields of the structure along with their offsets and types:
0: kd> dt nt!_ksemaphore
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : Int4B
In addition, you can optionally specify an address to be interpreted as the specified type so that you can inspect the fields of the structure:
0: kd> dt nt!_ksemaphore 8a084120
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n1
Now that we have the basics down, let’s see some advanced usage of this command that will allow us to quickly and easily navigate any structure that we’re interested in.
Inspecting Structure Fields
In the above example, the standard dt output showed us two fields of the KSEMAPHORE structure: Header and Limit. If we knew beforehand that we only cared about the Limit field, we could specify an additional parameter to the dt command and limit the output to only show the Limit field:
0: kd> dt nt!_ksemaphore Limit
+0x010 Limit : Int4B
0: kd> dt nt!_ksemaphore 8a084120 limit
+0x010 Limit : 0n1
Note that the Header field itself was a data structure, though it wasn’t expanded in the standard dt output and it won’t be expanded if we limit the output to just show the Header field:
0: kd> dt nt!_ksemaphore header
+0x000 Header : _DISPATCHER_HEADER
0: kd> dt nt!_ksemaphore 8a084120 Header
+0x000 Header : _DISPATCHER_HEADER
However, you can use standard C syntax to inspect specific fields of the substructure:
0: kd> dt nt!_ksemaphore header.type
+0x000 Header :
+0x000 Type : UChar
In order to actually enumerate the fields of that structure we’re going to need to specify yet another parameter. Now, this is where the dt command syntax gets messy and inconsistent. There are, in fact, three different ways to expand substructures with the dt command: /r, ., and ->*. Let’s see when each of these are appropriate.
Structure Expansion With /r
The /r switch to the dt command is the, “recursively dump the subfields” switch. The switch takes a number between 1-9 to indicate the depth to which the command should traverse dumping structures. Using this method is only effective if you are not specifying a subfield of the structure. For example, these work as expected:
0: kd> dt nt!_ksemaphore /r1
+0x000 Header : _DISPATCHER_HEADER
+0x000 Type : UChar
+0x001 Absolute : UChar
+0x002 Size : UChar
+0x003 Inserted : UChar
+0x004 SignalState : Int4B
+0x008 WaitListHead : _LIST_ENTRY
+0x010 Limit : Int4B
0: kd> dt nt!_ksemaphore /r2 8a084120
+0x000 Header : _DISPATCHER_HEADER
+0x000 Type : 0x5 ''
+0x001 Absolute : 0 ''
+0x002 Size : 0x5 ''
+0x003 Inserted : 0 ''
+0x004 SignalState : 0n0
+0x008 WaitListHead : _LIST_ENTRY [ 0x898a0090 - 0x898a0090 ]
+0x000 Flink : 0x898a0090 _LIST_ENTRY [ 0x8a084128 - 0x8a084128 ]
+0x004 Blink : 0x898a0090 _LIST_ENTRY [ 0x8a084128 - 0x8a084128 ]
+0x010 Limit : 0n1
However, if you attempt to expand just the Header field, you’ll see that the switch is ignored:
0: kd> dt nt!_ksemaphore /r1 Header
+0x000 Header : _DISPATCHER_HEADER
0: kd> dt nt!_ksemaphore /r1 8a084120 Header
+0x000 Header : _DISPATCHER_HEADER
In order to perform that type of expansion, we’ll need the . and ->* switches.
Structure Expansion With .
In our last example, we were trying to only expand the Header field of the KSEMAPHORE structure but the /r switch was ineffective. In order to expand the embedded structure by name in that case, we need to follow the structure name with a period. This indicates to the engine that you would like to view only that member of the structure and you would like to expand it:
0: kd> dt nt!_ksemaphore 8a084120 Header.
+0x000 Header :
+0x000 Type : 0x5 ''
+0x001 Absolute : 0 ''
+0x002 Size : 0x5 ''
+0x003 Inserted : 0 ''
+0x004 SignalState : 0n0
+0x008 WaitListHead : _LIST_ENTRY [ 0x898a0090 - 0x898a0090 ]
In addition, you can keep adding additional periods to keep increasing the depth of the expansion:
0: kd> dt nt!_ksemaphore 8a084120 Header..
+0x000 Header :
+0x000 Type : 0x5 ''
+0x001 Absolute : 0 ''
+0x002 Size : 0x5 ''
+0x003 Inserted : 0 ''
+0x004 SignalState : 0n0
+0x008 WaitListHead : [ 0x898a0090 - 0x898a0090 ]
+0x000 Flink : 0x898a0090 _LIST_ENTRY [ 0x8a084128 - 0x8a084128 ]
+0x004 Blink : 0x898a0090 _LIST_ENTRY [ 0x8a084128 - 0x8a084128 ]
Structure Expansion With ->*
In the above case, the period suffix was appropriate because the field of the structure that we cared about was an embedded structure, not a pointer to a structure. Let’s try that again with a pointer to a structure, such as the Dpc field of the KTIMER:
0: kd> dt nt!_ktimer b46c82a0
+0x000 Header : _DISPATCHER_HEADER
+0x010 DueTime : _ULARGE_INTEGER 0xd2`333b7e55
+0x018 TimerListEntry : _LIST_ENTRY [ 0x8a06fb10 - 0x80562628 ]
+0x020 Dpc : 0xb46c82e0 _KDPC
+0x024 Period : 0n0
0: kd> dt nt!_ktimer b46c82a0 Dpc
+0x020 Dpc : 0xb46c82e0 _KDPC
0: kd> dt nt!_ktimer b46c82a0 Dpc.
+0x020 Dpc :
Cannot find specified field members.
Oops! That doesn’t work so hot…Instead, we need a different suffix to the field name: ->* (no, I’m not joking, that’s really the syntax
). Taking our previous example:
0: kd> dt nt!_ktimer b46c82a0 Dpc->*
+0x020 Dpc :
+0x000 Type : 0n19
+0x002 Number : 0 ''
+0x003 Importance : 0x1 ''
+0x004 DpcListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x00c DeferredRoutine : 0xb46be385 void rdbss!RxTimerDispatch+0
+0x010 DeferredContext : (null)
+0x014 SystemArgument1 : (null)
+0x018 SystemArgument2 : (null)
+0x01c Lock : (null)�
There are, of course, lots of other things that can be done with the dt command as well. However, hopefully that clears up some confusion and motivates you to find some other interesting things in the docs!
Bonus Tip: Quick Access to Last Type Used
Before we leave, here’s one last quick tip about this command. The dt command always stores the last type inspected and gives you quick access to the previous type by accepting a period character for the type name. This is best explained with an example. So, let’s say we want to look at two KSEMAPHORE structures. We actually only need to type the entire type name out the first time, the second time we can save a keyboard and just use a period for the type name:
0: kd> dt nt!_ksemaphore 8a084120
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n1
0: kd> dt . 8a0281e0�
Using sym nt!_ksemaphore
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n2147483647