Freigeben über


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).
    1. ? @@(#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.
    2. 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! d

  • Anonymous
    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