在驱动程序开发中,经常需要一个驱动程序调用另一个驱动程序。例如,虚拟串口转USB设备的驱动程序,这种驱动程序首先创建一个虚拟串口设备,对这个虚拟串口设备的读写请求会转发到一个USB设备上去。这时就需要在虚拟串口驱动程序中调用USB驱动程序。
- 同步调用方法
本章节假设DriverA是将要被调用的目标驱动程序。
DriverB可以有多种方法调用DriverA,可以是同步,也可以使异步。在驱动中打开设备要使用ZwCreateFile内核函数打开同步设备或者异步设备。
这里介绍DriverB用同步的方式调用DriverA,其步骤如下:
(1)在DriverB中用ZwReadFile内核函数读取DriverA的设备对象。ZwReadFile内核函数内部会创建IRP_MJ_READ类型的IRP,然后将这个IRP当做参数传递给DriverA。
(2)DriverA的派遣函数没有结束IRP请求,而是将IRP挂起。
(3)ZwReadFile会一直等待IRP中的一个事件,此时当前线程进入睡眠状态。
(4)3s后,触发了DriverA的定时器例程,这时IRP请求被取消。
(5)由于相关事件被设置,刚才休眠的线程恢复运行,ZwReadFile内核函数退出。
DriverB的R3层代用来打开DriverB对应的驱动设备,码如下:
1 int main(){ 2 getchar(); 3 HANDLE hDevice = 4 CreateFile("\\\\.\\DriverBDDK", 5 GENERIC_READ,//| GENERIC_WRITE 6 FILE_SHARE_READ, NULL, 7 OPEN_EXISTING, 8 FILE_ATTRIBUTE_NORMAL,//|FILE_FLAG_OVERLAPPED, 9 NULL); 10 printf("GetLastError:%d\n", GetLastError()); 11 if (hDevice == INVALID_HANDLE_VALUE){ 12 printf("Open device failed!\n"); 13 } 14 else{ 15 printf("Open device succeed!\n"); 16 } 17 OVERLAPPED overlap_read = { 0 }; 18 char buffer[10]; 19 DWORD dwRead; 20 21 ReadFile(hDevice, buffer, 10, &dwRead, &overlap_read); //&overlap_read 22 23 CloseHandle(hDevice); 24 system("pause"); 25 return 0; 26 }
打开之后会将IRP_MJ_READ传给底层驱动DriverB,DriverB的派遣函数接收并处理这个IRP:
1 NTSTATUS HelloDDKRead_DriverB(PDEVICE_OBJECT pDevObj, PIRP pIrp){ 2 UNREFERENCED_PARAMETER(pDevObj); 3 DbgPrint("Enter HelloDDKRead_DriverB!\n"); 4 NTSTATUS status = STATUS_SUCCESS; 5 UNICODE_STRING DeviceName; 6 RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDDKDevice"); 7 OBJECT_ATTRIBUTES objectAttributes; 8 InitializeObjectAttributes(&objectAttributes, 9 &DeviceName, 10 OBJ_CASE_INSENSITIVE, 11 NULL, NULL 12 ); 13 HANDLE hDevice; 14 IO_STATUS_BLOCK status_block; 15 status = ZwCreateFile(&hDevice, 16 FILE_READ_ATTRIBUTES | SYNCHRONIZE, 17 &objectAttributes, 18 &status_block, 19 NULL, 20 FILE_ATTRIBUTE_NORMAL, 21 FILE_SHARE_READ, 22 FILE_OPEN_IF, 23 FILE_SYNCHRONOUS_IO_NONALERT, 24 NULL, 0 25 ); 26 if (NT_SUCCESS(status)){ 27 ZwReadFile(hDevice, NULL, NULL, NULL, 28 &status_block, 29 NULL, 0, NULL, NULL); 30 } 31 ZwClose(hDevice); 32 pIrp->IoStatus.Status = status; 33 pIrp->IoStatus.Information = 0; 34 IoCompleteRequest(pIrp, IO_NO_INCREMENT); 35 DbgPrint("Leave HelloDDKRead_DriverB!\n"); 36 return status; 37 }
这个派遣函数会打开另外一个驱动,并传送IRP给它,它传送了IRP_MJ_CREATE和IRP_MJ_READ,DriverA的接收IRP_MJ_READ的代码为:
1 NTSTATUS HelloDDKRead_Timeout(PDEVICE_OBJECT pDevObj, PIRP pIrp){ 2 DbgPrint("Enter HelloDDKRead_Timeout!\n"); 3 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) 4 pDevObj->DeviceExtension; 5 IoMarkIrpPending(pIrp); 6 pDevExt->currentPendingIRP = pIrp; 7 ULONG ulMicroSecond = 3000000; 8 LARGE_INTEGER timeout = RtlConvertLongToLargeInteger(-10 * ulMicroSecond); 9 KeSetTimer(&pDevExt->pollingTimer,//设置完就开始计时,本示例设置的超时时间是3s 10 timeout, 11 &pDevExt->pollingDPC); 12 DbgPrint("Leave HelloDDKRead_Timeout!\n"); 13 return STATUS_PENDING; 14 }
这时这个派遣函数会调用定时器例程,定时器例程会取消这个IRP:
1 VOID TimerOutDPC(PKDPC pDpc, PVOID pContext, PVOID SysArg1, PVOID SysArg2){ 2 UNREFERENCED_PARAMETER(pDpc); 3 UNREFERENCED_PARAMETER(SysArg1); 4 UNREFERENCED_PARAMETER(SysArg2); 5 6 DbgPrint("Enter TimerOutDPC!\n"); 7 PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pContext; 8 PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension; 9 PIRP currentPendingIRP = pdx->currentPendingIRP; 10 currentPendingIRP->IoStatus.Status = STATUS_CANCELLED; 11 currentPendingIRP->IoStatus.Information = 0; 12 IoCompleteRequest(currentPendingIRP, IO_NO_INCREMENT); 13 DbgPrint("Leave TimerOutDPC!\n"); 14 }
运行结果如下:
一开始加载驱动失败,发现原因在于:
- 异步调用方法一
异步读取主要是指ZwReadFile内核函数在没有等待DriverA真正结束IRP时,就已经退出。其内部操作过程如下:
(1)ZwReadFile内核函数内内部创建IRP_MJ_READ类型的IRP,然后将这个IRP传递给DriverA的派遣函数
(2)DriverA的派遣函数没有结束IRP请求,而是将IRP“挂起”
(3)ZwReadFile内核函数发现DriverA将IRP_MJ_READ“挂起”,于是它直接返回,返回值是STATUS_PENDING,这代表读操作正在进行中。
ZwReadFile内核函数退出后,无法得知挂起的IRP何时被结束。因此,可以为IRP设置一个完成例程。当IRP结束时就触发这个完成例程。
DriverB的派遣函数代码:
1 NTSTATUS HelloDDKRead_DriverB_ASY(PDEVICE_OBJECT pDevObj, PIRP pIrp){ 2 UNREFERENCED_PARAMETER(pDevObj); 3 DbgPrint("Enter HelloDDKRead_DriverB_ASY!\n"); 4 NTSTATUS status = STATUS_SUCCESS; 5 UNICODE_STRING DeviceName; 6 RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDDKDevice"); 7 OBJECT_ATTRIBUTES objectAttributes; 8 InitializeObjectAttributes(&objectAttributes, 9 &DeviceName, 10 OBJ_CASE_INSENSITIVE, 11 NULL, NULL); 12 HANDLE hDevice; 13 IO_STATUS_BLOCK status_block; 14 status = ZwCreateFile(&hDevice, 15 FILE_READ_ATTRIBUTES, 16 &objectAttributes, 17 &status_block, 18 NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, 19 FILE_OPEN_IF, 0, NULL, 0); 20 21 KEVENT event; 22 KeInitializeEvent(&event, SynchronizationEvent, FALSE); 23 LARGE_INTEGER offset = RtlConvertLongToLargeInteger(0); 24 if (NT_SUCCESS(status)){ 25 status = ZwReadFile(hDevice, NULL, CompleteDriverA_READ, &event, 26 &status_block, NULL, 0, &offset, NULL); 27 } 28 if (status == STATUS_PENDING){//会接收到完成例程设置好的事件 29 DbgPrint("DriverB waiting...\n"); 30 KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); 31 } 32 ZwClose(hDevice); 33 status = STATUS_SUCCESS; 34 pIrp->IoStatus.Status = status; 35 pIrp->IoStatus.Information = 0; 36 IoCompleteRequest(pIrp, IO_NO_INCREMENT); 37 38 DbgPrint("Leave HelloDDKRead_DriverB_ASY!\n"); 39 return status; 40 }
其中完成例程代码如下:
1 VOID CompleteDriverA_READ(PVOID context, PIO_STATUS_BLOCK pStatus_block,ULONG u){ 2 UNREFERENCED_PARAMETER(pStatus_block); 3 UNREFERENCED_PARAMETER(u); 4 DbgPrint("Enter CompleteDriverA_READ!\n"); 5 KeSetEvent((PKEVENT)context, IO_NO_INCREMENT, FALSE); 6 }
从上面标红的代码可以看出,这里是想DriverA发送了IRP,而DriverA的IRP_MJ_READ派遣函数
将IRP挂起,因此代码执行进入if (status == STATUS_PENDING)中。而if (status == STATUS_PENDING)中KeWaitForSingleObject等待的是event事件被设置,要设置这个条件就要触发完成例程,而要触发完成例程就要等到发给DriverA的那个IRP被完成。DriverA的派遣函数中调用了定时器例程,会隔几秒后完成这个IRP。
从运行结果也可以看出:
一上来ZwReadFile得到Pending结果,于是进入if (status == STATUS_PENDING),KeWaitForSingleObject等待,此时同时发生的事情就是DriverA的计时器例程在运行,3s后计时器例程触发并运行完毕(完成了这个IRP),完成了IRP就又触发了DriverB的完成例程,随后event被设置,KeWaitForSingleObject后续代码继续得到运行。
- 异步调用方法二
该方法是通过文件对象判断读取是否完毕。每打开一个设备,都会伴随存在一个关联的文件对象。利用内核函数ObReferenceObjectByHandle可以获得和设备相关的文件对象指针。当IRP_MJ_READ请求被结束后,文件对象的子域event会被设置,因此用文件对象的event子域可以当做同步点使用。
DriverB的派遣函数代码为:
1 NTSTATUS HelloDDKRead_DriverB_ASY_FILEOBJ(PDEVICE_OBJECT pDevObj, PIRP pIrp){ 2 UNREFERENCED_PARAMETER(pDevObj); 3 DbgPrint("Enter HelloDDKRead_DriverB_ASY_FILEOBJ!\n"); 4 NTSTATUS status = STATUS_SUCCESS; 5 UNICODE_STRING DeviceName; 6 RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDDKDevice"); 7 OBJECT_ATTRIBUTES objectAttributes; 8 InitializeObjectAttributes(&objectAttributes, 9 &DeviceName, 10 OBJ_CASE_INSENSITIVE, 11 NULL, NULL); 12 HANDLE hDevice; 13 IO_STATUS_BLOCK status_block; 14 status = ZwCreateFile(&hDevice, 15 FILE_READ_ATTRIBUTES, 16 &objectAttributes, 17 &status_block, 18 NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, 19 FILE_OPEN_IF, 0, NULL, 0); 20 LARGE_INTEGER offset = RtlConvertLongToLargeInteger(0); 21 if (NT_SUCCESS(status)){ 22 status = ZwReadFile(hDevice, NULL, NULL, NULL, &status_block, NULL, 0, &offset, NULL); 23 } 24 if (status == STATUS_PENDING){ 25 PFILE_OBJECT pFileObject; 26 status = ObReferenceObjectByHandle(hDevice, EVENT_MODIFY_STATE, *ExEventObjectType, 27 KernelMode, (PVOID*)&pFileObject, NULL); 28 if (NT_SUCCESS(status)){ 29 DbgPrint("DriverB:Waiting...\n"); 30 KeWaitForSingleObject(&pFileObject->Event, Executive, KernelMode, 31 FALSE, NULL); 32 DbgPrint("DriverA IRP completed!\n"); 33 ObDereferenceObject(pFileObject); 34 } 35 } 36 ZwClose(hDevice); 37 status = STATUS_SUCCESS; 38 pIrp->IoStatus.Status = status; 39 pIrp->IoStatus.Information = 0; 40 IoCompleteRequest(pIrp, IO_NO_INCREMENT); 41 DbgPrint("Leave HelloDDKRead_DriverB_ASY_FILEOBJ!\n"); 42 return status; 43 }
代码的思路和上一例差不多,只不过这里把DriverA的定时器例程设置为7s后启动:
- 通过符号链接打开设备
很多情况下,使用者不容易知道具体的设备名,而只知道符号链接。例如,“C:”代表第一个硬盘分区,而“C:”就是一个符号链接,它指向一个磁盘分区设备。
示例代码如下:
1 NTSTATUS HelloDDKRead_DriverB_Symbolic(PDEVICE_OBJECT pDevObj, PIRP pIrp){ 2 UNREFERENCED_PARAMETER(pDevObj); 3 DbgPrint("Enter HelloDDKRead_DriverB_Symbolic!\n"); 4 NTSTATUS status = STATUS_SUCCESS; 5 UNICODE_STRING DeviceSymbolicLinkName; 6 RtlInitUnicodeString(&DeviceSymbolicLinkName, L"\\??\\HelloDDK"); 7 OBJECT_ATTRIBUTES objectAttributes; 8 InitializeObjectAttributes(&objectAttributes, 9 &DeviceSymbolicLinkName, 10 OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, 11 NULL, NULL); 12 HANDLE hSymbolic; 13 //得到符号链接句柄 14 status = ZwOpenSymbolicLinkObject(&hSymbolic, FILE_ALL_ACCESS, &objectAttributes); 15 #define UNICODE_SIZE 50 16 UNICODE_STRING LinkTarget; 17 LinkTarget.Buffer = (PWSTR)ExAllocatePool(PagedPool, UNICODE_SIZE); 18 LinkTarget.Length = 0; 19 LinkTarget.MaximumLength = UNICODE_SIZE; 20 ULONG unicode_length; 21 //通过符号链接得到设备名 22 status = ZwQuerySymbolicLinkObject(hSymbolic, &LinkTarget, &unicode_length); 23 DbgPrint("The device name is %wZ\n", &LinkTarget); 24 InitializeObjectAttributes(&objectAttributes, 25 &LinkTarget, 26 OBJ_CASE_INSENSITIVE, 27 NULL, NULL); 28 HANDLE hDevice; 29 IO_STATUS_BLOCK status_block; 30 status = ZwCreateFile(&hDevice, 31 FILE_READ_ATTRIBUTES | SYNCHRONIZE, 32 &objectAttributes, 33 &status_block, 34 NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, 35 FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); 36 if (NT_SUCCESS(status)){ 37 ZwReadFile(hDevice, NULL, NULL, NULL, &status_block, NULL, 0, NULL, NULL); 38 } 39 ZwClose(hDevice); 40 ZwClose(hSymbolic); 41 ExFreePool(LinkTarget.Buffer); 42 status = STATUS_SUCCESS; 43 pIrp->IoStatus.Status = status; 44 pIrp->IoStatus.Information = 0; 45 IoCompleteRequest(pIrp, IO_NO_INCREMENT); 46 DbgPrint("Leave HelloDDKRead_DriverB_Symbolic!\n"); 47 return status; 48 }
运行结果如下: