This fifth post of the ClrMD series shows how to leverage this API inside a WinDBG extension. The associated code allows you to translate a task state into a human readable value.
Part 1: Bootstrap ClrMD to load a dump.
Since the beginning of this series, you have seen how to use ClrMD to write your own tool to extract meaningful information from a dump file (or a live process). However, most of the time, you are also using WinDBG and SOS to navigate inside the .NET data structures.
It would be convenient if you could leverage the new .NET exploration features based on ClrMD the same way you are using SOS. This post will explain how to achieve this goal by implementing an extension that exports commands callable from within WinDBG.
Deciphering a Task status
During one of our debugging investigations, we needed to get the value of the Status property for a few Task instances. If you take a look at the implementation of the property getter in a decompiler (or from source code), you will see that it is computed based on the value of the internal m_stateFlags field.
In WinDBG, the !DumpHeap -stat command lists all types with their instance count. If the .prefer_dml 1 command has been set, you even get hyperlinks on some values such as the address or MT (for MethodTable). If you click the MT value for System.Threading.Tasks.Task, you get all instances of type Task:
Click any address and look at the value of the m_stateFlags field:
It is easy to automate the retrieval of the m_stateFlags instance field value with ClrMD as explained earlier:
The ClrType corresponding to the address is first checked to ensure that it represents a Task instance. Next, its GetFieldByname helper method returns a ClrInstanceField that provides the status via its GetValue function.
The next step is to transform this number into a TaskStatus enumeration value by simply using a decompiler and copying the logic from the Task getter code:
It would be a time saver if this translation could be done by a command right inside WinDBG instead of relying on another tool based on ClrMD in which addresses are pasted.
WinDBG extension 101
In addition of being a native Windows debugger, WinDBG supports extensions: .dll files that you load with the .load command. They are exporting commands that are callable from within WinDBG with the “!” prefix. These commands are usual native exports that can be seen with tools such as http://www.dependencywalker.com/ as shown by the next screenshot:
As you can see, all SOS commands are functions exported by the sos.dll native binary. Before digging into the extension functions implementation, notice that a few other functions could also be exported. Among them, the DebugExtensionInitialize function provides version information (i.e. which version of the debugging API is expected) and must be exported to be called by WinDBG when the dll is loaded.
Read this post for more details about how to develop a native WinDBG extension.
All extension command functions take two parameters:
- An IDebugClient instance to interact with WinDBG
- An ANSI string for the arguments (such as “-stat” for !dumpheap)
The bridge between your extension commands and WinDBG is provided by the IDebugClient COM interface. But don’t be scared: no need to manually deal with native COM interface with ClrMD! The DataTarget.CreateFromDebuggerInterface method takes an IDebugClient interface and returns an instance of DataTarget. As you might remember from the initial post of this series, DataTarget is the gateway to the dump (or live-debugged attached process): we are now back to the known ClrMD world.
Reuse ClrMD Samples
Hopefully, most of the glue to bind the native world to ClrMD is already available! You simply reuse the partial DebuggerExtensions class given in the samples.
You extend the class with your extension methods that take the following signature:
The first parameter is a pointer to the IDebugClient interface provided by WinDBG. The first thing to do in your extension command method is to call the InitApi static method with the interface pointer and let the magic happens.
After that call, the output of the Console will be redirected to WinDBG and your code is free to use the following properties to access the dump via ClrMD:
The second parameter args received by your method is a string that contains the parameters added by the user after the name of your command. For example, if the user types “MyCommand param1 param2”, the args parameter will be “param1 param2”.
Exposing native functions
The last part of magic glue is how to export a native function from a .NET assembly. This is made possible by the UnmanagedExports nuget package by Robert Giesecke.
Once added to your project, decorate the functions to export with the DllExport attribute and the native name of the function that will be visible in WinDBG as a command.
There is a little trick here: the names of exported functions are case sensitive for WinDBG. If you take a look again at sos.dll in Dependency Walker and sort exports by Function column, you will notice a few duplicates such as CLRStack / ClrStack / clrstack as shown in the following screenshot:
For usability sake, it is a good practice to provide several syntaxes for the same command, including short version such as !dso for !DumpStackObject in SOS. Unfortunately the DllExport attribute does not allow multiple applications on the same method with different exported names. You need to define a different method per exported name and all of them will call the same internal helper method.
Thanks to the GetTaskStateFromAddress and GetTaskState helper methods described earlier, the implementation of the OnTkState method is straightforward once the address or the value has been extracted from the args parameter.
Don’t forget your user: implement help
A good extension always provides an help command that (1) lists the available commands with shortcuts and (2) additional details on each command. Simply add a new file that defines the exports for help/Help and parses the string argument if needed.
Tips to use the extension
Don’t forget that you might need two versions of your assembly: one for the x86 version of WinDBG if your applications are 32 bit and one for the x64 version of WinDBG in the 64 bit case. If you want to be able to easily load your extension with the .load <myextension> command, copy it with Microsoft.Diagnostics.Runtime.dll (i.e. ClrMD assembly) to the winext subfolder of x64/x86 WinDBG folders:
Before being able to use any of its commands, you must load SOS with the well-known .loadby sos clr mantra. But this is not enough: you also have to run at least one SOS command. You are now ready to call any of your extension commands!
The next episodes will bring you into the mysteries under the dynamic keyword and how to simplify the syntax to leverage ClrMD.
Post written by:
Staff Software Engineer, R&D.
Senior Software Engineer
Our lovely Community Manager / Event Manager is updating you about what's happening at Criteo Labs.See DevOps Engineer roles