1
votes

I am writing some code to help track GDI leaks and understand the WINAPI better. I'm doing this by detouring all of the GDI functions and logging the creation and destruction of all of the handles listed.

Turns out that a HBITMAP is being created using CreateDIBitmap() and is not destroyed using DeleteObject() (or any of the other destruction function calls listed), and then sometime later a CreateBitmap() call results in the same handle as the previously mentioned function. There are a lot of HBITMAPs (and other handles) created in between.

I'm just wondering if there is some other way to destroy a HBITMAP that isn't listed in the documentation? Or is there some way to generate the same HBITMAP? Does anybody know?

I'm a little worried that I found some sort of GDI corruption.

Edit

Just for an update, here is what I am seeing in my logs (this is only one instance, haven't checked if all other instances are the same):

  21133     107110: |>Creating HBITMAP # 707/782 with function CreateDIBSection
  21134     107110: |<Created  HBITMAP # 707/782 0xAC057A00 with function CreateDIBSection
  21135     107125: |>Creating HBITMAP # 708/783 with function CreateBitmap
  21136     107125: |<Created  HBITMAP # 708/783 0xA9057B85 with function CreateBitmap
  21137     107125: |>Creating HDC # 16/16 with function CreateCompatibleDC
  21138     107125: |<Created  HDC # 16/16 0x5F01466B with function CreateCompatibleDC
  21139     107125: |>Creating HICON # 35/35 with function CreateIconIndirect
  21140     107125:  |>Creating HBITMAP # 709/784 with function CreateDIBitmap
  21141     107125:  |<Created  HBITMAP # 709/784 0x67055812 with function CreateDIBitmap
  21142     107141:  |>Creating HBITMAP # 710/785 with function CreateBitmap
  21143     107141:  |<Created  HBITMAP # 710/785 0x9605596F with function CreateBitmap
  21144     107141:  |>Creating HDC # 17/17 with function CreateCompatibleDC
  21145     107141:  |<Created  HDC # 17/17 0xD7011ACD with function CreateCompatibleDC
  21146     107141:  |>Destroying HDC # 17/17 0xD7011ACD with function DeleteDC
  21147     107156:  |<Destroyed handle 0xD7011ACD with function DeleteDC
  21148     107156: |<Created  HICON # 35/35 0x653526D3 with function CreateIconIndirect
  21149     107156: |>Destroying HBITMAP # 710/785 0xA9057B85 with function DeleteObject
  21150     107156: |<Destroyed handle 0xA9057B85 with function DeleteObject
  21151     107156: |>Destroying HDC # 16/16 0x5F01466B with function DeleteDC
  21152     107156: |<Destroyed handle 0x5F01466B with function DeleteDC

...and some time later (about 9 seconds)...

  25319     118172: |>Creating HBITMAP # 862/937 with function CreateBitmap
  25320     118172: |<Created  HBITMAP # 862/937 0x9605596F with function CreateBitmap *
  25321     118172: |>Creating HDC # 16/16 with function CreateCompatibleDC
  25322     118172: |<Created  HDC # 16/16 0x39013C5B with function CreateCompatibleDC
  25323     118172: |>Creating HICON # 36/36 with function CreateIconIndirect
  25324     118172:  |>Creating HBITMAP # 862/937 with function CreateDIBitmap
  25325     118188:  |<Created  HBITMAP # 862/937 0x27056374 with function CreateDIBitmap
  25326     118188:  |>Creating HBITMAP # 863/938 with function CreateBitmap
  25327     118188:  |<Created  HBITMAP # 863/938 0xD20538B5 with function CreateBitmap
  25328     118188:  |>Creating HDC # 17/17 with function CreateCompatibleDC
  25329     118188:  |<Created  HDC # 17/17 0xD9015812 with function CreateCompatibleDC
  25330     118188:  |>Destroying HDC # 17/17 0xD9015812 with function DeleteDC
  25331     118188:  |<Destroyed handle 0xD9015812 with function DeleteDC
  25332     118203: |<Created  HICON # 36/36 0x087718AD with function CreateIconIndirect
  25333     118203: |>Destroying HBITMAP # 863/938 0x9605596F with function DeleteObject
  25334     118219: |<Destroyed handle 0x9605596F with function DeleteObject
  • The 1st column is the sequence count (useful when trying to order the sequence of events in multiple threads).
  • The 2nd is the time in ms from the first log item.
  • The : spaces | is a visual graph to see how far the calls are nested, relative to all of the ones I'm looking at.
  • The > represents just before going into the function, and the < represents just after leaving the function.
  • Then it states if creating or destroying and what object is being created or destroyed.
  • x/y where x is the number of those objects that are about to be created or the remaining number that exist after being destroyed, and y is the same thing, except it represents the base type (the type that is deleted).
  • The hex number is the handle value
  • Then the function used to create/destroy the object
  • If the line ends in a * it means that this handle has been given out before but hasn't been destroyed.

It's kind of interesting how much information is there and how the nesting works (really great for tracking window messages).

One other thing I noticed was this:

  25349     118250: |>Destroying HICON # 36/36 0x087718AD with function DestroyIcon
  25350     118250: |<Destroyed handle 0x087718AD with function DestroyIcon

The HICON gets destroyed, but the contained HBITMAPs don't, and they aren't referenced again anywhere in the logs (except when they are unexpectedly recycled). So, if this only occurs for HICONs, then it could be that DestroyIcon() isn't playing nice and is using some undocumented function. I'll have to investigate the rest of the logs.

1
I think what you observe is some handle cache functionality of the Windows OS.VuVirt
@VuVirt, and how would that work. These are't named objects. How would it determine that the same handle should be used?Adrian
HGDIOBJ is not an address (means not unique) but rather index of a slot in some table of finite size. So technically you can get same HGDIOBJ value when that table overflows.c-smile
@c-smile, but that would result in errors, which would be bad. The same handle should only be created if any preexisting handle was destroyed.Adrian
Maybe it is because you are detouring only publically available GDI functions? I bet there are a bunch of undocumented APIs used internally. For example does DestroyIcon call DeleteObject or some other method to dispose of icon bitmaps?user7860670

1 Answers

1
votes

Ok. Looking thoroughly through the logs, and logging how many GDI objects are around with GetGuiResources(), it would appear that there are HBITMAPs created when creating an HICON. However, the DestroyIcon() function bypasses the DestoryObject() function call when destroying them.

I guess someone at MS wanted to save a few cycles by doing this? Whatever.

Still, this was an interesting exercise and the logging appears to work very well.