Debugger command (!list) that makes my life easier
Yesterday I introduced the dl command and demonstrated some of its limitations. Today I will talk about !list. Let's take yesterday's data structure, MY_DATA. What if the LIST_ENTRY is at the end of the structure or there is more data in your structure that fits into two pointer sized fields so that dl cannot display them? This is where the extension !list comes in very handy.
Getting all the parameters to this extension correct is a bit difficult and it took me quite a bit of experimentation to get it to work, so hopefully I will be able to make it easier to understand and you won't have to go through the same pain I did. First, let's rearrange the fields in MY_DATA so that the LIST_ENTRY is the last field, recompile, and rerun our test with the same breakpoint set on "return 0;" and run dl again to see the changes
typedef struct _MY_DATA {
LIST_ENTRY ListEntry;
ULONG Data;
ULONG Data2;
LIST_ENTRY ListEntry;
} MY_DATA, *PMY_DATA;
0:000> dl head
0013fbf0 004d4098 004d4138 00000005 0013fc40
004d4098 004d40c0 0013fbf0 abababab abababab
004d40c0 004d40e8 004d4098 abababab abababab
004d40e8 004d4110 004d40c0 abababab abababab
004d4110 004d4138 004d40e8 abababab abababab
004d4138 0013fbf0 004d4110 abababab abababab
As you can see for all rows except for the first, that the data after the LIST_ENTRY is unitialized and the usefulness of the output is much less. So, let's run !list and see what we get
0:000> !list "-t _LIST_ENTRY.Flink -x \"? @@(#CONTAINING_RECORD(@$extret, _MY_DATA, ListEntry));dt _MY_DATA @@(#CONTAINING_RECORD(@$extret, _MY_DATA, ListEntry))\" @@(head.Flink)"
Evaluate expression: 5062800 = 004d4090
+0x000 Data : 1
+0x004 Data2 : 0x101
+0x008 ListEntry : _LIST_ENTRY [ 0x4d40c0 - 0x13fbf0 ]
[...]
Evaluate expression: 5062960 = 004d4130
+0x000 Data : 5
+0x004 Data2 : 0x105
+0x008 ListEntry : _LIST_ENTRY [ 0x13fbf0 - 0x4d4110 ]
So now we have all our structure, but there that has got to be one of the worst command lines I have seen in a long time! Let's take it apart and try to make some sense of it.
- The entire command line is quoted which takes care of the beginning and ending "s
- -t _LIST_ENTRY.Flink tells !list how to move from one entry to the next. !list will evaluate the expression to find the offset of the next list items pointer given the current list item pointer. My first impression was that it would be -t _MY_DATA.ListEntry.Flink, but !list requires an offset relative to the current pointer while _MY_DATA.ListEntry.Flink provides an absolute offset from the beginning of the structure.
- -x indicates what command you want to run for each entry in the list. You can optionally specify the -e flag for !list to echo the command, but for this case the echo was completely useless. The -x command also needs to be quoted which is why it is encased in escaped (\") quotes. The command being executed is actually 2 commands (separated by the semicolon).
- ? @@(#CONTAINING_RECORD(@$extret, _MY_DATA, ListEntry)) will dump the current _MY_DATA pointer value for the given entry. @$extret evaluates to the current item pointer in the list (a PLIST_ENTRY in this case). This value is not necessarily the same as the start of the structure so I use #CONTAINING_RECORD to adjust accordingly (just like it would in real live code). I evaluate the the pointer value because the next command, dt _MY_DATA @@(#CONTAINING_RECORD(@$extret, _MY_DATA, ListEntry)), does not print it out and I like to see what each entry's value is.
- dt _MY_DATA @@(#CONTAINING_RECORD(@$extret, _MY_DATA, ListEntry)) will dump out the data structure for the given entry the same way it would work if you ran the dt command from the prompt
- @@(head.Flink) tells !list where the first entry in the list is. This is not the list head, it is the address of the first entry. This is why I am evaluating getting the value of head.Flink and not &head
Comments
- Anonymous
August 10, 2006
The comment has been removed - Anonymous
August 10, 2006
The comment has been removed - Anonymous
January 29, 2007
Thanks! Your blog is very useful. I've been writing WinDbg scripts to automate crash dump analysis, for example, the following script based on debugger.chm sample to extract all process thread stacks, check critical sections, etc: $$ $$ Dmp2Txt: Dump all necessary information from complete full memory dump into log $$ .logopen /d !analyze -v !vm lmv !locks !poolused 3 !poolused 4 !exqueue f !irpfind r $t0 = nt!PsActiveProcessHead .for (r $t1 = poi(@$t0); (@$t1 != 0) & (@$t1 != @$t0); r $t1 = poi(@$t1)) { r? $t2 = #CONTAINING_RECORD(@$t1, nt!_EPROCESS, ActiveProcessLinks); .process @$t2 .reload !process @$t2 !ntsdexts.locks lmv } .logclose $$ $$ Dmp2Txt: End of File $$
- Dmitry
Anonymous
January 29, 2007
cool stuff! dAnonymous
February 11, 2007
I prefer dt command to !list (less to type, especially if the structure is big and I'm looking for specific fields). For your example it should be like this: dt _MY_DATA -l ListEntry.Flink 0013fbf0
- Dmitry
Anonymous
February 11, 2007
My previous comment has an error: should be: dt _MY_DATA -l ListEntry.Flink 004d4098-8 I've just noticed that you already covered the use of dt in the previous post. Anyway structures I use have LIST_ENTRY at the beginnig and this is why I prefer dt.Anonymous
February 12, 2007
Of course, if you ever stray from putting the LIST_ENTRY as the first field or if you have 2 LIST_ENTRYs, this assumption can fail. You might also want to look at the #CONTAINING_RECORD and #FIELD_OFFSET keywords that you can use in the evaluator d