GDI对象调试器

        GDI编程是所有Windows编程的基础组成部分。GDI作为连接应用程序和Windows图形引擎之间的桥梁其重要性不言而喻,关于GDI讲解的书籍和文档也多如牛毛,但像《Windows图形编程》这样深入讲解GDI内部机理的书籍却不可多得,与之相关的剖析、调试、诊断工具更是少之又少且大都已不合时宜沦为古玩。
        像Bear(http://www.the-sz.com/products/bear/)这种仍能正常工作且还在更新的已极为稀有,几乎已是唯一一个能够预览GDI对象图样的工具。即使Bear已是唯一一款能预览GDI对象图样的工具,但仍不具备调试GDI对象(如:访问某个GDI对象时中断)或转储GDI对象(如:转储位图对像到磁盘)的能力。
        还有一款叫GDIView(http://www.nirsoft.net/utils/gdi_handles.html)的工具也能勉强作为GDI的分析和诊断工具,但无法预览GDI对象图样,它只能展示某个进程中各类GDI对象的现存数量以及GDI对象的执行体结构,这对于诊断GDI泄漏和分析绘制时发生的问题似乎没有太大帮助。

        我一直以为有朝一日微软会为VS增加调试GDI对象的功能,或某人会为此付诸一些时间做出一个像样的插件,可惜并没有,所以干脆自己写了一个,并没有什么悬念。现在拿来共享-为方便使用GDI编程的人们;希望此工具的出现能减少十分钟花在查找GDI问题上的时间。


GDI对象调试器

        我并不认为也不愿尝试能写出优于VS的调试器;调用DebugActiveProcess或以DEBUG_PROCESS标志CreateProcess的机会留给VS。“GDI对象调试器”(或许称之“GDI对象调试插件”更好)只是注入一部分代码(下称抓取模块)到目标进程实现GDI对象或消息劫取。被称为“GDI对象调试器”仅因行为像调试器而已;能实现以下功能:

  • 预览目标进程的GDI对象图样。
  • 分析DC属性(包括颜色、点、尺寸、矩形、区域、路径、映射模式、选中的对象等等所有可获得的DC属性;详参图1.1)。
  • 查看创建GDI对象时的栈帧;能定位到创建GDI对象的帧,藉此得以轻易的分析GDI对象泄漏并找到发生泄漏的代码。
  • 断点;可设立对象访问断点和窗口绘制断点和消息断点(窗口发生某消息时中断)。
  • 转储GDI对象到磁盘(如转储位图或DC中选中的位图)。
  • 还原GDI对象结构;包括已公开的为API所用的形如 BITMAP、LOGFONT的结构和未公开的执行体中NT GDI对象表存储的GDIOBJECT结构。
  • 分析GDI句柄结构;包括对象在NT GDI对象表中的索引、栈对象标记、对象类型等。
  • 查看各类GDI对象占用率(GDI对象数量)。
注:
        上表斜体字标注的项表示该功能已实现但暂无UI支持;我稍后会上传源码到我的资源中,用户可下载源码自行修改UI来支持上述功能。


图1.1
GDI对象调试器

GDI对象调试器附加后对目标进程产生[可能]的影响

        HOOK SSDT表中函数无法很好推论用户行为且需要写驱动。GDI对象调试器大概挂接了近300个操作GDI对象的应用层(API,包括一些未导出的)函数,所以,需要将一部分代码注入到目标进程执行,这会影响目标进程运行环境,以下列出注入后可能对目标进程产生的影响:

  • 使用CreateEvent创建一个名为PID:%d_FH_InitializeCompleteEvent的初始化完成事件对象。
  • 使用CreateFileMapping在全局名字空间创建一个名为PID:%d_FH_InitializeFailedDescriptBufferName的文件映射对象用于传递初始化失败消息。
  • 使用CreateFileMapping在全局名字空间创建一个名为PID:%d_FH_GDIObjectRobberStack的文件映射对象用于向调试进程传递GDI对象。
  • 使用CreateFileMapping在全局名字空间创建一个名为PID:%d_FH_GDIObjectRobberOption的文件映射对象用于接收调试进程设置的选项和断点等指令。
  • 使用CreateFileMapping在全局名字空间创建一个名为PID:%d_FH_WindowMessageRobberStack的文件映射对象用于向调试进程发送目标进程中某个线程正在发生的消息。
  • 抓取模块 初始化过程还会挂接 NtQueryInformationThreadNtTerminateThread用来仿制 TEB 来存储抓取模块 所需要的线程环境块(包括递归检测和消息帧链等),还会挂接InternalCallWinProc函数实现消息劫取,由于InternalCallWinProc函数会被很多线程频繁调用,可能会导致性能下降。
  • HOOK后的GDI API函数会在Detour函数内发生额外的转储对象到调试进程操作,如果是创建GDI对象的API还会包括回溯栈帧操作,这些过程可能导致性能大幅下降。

注:

  • 此处列举的不代表全部,且将来可能还有扩充,但承诺只使用影响较小的Windows内核对象作为进程间通讯手段,不会使用COM(改变线程模型或暗含线程模型为……的假设)或Socket(初始化为特定版本)等可能改变运行环境的方式。
  • 以上对象名称中 PID:%d 处的 %d 代表目标进程的进程ID。

获得GDI对象调试器:

        到此 http://download.****.net/download/passfuhao/10158251(或老版本下载地址:http://download.****.net/download/passfuhao/9910116)下载“GDI对象调试器.rar”文件并解压得到 GDIObjectView 目录,其内包含两个核心文件为:GDIObjectRobber.dll和GDIObjectView.exe,分别为抓取模块 和主程序。用户需要通过GDIObjectView.exe主程序向目标进程注入GDIObjectRobber.dll,随后既可观查到目标进程的GDI对象信息(参考图1.2)。

图1.2

GDI对象调试器


使用GDIObjectView.exe


GDIObjectView 需要命令行参数表示目标进程和调试符号路径,命令行参数如下:

        GDIObjectView { [/PID ProcessId | /IM ImageName] } [PDBFiles1; PDBFiles2; PDBFiles...n]

参数列表:

/PID  ProcessId 指定要附加的进程ID。GDIObjectView将附加到该进程中读取GDI对象。
/IM ImageName 指定要附加的进程映像名称。GDIObjectView将附加到该进程中读取GDI对象。
 /CMD CommandLine 指定启动新进程所使用的命令行参数。GDIObjectView将以挂起方式启动该进程并在附加GDI对象抓取模块后恢复进程。
PDBFiles1..n MultiplePDBFilesDirectory 调试符号文件目录(多个目录用 ';' 间隔)。GDIObjectView通过这些目录下的符号文件还原目标进程创建GDI时的栈帧源信息。若该忽略该参数则GDIObjectView从默认目录加载调试符号。(参见注意项第1、2条)。

例:

        GDIObjectView /IM Notepad.exe C:\Symbols
        GDIObjectView /PID 1234 SRV*\Symbols* http://msdl.microsoft.com/download/symbols
        GDIObjectView /CMD "C:\window\notepad.exe C:\123.txt" C:\Symbols
        GDIObjectView /IM QQ.exe

注意:

        1、若忽略 PDBFiles1...n 参数GDIObjectView将从环境变量 _NT_SYMBOL_PATH 和 _NT_ALTERNATE_SYMBOL_PATH 指向的目录搜索符号文件。
        2、若 PDBFiles1..n 参数指向的符号路径包含网络路径或在忽略 PDBFiles1...n 参数的情况下环境变量 _NT_SYMBOL_PATH 和 _NT_ALTERNATE_SYMBOL_PATH 包含了网络路径,则GDIObjectView将尝试下载符号,这可能导致GDIObjectView加载时间过长。
        3、若没有指定正确的调试符号路径,你将只能看到类似 ModuleName.dll!00e090f0 这样的栈帧。


另附几张图:

图1.3,配合VS调试时查看创建GDI对象的栈帧:

GDI对象调试器

 图1.4,查看创建兼容DC的栈帧:

GDI对象调试器

图1.5,设置断点:

GDI对象调试器

两个已知的BUG

1、以GetDC得到的HDC作实参调用CreateCompatibleDC,然后使用SelectObject向兼容DC中选入一个LoadImage得到的位图会导致创建兼容DC的栈帧丢失。
2、忘了。
暂时没时间解决。

有关分析和诊断GDI错误的其它值得参考的工具和文章:

用 Windows XP 的两个强有力的工具在您的代码中检测并堵塞GDI 泄漏:https://msdn.microsoft.com/zh-cn/library/aa686029.aspx

Bear:http://www.the-sz.com/products/bear/

GDIView:http://www.nirsoft.net/utils/gdi_handles.html

 

附,工具和源码下载地址:


已编译的工具:

****下载:http://download.****.net/download/passfuhao/10158251(无法上传0分资源,需要2积分)

百度网盘下载:https://pan.baidu.com/s/1jI1TeS2


源码:

****下载:http://download.****.net/download/passfuhao/10158252 (无法上传0分资源,需要2积分)

百度网盘下载:https://pan.baidu.com/s/1skS3dQt


答复HOOK近300个函数的质疑:

        总是需要用同样的话回答如何写近300个detour函数的问题,实际只写了一个detour函数,看这儿:http://blog.****.net/passfuhao/article/details/78775308


2017年12月13日18时23分

付浩