WinDbg+SOS has been used by .NET developers for years. It is a very powerful profiling/analysis tool that unfortunately is quite hard to use and exposes native-only API. By releasing ClrMD library Microsoft makes CLR heap memory inspection accessible to regular C# developers and enables them to write customized profiling tools. I have created a simple ScriptCS script pack that allows for interactive debugging under REPL. Now we can use both C# and some of SOS features under the same console :). More after the break.
Not that long ago .NET Runtime team announced the beta release of Microsoft.Diagnostics.Runtime component (aka ClrMD).
The package delivers managed API for .NET process and crash dump inspection which is similar to SOS Debugging Extensions. If you are a seasoned Windows programmer you probably have been using WinDbg a couple of times, for example to track and identify logical memory leaks in your applications.
ClrMD simply brings some of that capabilities as an API. That is a big deal for a couple of reasons:
- Makes memory profiling and process analysis much easier for regular C#/.NET developers,
- Allows people and businesses to write custom diagnostic tools tailored for their needs.
Remember that ClrMD is still in beta phase and also that attaching and debugging is an invasive process (don't run it on production servers).
For more in depth introduction to the topic please read the original blog post
ScriptCS and REPL
ScriptCS is a cool project started by @gblock and inspired by @filip_woj that uses Roslyn and NuGet to make C# scripting easy (no .csprojs required). It is getting a lot of traction in the community recently - it is definitely one of my favourite initiatives in the C# world, putting Roslyn to a great use.
ClrMD + ScriptCS = ScriptCS.ClrDiagnostics
Based on the above I've created a script pack that brings ClrMD into ScriptCS and is aimed to provide interactive CLR diagnostics environment under ScriptCS REPL.
It is available on GitHub and NuGet as ScriptCS.ClrDiagnostics
Here is how you can use it:
- Install ScriptCS using nightly builds (this is needed due to a bug in current version that prevents from installing prerelease packages) cinst scriptcs -pre -source https://www.myget.org/F/scriptcsnightly/ Alternatively you can build from source
- Install ScriptCS.ClrDiagnostics:
scriptcs -install ScriptCs.ClrDiagnostics -pre
- Launch ScriptCS in REPL mode (you may need to get latest version for that):
- You should be able to load the script pack and attach to a process like this:
// Load ClrDiag object > var c = Require<ClrDiag>(); // Attach to process > c.Attach(6152) Attaching to process PID=6152 Using CLR Version=v4.0.30319.18033 DACFileName=mscordacwks_amd64_Amd64_4.0.30319.18033.dll Succesfully attached to process PID=6152 Name=WpfApplication2
You can also use the process name:
Check current state
> c.IsAttached True > c.Process.WorkingSet64 51265536
ClrMD API is available to use, eg. ClrRuntime
> c.Clr.Threads.Count 2
Script pack also provides some helpers that should make analysis easier:
> c.PrintTypes("System."); Total size Count Name 167.82KB 398 System.Object 143.29KB 2404 System.String 126.98KB 2385 System.Delegate 126.98KB 2385 System.Delegate
PrintTypes() will simply output all the types on managed heaps sorted by total memory consumed, it lets you optionally filter by type name and limit returned result set.
You can also display each thread's call stack using PrintStackTrace():
> c.PrintStackTrace() Stacktrace for ThreadId=3200 0 A9E9B8 InlinedCallFrame 0 A9E9B8 InlinedCallFrame 7F92AF92093 A9E990 DomainBoundILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32) 7F92AF87640 A9EA60 System.Windows.Threading.Dispatcher.GetMessage(System.Windows.Interop.MSG ByRef, IntPtr, Int32, Int32) 7F92AF85E9E A9EB20 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame) 7F9272172DA A9EBC0 System.Windows.Application.RunInternal(System.Windows.Window) 7F927216BD7 A9EC60 System.Windows.Application.Run() 7F8E3700107 A9ECA0 WpfApplication2.App.Main()
Or for one thread using index:
> c.PrintStackTrace(0) Stacktrace for ThreadId=3200 0 A9E9B8 InlinedCallFrame 0 A9E9B8 InlinedCallFrame 7F92AF92093 A9E990 DomainBoundILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
After you are done, detach from the process like this:
> c.Detach() Successfully detached from process PID=6152 Name=WpfApplication2
ScriptCs.ClrDiagnostics lets you also specify DAC file (mscordacwks.dll) location while attaching:
> c.Attach(6152, @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscordacwks.dll");
If you encounter an error with StackOverflow being thrown in REPL console - it is caused by jsv serialization recently added to ScriptCS. You may need to wait for the fix or manually apply a simple workaround (use simple .ToString() in place of object serialization to jsv).
The project has been created as an experiment, but it shows how great capabilities ScriptCS REPL has.
Oh, and you can also do that: