zoukankan      html  css  js  c++  java
  • [D3D] 用PerfHUD来调试商业游戏

    来源:http://blog.csdn.net/RAINini/archive/2008/10/28/3162112.aspx

             PerfHUD是NV一个非常好用的工具,可以用于查看游戏的运行效率,找出瓶颈,也可以用于分析游戏渲染流程,看每个DPC的渲染操作,包括渲染状态,所用的shader等,非常强大。

             PerfHUD正常的用途是用于调试自己写的程序,但是有时候看到别的游戏一些好的效果,也想了解是怎么实现的,这时,PerfHUD也可以派上用场。

             要使用PerfHUD,就要对自己的D3D程序进行修改,在CreateDevice时,Adapter这个参数不要选用D3DADAPTER_DEFAULT,而是枚举所有的Adapter,选中Description中带有“PerfHUD”字符串的那个Adapter,一般来说,对于单显卡的机器,通常都是第二个Adapter,我没有多显卡,所以不知道多显卡情况下是不是往后顺移。

             同时,DeviceType不要选D3DDEVTYPE_HAL,而要选D3DDEVTYPE_REF。

             通过这样来创建Device的程序,才能用PerfHUD来挂接并分析,更详细的创建过程,查看PerfHUD的帮助文档吧。

             由上可以看出,对于别人的程序,Device都不是自己创建的,似乎不能用PerfHUD来分析了,但经过对要分析的程序做一些手脚后,也是可以的,以下拿《魔兽世界》来举例。

             首先,打开VC(我用的2005,2003大同小异,VC6没试过),确保Options里的这个选项是选中的,

     

             只有选中它了,才能用函数名称来作为断点断住DLL里的函数。

             然后用VC来把要分析的游戏的exe执行起来,点击VC的File->Open ->Project,在弹出的对话框中选中要分析的exe文件,如下图:

     

             把文件加进来后,在Solution下可以看到该文件,右键选Debug->Step into new instance,启动程序,然后下一个函数断点,点击菜单中的Debug->New Breakpoint->Break at function,在弹出的对话框中写入Direct3DCreate9,如下图:

     

             点OK后,就下好断点了,如果一个程序用的D3D,那么应该会在这个断点断住,要靠它来获取IDirect3D9这个interface的指针。

             做完以上步骤后,按F5让程序继续运行,应该会断在刚才那个函数断点,如下图:

     

             这时按Shift+F11,退出这个函数体,如果创建成功,应该就获取到了IDirect3D9,因为这个函数的返回值就是IDirect3D9的指针,只需看eax这个寄存器的值,就能找到这个指针,因为eax是存放函数返回值。

             在watch窗口里打入eax,当前eax的值是0x0016CFC0。

             打开一个memory窗口,把0x0016CFC0这个值敲进去,然后点在memory窗口下点右键,用4-byte Integer来查看内存,把0x0016CFC0这块内存的前4个字节的值拷贝下来,我机器上显示的值是4b641a98,为什么要拷贝这个值呢,因为一个带虚拟函数的类的指针所指向的地址,最开头的4个字节就是虚函数表的指针,通过它能找到各个虚函数的函数地址。

             在memory窗口里把这个值加上h后敲进去,同样用4-byte Integer来查看,得到就是IDirect3D9这个接口的各个函数的入口地址,真正要找的就是CreateDevice这个函数的地址,这时,打开你装的D3D SDK的d3d.h,搜索IDirect3D9这个接口的声明,应该会找到如下代码:

    DECLARE_INTERFACE_(IDirect3D9, IUnknown)
    {
    /*** IUnknown methods ***/
    STDMETHOD(QueryInterface)(THIS_ REFIID riid,
    void** ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef)(THIS) PURE;
    STDMETHOD_(ULONG,Release)(THIS) PURE;

    /*** IDirect3D9 methods ***/
    STDMETHOD(RegisterSoftwareDevice)(THIS_
    void* pInitializeFunction) PURE;
    STDMETHOD_(UINT, GetAdapterCount)(THIS) PURE;
    STDMETHOD(GetAdapterIdentifier)(THIS_ UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER9
    * pIdentifier) PURE;
    STDMETHOD_(UINT, GetAdapterModeCount)(THIS_ UINT Adapter,D3DFORMAT Format) PURE;
    STDMETHOD(EnumAdapterModes)(THIS_ UINT Adapter,D3DFORMAT Format,UINT Mode,D3DDISPLAYMODE
    * pMode) PURE;
    STDMETHOD(GetAdapterDisplayMode)(THIS_ UINT Adapter,D3DDISPLAYMODE
    * pMode) PURE;
    STDMETHOD(CheckDeviceType)(THIS_ UINT Adapter,D3DDEVTYPE DevType,D3DFORMAT AdapterFormat,D3DFORMAT BackBufferFormat,BOOL bWindowed) PURE;
    STDMETHOD(CheckDeviceFormat)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat) PURE;
    STDMETHOD(CheckDeviceMultiSampleType)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType,DWORD
    * pQualityLevels) PURE;
    STDMETHOD(CheckDepthStencilMatch)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat) PURE;
    STDMETHOD(CheckDeviceFormatConversion)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SourceFormat,D3DFORMAT TargetFormat) PURE;
    STDMETHOD(GetDeviceCaps)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS9
    * pCaps) PURE;
    STDMETHOD_(HMONITOR, GetAdapterMonitor)(THIS_ UINT Adapter) PURE;
    STDMETHOD(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS
    * pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) PURE;

    #ifdef D3D_DEBUG_INFO
    LPCWSTR Version;
    #endif
    };

             数了一下,发现CreateDevice是第17个函数,所以应该在虚函数表里的第17个元素,所以在4b641a98h所指向的内存往后数,第17个指针就是CreateDevice的函数入口点,我机器上的值是4b6c1670,应该说,在没做特别处理的情况下,同个D3D的版本,某个函数的函数地址都是一样的。

             在Disassembly代码窗口顶部的Address里输入4b6c1670h这个值,然后敲回车,就能定位到CreateDevice的函数体,按F9下个断点,然后按F5让程序继续运行,应该能断到这个函数,如下图:

              这时,查看esp的值,找出调用这个函数的地方,当前esp的值是005a4c4b,在刚进入函数体时,esp保存的是这个函数返回后要继续执行的下条指令的地址,所以在这条指令的上方,就是调用CreateDevice的代码了,同样,在Disassembly窗口里输入005a4c4bh,定位到该条指令,如下图:

     

             断点上方的那条call指令就是调用CreateDevice的地方

             上图中,从那条call指令往上数,第一个push是push this指针,也就是IDirect3D接口的指针push进去,第二个push就push第一个参数,可以看到,它原来是直接push 0的,也就是用D3DADAPTER_DEFAULT来作为第一个参数传给CreateDevice,再上面的一个push就是放入第二个参数的,可以看到是以下指令完成的,

    mov ebx,1
    push ebx

    也就是说把1做为第二个参数,其实也就是D3DDEVTYPE_HAL,所以,《魔兽世界》调用CreateDevice是中规中矩的。这时要做的就是把修改这段代码,从mov ebx,1这条指令开始替换,把它变成push 2,push 1,也就是把传给CreateDevice的第一个参数改成1,代表第二个Adapter,把第二个参数改成2,也就是D3DDEVTYPE_REF所代表的值。

             上图里的这三条指令就是要替换掉的指令:

    005A4C3D BB 01 00 00 00 mov ebx,1
    005A4C42
    53 push ebx
    005A4C43 6A
    00 push 0

             要替换成

    Push 2
    Push
    1

             这两条指令,一共是要替换8个字节的机器码。

             这时可以通过直接修改exe文件本身来达到目的,先把wow.exe复制一份,我把复制后的exe起名为wow_m.exe,然后用UltraEdit打开该exe文件,查找要修改的指令的机器码,从mov ebx,1这条指令的机器码搜索起,可以看到,该条指令的机器码是BB 01 00 00 00,一般来说,搜索的字节越多,越准确,因为你搜得太少,极有可能有好多个地方调用了同条指令,所以我直接把这条指令后面的机器码也加上,如下图:

     

             记得把“查找 ASCII”这个勾去掉,然后点下一个,就能定位到该指令所在的地方。一定要确保找到的地方只有一处,如果有多处,就用再多一些的机器码来搜索。最终定位到的地方如下图:

     

             从BB这个字节开始替换,把光标定位在第一个B处,然后顺序输入6A 02 6A 01,也就是push 2和push 1的机器码。这时还剩下4个字节,可全部替换成90,替换后的代码如下:

     

             修改工作到此结束,保存该exe,然后就可以用PerfHUD来调试了,下图就是挂接上PerfHUD后的《魔兽世界》运行截图:

     

     

    总结以上步骤:

    1.在程序启动时下断点,断到执行Direct3DCreate9的地方,获取到IDirect3D指针。

    2.用IDirect3D指针获取到虚函数表指针,查找到CreateDevice的函数体。

    3.在该函数体处下断点,断住后用esp来找到调用该函数的地方。

    4.找到给CreateDevice传参数的代码,并修改成PerfHUD所要求的参数。

    只要是没加过密的D3D9程序,应该都可以用以上方法来修改,我只试过《Crysis》,《WOW》,《PES2009》等少数游戏。

    希望此文能帮助各位有兴趣探索别的游戏制作方法的朋友。

  • 相关阅读:
    shell习题第12题:批量创建用户
    shell习题第11题:输入数字执行命令
    二、python数据类型、字符编码、文件处理
    C语言之控制语言:分支和跳转
    c语言之控制语句:循环
    C语言之运算符、表达式和语句
    Python集合及其运算
    Python文本处理
    Python迭代器与格式化
    Python装饰器
  • 原文地址:https://www.cnblogs.com/hcbin/p/1783671.html
Copyright © 2011-2022 走看看