一起谈.NET技术,Visual Studio插件GDIWatch实现浅析

  GDIWatch 是Virgo Software 开发的一个for Visual Studio的插件,支持2005/2008/2010,它的功能主要是在一个类似watch的窗口上显示被调试程序的GDI对象的当前状态,比如HBRUSH的颜色,大小,图片等等,并且它还能在调试过程中高亮显示有变化的项目,方便程序员跟踪调试画图函数。

  下载地址: http://www.gdiwatch.com/GDIWatch.msi

  (小声说一下,crack在文中提供了) 

  这是官方的截图:

  顺便再贴一个 GDIWatch 在 VS2010上使用的效果图:

  感觉还不赖,使用起来也挺方便的,就是拽个变量到它上面就可以了。

  GDIWatch 不是免费软件,作者给了15天的试用期,如果需要继续使用就要到官网 www.gdiwatch.com 联系作者获取注册码。

  P.S. 话说前天我在公司正好想上他的网站看看价钱如何,结果发现他的主页不知出现神马问题没法显示了,囧啊。

  P.P.S. 印象中貌似是要100多美刀的样子。

  P.P.P.S. 在15天后我偶尔还想继续使用,但是中国国情告诉我,花100多美刀买个插件是稍微有点贵了的说,而且目前在公司还没用上VS2010,所以便可耻地尝试crack,没想到很好crack的说,稍微改动一下居然就搞定了,主要是该作者的防范意识不够啊,犯了很多防破解的大忌,给了人家很多线索,有需要的童鞋请猛击此处下载,适用于1.5.1.254版本,替换原版之前请自行备份以防万一!

  好了, 言归正传,我当初之所以找到这个软件是因为前阵子一直在写画图的代码,本来是想说在网上找个VC6的插件的(没办法,公司还是在用),先是在 CodeProject 上找到一篇某位国人很久以前发表的文章,可是他居然不是开源的(这不坑爹吗),而且远没有 GDIWatch 那么方便好用(不给力啊),最奇怪的是CodeProject 居然让他把文章给发表上去了(我勒个去),真是无奈。

  不过该作者倒是简单提到了一下他实现的方法:

  The steps to do watch Image is :
  (1)get the selection text by ISelectionText interface
  (2)get the value of selection text by IDebugger interface
  (3)Read the memeory or bitmap data from the debugged process memory space
  (4)show it

  最后只找到这个支持VS2005+的 GDIWatch,于是开始寻思这玩意怎么实现,我想如果不是很复杂的话说不定可以在闲暇时间做一个for VC6的版本出来的说。

  我首先思考的是要实现这样的插件最重要是要解决哪些问题:

  1、最最重要的是,必须能够跨进程“访问”被调试进程的GDI objects,这是当然的;

  2、必须能跟VS协调运作,响应调试动作并及时更新GUI,要像VS自己的watch那么好用;

  3、必须有界面能显示GDI objects,这......必须的;

  当然要完善这个插件的话,还需要尽量满足下列条件:

  1、避免使用undocumented trick,保证兼容性;

  2、如GDIWatch那样支持拖放变量名到GUI上;

  3、高亮有变化的内容,方便跟踪;

  在定下上面这些条件后,下一步就是逐个解决问题了。

  首先,要获取GDI对象的属性,基本是要走这条路:

DWORD GetObjectType(__in  HGDIOBJ h);

HGDIOBJ GetCurrentObject(__in  HDC hdc,__in  UINT uObjectType);

int GetObject(__in   HGDIOBJ hgdiobj, __in   int cbBuffer, __out  LPVOID lpvObject);

  然而,GDI对象是基于进程的,GDIWatch作为一个插件,也就是VS的一个DLL,它如果要拿被调试进程的GDI对象句柄来直接用必然是不行的,

  GDI objects 也不在 DuiplicateHandle 这个API支持的 object handle 的范畴之内。

  当然了,GDI对象毕竟也是数据,在用户模式不能做到的,在内核模式肯定有奇淫巧计可以做到,比如说访问GDI对象表:

http://topic.csdn.net/t/20031009/14/2337150.html

http://hi.baidu.com/qzccan/blog/item/154b542375171440ac34de08.html

  说起来有一款软件很可能就是这么实现的,叫做 GDIView,它可以查看指定进程当前打开的所有GDI objects并显示其属性:

  不过这些都属于tricks,不是标准的做法,而且我也不熟悉具体实现方法,所以只能放弃。

  其实,毕竟目标进程是在被调试的状态下,这还是给了插件解决这个问题的环境,或者说至少有一些条件可以被利用。

  调试器是可以有办法读写被调试进程的内存的,可以在被调试进程的运行空间插入一段代码让它执行,只要上面提到的 GetObjectType 等API是在被调试进程的领域执行的,那么句柄就是有效的,自然能得到所需的结果。

  要读写内存,必然是这条路:

HANDLE WINAPI OpenProcess(__in  DWORD dwDesiredAccess,  __in  BOOL bInheritHandle,  __in  DWORD dwProcessId);

BOOL WINAPI ReadProcessMemory(__in   HANDLE hProcess,  __in   LPCVOID lpBaseAddress,  __out  LPVOID lpBuffer,  __in   SIZE_T nSize,  __out  SIZE_T *lpNumberOfBytesRead);

LPVOID WINAPI VirtualAllocEx(__in HANDLE hProcess,  __in_opt LPVOID lpAddress,  __in SIZE_T dwSize,  __in DWORD flAllocationType,  __in DWORD flProtect);

BOOL WINAPI WriteProcessMemory(__in   HANDLE hProcess,  __in   LPVOID lpBaseAddress,  __in   LPCVOID lpBuffer,  __in   SIZE_T nSize,  __out  SIZE_T *lpNumberOfBytesWritten);

  接下来的事情大概是这样:

  设计一段代码,主要做的事情是接受指定的GDI句柄,然后通过 GetObjectType/GetCurrentObject/GetObject 等API去获取 GDI object 的相关信息,然后将结果保存在某个buffer。

  假设这段代码是一个C函数,那么代码大致是:

typedef struct tagBrushInfo
{
    HBRUSH      hBrush;
    LOGBRUSH    logBrush;
}BrushInfo, *PBrushInfo;
typedef struct tagPenInfo
{
    HPEN		hPen;
    LOGPEN      logPen;
}PenInfo, *PPenInfo;
typedef struct tagDCInfo
{
    HDC         hDC;
    BrushInfo   brushInfo;
    PenInfo     penInfo;
}DCInfo, *PDCInfo;
LPVOID GetGDIObjectInfo(HGDIOBJ hGDIObjects)
{
	LPVOID pInfo = NULL;
    DWORD dwObjType = GetObjectType(hGDIObjects);
    switch ( dwObjType )
    {
    case OBJ_DC:
        {
			PDCInfo pDCInfo = new DCInfo;
            pDCInfo->hDC = (HDC)hGDIObjects;
            // retrieve the brush info
            pDCInfo->brushInfo.hBrush = (HBRUSH)GetCurrentObject(pDCInfo->hDC, OBJ_BRUSH);
            if ( pDCInfo->brushInfo.hBrush )
            {
                GetObject(pDCInfo->brushInfo.hBrush, sizeof(LOGBRUSH), &pDCInfo->brushInfo.logBrush);
            }
            // retrieve the pen info
            pDCInfo->penInfo.hPen = (HPEN)GetCurrentObject(pDCInfo->hDC, OBJ_PEN);
            if ( pDCInfo->penInfo.hPen )
            {
                GetObject(pDCInfo->penInfo.hPen, sizeof(LOGPEN), &pDCInfo->penInfo.logPen);
            }
            pInfo = pDCInfo;
        }
        break;
    case OBJ_BRUSH:
        if ( hGDIObjects )
        {
			PBrushInfo pBrushInfo = new BrushInfo;
            GetObject(hGDIObjects, sizeof(LOGBRUSH), &pBrushInfo->logBrush);
			pInfo = pBrushInfo;
        }
        break;
    }
    return pInfo;
}

  接下来就是要把 GetGDIObjectInfo 这个函数的代码通过某种方式拷贝到被调试进程中,方法很多,其中一种方法是通过插件内实现一份该函数,然后设法计算出函数体的二进制代码长度,从而将函数代码拷贝,一个具体的例子是 CodeProject 上非常著名的文章 Three Ways to Inject Your Code into Another Process 中:

static DWORD WINAPI ThreadFunc (INJDATA *pData)
{
    pData->fnSendMessage( pData->hwnd, WM_GETTEXT,    // Get password
                          sizeof(pData->psText),
                          (LPARAM)pData->psText );
    return 0;
}
// This function marks the memory address after ThreadFunc.
// int cbCodeSize = (PBYTE) AfterThreadFunc - (PBYTE) ThreadFunc.
static void AfterThreadFunc (void)
{
}

  可以看出是利用编译器生成代码的习惯,通过一个额外的空函数 AfterThreadFunc 得到 ThreadFunc 的可能大小(即 nCodeSize = AfterThreadFunc - ThreadFunc)。

  此外也可以尝试基于X86汇编指令自行组装 GetGDIObjectInfo 的二进制代码,不过不是很容易阅读和维护代码。

  不过这里还有一个需要注意的地方,CodeProject 的那篇文章提到了,就是同一个API的地址在不同进程中可能会被映射到不同的地址上,所以要拷贝的代码中肯定是不能直接那样调用的,LoadLibrary 和 GetProcAddress 就是很好的一个能得到正确的地址的方法。前面的 GetGDIObjectInfo 函数还使用了 new operator,也要对应修改为API函数如 VirtualAlloc 等。

  在终于把这个GetGDIObjectInfo函数的代码拷贝到目标进程后,下一步最为重要,就是要设法让被调试进程执行该函数。

  既然插件已经是调试器的小弟,那么当然可以利用debug API来实现,而不必用到 CreateRemoteThread 这样感觉稍微猥琐的方法。

  VS 应该是通过 WaitForDebugEvent 等一系列API来进行调试的,所以可以拦截它,比如在先调用 SuspendThread 把当前进程中所有非插件模块所在线程给暂停掉,然后它的函数头部加个 jmp,让它先跳转到自己的一个函数,在这个函数里,要先进行一些逻辑判断,在适合的时机利用 GetThreadContext/SetThreadContext 来操作被调试进程,比如修改eip,然后 ContinueDebugEvent 让被调试进程执行 GetGDIObjectInfo 函数,在取得GDI对象的信息buffer后,拷贝到插件自己的内存空间上,调用 ResumeThread 恢复所有之前被暂停的线程,最后不要忘了还要跳转回 WaitForDebugEvent 的函数里。

  关于运用debug API的,最近的 Writing Windows Debugger 系列文章貌似不错,我有时间要看看。

  做完上面这些事情后,可以给插件的窗口post 一个消息,让它读取 GetGDIObjectInfo 返回的结果并更新GUI。

  至于BITMAP这个比较特殊的对象,可以用 CreateDIBSection 这个API。

  可是事情到此还没完,因为还要写VC6插件的代码,还好这个问题已经有一篇非常棒的文章可以参考:Undocumented Visual C++

  最后就是那个类似watch窗口的属性列表控件,我没找到现成的,不过倒是有一个还不错的封装类 CPropTree,只是还需要在它的基础上加不少代码进行增强。

  P.S. 终于把这几天的想法记录下来,感觉真是说起来容易做起来难啊,这个小小的插件要真正实现起来还是相当麻烦的,有大量的工作要做,难怪人家要卖 100 多美刀的说......

时间: 2024-11-05 22:00:21

一起谈.NET技术,Visual Studio插件GDIWatch实现浅析的相关文章

Visual Studio插件GDIWatch实现浅析

GDIWatch 是Virgo Software 开发的一个for Visual Studio的插件,支持2005/2008/2010,它的功能主要是在一个类似watch的窗口上显示被调试程序的GDI对象的当前状态,比如HBRUSH的颜色,大小,图片等等,并且它还能在调试过程中高亮显示有变化的项目,方便程序员跟踪调试画图函数. 下载地址: http://www.gdiwatch.com/GDIWatch.msi (小声说一下,crack在文中提供了) 这是官方的截图: 顺便再贴一个 GDIWat

Visual Studio插件GDIWatch实现浅“.NET研究”析

GDIWatch 是Virgo Software 开发的一个for Visual Studio的插件,支持2005/2008/2010,它的功能主要是在一个类似watch的窗口上显示被调试程序的GDI对象的当前状态,比如HBRUSH的颜色,大小,图片等等,并且它还能在调试过程中高亮显示有变化的项目,方便程序员跟踪调试画图函数. 下载地址: http://www.gdiwatch.com/GDIWatch.msi (小声说一下,crack在文中提供了)&nbsp上海网站建设; 这是官方的截图: 顺

ReSharper 6发布 智能化微软Visual Studio插件

ReSharper 6更新日志: Rich support for JavaScript, CSS, and ASP.NET MVC 3 Razor view engine including code inspections, navigation and usage search, and extended code completion. Navigation and search improved with new features and usability enhancements.

一个Visual Studio插件,在跨加载项目时保存你打开的文件

前阵子我的好友Sam Saffron(来自Stack Overflow和Mini Profiler)在 Skype上向我抱怨他发现了一件十分讨厌的事情,每次他在Visual Studio之外更新他的项目时,他都会遇到一个提示"重新加载项目",并会失去所有他已打开的文件,因为 Visual Studio 将关闭它们. 这显然在StackOverflow网站上成为一个话题.因为它们采用分布式的源控件,经常有十个或更多的人在同一项目内部编码,所以它们一直在集成.更新它们的项目来测试它时,所有

ReSharper 6.0 Beta 2发布 智能化微软Visual Studio插件

无庸置疑,ReSharper是最智能化的微软Visual Studio插件.它包括一系列丰富的能大大增加C#和Visual http://www.aliyun.com/zixun/aggregation/11183.html">Basic.net开发者生产力的特征.使用ReSharper,你可以进行深度代码分析,智能代码协助,实时错误代码高亮显示,解决方案范围内代码分析,快速代码更正,一步完成代码格式化和清理,业界领先的自动代码重构,高级的集成单元测试方案,和强大的解决方案内导航和搜索.实

Visual Studio 15 插件新特性

之前已经报道过,在Visual Studio 15中Microsoft优先要处理的问题就是缩减启动时间和编辑器安装包的大小,而这势必会改变插件的使用方式.正如Microsoft公司的Tim Sneath所说,为了支持这些新功能插件开发者需要做出一些改变. Microsoft在Visual Studio插件方面始终面临着一个困境:为了让插件既有用又功能强大,Microsoft将很多本来只能自己使用的Visual Studio API开放了出来.这样做的负面影响就是难以禁用具有不良行为.影响编辑器性

Visual Studio 2008中的SQL数据库发布

数据库发布向导(Database Publishing Wizard)是一个流行的Visual Studio插件,它支持把本地的数据库部署到远程主机上去.Visual Web Developer团队宣布将把这个向导集成到Visual Studio 2008之中. 目前Visual Studio 2008的Beta 2版本还没包含这个向导,不过预计2008年2月发布的VS 2008 RTM将会一并安装这个向导的1.2版.这个数据库发布插件现在的版本是1.1,从属于SQL Server Hostin

性能: 使用Visual Studio分析器找出应用程序瓶颈

本文讨论: 以性能瓶颈为目标 应用程序代码分析 比较分析数据 性能报告 本文使用了以下技术: Visual Studio 2008 在过去十年间,涌现了许多新的软件技 术和平台.每种新技术都要求掌握专门的知识才能创建出性能良好的应用程序.现在,由于各种 Internet 技术(如博客)使失望的用户可轻松地否定您的应用程序,因此您确实需要将性能放到首要位 置.在计划早期,就应添加响应性能要求并创建原型来确定可能的技术限制.在整个开发过程中,还应衡 量应用程序的各个性能方面以发现可能的性能下降,同时

VSTO 3.0: 用Visual Studio 2008开发Office业务应用程序

本文讨论: Visual Studio 2008 新增功能 为 Outlook 创建自定义窗体区域 连接数据源 添加搜索功能 本文使用了以下技术: Visual Studio 2008 到目前为止,我敢确定您已经听说了一些有关 Visual Studio 2008 的趣闻,它具有一些强大的功能,如支持 LINQ.改进了 Web 开发以及与 Windows Vista 和 SharePoint 紧密集成等等.不过 Visual Studio 2008 真正突出的一个特点是支持 Microsoft