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.