This article is the first of a three-parts series on how to extend the new Windows 10 WinDbg app in order to make your .NET debugging easier and faster. In the first part of the series, you are going to see how to add a button to the ribbon and react to commands. For that, we will create a button to load the SOS.dll extension for us:
In the second part, we will learn how to add custom panels to the UI, and use them to open multiple command windows with history:
In the third part, an interpreter will be embedded to let you type and run C# code. That code will have access to ClrMD and DynaMD to help you analyzing your app and CLR data structures as demonstrated in our previous series:
Be advised that the new WinDbg is still in preview, and the extensibility points aren’t documented. What is explained in those articles could change at any time, without any warning from Microsoft.
Also, please note that those articles assume preliminary knowledge of WPF and MEF (Managed Extensibility Framework).
Discovering the extension point
The old WinDbg is that tough child you love to hate. It is the one application you cannot remove from your debugging toolbox, the one you turn to in almost every postmortem debugging session. Yet, it is the absolute antithesis of enjoyable and efficient UX, a UI inherited straight from the early 90s that doesn’t answer even your basic needs without a certain dose of pain.
Of course, you can add new commands that can save you a lot of time, but it’s not really possible to tweak the UI.
In that light, you can imagine how excited we were when the new WinDbg was revealed.
After playing with the new UI a little bit, I was curious of what extension capabilities it offered. The first step was to find the binaries of the application. You could list the installed app packages yourself or simply start the new WinDbg and find it in Process Explorer to get the image location.
Launching a decompiler and spelunking in the C:\Program Files\WindowsApps\Microsoft.WinDbg_18.104.22.168_x86__8wekyb3d8bbwe folder, I started by confirming that the new UI is written in WPF. It means that, at the very least, it should be possible to modify the IL to inject custom code.
Digging further, it seems that WinDbg uses MEF to automatically load any DLL put in the Extensions subfolder, as well as in the %LOCALAPPDATA%\DBG\UIExtensions folder.
Setting up the project
As mentioned in the introduction, we’re going to create a helper to load SOS with a click from the mouse.
First, let’s start by creating a new Class Library project. To save time, don’t hesitate to set a post-build action to copy the project output to the %LOCALAPPDATA%\DBG\UIExtensions folder. This way, you can quickly test your modifications just by compiling your project and launching WinDbg. It would also work with the Extensions subfolder of WinDbg, but I don’t recommend it since the store application folder has very restrictive permissions and is a pain to deal with. Maybe a forthcoming version of WinDbg will provide a dedicated UI to install our extensions; who knows?…
Next, add a reference to DbgX.Interfaces.dll, DbgX.Interfaces.Internal.dll, DbgX.Util.dll, and Fluent.dll (all of them are in the WinDbg binaries folder). Just make sure to set “Copy Local” to “false” for each of those references, so you won’t inadvertently copy them to the extensions folder of WinDbg. Add a SosLoaderViewModel class that implements the IDbgRibbonTabGroupExtension interface. This interface will be called by WinDbg to know which controls are exposed by the extension. This interface has a single property, “Controls”, that will be called by Windbg to know what UI controls to add to the ribbon. Just return an empty list for now Note that you will also need to add a reference to PresentationFramework and System.xaml WPF assemblies and target a .NET framework higher or equal to 4.6.1 to make it compile.
Also, decorate the class with the RibbonTabGroupExtensionMetadata attribute. It has three parameters, extendedTabName, extendedGroupName, and order, that indicate where in the ribbon the UI element should be inserted. For now we’ll stick to the Home tab (“HomeRibbonTab”), in the Help group (“Help”), with no particular order (0).
Last but not least, add a reference to MEF (System.ComponentModel.Composition) and use the Export attribute to declare that the class should be discovered as a IDbgRibbonTabGroupExtension:
So far, since we left the Controls property empty, nothing is displayed in WinDbg. This is hardly exciting, so let’s build a bit of UI.
Add a new WPF user control to the project (make sure not to pick the Winform user control), and name it SosLoaderButton:
In the code-behind file, remove the “UserControl” inheritance (because we’re going to change the base class in the XAML), and change the constructor to accept a SosLoaderViewModel. Assign it as the DataContext to be able to use it for databinding.
In the XAML, change the root node to “fluent:ToggleButton” and declare the fluent namespace as “urn:fluent-ribbon” to replace the local namespace. Set the Header property to “SOS”. You can also associate an icon using the “LargeIcon” property.
Now that we have a button, we just need to expose it from the viewmodel (of course, if you want to do true MVVM you may want to separate the IDbgRibbonTabGroupExtension from the actual viewmodel, but that’s not really the point here).
Now, if you start WinDbg (assuming you’ve properly copied your dll to the extension folder), you should see the new button appear:
To load the SOS.dll WinDbg extension, we just need to ask WinDbg to execute the command “.loadby sos clr” when the button is clicked.
First, let’s bind the IsChecked and Command properties of our button, as we’re going to need them later:
Now we need a way to ask WinDbg to execute a command. For that, we need to import the IDbgConsole interface from the DbgX.Interfaces.Services namespace in the viewmodel:
To create the WPF command bound to the button, we’re going to use the AsyncDelegateCommand helper provided in DbgX.Util. But any WPF command would do the trick, so feel free to use your own implementation. Inside, we call the ExecuteCommandAsync method of the IDbgConsole to load SOS:
It works, but the status of the toggle button isn’t consistent: if we press it twice, then it won’t be toggled anymore, even though SOS is still loaded. Additionally, if the developer manually loads SOS out of habit, it would be nice if the button was automatically updated.
Listening to commands
To do so, we’re going to implement the IDbgCommandExecutionListener interface in the viewmodel. This interface only defines the OnCommandExecuted method that is called, as the name implies, when a command is executed. If the command is “.loadby sos clr”, we update the IsLoaded property, bound to the toggle button. We also need to implement INotifyPropertyChanged to make sure that the view picks the changes.
Note that we don’t have to set the IsLoaded property in the LoadSos method, because OnCommandExecuted isn’t limited to commands typed by the user, and will be raised in response to the call to _console.ExecuteCommandAsync. Basically, we’re listening to all commands received by the debugging console, either typed by the user in the UI or silently sent by any extensions, including our own actions.
This is looking good so far, but there is still one issue to fix. If we press the button before a debugging session is started, the command will fail (an exception will actually be thrown, and caught internally by WinDbg), and we will still mark SOS as loaded. It’d be great if we could activate the button only after a debugging session has started.
Monitoring the debugging engine
For that, we need to implement IDbgEngineConnectionListener. To know the status of the debugging engine at a given time, we’re also going to import IDbgEngineControl and use its ConnectionState property. Our MEF imports/exports now look like:
When creating the AsyncDelegateCommand, we now also use the second constructor parameter of the constructor, which is the CanExecute callback of the command. Inside, we check the status of the engine, and whether we previously loaded SOS:
Last but not least, we need to invalidate the status of the command when OnEngineConnectionChanged is called (inherited from IDbgEngineConnectionListener):
And we’re done! The full file can be seen here.
In this article, we’ve seen how we can extend the WinDbg UI and interact with it in just a few lines of code. Of course, the use-case was a bit naïve and not that useful, but we’ll see in the next articles how to implement much more powerful features, that can provide real productivity boosts when debugging applications.
Post written by:
Staff Software Engineer, R&D.
Staff Software Engineer, R&D.
Our lovely Community Manager / Event Manager is updating you about what's happening at Criteo Labs.See DevOps Engineer roles