5 minute read

Update 2021: This is a re-post of a post from Johan Straarup - I am posting it here as his blog has been decommissioned

Did you know you can build your own advanced commands using for each, if, etc? The complete list of control tokens are:

  • .if
  • .else
  • .elseif
  • .foreach
  • .for
  • .while
  • .do
  • .break
  • .continue
  • .catch
  • .leave
  • .printf
  • .block

Using these command tokes you can send quite advanced instructions to the debugger that not only will make your job a lot easier, but also impress your manager immensely. :)

.foreach

Let’s begin with an easy example. Imagine you want to investigate all strings on the heap that are 6500 bytes or more. To list them you’d simply type !dumpheap -type System.String -min 6500. This will give you the following information:

0:000> !dumpheap -type System.String -min 6500
------------------------------
Heap 0
Address       MT     Size
790da154 790f9244     9280
0264c4d0 790f9244    32788
total 2 objects
------------------------------
Heap 1
Address       MT     Size
total 0 objects
------------------------------
Heap 2
Address       MT     Size
0b62e790 790f9244    11284
total 1 objects
------------------------------
Heap 3
Address       MT     Size
0e6839d0 790f9244    32788
0e717904 790f9244    32788
0fb2a320 790f9244     6828
total 3 objects
------------------------------
total 6 objects
Statistics:
      MT    Count    TotalSize Class Name
790f9244        6       125756 System.String
Total 6 objects

So far, so good. The problem is that in order to investigate each string you’d have to run !dumpobject (!do) on every address. This might be acceptable now that we’re only dealing with 6 strings, but what if it were 25, or 100? I don’t know if you’re aware of this, but if you pass the -short argument to !dumpheap it will give you the minimum information (just the addresses of the objects in question):

0:000> !dumpheap -type System.String -min 6500 -short
790da154
0264c4d0
0b62e790
0e6839d0
0e717904
0fb2a320
------------------------------

Now, let’s use this information in a .foreach-clause:

0:000> .foreach(myVariable {!dumpheap -type System.String -min 6500 -short}){!do myVariable;.echo *************}
Name: System.String
MethodTable: 790f9244
EEClass: 790f91a4
Size: 9280(0x2440) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String:
WFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWGRkAiEPDxYCHwEFZFhYWFhYWFhYWFhYWFhYWFhYWFhYWF
hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhY
WFhYWFhYWFhYWFhkZAInDw8WCh8CBQFFHwMFCjIyLzExLzIwMDcfBAUBVh8FBQFFHwYFASpkZAIpD2QWBGYPZBYEAg
...

Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fdb60  4000096        4         System.Int32  1 instance     4632 m_arrayLength
790fdb60  4000097        8         System.Int32  1 instance     4631 m_stringLength
790fad38  4000098        c          System.Char  1 instance       3c m_firstChar
790f9244  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  000d5eb8:790d57b4 000fb4c0:790d57b4 000ca848:790d57b4 1d8334d8:790d57b4 <<
79122994  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  000d5eb8:026203f0 000fb4c0:02624504 000ca848:026745f0 1d8334d8:026dcef4 <<
*************
Name: System.String
MethodTable: 790f9244
EEClass: 790f91a4
Size: 32786(0x8012) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String:
g8PFgIfAQUHYWFhYWFhYWRkAgMPDxYCHwEFCWFhYWFhYWFhYWRkAgQPDxYCHwEFCjI4LzEyLzIwMDdkZAIvD2QWAmY
PZBYCZg9kFgICAw8PFgIfAQUFWFhYWFhkZAIxDw8WAh8JZ2QWBGYPZBYEAgEPZBYCZg9kFgQCAQ8WAh8IBQMxcHgWA
gIBDw8WAh8JaGRkAgMPZBYEAgEPDxYCHwEFCkFkZCBSZWNvcmRkZAIDDw8WAh8ABRZ+L0ltYWdlcy9UaXRsZS9OZXc
...

Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fdb60  4000096        4         System.Int32  1 instance    16385 m_arrayLength
790fdb60  4000097        8         System.Int32  1 instance    10960 m_stringLength
790fad38  4000098        c          System.Char  1 instance       3c m_firstChar
790f9244  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  000d5eb8:790d57b4 000fb4c0:790d57b4 000ca848:790d57b4 1d8334d8:790d57b4 <<
79122994  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  000d5eb8:026203f0 000fb4c0:02624504 000ca848:026745f0 1d8334d8:026dcef4 <<
*************
Name: System.String
MethodTable: 790f9244
EEClass: 790f91a4
Size: 11282(0x2c12) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String:
WFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWGRkAiEPDxYCHwEFZFhYWFhYWFhYWFhYWFhYWFhYWFhYWFa
YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWF
hYWFhYWFhYWFhYWFhYWFhkZAInDw8WCh8CBQFFHwMFCjIyLzExLzIwMDcfBAUBVh8FBQFFHwYFASpkZAIpD2QWBGYPZ
...

Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fdb60  4000096        4         System.Int32  1 instance     5633 m_arrayLength
790fdb60  4000097        8         System.Int32  1 instance     3092 m_stringLength
790fad38  4000098        c          System.Char  1 instance       5b m_firstChar
790f9244  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  000d5eb8:790d57b4 000fb4c0:790d57b4 000ca848:790d57b4 1d8334d8:790d57b4 <<
79122994  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  000d5eb8:026203f0 000fb4c0:02624504 000ca848:026745f0 1d8334d8:026dcef4 <<
*************
Name: System.String
MethodTable: 790f9244
EEClass: 790f91a4
Size: 32786(0x8012) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String:
SRU5UIFBBR0U6IDMgb2YgMTVkZAILD2QWAmYPZBYcAgcPDxYCHwEFZFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWF
hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYW
FhYWFhkZAINDw8WCh4BRQUBRR4CVFQFCjIyLzExLzIwMDceAlRWBQFWHgJURQUBRR4CVFIFASpkZAITDw8WCh8CBQFF
...

Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fdb60  4000096        4         System.Int32  1 instance    16385 m_arrayLength
790fdb60  4000097        8         System.Int32  1 instance    10960 m_stringLength
790fad38  4000098        c          System.Char  1 instance       3c m_firstChar
790f9244  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  000d5eb8:790d57b4 000fb4c0:790d57b4 000ca848:790d57b4 1d8334d8:790d57b4 <<
79122994  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  000d5eb8:026203f0 000fb4c0:02624504 000ca848:026745f0 1d8334d8:026dcef4 <<
*************
Name: System.String
MethodTable: 790f9244
EEClass: 790f91a4
Size: 32786(0x8012) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String:
0b25Ib21lBR9BcHBsaWNhdGlvbk5hdmlnYXRpb246aWJ0TG9nb3V0BTdfY3RsMDpQZXJzb25BcHBsaWNhdGlvbkRlZ3J
lZUxpc3Q6U3lzdGVtVGl0bGU6RWRpdEltYWdlBT5fY3RsMDpQZXJzb25BcHBsaWNhdGlvbkRlZ3JlZURpcGxvbWFMaXN
0OlN5c3RlbVRpdGxlOkVkaXRJbWFnZQVDX2N0bDA6UGVyc29uQXBwbGljYXRpb25PdGhlclF1YWxpZmljYXRpb25MaXN
...

Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fdb60  4000096        4         System.Int32  1 instance    16385 m_arrayLength
790fdb60  4000097        8         System.Int32  1 instance    10960 m_stringLength
790fad38  4000098        c          System.Char  1 instance       3c m_firstChar
790f9244  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  000d5eb8:790d57b4 000fb4c0:790d57b4 000ca848:790d57b4 1d8334d8:790d57b4 <<
79122994  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  000d5eb8:026203f0 000fb4c0:02624504 000ca848:026745f0 1d8334d8:026dcef4 <<
*************
Name: System.String
MethodTable: 790f9244
EEClass: 790f91a4
Size: 6826(0x1aaa) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String:
/wEPDwULLTEyMTQ5MDkyMjgPZBYCAgEPZBYGAgcPZBYEAgEPDxYCHghJbWFnZVVybAUrfi9pbWFnZXMvbmF2ZAILD2QW
aWdhdGlvbi9wYWdlcy9QYWdlMlByb2dyZXNzLmdpZmRkAgIPDxYCHgRUZXh0BRVDVVJSRU5UIFBBR0U6IDMgb2YgMTVk
ZAILD2QWAmYPZBYcAgcPDxYCHwEFZFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhY
...

Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fdb60  4000096        4         System.Int32  1 instance     3405 m_arrayLength
790fdb60  4000097        8         System.Int32  1 instance     3404 m_stringLength
790fad38  4000098        c          System.Char  1 instance       2f m_firstChar
790f9244  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  000d5eb8:790d57b4 000fb4c0:790d57b4 000ca848:790d57b4 1d8334d8:790d57b4 <<
79122994  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >> Domain:Value  000d5eb8:026203f0 000fb4c0:02624504 000ca848:026745f0 1d8334d8:026dcef4 <<
*************
Unknown option: ------------------------------
*************

Let’s analyze the exact syntax. Here’s the command

.foreach(myVariable {!dumpheap -type System.String -min 6500 -short}){!do myVariable;.echo *************}

“myVariable” is, as the name implies, the name of the variable that I wish to use for the data generated by the first set of commands. The second set of commands is what I wish to execute for each token. First I run !do on the variable, and then I use the .echo-command to print a separator in order to make it a bit easier on the eyes.

There are additional parameters you can use. For example you can choose to skip every n number of variables, or specify a text file to be parsed and used as tokens instead. Take a look at the windbg documentation if you’re interested.

.shell

I first saw this being used by my colleague Doug Stewart, who is a genius with these things.

0:000> .shell -i - -ci "!iisinfo.clientconns" FIND /c "Request active"
40
.shell: Process exited

What it does is, it runs !iisinfo.clientonns and uses the MS-DOS FIND-command to count the number of times the string “Request active” appears. Off course you could use it to search for certain strings from any type of output, like ".shell -i - -ci "!do 0b62e790" FIND /c /i "<table"" or whatever suits your needs.

Let’s take a quick look at the syntax.

When it comes to .shell the -i option is mandatory. It specifies the file we want to use for input. In this scenario we don’t want to use a file, so we add another hyphen, resulting in the following syntax: .shell -i -

We then add the -ci option, which states that we should treat the following commands as an input file instead. .shell -i - -ci "!iisinfo.clientconns"

Finally we state what shell command we wish to run using this input. .shell -i - -ci "!iisinfo.clientconns" FIND /c "Request active".

Naturally we can use any complicated command we wish in the statement, so instead of !iisinfo.clientconns we could run one of our .foreach-loops instead.

/ Johan