Debugger command (dl) that makes my life easier


The use of the LIST_ENTRY structure in WDM is quite pervasive. It is used for nearly
all list keeping tasks. I have used it extensively in the past and KMDF uses it
quite a bit as well. There are two debugger commands that help in viewing the contents
of a list. I will talk about dl today and !list tomorrow.

Here is an example usage of LIST_ENTRY (ignoring the fact that it leaks memory):


typedef struct _MY_DATA {
LIST_ENTRY ListEntry;
ULONG Data;
ULONG Data2;
} MY_DATA, *PMY_DATA;

int __cdecl main()
{
LIST_ENTRY head;
ULONG i;

InitializeListHead(&head);

for (i = 0; i < 5; i++) {
PMY_DATA pData = (PMY_DATA) LocalAlloc(LPTR, sizeof(MY_DATA));
if (pData == NULL) {
return 1;
}

pData->Data = i + 1;
pData->Data2 = 0x100 + i + 1;

InsertTailList(&head, &pData->ListEntry);
}

return 0;
}

Assuming we have set a breakpoint at “return 0;“,
you can use the dl command to dump the list contained in the local variable ‘head’.


0:000> dl head
0024f7d0 00364090 00364130 00000005 0024f820
00364090 003640b8 0024f7d0 00000001 00000101
003640b8 003640e0 00364090 00000002 00000102
003640e0 00364108 003640b8 00000003 00000103
00364108 00364130 003640e0 00000004 00000104
00364130 0024f7d0 00364108 00000005 00000105

Let’s take the output apart and see what we are told. The first column is the
address being dumped. The other four columns are the 4 pointer sized values that
the first column points to. Of these four columns, the LIST_ENTRY is contained in
the first (Flink) and second (Blink) columns. The remaining two columns are pure data
and dependent on your data structure. So, let’s annotate the previous output:


[addr] [Flink] [Blink] [Data] [Data]
0024f7d0 00364090 00364130 00000005 0024f820
00364090 003640b8 0024f7d0 00000001 00000101
[…]

The first row (0024f7d0 00364090 00364130 00000005 0024f820) is the list head,
so the the final 2 column values (00000005 0024f820) should
be ignored. The remaining rows are our structure MY_DATA; let’s look at its layout in memory:


0:000> dt _MY_DATA
+0x000 ListEntry : _LIST_ENTRY
+0x008 Data : Uint4B
+0x00c Data2 : Uint4B

It is no coincidence that I put Data and Data2 after the ListEntry field in the
structure. By placing these values after the ListEntry, the dl command
dumped my data structure for me without any additional work. I chose a simple
pattern for the Data and Data2 fields to further illustrate this concept. If we look at the first row
(00364090 003640b8 0024f7d0 00000001 00000101), the MY_DATA at 0x00364090 has a
Data value of 1 and a Data2 value of 0x101. You can also use the
dlb command to dump the list backwards if want.

One word of caution though. The output for a 64 bit list is a little different then the 32 bit
output. Each entry takes up two lines instead of one! Also, the layout
trick that worked nicely for 32 bits does not work as nicely for 64.


0:000> dl head
00000000`001ff930 00000000`00383730 00000000`00383870
00000000`001ff940 00000000`00000000 00000000`ff8213ac

00000000`00383730 00000000`00383780 00000000`001ff930
00000000`00383740 00000101`00000001 abababab`abababab

00000000`00383780 00000000`003837d0 00000000`00383730
00000000`00383790 00000102`00000002 abababab`abababab

[…]

I annotated the output to clarify the data. For 64 bits I like to “fix” this issue
by specifying how many pointers to dump (two). Unfortunately, to specify the number
of pointers to dump you need to specify the maximum number of entries. I use a very large value
(100) so that I do not accidentally clip the output


0:000> dl head
[addr] [Flink] [Blink]
00000000`001ff930 00000000`00383730 00000000`00383870
[addr continued] [Data] [Data]
00000000`001ff940 00000000`00000000 00000000`ff8213ac

[addr] [Flink] [Blink]
00000000`00383730 00000000`00383780 00000000`001ff930
[addr continued] [Data] [Data]
00000000`00383740 00000101`00000001 abababab`abababab
[…]

0:000> dl head 100 2
00000000`001ff930 00000000`00383730 00000000`00383870
00000000`00383730 00000000`00383780 00000000`001ff930
00000000`00383780 00000000`003837d0 00000000`00383730
[…]

One final note, the dl command does not actually
rely on the LIST_ENTRY structure, it can be used for nearly any list type as long
as the pointer to the next item in the list can be retrieved dereferencing the
current entry’s address. Tomorrow’s entry on !list
will describe how to more robustly dump each entry in the list.

Comments (2)

  1. Yesterday I introduced the dl command and demonstrated some of its limitations. Today I will talk about…