DirectX 10图形应用程序截获技术的研究

2015-07-24 19:01李一天
微型电脑应用 2015年7期
关键词:调用应用程序投影

李一天

DirectX 10图形应用程序截获技术的研究

李一天

DirectX是主流图形API,在多媒体、娱乐等领域得到广泛应用。为实现图形表面的跨进程共享,从DirectX 10开始使用DirectX 图形基础架构(DXGI)对图形硬件进行底层管理。DXGI带来性能提升的同时也给图形应用程序的截获工作带来困难。为完成DirectX 10程序的拦截,需要同时截获多个图形库的函数。针对此问题,完善DLL替换+Detours的图形应用程序截获框架,克服现有截获技术的缺陷,完成DirectX 10程序的截获工作,并解决因DXGI、D3D10图形库间依赖关系产生的进程死锁问题。最后,基于该截获框架,开发出一套单机驱动的多投影显示系统,DirectX 10程序不需做任何修改就能多投影显示。实验结果表明,使用所提出的截获技术后图形应用程序画面流畅,运行稳定。

函数截获;Detours;多投影显示

0 引言

图形应用程序主要使用两种图形库:Direct3D图形库和OpenGL图形库。Direct3D由微软开发,在微软平台(Windows和Xbox)上提供图形应用程序开发接口。OpenGL定义了一套跨平台、跨语言的图形应用程序编程接口。由于多数图形应用程序可以在Windows平台上使用Direct3D运行,本文的讨论限定在Windows平台上,并以Direct3D为主。

目前大多数操作系统以用户态动态链接库(Dynamic Linking Library,或DLL)的形式向应用程序提供大多数系统服务。对于图形服务而言,操作系统通过用户态的动态链接图形库(opengl32.dll或d3d9.dll)向应用程序提供图形绘制及显卡资源管理功能。

Y. Mao提出DLL替换+Detours的截获技术[1],在不修改应用程序源代码和二进制镜像的前提下,可以截获D3D9图形库所有函数。从DirectX 10开始,DirectX使用DirectX Graphics Infrastructure(DXGI)对图形硬件进行底层管理[2]。为完成对DirectX 10图形应用程序的拦截,需要同时截获多个图形库的函数,加大了截获难度。例如,若想截获图形应用程序的帧缓存更新(Present)以及绘制函数(Draw),我们必须同时完成对dxgi.dll和d3d10.dll动态链接库的截获工作。

综合以上情况,本文深入研究图形应用程序的截获技术以及DirectX 10多媒体绘制引擎的工作机理,设计DirectX 10图形应用程序的截获框架,在不修改应用程序源代码和二进制镜像的前提下,完成DirectX 10图形应用程序的函数截获。

为了测试截获技术的性能以及适用性,基于该截获框架,我们利用已有的多投影显示的几何校正、边缘融合算法[3],开发出一套单机驱动的多投影显示系统。该系统对DirectX 10程序是透明的,程序源码不需做任何修改就能多投影显示。实验结果表明,使用本文的截获技术后图形应用程序的画面流畅,运行稳定。

1 相关截获技术

Windows API拦截(Hook)的机制有2种,分为内核级的拦截和用户级的拦截[4]。内核级的拦截主要是通过一个内核模式的驱动程序来实现,它的功能强大,能捕捉到系统活动的任何细节,但实现难度也较大。而用户级的拦截则通常是在普通的DLL中实现整个API的拦截工作,本文讨论的即为用户级拦截。在Windows平台下拦截API函数一般有以下3种方法:

(1)修改函数IAT(Import Address Table)表和ET(Export Table)表

在Window 32操作系统中,可执行文件是基于Microsoft设计的PE(Protable Executable)格式[2],如图1所示:

图1 可执行模块的PE结构

每个PE文件的文件头都包含了该模块的输入函数入口地址表和输出函数入口地址表[5],即IAT表和ET表。只要把输入表中目标API函数的地址改为替代函数的地址, 那么当可执行模块调用该API函数时就会变成对替代函数的调用,从而实现API函数的拦截。这种方法比较容易实现,应用也很广泛。该方法缺点在于无法截获来自模块内部的函数调用。

(2)修改API函数的入口指针

程序的执行都是在内存中完成的,系统动态链接库(DLL)在程序运行时被动态地载入内存中。而每个API函数在DLL中都有自己固定的地址,只需要使用Win32 API函数GetProcAddress就可以得到需要拦截的API函数的入口地址。这种方法需要注意的是,在替换目标API函数之前,需要先保留API函数入口地址处的前几个指令字节。然后用JMP指令或INT指令,直接或间接地跳转到替代函数的代码入口地址。在替代函数生命域结束前恢复目标API的前几个指令字节,保证其原来的程序正常运行。该方法缺点在于无法截获重定向前对目标函数的调用。

(3)动态链接库替换(DLL替换)

在Windows平台下,操作系统通过用户态的动态链接图形库向应用程序提供图形绘制及显卡资源管理功能,而应用程序是通过DLL的文件位置和文件名来加载DLL。DLL替换使用一个与原始DLL相同导出函数的DLL(自定义DLL),截获应用程序对原始DLL的导出函数的调用。该方法不需要修改应用程序的源代码,且由于函数截获在应用程序运行前生效,应用程序在运行过程中对原始DLL导出函数的所有调用均可被截获。

该方法的缺点是对于提供面向对象的接口,DLL替换只能以对象为单位进行截获,即为了截获某个对象的成员方法,必须截获该对象的创建方法,及该对象的所有公共成员,因此开发截获库时工作量大。而且与系统兼容性差,图形库升级后,自定义DLL通常需要重新编写。

2 图形应用程序截获框架

从DirectX10开始用DXG1对图形硬件进行底层管理[6]。为完成图形应用程序的拦截,需要同时截获多个图形库的函数,加大了截获难度。针对此问题,本文完善DLL+Detours截获框架,完成对DXGI图形库、D3D10图形库中目标函数的截获工作,并解决因DXGI、D3D10图形库间依赖关系产生的进程死锁问题。

2.1 DLL替换+Detours的截获框架

Y. Mao提出DLL替换+Detours 的截获技术[7],在不修改应用程序源代码和二进制镜像的前提下,可以截获 D3D9图形库所有函数。Detours是微软开发的一个函数库,以函数为单位进行截获,可以克服DLL替换的诸多缺点。Detours以函数为单位进行截获,截获时只需要知道目标函数在内存中的地址。根据该地址,Detours在运行时替换目标函数的前5个字节,将目标函数重定向到用户自定义的函数[8]。替换后DLL内部和外部对目标函数的所有调用将被截获。

对DirectX 9应用程序绘制函数的截获过程,如图2所示:

图2 Direct3D 9 绘制函数截获过程

自定义动态链接库d3d9.dll具有与原始d3d9.dll相同的导出函数及文件名。我们将自定义d3d9.dll拷贝到与原始d3d9.dll同一文件夹下,同时将原始d3d9.dll重命名为orgd3d9.dll。自定义d3d9.dll载入内存后程序自动调用其DLL初始化函数。此时我们加载原始的图形库(orgd3d9.dll),并使用Detours替换orgd3d9.dll的绘制函数的前5个字节,将其重定向到d3d9.dll中的绘制函数,完成对绘制函数的截获工作。

2.2 截获框架的拓展

为了实现图形表面的跨进程共享,从DirectX 10开始使用DirectX 图形基础架构,简称DXGI。DXGI提供了对图形硬件进行底层管理的功能如图3所示:

图3 DirectX 图形基础架构

使得不同D3D的API之间也能共享资源。DXGI的主要功能包括,枚举显示设备、创建管理交换链以及全屏模式的切换等[9]。

DXGI带来性能提升的同时,也给图形应用程序的截获工作带来困难。像交换链的创建、全屏模式切换等函数都是在DXGI图形库中定义的。为完成DirectX 10图形应用程序的函数截获,多数情况下我们需要同时截获多个图形库。例如,若想截获图形应用程序的帧缓存更新以及绘制函数,我们必须同时完成对dxgi.dll和d3d10.dll动态链接库的截获工作。

针对此问题,本文对DLL替换+Detours的截获框架进行拓展,在不修改应用程序源代码和二进制镜像的前提下,截获dxgi.dll以及d3d10.dll中所有的函数。

对DirectX 10图形应用程序的截获过程,如图4所示:

图4 Direct3D 10函数截获框架

这里我们自定义DXGI动态链接库,在自定义dxgi.dll的初始化函数中加载D3D10图形库(d3d10.dll),并使用Detours完成对dxgi.dll以及d3d10.dll目标函数的截获工作。

这里需要特别注意DLL间的加载顺序。在多线程环境下,DllMain里的代码容易引发线程死锁[4]。系统在装载DLL的时候会自动产生一个Loader Lock,避免多个DLL同时被装载。LoaderLock从LoadLibrary函数调用的开始就自动加锁,直到DllMain 退出为止。此时若DLL存在依赖关系,极易发生进程死锁。

例如,我们自定义D3D10图形库(d3d10.dll),在自定义的d3d10.dll中加载原始d3d10.dll以及dxgi.dll,进而截获目标函数。系统在装载自定义d3d10.dll时,自动加锁产生一个Loader Lock L。之后在加载dxgi.dll时,工作线程自动产生一个新的Loader Lock G。因为dxgi.dll对d3d10.dll存在依赖关系,工作线程会一直尝试获取d3d10.dll的锁L,进而导致进程的死锁如图5所示:

图5 进程死锁

为避免死锁,在同时截获多个动态链接库时,要注意DLL间的依赖关系。把被依赖项作为自定义DLL,在其初始化函数DllMain里加载其他的DLL。DLL间的依赖关系可以通过依赖查看工具Depends进行查看,d3d10.dll的依赖关系,如图6所示:

图6 d3d10.dll的依赖关系

3 自定义图形库DLL的设计与实现

我们编写自定义dxgi.dll,在自定义dxgi.dll的DLL_PROCESS_ATTACH中加载原始dxgi.dll以及d3d10.dll,并使用Detours完成目标函数的截获工作。

3.1 自定义dxgi.dll的实现

使用DLL替换技术时,自定义的DLL必须与原始的DLL具有相同的导出函数表。在Windows平台可以通过depends工具查询DLL的导出函数名称以及依赖的函数库。depends.exe可以列出DXGI图形库(dxgi.dll)的导出函数表如图7所示:

图7 DXGI图形库导出函数表

由于Direct3D的运行时使用Component Object Model(COM)兼容的对象,它可以向用户提供基于COM的、面向对象的编程接口,因此DLL导出函数表仅包含44个函数(见图7)。其中常用的导出函数有CreateDXGIFactory等。

为了导出相同的函数列表,自定义DLL中必须实现并导出原始DLL函数导出表中所有的函数。在Microsoft Visual Studio开发环境中,有两种声明方式。第一种是把导出的函数声明为全局函数,且在一个.def文件中声明函数名。另一种方式是在函数声明中加上__declspec(dllexport)。这里我们采用第一种声明方式。

根据不同的情况,截获库中的导出函数可以以不同的方式实现。常规方法是使用与原始导出函数具有相同签名(包括函数名、返回值和参数列表)的函数,并在该函数中直接调用原始函数。该方法要求提供函数的原型。

以dxgi.dll中的CreateDXGIFactory函数为例,在自定义DLL中该函数实现如图8所示:

图8 知道原型的函数实现

自定义的CreateDXGIFactory将调用参数拷贝到新的堆栈页中,并调用原始DLL中的原始函数(下文称为目标函数),即OrgCreateDXGIFactory。该函数地址在应用程序加载自定义DLL后,通过DLL初始化函数进行初始化。

然而对于不常用的导出函数,标准文档中可能没有明确地说明其原型。如dxgi.dll中的D3DKMTUnlock等导出函数在标准DirectX文档中没有说明。对于这些函数,在实现中我们通过汇编指令直接跳转到原始DLL中的目标函数,其实现如图9所示:

图9 没有原型说明的函数实现

当应用程序调用该函数时,我们通过一条JMP指令直接跳转到目标函数,其实现不依赖于参数和返回值。为了使目标函数能够正确执行,我们必须确保跳转到目标函数后寄存器状态没有发生改变。为此,我们使用C++的naked关键字以保证编译器编译该方法时不会生成除该JMP指令之外的其他指令。为了通过编译,这些函数的返回值被定义为void。对于大部分导出函数我们可以使用同样的方法实现,而无需知道其函数原型。

3.2 目标函数的截获

对于需要修改实现的函数,由于函数的实现通常与函数的原型有关,我们必须以图8的方式实现。以截获应用程序对图形库的帧缓冲更新函数的调用为例,说明目标函数的截获过程。

使用Detours以函数为单位进行截获时,我们需要获得目标函数的地址。Direct3D使用COM兼容对象,它向用户提供基于COM的、面向对象的编程接口。Present函数的地址无法从函数导出表中直接获取,我们需要使用Detours依次完成CreateDXGIFactory、CreateSwapChain函数的截获如图10所示:

图10 获取Present函数地址

CreateDXGIFactory是DXGI图形库的导出函数,它的成员变量中提供了IDXGIFactory接口地址。通过IDXGIFactory接口,我们可以获取其成员函数CreateSwapChain的地址。截获CreateSwapChain函数,获取其成员变量IDXGISwapChain接口地址,最终获得IDXGISwapChain接口的Present函数地址。

在获取接口的成员函数地址时,它们的地址无法直接获取。我们使用COM的基于C语言的编程接口获取这些函数的地址。在C语言环境中,COM对象由结构表示,其函数指针存放在该结构的虚函数表成员中,因此可以通过lpVtbl获得Present函数的地址[6]。使用Detours完成Present函数的截获部分如图11所示:

图11 Present函数的截获

4 实验结果

为测试DirectX 10图形应用程序截获技术的适用性和性能,基于本文提出的的截获框架,利用已有的多投影显示的几何校正、边缘融合算法,开发出一套单机驱动的多投影显示系统。该系统对DirectX 10应用程序来说是透明的,程序源码不用做任何修改就能进行多投影显示。

在测试中我们使用Canon EOS 20D数码摄像机获取几何校正参数和颜色校正参数。该相机支持RAW输出格式,可直接用于ITF 恢复。由于该系统的投影幕为非平面,为此我们使用复旦大学软件学院交互式图形学实验室开发的基于二次曲线的几何校正工具,可以方便地校正柱面投影幕上的几何错位。

测试过程中投影系统由一台PC驱动,该机器配有Intel Core Duo CPU P8600,ATI Mobility Radeon HD 4300图形显示卡,及2GB内存。系统的桌面分辨率为3072×768像素。通过一个Matrox TripleHead2Go设备,显卡的输出被分为3个分辨率为1024×768像素的输出通道。每个输出通道与一个EPSON EMP-8300 LCD投影仪相连。测试基准程序我们使用Ubisoft 开发的阿凡达同名游戏 Avatar。Avatar支持宽屏显示模式,可以在全屏模式下以3072×768像素的分辨率运行。Avatar同时支持DirectX 9.0和DirectX 10.0如图12所示:

图12 单机驱动的多通道投影系统

图12 a)为画面校正前PC端Avatar原始的效果截图。图12 b)为通过我们的截获技术和校正技术,对画面进行几何校正和颜色校正后PC端的Avatar的效果截图。PC通过三屏宝与投影仪相连后,会在投影幕上获得连续、统一的画面。

我们测试了应用截获技术前后Avatar的绘制帧率与稳定性。移植后Avatar的绘制帧率如图13所示:

图13 输入三通道时,使用函数截获技术移植前后Avatar的绘制帧率

显卡输出3个通道,每个通道的分辨率为1024×768。移植的开销主要来自两部分:画面截获和校正绘制。使用DLL替换+Detours进行函数截获的开销非常小,因此,校正绘制决定了移植技术对程序性能的影响。测试表明,函数截获和画面校正前Avatar的绘制帧率60帧/秒,经过移植后Avatar的绘制帧率能达到50帧/秒,并且游戏运行稳定。

5 总结

本文完善DLL替换+Detours的截获框架,在不修改图形应用程序源代码的情况下完成对DirectX 10库函数的截获工作。基于该框架,利用已有的多投影显示的几何校正、边缘融合算法,开发出一套单机驱动的多投影显示系统,DirectX 10图形应用程序不用做任何修改就能多投影显示。实验结果表明,该截获技术对图形应用程序的性能影响小,程序画面流畅,运行稳定。未来我们将研究DirectX 11图形应用程序的截获技术。

[1] 毛燕东.面向单机驱动的多通道投影系统的图形应用程序移植与绘制技术[D].上海:复旦大学, 2009:24-28.

[2] DXGI Overview[EB/OL]. https://msdn.microsoft.com/en -us/library/bb205075-(VS.85).aspx, 2015,04.

[3] Jiang Z. Mao Y. Qin B. et al. A high resolution video display system by seamlessly tiling multiple projectors[C]. IEEE International Conference on Multimedia and Expo. 2007: 2070-2073.

[4] Father H. Hooking Windows API-Technics of Hooking API Functions On Windows[J]. Assembly Programming Journal, 2004, 2(2).

[5] Pietrek M. Peering Inside the PE: A Tour of the Win32 Protable Executable File Format[J].Microsoft Systems Journal,1994,9:27-30.

[6] Win32 Portable Executable File Format.[EB/OL]. https://msdn.microsoft.co m/en-us/library/ms809762.aspx ,2015,04.

[7] Dynamic-Link Library Best Practices[EB/OL]. https://msdn.microsoft.com/en-us/library/windows /desktop/dn633971(v=vs.85).aspx, 2015,03.

[8] Hunt G. and Brubacher D. Detours: Binary interception of win32 functions[C]. Proceedings of the 3rd USENIX Windows NT Symposium,1999:12-13.

[9] Hunt, Galen C. and Michael L. Scott. Intercepting and Instrumenting COM Applications[C]. Proceedings of the Fifth Conference on Object-Oriented Technologies and Systems (COOTS’99),1999 :45-5'6.

TP393

A

2015.04.17)

1007-757X(2015)07-0022-05

李一天(1990-),男,安徽省人,复旦大学,软件学院,硕士研究生,研究方向:计算机图形学,人机交互技术,上海,201203

猜你喜欢
调用应用程序投影
解变分不等式的一种二次投影算法
基于最大相关熵的簇稀疏仿射投影算法
核电项目物项调用管理的应用研究
删除Win10中自带的应用程序
找投影
找投影
谷歌禁止加密货币应用程序
基于系统调用的恶意软件检测技术研究
利用RFC技术实现SAP系统接口通信
三星电子将开设应用程序下载商店