


NVPerfHud4是Nvidia推出的配合Geforce6系列顯卡對DX9應用程序進行性能剖析的強大工具,通過它可以從宏觀和微觀兩個角度剖析圖形應用程序性能。宏觀上可以看到整體繪制中CPU、GPU分別占用了多少時間,PS、VS分別占用了多少時間,CPU等待時間,GPU等待時間。微觀上可以看到每一個DP調用的過程,顯示每一個DP調用過程中PS、VS和光柵化分別占用了多少時間以及每個DP調用所使用的VS、PS代碼,所用到的貼圖和所有繪制狀態。得到如此強大的功能對應用程序代碼的修改卻只需要一句,就是用下面的方式創建3D設備:
g_pD3D->CreateDevice( g_pD3D->GetAdapterCount()-1, D3DDEVTYPE_REF, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice );
其中最關鍵的參數是前兩個,也就是說只要按照上面方法創建3D設備的應用程序都可以通過NVPerfHud進行剖析。要用NVPerfHud對Farcry進行剖析就是要在Farcry的二進制代碼中找到CreateDevice()函數的調用位置,并將調用參數按要求修改。下面簡單說說這個過程。
用IDA打開Farcry的D3D9繪制模塊XRenderD3D9.dll,在Strings窗口發現了我們感興趣的字符串"Creating D3D device (Adapter format: %s, BackBuffer format: %s, Depth format: %s)",用右鍵菜單跳到引用這個字符串的地址發現如下代碼:
01 push offset aCreatingD3dDev ; "Creating D3D device (Adapter format: %s"...
02 .text:38054CF3 push ecx
03 .text:38054CF4 call dword ptr [edi+8]
04 .text:38054CF7 mov eax, dword_389E67D8
05 .text:38054CFC add esp, 14h
06 .text:38054CFF test eax, eax
07 .text:38054D01 jnz short loc_38054D51
08 .text:38054D03 mov al, [esi+1D650h]
09 .text:38054D09 test al, al
10 .text:38054D0B jz short loc_38054D15
11 .text:38054D0D mov eax, [esi+1D65Ch]
12 .text:38054D13 jmp short loc_38054D1B
13 .text:38054D15
14 .text:38054D15 loc_38054D15: ; CODE XREF: sub_38054B00+20Bj
15 .text:38054D15 mov eax, [esi+1D694h]
16 .text:38054D1B
17 .text:38054D1B loc_38054D1B: ; CODE XREF: sub_38054B00+213j
18 .text:38054D1B mov ebp, [esp+21Ch+var_20C]
19 .text:38054D1F mov ecx, [eax]
20 .text:38054D21 mov eax, [esi+1D620h]
21 .text:38054D27 mov edx, [eax]
22 .text:38054D29 lea edi, [esi+1E698h]
23 .text:38054D2F push edi
24 .text:38054D30 lea ebx, [esi+1D6C0h]
25 .text:38054D36 push ebx
26 .text:38054D37 push ebp
27 .text:38054D38 mov ebp, [esi+1F494h]
28 .text:38054D3E mov ebp, [ebp+0]
29 .text:38054D41 push ebp
30 .text:38054D42 mov ebp, [esp+22Ch+var_208]
31 .text:38054D46 mov ebp, [ebp+4]
32 .text:38054D49 push ebp
33 .text:38054D4A push ecx
34 .text:38054D4B push eax
35 .text:38054D4C call dword ptr [edx+40h]
36 .text:38054D4F jmp short loc_38054D8A
37 .text:38054D51
38 .text:38054D51 loc_38054D51: ; CODE XREF: sub_38054B00+201j
39 .text:38054D51 mov ecx, [esp+21Ch+var_20C]
40 .text:38054D55 mov edx, [esi+1F494h]
41 .text:38054D5B mov eax, [esi+1D620h]
42 .text:38054D61 mov ebp, [eax]
43 .text:38054D63 lea edi, [esi+1E698h]
44 .text:38054D69 push edi
45 .text:38054D6A lea ebx, [esi+1D6C0h]
46 .text:38054D70 push ebx
47 .text:38054D71 and ecx, 0FFFFFFEFh
48 .text:38054D74 push ecx
49 .text:38054D75 mov ecx, [edx]
50 .text:38054D77 push ecx
51 .text:38054D78 push 2
52 .text:38054D7A push eax
53 .text:38054D7B call dword ptr [ebp+10h]
54 .text:38054D7E mov edx, [esi+1D620h]
55 .text:38054D84 dec eax
56 .text:38054D85 push eax
57 .text:38054D86 push edx
58 .text:38054D87 call dword ptr [ebp+40h]
第1行將我們感興趣的字符串地址壓入堆棧,第3行應該是輸出日志之類的調用,下面應該離創建設備的調用(CreateDevice)不遠了。再看看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個函數,也就是說它在虛函數表里的偏移量應該是0x40。再回頭看上面的匯編代碼,可以發現有兩處"call dword ptr [ebp+40h]"這樣的函數調用,這應該就是對CreateDevice()函數的兩次調用了。再看第53行還有這樣一個調用"call dword ptr [ebp+10h]",對照IDirect3D9接口定義我們知道這是GetAdapterCount()函數,第55行對函數GetAdapterCount()的返回值減一,這不正是我們需要的CreateDevice()函數的第一個參數嗎,那再看看第二個參數是不是也是我們需要的D3DDEVTYPE_REF。第51行的指令"push 2"壓入CreateDevice()函數的第二個參數,查找相關頭文件我們發現D3DDEVTYPE_REF的值正好是2,這使我們相信第58行的CreateDevice()調用就是為NVPerfHud提供的,我們甚至都不需要修改調用參數,只需要把程序的執行路徑引到這里就行了。
將IDA的代碼窗口切換到圖形視圖可以更容易地看到代碼跳轉路徑。

可以看到代碼在最上面分為兩叉,右邊的綠色箭頭指向我們需要的CreateDevice()調用,我們只需要把最上面那個框里的"jnz short loc_38054D51"指令修改成"jz short loc_38054D51",也就是取反條件判斷,就能讓代碼走我們需要的執行路徑。在十六進制編輯窗口找到相關代碼,將75改為74(jnz改為jz),然后存盤就完成了對XRenderD3D9.dll的修改。