《Windows驱动开发技术详解》之驱动程序调用驱动程序——以文件句柄形式调用其它驱动程序

在驱动程序开发中,经常需要一个驱动程序调用另一个驱动程序。例如,虚拟串口转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 }

运行结果如下:

posted @ 2016-06-14 20:30  _No.47  阅读(2028)  评论(0编辑  收藏  举报