SPT - A WinDBG extension for debugging .NET applications

Update: I’ve written a newer/better version of this for .NET 4.5 and open sourced it, see later posts on my blog for more details. This version is legaacy and no longer maintained.

In my spare time I've been working on a WinDBG extension containing some useful methods that automate pretty common .NET debugging tasks for me, and hopefully other people as well.  The extension right now targets .NET 4/x64 only, as that's my primary development platform.  I'll probably make a x86 build soon (especially if people request it), it shouldn't be very difficult to make it work on 32 bit platforms.  Currently a few commands support DML (!DumpSqlConnectionPools is the most noticeable), I'll be adding support for DML to other commands soon.

A few of the commands are targeted at ASP.NET/server apps (!DumpHttpContext, !DumpASPNetRequests, !DumpSqlConnectionPools, !DumpThreadPool for example), but most are fairly general purpose.  In the next few blog posts I'll go into details about the implementation of each of the commands, but for now here's a list of them with quick summaries and examples.  Feel free to comment/email me with any comments/suggestions.

Download the extension here (x64)

And the x86 version here

 

ASP.NET

!DumpASPNetRequests

Prints out all threads with a HTTP context.
Example output:

ThreadID  HttpContext       StartTime             URL + QS 
0123      0000000002f62b38  05/29/2011 18:23:02   /hello/world.aspx  
0124      0000000002f62c98  05/29/2011 18:23:05   /test.aspx?w00t=1
...
 

!FindHttpContext

Prints the context information for the current thread, including the managed thread object address (System.Threading.Thread), ExecutionContext, and Logical/IllogicalCallContext.
Example output:

OS ThreadId: e64, N: 0
Managed Thread @ 0000000002f58580

Thread ExecutionContext @ 0000000002f64af8
IllogicalCallContext @ 0000000002f64c40
HostContext (HttpContext) @ 0000000002f64d80

Note: of all the commands here, this is the most likely to break from service releases.  It relies on reversed offsets to find the managed thread object from the unmanaged thread object.

Hashtables/Dictionaries/Etc

!DumpDictionary (!DumpHashtable, !ddct, !dht)

Note: I used hashtable/dictionary pretty interchangeably for the rest of this post, anything that supports Hashtables also supports Dictionaries.

Iterates over all the key/value pairs in a dictionary, printing them out, and translating keys/values to strings if possible.
Example:

0:000> !ddct 0000000002f621c0  
entry at: 0000000002f622e8
key: 0000000000000001 [(null), 1]
value: 0000000002f62128 [System.String, "test"]
hashCode: 0x00000001

 

entry at: 0000000002f62300
key: 0000000000000002 [(null), 2]
value: 0000000002f62150 [System.String, "two"]
hashCode: 0x00000002

These methods have a few switches you can use.

  • The first is -short, when used prints only the key/value/hash tripplet for each entry, useful for feeding into a script.
  • The second is -kdo and -vdo.  These switches allow you to pass a tokenized command in for the key/value in each entry.  For instance, say you want to print out all the values in the dictionary, you could run

    !ddct -vdo "!do %p" 0000000002f621c0

These switches are one of my favorite features and have saved me a ton of time already.

Note: DumpDictionary and DumpHashtable are both actually the same function, the export table links them together.

Note 2: This also supports the HybridDictionary class, just use any of the above functions on it.

 

!DumpMemoryCache (!dmc)

Basically the same as !DumpDictionary, but goes through all the sub-hashtables inside the memory cache object.  Doesn't support the same switches as !DumpDictionary yet though.

 

Other misc stuff

!FullStack (!fs)

Prints out the mixed-mode callstack of the current thread.
Example:  (parameters snipped by me so it doesn't wrap on my blog)

       IP        M  Name 

000000007745153a 0 ntdll!ZwRequestWaitReplyPort+a
0000000076d42f58 0 KERNEL32!ConsoleClientCallServer+54
0000000076d75321 0 KERNEL32!ReadConsoleInternal+1f1
0000000076d8a762 0 KERNEL32!ReadConsoleA+b2
0000000076d58e04 0 KERNEL32!TlsGetValue+899e
000007feef4f17c7 0 clr!DoNDirectCall__PatchGetThreadCall+7b
000007feec6834a1 1 DomainNeutralILStubClass.IL_STUB_PInvoke(...)+101
000007feece2f58a 1 System.IO.__ConsoleStream.ReadFileNative(...)+ba
000007feece2f3f2 1 System.IO.__ConsoleStream.Read(...)+62

(Note: I've seen some version of dbghelp have problems resolving native methods in CLR.dll to symbols.  The latest version seems to work fine though.)

 

!FormatDT

Given a System.DateTime or TimeSpan, formats it as a string MM/dd/yyyy hh:mi:ss.  I think psscor2 had something similar back in the day.

 

!EvalExpression (!evalexpr)

Given an expression consisting of field access or hashtable lookups, evaluates the expression starting at a root object.

Fields are separated by periods just like C++/C#.  Hashtable lookups are specified by the [ ] operator (like in C#) and are done in two ways.

  • The first is if the hashtable/dictionary key is a string, you can use a quoted string inside the [ ] operator to tell the evaluator you want to match a string.  For example _myHashtable["steve"].lastName will lookup the value in _myHashtable with the key of "steve", then evaluate the lastName field. 
  • The second is by hashcode.  If the expression inside the [ ] operator is an unquoted number, it's used to do a lookup by hashcode.

Also, if the root object itself is a hashtable, it's legal for the expression to begin with simply a [ ] operator.  For example, ["steve"].lastName.

Expressions ending with a "$" sign are evaluated to a string and the result string printed.  If the final result isn't a string object, stuff will probably break.

Note: Hashtable lookups are actually O(n) (you'd need a full-out IL interpreter like in VS to do it in O(1)), so they might get slow on really big dictionaries.

 

!PrintDelegateMethod (!pdm)

Attempts to resolve a delegate to a method name.  This should work at least for non-multicast delegates (eg delegates without an invocation list) to static or instance methods.  For instance method calls, the value in _methodPtr is simply a pointer to the managed method and resolved in a way similar to ip2md.  For static methods, the value in _methodPtrAux is an entry into the JIT stub table, and used to get a MethodDesc handle to the method, which is then resolved to a name.

 

!DumpThreadPool

Dumps the items currently in the .NET 4.0 ThreadPool queues, resolving work items to method names.  This currently supports work items enqueued via ThreadPool.QueueUserWorkItem and Task[<T>].  These are the only two classes in .NET 4 that currently implement IThreadPoolWorkItem.  This method only works if ThreadPoolGlobals.useNewWorkerQueue = true.

There's a also special case: if a work item was created via BeginInvoke, DumpThreadPool will attempt to resolve it to the real method being called by looking at the MethodCall object's MethodInfo field.  For this to work, the method being called must be System.Runtime.Remoting.Proxies.AgileAsyncWorkerItem.ThreadPoolCallBack, and the callback argument must be a System.Runtime.Remoting.Proxies.AgileAsyncWorkerItem.

 

!DumpSqlConnectionPools (!dsql)

Dumps out all SQL connection pools in all app domains of the process.

Example output:

0:046>  !dumpsqlconnectionpools;
[0] Factory @ 00000001df0614c8
Connection String: <snip>
PoolGroup:    000000011f0877a8
    SID:  S-1-5-21-3430578067-4192788304-1690859819-9294
    Pool: 00000001bf077d08, State: 1, Waiters: 0, Size: 12
    Connections:
              ConnPtr           State  #Async  Created            Command/Reader    Timeout  Text
        [000] 00000001bf0788e8      1       0  6/10/2011 17:30:32
        [001] 000000015f1ebf80      1       0  6/10/2011 17:30:33
        [002] 000000013f30da08      1       0  6/10/2011 17:30:39
        [003] 000000013f3315e0      1       0  6/10/2011 17:30:54
        [004] 000000011f4852f8      1       0  6/10/2011 17:30:54
        [005] 00000001df1b5998      1       0  6/10/2011 17:30:54
        [006] 00000000ff41da90      1       0  6/10/2011 17:30:54
        [007] 00000001bf1c08f8      1       0  6/10/2011 17:30:54
        [008] 000000019f5f8080      1       0  6/10/2011 17:30:54
        [009] 000000015f2b4f90      1       0  6/10/2011 17:30:54
        [00a] 000000013f344120      1       0  6/10/2011 17:30:54
        [00b] 000000019f60ab18      1       0  6/10/2011 17:30:54

Connection String: <snip>;
PoolGroup:    000000017f125730
    SID:  S-1-5-21-3430578067-4192788304-1690859819-9294
    Pool: 000000017f4495b8, State: 1, Waiters: 0, Size: 4
    Connections:
              ConnPtr           State  #Async  Created            Command/Reader    Timeout  Text
        [000] 000000017f449f50      1       0  6/10/2011 17:30:54
        [001] 000000017f471280      1       0  6/10/2011 17:30:54
        [002] 00000000ff4305b8      1       0  6/10/2011 17:30:54
        [003] 00000000ff442f50      1       1  6/10/2011 17:30:54  000000017f4372a0       50  do_something_prc

What are we seeing here?  Connection pooling is basically a 5 level hierarchy:

  • Connection String - top level, defines the server and connection options.
    • Pool group - Second level, varies based on the identity used to connect to the DB if using SSPI
      • Pool - Actually contains the SqlConnection instances.
        • SqlConnection - The DB connection instance
          • Active readers - SqlDataReaders that are actively doing something

Some fields that probably need some explaining:

  • Pool -> State
    • Corresponds to the System.Data.ProviderBase.DbConnectionPool+State enum, Initializing, Running, Shutting Down
  • Connection -> State
    • Corresponds to the System.Data.ConnectionState enum.
  • Created is the time the connection was first opened in UTC.
  • Command/Reader
    • If Async = 1, the pointer is to a command, otherwise it's to a SqlDataReader.  With a SqlCommand, you can get to the reader via the _cachedAsyncState field, with a SqlDataReader, you can get to the command via the _command field.

Note: This doesn't currently dump connections in the transacted connection pools.

 

!FindTimers

(x86 only for now)

Dumps out all timers registered in the process.

Example:

0:006> !findtimers
Handle            TimerObj          ADID  Period[ms]  State             Context           Delegate          Method
          293e48  00000000022cbbb4     1        1000  00000000022cbb48  00000000022cbbc8  00000000022cbb6c  SomeCallback(System.Object)

Columns are:

  • Handle
    • The timer handle, can be used to cross reference with an existing TimerBase object
  • TimerObj
    • A reference to the managed _TimerCallback object
  • ADID
    • The appdomain ID the timer is associated with
  • State
    • The state object associated with the constructor.
  • Context
    • The ExecutionContext captured by the timer.
  • Delegate
    • The delegate that will be called when the timer fires.

!SPTHelp

Prints out the built-in help.


© 2023. All rights reserved.