GDI对象泄漏问题排查分析

本文详细阐述了GDI对象泄漏的问题,包括其原理、检测方法(如任务管理器、GDIView工具、WinDBG和MemoryValidator),以及定位和解决策略,强调了及时释放GDI对象以防止内存泄漏的重要性。
摘要由CSDN通过智能技术生成

GDI对象泄漏问题排查分析

和其他内存对象一样,GDI对象也是一种内存对象,它也会占用相关内存,如果没有及时使用DeleteObject等函数去删除释放GDI对象的话,会造成内存泄漏。

GDI对象造成的泄漏往往表现有如下现象:

  1. UI操作卡顿或者整个程序运行卡顿,或者程序直接卡死。
  2. 造成程序崩溃;有一些UI库在使用GDI对象的时候,并不判断分配的GDI对象是否成功(默认当作成功),当GDI泄漏之后,导致GDI对象无法分配,直接使用造成崩溃。
  3. UI显示异常;例如有一些软件容错性处理的不错,当GDI无法分配的时候,并不会对软件造成使用上的影响,只是UI资源加载异常。

本文我们来分析一下GDI对象造成的泄漏问题应该如何分析。

1. 技术概述

GDI对象也是一种内存资源,会消耗内存,在Windows下面有位图,画刷,DC等GDI对象,下面我们统计一下Windows的GID对象,如下:

GDI objectCreator functionDestroyer function
BitmapCreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDIBitmap, CreateDIBSection, CreateDiscardableBitmapDeleteObject
BrushCreateBrushIndirect, CreateDIBPatternBrush, CreateDIBPatternBrushPt, CreateHatchBrush, CreatePatternBrush, CreateSolidBrushDeleteObject
DCCreateDCDeleteDC, ReleaseDC
Enhanced metafileCreateEnhMetaFileDeleteEnhMetaFile
Enhanced-metafile DCCreateEnhMetaFileCloseEnhMetaFile
FontCreateFont, CreateFontIndirectDeleteObject
Memory DCCreateCompatibleDCDeleteDC
MetafileCreateMetaFileDeleteMetaFile
Metafile DCCreateMetaFileCloseMetaFile
PaletteCreatePaletteDeleteObject
Pen and extended penCreatePen, CreatePenIndirect, ExtCreatePenDeleteObject
RegionCombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn, CreateRectRgn, CreateRectRgnIndirect, CreateRoundRectRgn, ExtCreateRegion, PathToRegionDeleteObject

当我们使用GDI创建函数创建GDI对象之后,但是并不使用销毁函数销毁其对象之后就会造成GDI内存泄漏,如下:

void GdiLeak()
{
    HPEN hPen = NULL;
    hPen = CreatePen(PS_SOLID,5,RGB(255,0,0));

    //...

    //DeleteObject(hPen);
    //hPen = NULL;
}

如果我们平时忘记调用DeleteObject(hPen)的时候,就会造成GDI对象被泄漏。

2. 关于GDI句柄

我们知道一个内核对象都是通过句柄来进行引用的,一个内核对象的句柄其实是内核数据结构的一个哈希表索引;那么GDI句柄是什么呢?可以参考Windows GDI句柄分析;在这一篇文章中,我们得知GDI句柄在单个进程中默认被限制在了10000个,这里我们做一个实验验证一下。

我们使用如下代码泄漏GDI对象:

void CGdiLeakDlg::OnBnClickedButtonPenLeak()
{
	for (int i = 0; i < 5000; ++i)
	{
		HPEN hPen = NULL;
		
		hPen = CreatePen(PS_SOLID, 5, RGB(255, 0, 0));
	}
}

void CGdiLeakDlg::OnBnClickedButtonBrushLeak()
{
	for (int i = 0; i < 5000; ++i)
	{
		HBRUSH hBrush = CreateSolidBrush(RGB(200, 200, 200));
	}
}

然后使用ProcExp来查看进程的GDI句柄信息,如下:
在这里插入图片描述

我们可以看到GDI Handles默认值是:10000个,无法继续再次创建了;那么我们可以看一下是否是这样的,如下:
在这里插入图片描述

当我们再次改变窗口大小的时候,创建已经无法渲染了(应该是无法创建GDI资源了)。

3. GDI泄漏检测

当遇到(或者怀疑)GDI内存泄漏的时候,我们可以使用两个工具进行查看,其一是任务管理器(选择GDI对象列),如下:
在这里插入图片描述

其二是使用一个GDIView的工具,这一款工具专门用来查看进程GDI信息,例如我们可以看到GDI信息如下:

在这里插入图片描述

这个工具可以查看到所有GDI的具体信息,以及每个对象的句柄等有用信息。

当然我们使用其他工具也可以查看GDI的句柄信息,例如:ProcExp或者ProcessHacker以及其他ARK工具。

4. GDI泄漏定位

GDI泄漏的定位和内存以及其他内核对象,我们可以通过如下方法:

  1. 使用WinDBG动态调试。
  2. 借助检测工具。

这里我们分析一下WinDBG的方法,例如上述,我们可以看到很多的句柄都是泄漏的Pen对象,那么我们就可以针对创建Pen的函数添加断点:

0:004> x gdi32!CreatePen*
76666440          GDI32!CreatePenStub (_CreatePenStub@12)
76667380          GDI32!CreatePenIndirectStub (_CreatePenIndirectStub@4)

0:004> bm gdi32!CreatePen*
  1: 76666440          @!"GDI32!CreatePenStub"
  2: 76667380          @!"GDI32!CreatePenIndirectStub"

0:004> g
ModLoad: 755c0000 755d2000   C:\WINDOWS\SysWOW64\kernel.appcore.dll
ModLoad: 696a0000 69782000   C:\WINDOWS\SysWOW64\textinputframework.dll
ModLoad: 7a2e0000 7a3ab000   C:\WINDOWS\SysWOW64\CoreMessaging.dll
ModLoad: 7a3b0000 7a643000   C:\WINDOWS\SysWOW64\CoreUIComponents.dll
ModLoad: 74310000 743fb000   C:\WINDOWS\SysWOW64\wintypes.dll
ModLoad: 74290000 7429b000   C:\WINDOWS\SysWOW64\CRYPTBASE.DLL
ModLoad: 72e40000 72ed6000   C:\WINDOWS\SysWOW64\TextShaping.dll
Breakpoint 1 hit

然后我们就可以查看断点处的调用,就可以发现是哪里存在泄漏了,如下(指示了GdiLeak!CGdiLeakDlg::OnBnClickedButtonPenLeak在调用CreatePen可能导致泄漏):

0:000> kc
 # 
00 GDI32!CreatePenStub
01 GdiLeak!CGdiLeakDlg::OnBnClickedButtonPenLeak
02 GdiLeak!_AfxDispatchCmdMsg
03 GdiLeak!CCmdTarget::OnCmdMsg
04 GdiLeak!CDialog::OnCmdMsg
05 GdiLeak!CWnd::OnCommand
06 GdiLeak!CDialogEx::OnCommand
07 GdiLeak!CWnd::OnWndMsg
08 GdiLeak!CWnd::WindowProc
09 GdiLeak!AfxCallWndProc
0a GdiLeak!AfxWndProc

当然网上还有人提出了使用Memory Validator工具检测的方法,对于这个工具的使用可以参考https://www.softwareverify.com/blog/theres-more-than-one-way-to-leak-a-gdi-object/(不过这个工具并不是免费使用的)。

除了这些方法之外,还有https://github.com/Softanics/gdi-leaks也是为了解决GDI泄漏缩写的工具,我们可以利用这个工具,比较快速定位问题。

5. 总结

GDI对象作为一个Windows的内存资源,也占用其内存空间,并且需要用户自动管理和释放;为了降低GDI对象的资源泄漏,我们可以使用智能指针来管理GDI对象句柄。

当我们的程序出现:

  1. 程序崩溃。
  2. UI资源显示异常。
  3. 程序运行缓慢。

此时我们应当注意是否GDI资源出现问题,可以使用如上方式进行GDI检测和泄漏位置的定位。

6. 参考

关于GDI内存资源泄漏的其他参考,可以参见如下链接:

  1. https://learn.microsoft.com/en-us/archive/msdn-magazine/2001/march/resource-leaks-detecting-locating-and-repairing-your-leaky-gdi-code
  2. https://www.deleaker.com/blog/2021/12/16/gdi-leaks-how-to-identify-and-fix-them/
  3. https://learn.microsoft.com/en-us/archive/msdn-magazine/2003/january/detect-and-plug-gdi-leaks-with-two-powerful-tools-for-windows-xp
  4. https://www.softwareverify.com/blog/theres-more-than-one-way-to-leak-a-gdi-object/
  5. https://www.codeproject.com/Articles/5306193/Find-GDI-Leaks-With-Windows-Debugger
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值