小菜学习驱动也有一个来月,借看雪这块宝地写些心得。一来作为一个总结,帮助自己巩固提高;二来嘛,看着看雪这边人气很旺,也想混一个邀请码,好进一步学习。笔者毕竟处于入门阶段,文词中难免会有疏漏错误的地方,恳请大家多多指正。
常见的Windows驱动程序可以分成两类:一类是不支持即插即用的NT式驱动程序,而另一类则是支持即插即用的WDM式驱动程序。其中,NT式驱动程序的安装是基于服务的,可以通过修改注册表进行,也可以直接通过服务函数CreateService安装;但WDM式驱动程序则不同,它的安装通常是需要靠编写一个inf文件来进行。
驱动程序与其他的普通应用程序一样,都是有着自己的框架结构。下面,我们来介绍一下NT式驱动程序的框架构成。
和普通应用程序一样,NT式驱动程序也是需要导入头文件的,即“#include ntddk.h”。并且驱动程序所用到的变量和函数都是需要指定分配在分页或非分页内存中。顾名思义,分页内存就是在物理内存不够用时可能会被交换出去的,而非分页内存则不会被交换出去。对于那些需要高IRQL(Interrupt ReQuest Level,中断请求级别)的例程是绝对不能被交换出页面的,因此他们必须被定义为非分页内存。
驱动程序也有着自己的入口点,即DriverEntry。DriverEntry例程在驱动加载后便可退出内存,因此通常情况下,我们都将其加载到INIT内存区域。我们先来看一个真实的DriverEntry例程,对其有一个直观的认识后,再来分析其原理。
驱动程序与其他的普通应用程序一样,都是有着自己的框架结构。下面,我们来介绍一下NT式驱动程序的框架构成。
和普通应用程序一样,NT式驱动程序也是需要导入头文件的,即“#include ntddk.h”。并且驱动程序所用到的变量和函数都是需要指定分配在分页或非分页内存中。顾名思义,分页内存就是在物理内存不够用时可能会被交换出去的,而非分页内存则不会被交换出去。对于那些需要高IRQL(Interrupt ReQuest Level,中断请求级别)的例程是绝对不能被交换出页面的,因此他们必须被定义为非分页内存。
驱动程序也有着自己的入口点,即DriverEntry。DriverEntry例程在驱动加载后便可退出内存,因此通常情况下,我们都将其加载到INIT内存区域。我们先来看一个真实的DriverEntry例程,对其有一个直观的认识后,再来分析其原理。
代码:
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING ntDeviceName; // 驱动名称 UNICODE_STRING dosDeviceName; //符号链接名PDEVICE_EXTENSION deviceExtension; PDEVICE_OBJECT deviceObject = NULL; KdPrint(("[NTddkDriver] DriverEntry: %wZ\n", RegistryPath)); //创建设备对象 RtlInitUnicodeString(&ntDeviceName, L”\\\\.\\Ntddk”); Status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION),// DeviceExtension大小 &ntDeviceName,// 驱动名称 FILE_DEVICE_UNKNOWN,// 类型0,TRUE, &deviceObject);if(!NT_SUCCESS(Status)){ KdPrint(("[NTddkDriver] IoCreateDevice Error Code = 0x%X\n", Status));return Status; }deviceExtension = (PDEVICE_EXTENSION)deviceObject->DeviceExtension; //创建符号链接,以便win32应用程序调用驱动RtlInitUnicodeString(&dosDeviceName, L” \\DosDevices\\Ntddk”);Status = IoCreateSymbolicLink(&dosDeviceName, &ntDeviceName); if(!NT_SUCCESS(Status)){ KdPrint(("[NTddkDriver] IoCreateSymbolicLink Error Code = 0x%X\n", Status)); IoDeleteDevice(deviceObject); return Status; }//关联派遣函数DriverObject->MajorFunction[IRP_MJ_CREATE] = NtddkDispatchCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = NtddkDispatchClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = NtddkDispatchDeviceControl; DriverObject->DriverUnload = NtddkUnload; return Status; }
这是一个很微软风格的驱动程序函数的格式。个人拙见,对于一名程序员来说,养成一个良好的程序风格是很必要的。
下面,我们来分析一下这个驱动程序的入口点DriverEntry例程。DriverEntry是由内核中的I/O管理器负责调用的,它有两个参数,DriverObject和RegistryPath。其中,DriverObject是由I/O管理器传递进来的驱动对象,RegistryPath这是指向此驱动的注册表路径。
我们看到,DriverEntry首先定义了一些变量,这是按照C语言的格式来的(C语言要求变量的定义必须放在函数的最上端,而C++则无所谓)。接下来,使用RtlInitUnicodeString函数来初始化UNICODE字符串,将“\\\\.\\Ntddk”赋给驱动名称ntDeviceName,并IoCreateDevice创建设备对象。接下来,调用IoCreateSymbolicLink函数创建一个符号链接,用于应用程序找到驱动程序并与之通信。紧接着驱动程序会向Windows的I/O管理器注册一些回调函数。本例中代码的含义是:当驱动卸载时,调用NtddkUnload函数;当驱动程序接收到IRP_MJ_CREATE、IRP_MJ_CLOSE或IRP_MJ_DEVICE_CONTROL时,分别调用NtddkDispatchCreate、NtddkDispatchClose或NtddkDispatchDeviceControl函数。
现在大家可能会有疑问,MajorFunction是从何而来的?在驱动对象DriverObject中,有个函数指针数组,这就是MajorFunction,它里面每一个元素都记录着一个函数的地址对应着的IRP,我们可以通过简单的设置将这些IRP消息与相应的派遣函数关联起来。而编写驱动程序的精髓便在于这些派遣函数。在进入DriverEntry之前,I/O管理器会将_IopInvalidDeviceRequest 的地址填满整个MajorFunction 数组,因此除了我们自行设置过的IRP 之外,其他的IRP 都与系统默认的_IopInvalidDeviceRequest 函数关联。下面,我们来分析一下这个驱动程序的入口点DriverEntry例程。DriverEntry是由内核中的I/O管理器负责调用的,它有两个参数,DriverObject和RegistryPath。其中,DriverObject是由I/O管理器传递进来的驱动对象,RegistryPath这是指向此驱动的注册表路径。
我们看到,DriverEntry首先定义了一些变量,这是按照C语言的格式来的(C语言要求变量的定义必须放在函数的最上端,而C++则无所谓)。接下来,使用RtlInitUnicodeString函数来初始化UNICODE字符串,将“\\\\.\\Ntddk”赋给驱动名称ntDeviceName,并IoCreateDevice创建设备对象。接下来,调用IoCreateSymbolicLink函数创建一个符号链接,用于应用程序找到驱动程序并与之通信。紧接着驱动程序会向Windows的I/O管理器注册一些回调函数。本例中代码的含义是:当驱动卸载时,调用NtddkUnload函数;当驱动程序接收到IRP_MJ_CREATE、IRP_MJ_CLOSE或IRP_MJ_DEVICE_CONTROL时,分别调用NtddkDispatchCreate、NtddkDispatchClose或NtddkDispatchDeviceControl函数。
至此,整个DriverEntry例程便介绍完了。下面我们来一起了解下卸载函数例程NtddkUnload。老规矩,我们还是先给出一个完整的例程以得到一个直观的认识。
代码:
VOIDNtddkUnload( IN PDRIVER_OBJECT DriverObject) {UNICODE_STRING dosDeviceName; // 释放其他资源//删除符号链接 RtlInitUnicodeString(&dosDeviceName, L” \\DosDevices\\Ntddk”); IoDeleteSymbolicLink(&dosDeviceName);//删除设备对象IoDeleteDevice(DriverObject->DeviceObject);KdPrint(("[NTddkDriver] Unloaded")); }
卸载驱动例程是我们在DriverEntry中自己定义的。当卸载驱动程序时,I/O管理器会调用该例程,做一些扫尾工作。同DriverEntry例程一样,NtddkUnload也是先定义了一个UNICODE_STRING变量。接下来释放其他资源,删除符号链接,并删除设备对象,从而完成了驱动程序的卸载工作。
至于NtddkDispatchCreate、NtddkDispatchClose和NtddkDispatchDeviceControl这三个派遣例程是用于处理IRP的,这里只是讲述一个NT式驱动程序的框架,因而不多做介绍。大家可以查看源码,里面也只是提供了一个对IRP消息的回应,并没有其他功能。大家可以根据实际需要来编写自己的派遣函数。
到此,我们完成了一个NT式驱动程序的编写,通过编译,我们得到了相应的sys文件。现在该谈谈如何使用这一sys文件了。当然,我们是可以通过驱动加载程序,比如“KmdManager”来加载。但当我们编写出自己的驱动程序后,总不能一直用他人的应用程序来调用吧。因此,我们需要编写自己的NT式驱动调用程序。下面我们来一起了解如何通过服务编程来加载驱动程序,这里只给出相应的加载程序和卸载程序,程序比较简单,不再赘述。
至于NtddkDispatchCreate、NtddkDispatchClose和NtddkDispatchDeviceControl这三个派遣例程是用于处理IRP的,这里只是讲述一个NT式驱动程序的框架,因而不多做介绍。大家可以查看源码,里面也只是提供了一个对IRP消息的回应,并没有其他功能。大家可以根据实际需要来编写自己的派遣函数。
到此,我们完成了一个NT式驱动程序的编写,通过编译,我们得到了相应的sys文件。现在该谈谈如何使用这一sys文件了。当然,我们是可以通过驱动加载程序,比如“KmdManager”来加载。但当我们编写出自己的驱动程序后,总不能一直用他人的应用程序来调用吧。因此,我们需要编写自己的NT式驱动调用程序。下面我们来一起了解如何通过服务编程来加载驱动程序,这里只给出相应的加载程序和卸载程序,程序比较简单,不再赘述。
代码:
bool LoadDriver(LPCTSTR lpszDriverName, LPCTSTR lpszDriverPath) { char szDriverFilePath[MAX_PATH]; GetFullPathName(lpszDriverPath, MAX_PATH, szDriverFilePath, NULL); bool bRet = true; DWORD dwReturn; SC_HANDLE hServiceMgr = NULL; SC_HANDLE hServiceSys = NULL; // 打开服务管理器 hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!hServiceMgr) { printf("OpenSCManager() Error!\n"); bRet = false; goto END; } // 创建驱动服务 hServiceSys = CreateService(hServiceMgr, lpszDriverName, // 驱动服务名 lpszDriverName, // 驱动服务显示名 SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, // 内核驱动 SERVICE_DEMAND_START, // 手动启动 SERVICE_ERROR_IGNORE, // 忽略错误 szDriverFilePath, // 服务文件路径 NULL, NULL, NULL, NULL, NULL); if (!hServiceSys) { dwReturn = GetLastError(); if ((dwReturn != ERROR_IO_PENDING) && (dwReturn != ERROR_SERVICE_EXISTS)) { // 服务创建失败,未知错误 printf("CreateService() Error : %d!\n", dwReturn); bRet = false; goto END; } // 服务已经存在,只需打开 hServiceSys = OpenService(hServiceMgr, lpszDriverName, SERVICE_ALL_ACCESS); if (!hServiceSys) { printf("OpenService() Error!\n"); bRet = false; goto END; } } // 启动服务 bRet = StartService(hServiceSys, NULL, NULL); if (!bRet) { dwReturn = GetLastError(); if ((dwReturn != ERROR_IO_PENDING) && (dwReturn != ERROR_SERVICE_ALREADY_RUNNING)) { printf("StartService() Error! : %d\n", dwReturn); bRet = false; goto END; } else { if (dwReturn == ERROR_IO_PENDING) { // 设备被挂起 printf("StartService() Error! : ERROR_IO_PENDING\n"); bRet = false; goto END; } else { // 设备已经运行 bRet = true; goto END; } } } END: if (hServiceSys) { CloseHandle(hServiceSys); } if (hServiceMgr) { CloseHandle(hServiceMgr); } return bRet; }
代码:
bool UnloadDriver(LPCTSTR lpszSvrName){ bool bRet = true;SERVICE_STATUS SrvStatus; SC_HANDLE hServiceMgr = NULL; SC_HANDLE hServiceSys = NULL;// 打开服务管理器 hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);if (!hServiceMgr) { printf("OpenSCManager() Error!\n"); bRet = false; goto END; }// 打开驱动服务 hServiceSys = OpenService(hServiceMgr, lpszSvrName, SERVICE_ALL_ACCESS);if (!hServiceSys) { printf("OpenService() Error!\n"); bRet = false; goto END; }// 停止驱动服务 if (!ControlService(hServiceSys, SERVICE_CONTROL_STOP, &SrvStatus)){ printf("ControlService() Error!\n"); bRet = false; }// 卸载驱动服务 if (!DeleteService(hServiceSys)){ printf("DeleteService() Error!\n"); bRet = false; }END: if (hServiceSys){ CloseHandle(hServiceSys); } if (hServiceMgr) { CloseHandle(hServiceMgr); }return bRet; }