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问题上的时间。
我并不认为也不愿尝试能写出优于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
HOOK SSDT表中函数无法很好推论用户行为且需要写驱动,考虑PG等等老大难问题。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的文件映射对象用于向调试进程发送目标进程中某个线程正在发生的消息。抓取模块 初始化过程还会挂接 NtQueryInformationThread和NtTerminateThread用来仿制 TEB 来存储抓取模块 所需要的线程环境块(包括递归检测和消息帧链等),还会挂接InternalCallWinProc函数实现消息劫取,由于InternalCallWinProc函数会被很多线程频繁调用,可能会导致性能下降。HOOK后的GDI API函数会在Detour函数内发生额外的转储对象到调试进程操作,如果是创建GDI对象的API还会包括回溯栈帧操作,这些过程可能导致性能大幅下降。
注:
此处列举的不代表全部,且将来可能还有扩充,但承诺只使用影响较小的Windows内核对象作为进程间通讯手段,不会使用COM(改变线程模型或暗含线程模型为……的假设)或Socket(初始化为特定版本)等可能改变运行环境的方式。以上对象名称中 PID:%d 处的 %d 代表目标进程的进程ID。
到此 http://download.csdn.net/download/passfuhao/10158251(或老版本下载地址:http://download.csdn.net/download/passfuhao/9910116)下载“GDI对象调试器.rar”文件并解压得到 GDIObjectView 目录,其内包含两个核心文件为:GDIObjectRobber.dll和GDIObjectView.exe,分别为抓取模块 和主程序。用户需要通过GDIObjectView.exe主程序向目标进程注入GDIObjectRobber.dll,随后既可观查到目标进程的GDI对象信息(参考图1.2)。
图1.2
图1.3,配合VS调试时查看创建GDI对象的栈帧:
图1.4,查看创建兼容DC的栈帧:
图1.5,设置断点:
用 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.csdn.net/download/passfuhao/10158251(无法上传0分资源,需要2积分)
百度网盘下载:https://pan.baidu.com/s/1jI1TeS2
总是需要用同样的话回答如何写近300个detour函数的问题,实际只写了一个detour函数,看这儿:http://blog.csdn.net/passfuhao/article/details/78775308