One of the most complex issues are User/GDI object leaks because they are nearly invisible in memory dumps. Microsoft does not want us to peek into such “legacy” technology so no Windbg plugins exist anymore. Today everything is UWP, WPF (ok that’s legacy as well) and mostly some Chrome Browser based applications. With .NET 6 WPF and WinForms support has got some love again so we can expect new programmers will hit old fashioned User/GDI leaks again. These leaks are worse than “normal” Handle leaks because Windows imposes a limit of 10K GDI and User Handles per process. After that you cannot create any more User/GDI handles and you get either exceptions or partially filled windows where content or icons are missing.
Here is an application which leaks Window and GDI Handles on every button click:
public partial class Leaker : Form
{
public Leaker()
{
InitializeComponent();
}
List<Leaker> myChilds = new List<Leaker>();
private void button1_Click(object sender, EventArgs e)
{
var leaker = new Leaker();
var tmp = leaker.Handle;
myChilds.Add(leaker);
cCountLabel.Text = $"{myChilds.Count}";
}
}

In Task Manager we find that with every click we loose 6 Window handles:

These come from the
- Leaked Form
- Create Button
- Created Label
- 85 (Number) Label
- IME Hidden Window
- MSCTFIME Hidden Window

A very useful tool is Spy++ which is installed when you add C++ core features

If you intend to copy Spy++ to a different machine you need these dlls
.\1033\spyxxui_amd64.dll
spyxxhk_amd64.dll
spyxx_amd64.exe
to make it work. The resource dll in the 1033 folder is essential or it wont start.
How would you identify the call stack that did lead to the allocation of additional objects? The answer is a small and great ETW tool named Performance HUD from Microsoft.
After starting

you can attach to an already running process

After a few seconds an overlay window pops up





See my earlier post what else you can do with Performance HUD. It is one of the tools which save a lot of time once you know that they exist.
Another great feature of Performance HUD is to analyze UI Thread hangs. It employs some sophisticated analysis to detect that the UI thread is no longer processing window messages because some CPU hungry method is executing a long method call, or that you are stuck in a blocking OS call. Today everyone is using async/await in C#. A stuck UI thread should be a thing of the past. But now you can measure and prove that it is not hanging.
Lets add to our sample application a UI hang via TPL tasks which happens only in Debug Builds
private void cHang_Click(object sender, EventArgs e)
{
Task<int> t = Task.Run(async () => { await Task.Delay(5000); return 1; });
Debug.Print(t.Result.ToString());
}

My favorite button the Blame Someone which will unfold the stack with probably the longest stuck call:

This precisely points towards our stuck button handler which calls into Task.Result which seems not to be resolved properly but the later method calls are. The 5s hang is nicely colored and you can also pack this into an Email and send the issue to the maintainer of the corresponding code. I have seen plenty of similar issues in the past because Thread.Sleep is caught by many FXCop rules so people tend to use await with Task.Delay which is a Thread.Sleep in disguise.
This works not only with .NET Code but with any language as long as it supports pdbs and walkable stacks. It is great that this diagnostic has been added to Windows but why did it take so long? As far as I know Performance HUD is from the Office team which is a “legacy” but important application. Someone dealing with too many GDI/User object leaks was able to commit some lines to the Kernel to instrument the relevant locations with proper ETW events and we finally arrive with a feature complete leak tracer. With ETW we can track leaks
- OS Handles (File, Socket, Pipe, …)
- wpr -start Handle
- Analyze with WPA. This records handle data from all processes (system wide)

- GDI Handles (Cursor, Bitmap, Font, …)
- Performance HUD to record a single process
- User Handles (Window, Menu, Timer, …)
- Performance HUD to record a single process
- VirtualAlloc Memory (Records system wide all processes)
- wpr -start VirtualAllocation
- Analyze with WPA

- C/C++ Heap Memory (needs to be enabled for specific processes)
- Memory Snapshots with not released memory (== Leak)
- All allocations (very high volume)
- Analyze with WPA
- .NET Heap Memory (needs to inject a profiler into a specific process)
- Use PerfView to record and analyze with PerfView

Finally we have covered all frequent leak types with ETW events. No leak can hide anymore.
For memory mapped files there are some ETW events but unfortunately no analysis in the public version of WPA exist. These leaks are still more tricky to figure out than the rest which are now at least in principle easy. Recording the right data and not too much (ETW files > 6 GB will usually crash WPA) is still challenging but doable. Performance HUD is very well written. I have successfully processed a 80 GB ETL trace with no hiccups. It can not only attach to already running processes, but it can also analyze previously recorded data.
Now get back to work and fix your leaks and unresponsive UIs!
[…] GDI/User Object Leak Tracking – The Easy Way – Alois Kraus […]
LikeLike
[…] GDI/User Object Leak Tracking – The Easy Way (Alois Kraus) […]
LikeLike