5.2 编程差异
5.2.1 汇编嵌入变化
很多驱动开发者,之前习惯使用32位驱动开发,对于32位驱动开发,为了方便调试,可以在DriverEntry函数内部添加一个软中断,以便在调试时能在DriverEntry中中断下来。代码如下:
读者很快会发现上述代码用WDK编译64位驱动时会报错:nonstandard extension used: '__asm' keyword not supported on this architecture。这是为什么呢?原因是WDK编译器不再允许在64位模式下嵌入汇编。如果需要使用汇编,可以把汇编代码写成函数放到一个单独的asm文件中,然后通过函数的方式调用。
但是对于上面的例子,这个未免太小题大作了,例子代码在DriverEnty中加入_asm int 3汇编的目的只是为了方便调试。幸运的是,WDK提供了一个现成的函数可以帮助开发者完成这件事情:
这个函数的功能和直接写int 3的功能是一样的,读者不妨通过IDA工具查看这个函数的实现:
从IDA反汇编的结果可以看到,这个函数的确和int 3的作用是等价的。使用这个函数替代int 3之后,代码如下:
或者
如果读者在开发过程中确实需要使用汇编指令,而WDK又没有提供功能等价的函数,那么开发者只能按照上面介绍的方法,把汇编代码写成一个函数,并把这个函数放置到一个单独的asm文件中,在Sources文件中指定asm文件。
5.2.2 预处理与条件编译
在驱动开发过程中,有时候代码逻辑需要区分不同的平台编译不同的代码分支。比如需要开发一个注册表监控驱动,按照以往32位系统的常规做法,是通过挂钩系统服务描述表(SSDT)中注册表相关API而实现的,但在64位系统下,正如本章前面提到的PatchGuard机制的存在,导致挂钩SSDT方案不再适用,所以在代码中必须针对不同的系统平台编译不同方案的代码。
WDK中预置了一些宏来帮助开发者实现对不同的系统平台进行条件编译。
●_M_AMD64:当_M_AMD64被定义时,表示当前编译环境是AMD64。
●_M_IX64:当_M_IX64被定义时,表示当前编译环境是IA64。
一般情况下,内核代码只需要区分是32位平台还是64位平台,不需要精细到区分是AMD64平台还是IA64平台。如果只是区分32位和64位平台,则可以使用_WIN64宏,当_WIN64宏被定义,表示当前编译环境是64位(不区分是AMD64还是IA64)。
回看刚才的例子,例子中需要对不同的平台编译不同的注册表监控代码,借助_WIN64宏可以这样实现:
5.2.3 数据结构调整
本章开头向读者介绍了thunking技术,考虑一下这种情况:当一个32位应用程序通过IOCTL控制码的方式(DeviceIoControl)与64位驱动程序通信时,如果通信的数据结构里面包含指针,thunking是不会发生的。深入分析一下,在没有thunking的情况下,会遇到什么问题。请看下面的例子。
ObjectName为UNICODE_STRING类型,具体定义为:
对于32位应用程序来说,DRIVER_DATA结构体的大小为12字节,因为Event为4字节(sizeof(HANDLE) = 4),ObjecName为8字节(sizeof(UNICODE_STRING) = 8)。如表5-1所示。
表5-1 32位下结构体大小
而对于64位驱动程序来说,由于指针长度被扩展到8字节,且默认对齐方式也为8字节,所以对于DRIVER_DATA结构体来说,大小变成了24字节。具体计算方法为:Event为HANDLE类型,HANDLE其实是一个void*,占用8字节空间,而对于UNICODE_STRING结构体来说,前面的Length和MaximumLength各占2字节,后面的Buffer为指针类型,占用8字节;由于默认对齐方式为8字节,所以在MaximumLength成员后面有4字节作为数据对齐(Padding)占位没有被使用,因此结构体UNICODE_STRING的大小为16字节,整个DRIVER_DATA的大小为24字节。如表5-2所示。
表5-2 64位下结构体大小
当32位应用程序用DRIVER_DATA结构体和64位驱动程序通信时,驱动程序期望数据大小为24字节,而实际数据大小只有12字节,最终会导致驱动程序校验数据大小失败。
为了解决上述问题,在定义DRIVER_DATA结构体的时候,需要使用固定长度的类型来定义结构体成员,如使用VOID * POINTER_32来定义Event,使用UNICODE_STRING32来定义ObjectName:
使用固定长度的类型来定义成员变量之后,在不同平台下大小不再有歧义,这时应用程序和驱动程序都认为DRIVER_DATA的大小为12字节。