Click here to Skip to main content
15,880,427 members
Articles / Multimedia / GDI

Find GDI Leaks With Windows Debugger

Rate me:
Please Sign up or sign in to vote.
5.00/5 (24 votes)
25 Apr 2023MIT4 min read 14.6K   307   21   4
Tracing GDI Leaks with Windows Debugger
In this article, you will see how to trace GDI handle leaks with Windows Debugger and also learn how to fix them.

Introduction

This article is a walkthrough on finding and fixing GDI handle leaks with Windows Debugger. Windows Debugger should be a last resort, firstly do a search for BeginPaint()/EndPaint() in your entire codebase and examine the GDI code between these two function calls for undeleted handles and delete them.

Using Task Manager, we can add a GDI Objects column on the Details tab page to get the count of currently opened GDI handles by each process. One process can have a maximum of 10000 opened GDI handles. Systemwide limit for all processes is 65535. Right-click on the headers to select the columns to be shown.

Select Columns on Details Tab on Task Manager

Check GDI Objects option to add to the Details tab and close the dialog.

Check GDI Objects option

In my application, UI language changed 20 times can result in an unresponsive application because it has reached the limit of 10000 opened GDI handles. All the GDI objects created in between BeginPaint()/EndPaint() are double-checked to be released. Clearly, the leaks are happening elsewhere. To narrow down which type of GDI objects are leaking, we can use GDIView (UI-based) or GDIInquiry.exe (console-based). Given below are snapshots by GDIInquiry.exe before and after a UI language change.

GDIInquiry.exe PID

GDIInquiry.exe needs one argument which is the process ID that can be retrieved from Task Manager.

C:\temp>GDIInquiry.exe 7168
Bitmap:3
Brush:6
DeviceContext:20
Font:5
Palette:0
Pen:0
Region:5
Unknown:0
GDITotal:39

We can see that the font object count increased by 50 with every language change while other GDI objects remained unchanged.

C:\temp>GDIInquiry.exe 7168
Bitmap:3
Brush:6
DeviceContext:20
Font:55
Palette:0
Pen:0
Region:5
Unknown:0
GDITotal:89

After finding a list of the font creation functions on this MSDN page, I attached WinDbg from the file menu to my process with its PID and then put breakpoints with bp command on those functions. For your information, gdi32 is the name of the DLL without its file extension. Please note GDI handles can also be returned by functions located in the USER32.DLL, be sure to check the MSDN. In my case, I am only concerned with font creation functions in GDI32.DLL.

bp gdi32!createfonta

bp gdi32!createfontw

bp gdi32!createfontindirecta

bp gdi32!createfontindirectw

But WinDbg cannot locate those functions. Then I resort to x command for a case-insensitive search for the symbolic names beginning with createfont using the asterisk as a wildcard.

x gdi32!createfont*

The x command yields the following search results.

00007ffc`481f1210 GDI32!CreateFontWStub (void)

00007ffc`481f1630 GDI32!CreateFontIndirectW (void)

00007ffc`481fcf30 GDI32!CreateFontIndirectAStub (CreateFontIndirectAStub)

00007ffc`481fce50 GDI32!CreateFontAStub (CreateFontAStub)

00007ffc`481f87a0 GDI32!CreateFontIndirectExA (CreateFontIndirectExA)

00007ffc`481f87c0 GDI32!CreateFontIndirectExW (CreateFontIndirectExW)

Then I proceed to put a breakpoint on every single one of them. Additional command can be specified within quotes thereafter, which is executed after the breakpoint is hit. More than 1 command is separated with a semicolon. "kp" instructs the WinDbg to show call stack and parameters to each function There is another kp version that ends with an uppercase P letter(kP): the main difference is each parameter is displayed on its own line.

bp GDI32!CreateFontWStub "kp"

bp GDI32!CreateFontIndirectW "kp"

bp GDI32!CreateFontIndirectAStub "kp"

bp GDI32!CreateFontAStub "kp"

bp GDI32!CreateFontIndirectExA "kp"

bp GDI32!CreateFontIndirectExW "kp"

It could be pretty tiresome to set more than a few breakpoints. Fortunately, a bm command, to relieve us of this thankless chore, can be used to set multiple breakpoints at once using wildcard.

bm gdi32!createfont* "kp"

After setting the required breakpoints, type g command in WinDbg to let program execution resume. Type qd to detach the program and quit WinDbg after debugging is done.

g

Then proceed to do whatever action to produce the GDI leak, in my case, is changing UI language. Note here that you may have to break a few times before you encounter a real leak, meaning font creation without deletion. Soon enough, WinDbg breaks on CreateFontIndirectW but I am not at liberty to show my call stack publicly, instead, I'll explain the cause of the leak: In my CEdit derived class, a HFONT member is created to be set as the current font using SetFont() without calling DeleteObject() in its destructor. After fixing this leak, my Font count stabilized but "All GDI" count in GDIView and GDI Object count in Task Manager still grew rapidly at each language change.

The GDIView page states Notice: If you have a problem that the 'All GDI' value is increased, while there is no leak with the other GDI values, it means that you probably have a leak in the creation of icons or cursors (Icons and cursors are created without destroying them later).. So a review of all the icon and cursor creation code is done. The leak is from the LoadImage which is called multiple times. The chosen fix is not to delete the icon but to load the icon as shared (LR_SHARED), meaning only one icon instance will be loaded and released at the end of the application run.

C++
WNDCLASSEXW wnd = {0};
....
wnd.hIconSm = (HICON)::LoadImage(thisInstance, MAKEINTRESOURCE(1), 
              IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);

The fix is to add LR_SHARED to be digital OR with the last argument.

C++
wnd.hIconSm = (HICON)::LoadImage(thisInstance, MAKEINTRESOURCE(1), 
              IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR | LR_SHARED);

Why !htrace Command Cannot Be Used?

Readers may be wondering why I did not use !htrace command which is specifically for tracing handle leaks. !htrace works only with true kernel handles. You cannot use the !htrace extension command to track down the source of graphics device interface (GDI) handle leaks.

Conclusion

This article did a walkthrough on finding font and icon leaks. Another common type of GDI leak is bitmap which I did not cover. Make sure bitmap handles returned by GetIconInfo()/GetIconInfoExW()/GetIconInfoExA() from USER32.DLL are deleted. If you find this article useful, please consider an upvote to encourage me to write more on this subject.

History

  • 25th April, 2023: Added a note about systemwide GDI handle limit is 65535.
  • 23rd June, 2020: First release

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
Singapore Singapore
Shao Voon is from Singapore. His interest lies primarily in computer graphics, software optimization, concurrency, security, and Agile methodologies.

In recent years, he shifted focus to software safety research. His hobby is writing a free C++ DirectX photo slideshow application which can be viewed here.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Jeremy Falcon8-May-23 3:58
professionalJeremy Falcon8-May-23 3:58 
QuestionAnother tool to fix GDI leaks Pin
Member 1565261627-May-22 3:55
Member 1565261627-May-22 3:55 
QuestionAre you aware of Performance HUD? Pin
Alois Kraus2-Jul-21 7:09
Alois Kraus2-Jul-21 7:09 
AnswerRe: Are you aware of Performance HUD? Pin
Shao Voon Wong2-Jul-21 19:36
mvaShao Voon Wong2-Jul-21 19:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.