驱动程序入门篇
驱动程序分为两类: 一个是 Kernel(内核) 模式驱动,另一个是 Windows (用户窗口层)模式驱动。
这两种模式本质是相同,但细节不同。本文介绍的是内核模式驱动和驱动程序的安装与使用。
驱动程序同普通的 .exe,.dll 一样,都属于 PE 文件,而且都有一个入口函数。但 .exe 中,入口函数是 main() / WinMain() 和 Unicode 的 wmain() / wWinmain(),.dll 的入口函数则可有可无,它是 DllMain()。
所以驱动程序也有入口函数,而且是必须的,它是 DriverEntry() 函数。再次提示,它是必须的! 因为 I/O 管理器会首先调用驱动程序的DriverEntry() 函数,它的作用就像 DllMain() --完成一些初始化工作。
虽然我们有时候把 DriverEntry() 比作 main(),但二者在本质上不同,DriverEntry() 的生命周期非常短,其作用仅是将内核文件镜像加载到系统中时进行驱动初始化,调用结束后驱动程序的其他部分依旧存在,并不随它而终止。
所以我们一般可把 DriverEntry() 称为“入口函数”,而不可称为“主函数”。因此作为内核驱动来说,它没有一个明确的退出点,这应该是 atexit 无法在内核中实现的原因吧。什么是 atexit() 函数呢?它是注册终止函数(即main执行结束后调用的函数) !
DriverEntry()一共有 2 个参数:
1)PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针。我们操作驱动程序,全靠它,它是由 I/O 管理器传递进来的;
2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使用并不多,但要注意,在DriverEntry() 返回后,它可能
会消失,所以如果需要使用,记住先要保存下来。
DriverEntry() 等内核函数的返回值一般是 NTSTATUS 类型的。使用宏NT_SUCCESS(NTSTATUS status)可检测返回状态是否成功。它是一个 ULONG 值(无符号长整型,另一种表示形式:Unsigned long 变量名),具体的定义,请参见 DDK 中的 NTSTATUS.H 头文件,里边有详细的定义。
在DriverEntry中,一般需要做一下几件事情: 设置驱动卸载例程、 注册 IRP 的派遣函数、 创建设备对象等!并且驱动卸载例程与 IRP 派遣函数都是对驱动对象设置的。
既然要写驱动程序就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,IOCTL宏,或者直接 ReadFile() 或 WriteFile() 进行读写,在本文里我就采用一种简单的、但又很常用的 IOCTL 宏,它依赖的 IRP 派遣例程 IRP_MJ_DEVICE_CONTROL,Win32程序使用 DeviceIoControl() 与驱动进行通信,根据不同的 IOCTL 宏,输出不同的调试信息。
驱动程序与 I/O 管理器通信,使用的是 IRP,即 I/O 请求包。IRP 分为2部分:IRP 首部;IRP堆栈。
在驱动程序中,IRP 派遣例程起着很重要的作用,每个 IRP 派遣例程,几乎都有对应的 Win32 API 函数。
下面介绍 IRP 派遣例程 :
名称 | 描述 | 调用者 |
IRP_MJ_CREATE | 请求一个句柄 | CreateFile |
IRP_MJ_CLEANUP | 在关闭句柄时取消悬挂的IRP | CloseHandle |
IRP_MJ_CLOSE | 关闭句柄 | CloseHandle |
IRP_MJ_READ | 从设备得到数据 | ReadFile |
IRP_MJ_WRITE | 传送数据到设备 | WriteFile |
IRP_MJ_DEVICE_CONTROL | 控制操作(利用IOCTL宏) | DeviceIoControl |
IRP_MJ_INTERNAL_DEVICE_CONTROL | 控制操作(只能被内核调用) | N/A |
IRP_MJ_QUERY_INFORMATION | 得到文件的长度 | GetFileSize |
IRP_MJ_SET_INFORMATION | 设置文件的长度 | SetFileSize |
IRP_MJ_FLUSH_BUFFERS | 写输出缓冲区或丢弃输入缓冲区 | FlushFileBuffers\FlushConsoleInputBuffer \PurgeComm |
IRP_MJ_SHUTDOWN | 系统关闭 | InitiateSystemShutdown |
提到派遣例程,必须理解 IRP(I/O Request Package),即"输入/输出请求包"这个重要数据结构的概念。Ring3(用户层)通过 DeviceIoControl 等函数向驱动发出 I/O 请求后,在内核中由操作系统将 I/O 请求包转化为 IRP 的数据结构,并"派遣"到对应驱动的派遣函数中,如图下图所示。
Ring3(用户层)应用程序调用kernel32.dll导出的DeviceIoControl函数后,会调用到ntdll.dll导出的NtDeviceIoControlFile函数,进而调用到系统内核模块提供的服务函数NtDeviceIo ControlFile,该函数会将I/O请求转化为IRP包,并发送到对应驱动的派遣例程函数中。对于其他I/O相关函数,如CreateFile、ReadFile、WriteFile、GetFileSize、SetFileSize、CloseHandle等也是如此。
如上图,从 Ring3 (用户层)的 I/O 请求到内核的 IRP 结构的请求包
一个 IRP包 该发往驱动的哪个派遣例程函数,是由 IRP 结构中的 MajorFunction (IRP主类型)属性决定的,MajorFunction 属性的值是一系列宏,具体可在网上查询。
IRP 首部信息如下:
IO_STATUS_BLOCK IoStatus //包含 I/O 请求的状态
PVOID AssociatedIrp.SystemBuffer // 如果执行缓冲区 I/O,这个指针指向系统缓冲区
PMDL MdlAddress // 如果直接 I/O,这个指针指向用户缓冲区的存储器描述符表
PVOID UserBuffer // I/O 缓冲区的用户空间地址
IRP 堆栈信息如下:
UCHAR MajorFunction //(主要类型) 指示 IRP_MJ_XXX派遣例程
UCHAR MinorFunction //(IRP 的子类型) 同上,一般文件系统和 SCSI 驱动程序使用它
union Parameters { //MajorFunction的联合类型
struct Read //IRP_MJ_READ的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write //IRP_MJ_WRITE的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceIoControl //IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL的参数
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject //请求的目标设备对象的指针
PFILE_OBJECT FileObject //请求的目标文件对象的指针,如果有的话操作 IRP。
对于不同的 IRP 函数,操作也是不同的:有的只操作 IRP 首部;有的只操作 IRP 堆栈;还有操作 IRP 整体。
下面是一些常用的函数:
IRP整体:
名称 | 描述 | 调用者 |
IoStartPacket | 发送IRP到Start I/O例程 | Dispatch (派遣) |
IoCompleteRequest | 表示所有的处理完成 | DpcForIsr (连接中断和注册) |
IoStartNextPacket | 发送下一个IRP到Start I/O例程 | DpcForIsr |
IoCallDriver | 发送IRP请求 | Dispatch |
IoAllocateIrp | 请求另外的IRP | Dispatch |
IoFreeIrp | 释放驱动程序分配的IRP | I/O Completion (I/0完成) |
IRP堆栈:
名称 | 描述 | 调用者 |
IoGetCurrentIrpStackLocation | 得到调用者堆栈的指针 | Dispatch |
IoMarkIrpPending | 为进一步的处理标记调用者I/O堆栈 | Dispatch |
IoGetNextIrpStackLocation | 得到下一个驱动程序的I/O堆栈的指针 | Dispatch |
IoSetNextIrpStackLocation | 将I/O堆栈指针压入堆栈 | Dispatch |
/************************************************************************************************
* 驱动程序与应用程的通讯示例 *
*************************************************************************************************
* 应用程序类型:ControlAppliaction *
* 应用程序名称:Appinformation *
* 驱 动 名 称:AppDriver *
* 符号链接名称:AppSymbolLink *
*************************************************************************************************
* 简述:编写一个驱动程序与应用程之间的通信讯实例,让应用程序输入数据,进而发送 I/O 请求,当 *
* 系统接的 I/O 管理器在接收到应用层的设备读写请求后,将请求封装为一个 IRP 请求(包括 IRP 头部 *
* 和 IRP STACK_LOCATINO 数组),然后根据 IRP 的类型发送到相应类型派遣例程,派遣例程再把接 *
* 受到的 IRP发送到对应的设备的设备栈的最顶层的那个设备驱动(如果顶层的设备对象的派遣函数结 *
* 束了IRP的请求,则这次IO请求结束,如果没有将 IRP 请求结束,那么操作系统将IRP转发到设备栈的 *
* 下一层设备处理。如果这个设备的派遣函数依然没结束该IRP请求,则继续向下层设备进行转发)。 *
* IO请求结束后驱动会把处理后的数据返回应用程序。 *
*************************************************************************************************/
先介绍一下驱动程序和应用程序将要使用到的 Win32 API 函数:
1、DriverEntry(): 驱动程序的入口函数,它将内核文件镜像加载到系统中时进行驱动初始化。
2、DriverUnload():负责删除在DriverEntry中创建的设备对象并且将设备对象所关联的符号链接删除,另外,DriverUnload还负责对一些资源进行回收。
3、IoDeleteDevice():此 API 函数为删除设备对象的函数,只有一个参数,即为妖卸载的设备名称。
4、IoDeleteSymbolicLink() : 此 API 函数为删除符号链接的函数,只有一个参数,即为符号链接名称。
5、IoCreateDevice():此 API 函数创建设备对象供驱动程序使用。包含七个参数。
6、RtlInitUnicodeString():此 API 函数初始化一个Unicode字符串,主要用来实例化设备名称或符号链接名称。
7、IoCreateSymbolicLink():此 API 函数主要是对已实例化的设备名称和符号链接名称进行绑定。参数为符名和设备名
8、IoGetCurrentIrpStackLocation():此 API 函数向调用者返回一个 IRP 当前堆栈位置的指针,参数为设备对象和 IRP 指针(PIRP)。
9、IoCompleteRequest():此 API 函数表示调用方已完成所有 I/O 请求处理操作,并将给定的 IRP 传给 I/O 管理器。无返回值。
10、CTL_CODE() 宏:此宏为系统提供的,在wdm中定义的,用于创建一个唯一的32位系统I/O控制代码,这个控制代码包括4部分组成。
11、CreateFile(): 此函数创建或打开(文件,文件流,目录,磁盘,控制台缓冲区)等设备,并返回一个用来访问这些对象的句柄 。
12、DeviceIoControl(): 此函数直接发送一个控制代码到指定的设备驱动程序,使相应的设备来执行相应的操作。
13、CloseHandle():此函数关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄 。
============================================================================================================
下面请看驱动程序代码:
首先用 C++ 创建一个空白的“ 控制台应用程序”项目:然后新建一个源文件:Appinformation.cpp
代码如下:
#include <iostream> #include <tchar.h> #include <Windows.h> #include "InputBuffer.h" using std::cout; using std::endl; int _tmain(int argc, _TCHAR* argv[]) { /************************************函数说明********************************************* * 函数名:CreateFile * * 类型:Win32 API函数 * * 作用:这是一多功能的函数,可打开或创建如:控制台,通信资源,目录(只读打开), * * 磁盘驱动器,文件,邮槽,管道等对象,并返回可访问的句柄:。 * * 参数说明: * * LPCTSTR lpFileName : 普通文件名或者设备文件名 * * DWORD dwDesiredAccess : 访问对象的权限,希望以哪种方式打开,建议两者兼备。 * * DWORD dwShareMode : 共享模式,建议两者兼备。 * * LPSECURITY_ATTRIBUTES : 用来设定一个指向是否由子进程返回的句柄可以被继承 * * DWORD dwCreationDisposition : 表示判断文件存在或不存在,按需求如何创建 * * DWORD dwFlagsAndAttributes : 文件标志或设备属性 * * HANDLE hTemplateFile :模板文件,给创建的文件提供文件属性和扩展属性,一般为空 * * 返回值: 获取设备的句柄 * *****************************************************************************************/ HANDLE DeviceHandle = CreateFile( L"\\\\.\\AppSymbolLink", //\\??\\设备符号链接名称,注意这里要用 L 宽字符转换 GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, //枚举:OPEN_EXISTING 表示文件必须已经存在。 FILE_ATTRIBUTE_NORMAL, //此枚举表示文件没有其他属性集。 NULL); //模板文件,一般为空 if (DeviceHandle == INVALID_HANDLE_VALUE){ printf("获取文件或设备句柄失败!!"); getchar(); //读取清除缓冲区字符 return -1; } printf("获取驱动程序句柄成功 \n"); int a=10, b=20, c=30,e=0,f=0,g=0; InputBuffer(DeviceHandle, a, b, c); for (int i = 0; i < 3; i++) { switch (i){ case 0:{ e = OtnC[i]; } case 1:{ f = OtnC[i]; } case 2:{ g = OtnC[i]; } } //End switch (i) } //End for printf("a=%d,b=%d,c=%d\n",e, f, g); CloseHandle(DeviceHandle); //过程完成后,最好关闭句柄 cout << "驱动通讯完成" << endl; system("pause"); }
好了,按设备的符号链接打开了设备,并得到了它的句柄,有了句柄,我们就能更好的和它进行互动了!!
如果想和他通信,就需要发送通讯口令了,我们把发送口令的函数它写在头文件:InputBuffer.h中。
#pragma once #include <iostream> #include <Windows.h> #include "SetCtlCode.h" int OtnC[3] = { 0 }; //整形全局数组变量 /************************************函数说明**************************************** * 函数名:InputBuffer * * 类型:自定义无返回值函数 * * 作用:这个函数用于给控制代码函数提供参数,也为了其函数调用获取返回值。 * * 参数说明: * * DeviceHandle : 设备句柄,用于指定通信的设备 * * a : 通讯数据。 * * b : 通讯数据。 * * c : 通讯数据 * * 返回值: 无 ,因为返回值已赋给了全局变量 * ************************************************************************************/ void InputBuffer(HANDLE DeviceHandle, int a, int b, int c){ int ItnC[3] = { a, b, c }; ULONG wtWrite; int *InBuffer = ItnC; int *OutBuffer = OtnC; /************************************函数说明******************************** * 函数名:DeviceIoControl * * 类型:Win32 API函数 * * 作用:发送控制代码到指定设备驱动程序。 * * 参数说明: * * hDevice Long:设备句柄。用于指定通信的设备 * * dwIoControlCode Long:应用程序调用驱动程序的控制命令,就是IOCTL_XXX IOCTLs。 * * lpInBuffer Any:应用程序传递给驱动程序的数据缓冲区地址。 * * nInBufferSize Long:应用程序传递给驱动程序的数据缓冲区大小,字节数。 * * lpOutBuffer Any:驱动程序返回给应用程序的数据缓冲区地址。 * * nOutBufferSize Long:驱动程序返回给应用程序的数据缓冲区大小,字节数。 * * lpBytesReturned Long:驱动程序实际返回给应用程序的数据字节数地址。 * * lpOverlapped OVERLAPPED:针对同步操作,请用ByVal As Long传递零值 * * 返回值: 非0成功,0失败 * *****************************************************************************/ DeviceIoControl(DeviceHandle, SetCtlCodeA, InBuffer, sizeof(InBuffer)*3, OutBuffer, sizeof(OutBuffer)* 3, &wtWrite, NULL); printf("DeviceIoControl 发送控制代码完成 \n"); int e = 0, f = 0, g = 0; for (int i = 0; i < 3; i++) { OtnC[i] = OutBuffer[i]; } return ; }
看到没! DeviceIoControl 函数中的第二个参数就需要提供口令了,这里专业的说法叫,“I/O控制码”(也叫输入输出控制码),我们把这控制代码卸载另一个头文件中:SetCtlCode.h
#include <winioctl.h> /************************************宏说明*********************************** * 宏名:CTL_CODE * * 类型:Win32 宏命令 * * 作用:用于创建一个唯一的32位系统I/O控制代码。 * * 参数说明: * * DeviceType:设备类型 ,枚举如(FILE_DEVICE_UNKNOWN)未知设备 * * Function: 设备唯一功能标识码,有效范围:32768-65535 * * Method: I/O访问内存的使用方式,枚举,一般用METHOD_BUFFERED。 * * Access: 设备访问限制,,枚举,一般用FILE_ANY_ACCESS * * 返回值: 无 * *****************************************************************************/ //可以只写一个 #define SetCtlCodeA CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define SetCtlCodeB CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)
好了,到此,一个发送 I/O 请求包的 控制台应用程序就创建并编写完毕,下面来开始我们的驱动编写:
先创建一个 WDM 工程,然后添加一个头文件,名为:AppDriver.cpp
然后开入编写入口函数:
#ifdef _cplusplus extern "C" { #endif #include <ntddk.h> #include "AddDevice.h" #include "SendIRP.h" #ifdef _cplusplus } #endif #define PAGEDCODE cod_seg("PAGE") //将以下函数载入到分页内存中执行 #pragma PAGEDCODE //载入到分页内存中执行 VOID Unload(PDRIVER_OBJECT pDriverObject); extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistrPath) { CreateMyDevice(pDriverObject); //调用自定义的 CreateMyDevice 来创建设备对象 //注册派遣函数,与IRP 请求包的数据类型一一对应 pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Send_Irp; // 发送I/O管理器和其他系统组件、其他内核模式驱动 pDriverObject->MajorFunction[IRP_MJ_CREATE] = Send_Irp; // 创建设备,CreateFile会产生此IRP,返回一个句柄给用户应用程序 pDriverObject->MajorFunction[IRP_MJ_READ] = Send_Irp; // 读取设备内容,ReadFile会产生此IRP pDriverObject->MajorFunction[IRP_MJ_WRITE] = Send_Irp; // 改写设备内容,WriteFile时会产生此IRP pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Send_Irp; // 关闭保留的通往目标设备对象的句柄,CloseHandle会产生此IRP pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = Send_Irp; // 清除用户模式应用程序的目标设备对象的句柄,CloseHandle会产生此IRP pDriverObject->DriverUnload = Unload; //调用卸载函数 KdPrint(("AppDriver 驱动加载成功!")); return STATUS_SUCCESS; } VOID Unload(PDRIVER_OBJECT pDriverObject) { pDriverObject->DeviceObject = AppDevice; //指向设备对象 IoDeleteDevice(AppDevice); //卸载设备对象 IoDeleteSymbolicLink(&AppSymbolLinkName); //卸载符号链接 KdPrint(("驱动卸载成功!")); }
入口函数 DriverEntry 的任务包含了 初始化系统代码为驱动指令,还包含了对自定义 Unload 函数的调用和 IRP 请求包的派遣。
先我们利用 IoCreateDevice API函数来创建设备驱动,我把它写在另一个头文件中:AddDevice.h
#pragma once //仅编译一次,用途:常出现在头文件中,因为同一头文件会在许多源文件中多次引用,如果没有指定编译一次,则编译时出现重定义错误。 #ifdef _cplusplus extern "C" { #endif #include <ntddk.h> #ifdef _cplusplus } #endif static PDEVICE_OBJECT AppDevice; //设备对象全局变量 static UNICODE_STRING AppDeviceName, AppSymbolLinkName; //设备名称和符号链接名称全局变量 //创建设备 #define INITCODE cod_seg("INIT") // 初始化的时候载入内存,然后可以从内存中卸载掉 #pragma INITCODE NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject){ NTSTATUS Status; //返回值局部变量 RtlInitUnicodeString(&AppDeviceName, L"\\Device\\AppDriver"); //初始化设备名称 RtlInitUnicodeString(&AppSymbolLinkName, L"\\??\\AppSymbolLink"); //初始化符号链接名称 //调用 IoCreateDevice API 函数创建设备对象 Status=IoCreateDevice(pDriverObject, 0, &AppDeviceName, FILE_DEVICE_UNKNOWN, NULL, TRUE, &AppDevice); if (!NT_SUCCESS(Status)){ //判断返回值,看是否创建成功 switch (Status){ //如果没有成功,返回枚举错误消息 case STATUS_INSUFFICIENT_RESOURCES: KdPrint(("资源部足!!")); break; case STATUS_OBJECT_NAME_EXISTS: KdPrint(("指定对象名存在!!")); break; case STATUS_OBJECT_NAME_COLLISION: KdPrint(("对象名称冲突!!")); break; } } //设备创建成功后,就设置它的缓冲区读写方式,这里为(读写) AppDevice->Flags |= DO_BUFFERED_IO; //调用 IoCreateSymbolicLink API 函数对设备名称和符号链接名称进行绑定 Status=IoCreateSymbolicLink(&AppSymbolLinkName, &AppDeviceName); //判断返回值,看是否绑定成功 if (NTSTATUS(Status) != STATUS_SUCCESS){ //判断返回值,看是否绑定成功 IoDeleteDevice(AppDevice); //如果未成功,调用IoDeleteDevice 函数删除设备 KdPrint(("设备因符号链接绑定失败而被删除!!")); } KdPrint(("设备创建完成,设备已成功加载")); return Status; //如果成功就返回成功的宏 }
到此,我们就真正创建了一个驱动,没错确实创建了,但像个人样,但却没有灵魂啊,没有灵魂就没有意识,没有意识就是死的!!!
所以,我们就得给他构造自主的潜微的意识,让它能对别人的呼叫会有所回应!!这里就要用到 IoGetCurrentStackLocation 这个API函数来接受被人发出的信息(IRP 请求包)派遣,从哪里获取呢,这里所有的信息都存放在 Stack 堆栈上,所以此时就需要在堆栈上获得: 我们再次新建一个头文件SendIRP,在这里面编写:
#ifdef _cplusplus extern "C" { #endif #include <ntddk.h> #include "SetCtlCode.h" #ifdef _cplusplus } #endif #define INITCODE cod_seg("INIT") // 初始化的时候载入内存,然后可以从内存中卸载掉 #pragma INITCODE NTSTATUS Send_Irp(PDEVICE_OBJECT pDeviceObject, PIRP pIrp){ NTSTATUS Status; PIO_STACK_LOCATION Stack; //定义返回值和堆栈变量 Stack=IoGetCurrentIrpStackLocation(pIrp); //获取当前 IRP 在堆栈上的位置指针 UCHAR Irp_Type = Stack->MajorFunction; //获取 IRP 在堆栈中的类型 KdPrint(("Irp_Type=%d \n", Irp_Type)); //对 IRP 的类型进行判断,进行不同的处理 switch (Irp_Type){ case IRP_MJ_DEVICE_CONTROL:{ KdPrint(("In to IRP_MJ_DEVICE_CONTROL \n")); ULONG CTL_CODE = Stack->Parameters.DeviceIoControl.IoControlCode; ULONG InBufferLength = Stack->Parameters.DeviceIoControl.InputBufferLength; ULONG OutBufferLength = Stack->Parameters.DeviceIoControl.OutputBufferLength; int *OutBuffer = (int *)pIrp->AssociatedIrp.SystemBuffer; KdPrint(("Get CTL_CODE,CTL_CODE=%d \n",CTL_CODE)); KdPrint(("InBufferLength=%d \n" , InBufferLength)); KdPrint(("OutBufferLength=%d \n", OutBufferLength)); KdPrint(("*OutBuffer=%d \n", OutBuffer)); switch (CTL_CODE){ case Add_Ctl_Code:{ KdPrint(("In to Add_Ctl_Code \n")); int i, a, b, c; _asm{ //用汇编语句来(读取)缓冲区指针中的数据 mov eax,OutBuffer mov ebx,[eax] mov a,ebx mov ebx, [eax+4] mov b,ebx mov ebx, [eax+8] mov c,ebx } KdPrint(("a=%d,b=%d,c=%d \n",a,b,c)); i = a + b + c; KdPrint(("i=a + b + c,i=%d \n", i)); a =a*i, b=b*i, c=c*i; _asm{ //用汇编语句来(填充)缓冲区指针 mov eax,a mov ebx,OutBuffer mov [ebx],eax mov eax, b mov [ebx + 4],eax mov eax,c mov [ebx + 8],eax } pIrp->IoStatus.Information = OutBufferLength; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); KdPrint(("Leave the Add_Ctl_Code \n", i)); break; } case Sub_Ctl_Code:{ break; } } //End (switch (CTL_CODE)) break; } case IRP_MJ_CREATE:{ break; } case IRP_MJ_READ:{ break; } case IRP_MJ_WRITE:{ break; } case IRP_MJ_CLOSE:{ break; } case IRP_MJ_CLEANUP:{ break; } } // End (switch (Irp_Type)) return STATUS_SUCCESS; } //End Send_Irp Function
此断代码主要对在堆栈中获取其它应用程序发送的 I/O 请求包的 IRP 派遣,驱动程序再更具 IRP 的派遣类型就行不同的处理,然后,得到请求数据,并对它进行了重新计算,然后通过汇编语句对计算后的数据进行反馈给堆栈中,让发送I/O 请求包 的应用程序进行接受数。
接受 或发送 I/O 请求包 都同样需用 I/O 控制代码,才能完成!所以我们再来编写I/O 控制代码,把它写另一个头文件中:SetCtlCode.h
#define Add_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define Sub_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)
由于驱动已经包含了 IOCTL 宏,所以这里不必再包含<winioctl.h>头文件了。
到此整个应用程序和驱动整形就编写完成! 我们可以在虚拟机制中通过 DriverMonitor .exe 和 DbgView工具进行加载驱动并进行查看,然后打开AppInformation.exe 控制台应用程序进行发送 I/O请求包,就能到接受驱动计算过的数据了!