转载请标明是引用于 http://blog.csdn.net/chenyujing1234
欢迎大家提出意见,一起讨论!
代码及EzDriverInstaller下载地址 : http://www.rayfile.com/zh-cn/files/9840cf8f-c41f-11e1-b25b-0015c55db73d/
(编译环境:VS2008+DDK库(参考:Window XP驱动开发(十六) XP下新建驱动程序工程并编译的第二种方法))
我有一篇文章是介绍以文件句柄形式调用其它驱动程序的方法:
Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式)
现在介绍以设备指针调用其它驱动程序的方法。
1、通过设备指针调用其他驱动程序
前面介绍了如何使用ZwCreateFile内核函数打开设备,还介绍了如何用ZwReadFile内核函数读取设备。
这些操作和应用程序中的CreateFile和ReadFile 函数的使用很类似。其实,CreateFile和ReadFile这两个API函数分别调用了ZwCreateFile和ZwReadFile内核函数。
ZwReadFile内核函数内部会创建IRP_MJ_READ类型的IRP, 然后通过这个IRP传送到相应驱动的派遣函数中。
本节介绍的驱动程序调用其他驱动程序的方法,不是借用ZwCreateFile和ZwReadFile等内核函数,而是“手动”构造各个IRP,
然后将IRP传递到相应的驱动程序的派遣函数里。
1、1 用IoGetDeviceObjectPointer获得设备指针
每个内核中的句柄都会和一个内核对象的指针联系起来。例如,进程对象的句柄和进程对象的指针关联,
线程对象的句柄和线程对象的指针关联,内核事件句柄和内核对象关联。
ZwCreateFile内核函数可以通过设备名打开设备句柄,这个设备句柄和一个文件对象的指针关联。
IoGetDeviceObjectPointer内核函数可以通过设备名获得文件对象指针,而不是获得设备句柄,其声明如下:
IoGetDeviceObjectPointer(
__in PUNICODE_STRING ObjectName,
__in ACCESS_MASK DesiredAccess,
__out PFILE_OBJECT *FileObject,
__out PDEVICE_OBJECT *DeviceObject
);
第一个参数ObjectName:设备名,用UNICODE字符串表示;
第二个参数DesiredAccess :以什么样的权限得到设备句柄;
第三个参数FileObject:同时会返回一个和设备相关的文件对象指针;
第四个参数DeviceObejct:返回的设备对象指针。
Windows内核会为每一个对象指针保存一个“引用计数”,当对象被创建时引用计数为1。如果想引用这个对象,计数会加1。
如果删除对象时,Windows先将引用计数减1,如果引用计数不是0,系统不会删除对象。
当调用IoGetDeviceObjectPointer内核函数后,设备对象的引用计数就会加1,当用完这个设备对象后,应用调用ObDereferenceObject内核函数,
使其引用计数减1。
#define ObDereferenceObject(a) \
ObfDereferenceObject(a)
当第一次调用IoGetDeviceObjectPointer内核函数时,会根据设备名打开设备,这时文件对象指针计数为1。此后如果再次调用
IoGetDeviceObjectPointer打开设备,就不是真正地打开设备了,而是只将引用计数加1。打开设备时,系统会创建一个
IRP_MJ_CREATE类型的IRP,并将这个IRP传递到驱动程序的派遣函数中。
每次调用ObDereferenceObject内核函数都会将“引用计数”减1,如果减至0就会关闭设备。关闭设备时,系统会创建一个IRP_MJ_CLOSE类型的IRP,
将将其传递到相应驱动的派遣函数中。
从上述内容可以看出IoGetDeviceObjectPointer和ObDereferenceObject内核函数完全正确可以代替ZwCreateFile和ZwCloseFile内核函数。另外,这种方法还能获
得设备对象指针关联的文件对象指针。
1、2 创建IRP传递给驱动的派遣函数
本节介绍如何手动创建IRP,并将 其传递给相应的程序程序。这样的好处是比ZwReadFile内核灵活。ZwReadFile内核函数是针对设备句柄操作的,而传递IRP是通过设备对象的指针操作。
(1)可以通过IoBuildSynchronousFsdRequest和IoBuildAsynchronousFsdRequest两个内核函数创建IRP,它们分别用来创建同步类型的IRP和异步类型的IRP。
这两个内核函数可以创建IRP_MJ_PNP、IRP_MJ_READ、IRP_MJ_WRITE、MJ_FLUSH_BUFFERS和IIRP_MJ_SHUTDOWN类型的IRP。
可以通过IoBuildDeviceIoControlRequest内核函数创建IRP_MJ_INTERNAL_DEVICE_CONTROL和IRP_MJ_DEVICE_CONTROL两个类型的IRP,
这两个内核函数只能创建同步类型的IRP。
另外,还可以使用IoAllocateIrp内核函数,它可以创建任意类型的IRP。IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControlRequest这三个内核函数都是属于靠近上层的内核函数。
而IoAllocateIrp是比较底层的内核函数,以下三个内核都是通过IoAllocateIrp实现的。
(2)创建完IRP后,还要构造IRP的I/O堆栈,每层I/O堆栈对应一个设备对象。由于示例程序DriverA是单层驱动程序,所以只需要构造IRP的第一层I/O堆栈。
(3)最后是通过IoCallDriver内核函数调用相应的驱动。IoCallDriver 内核函数会根据IRP的类型,找到相应的派遣函数。
总结一下,手动创建IRP有以下几个步骤:
(1)先得到设备的指针。一种方法是用IoGetDeviceObjectPointer内核函数得到设备对象的指针;
另一种方法是通过ZwCreateFile内核函数先得到设备句柄,然后调用ObReferenceObjectByPointer内核函数通过设备句柄得到设备对象指针。
(2)手动创建IRP,有4个内核函数可以选择,它们是IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControlRequest和
IoAllocateIrq,其中IoAllocateIrp内核函数是最灵活的,使用也最复杂。
(3)构造IRP的I/O堆栈。
(4)调用IoCallDriver内核函数,其内部会调用设备对象的派遣函数。
1、3 用IoBuildSynchronousFsdRequest创建IRP
函数声明如下:
PIRP
IoBuildSynchronousFsdRequest(
__in ULONG MajorFunction,
__in PDEVICE_OBJECT DeviceObject,
__inout_opt PVOID Buffer,
__in_opt ULONG Length,
__in_opt PLARGE_INTEGER StartingOffset,
__in PKEVENT Event,
__out PIO_STATUS_BLOCK IoStatusBlock
);
第一个参数MajorFunction:这个参数是创建的IRP的主类型,IoBuldSynchronousFsdRequest函数只支持IRP_MJ_PNP、IRP_MJ_READ、IRP_MJ_WRITE、MJ_FLUSH_BUFFERS和IIRP_MJ_SHUTDOWN。
第二个参数DeviceObject:这个参数是设备对象指针,IRP将会传递给这个设备对象。
第三个参数Buffer:对于IRP_MJ_READ和IRP_MJ_WRITE,Buffer指的是输入和输出缓冲区
第四个参数Length:这个参数是缓冲区的大小
第五个参数StartingOffset:这个参数是偏移量;
第六个参数Event:这个参数是同步事件,这个创建同步类型的IRP的关键,后面会有介绍。
使用IoBuildSynchronousFsdRequest内核函数创建同步类型的IRP,关键在于第六个参数Event 。
在调用IoBuildSynchronousFsdRequest之前,需要准备一个事件,这个事件会和IRP请求关联,当IRP请求被结束时该事件被触发。
IoBuildSynchronousFsdRequest和IoBuildAsynchronousFsdRequest内核函数之间的区别就是是否提供事件。
下面的代码演示了如何使用IoBuildSynchronousFsdRequest内核函数创建同步类型IRP(在代码中的DriverB工程中):
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("DriverB:Enter B HelloDDKRead\n"));
NTSTATUS ntStatus = STATUS_SUCCESS;
UNICODE_STRING DeviceName;
RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );
PDEVICE_OBJECT DeviceObject = NULL;
PFILE_OBJECT FileObject = NULL;
//得到设备对象句柄,计数器加1
//如果是第一次调用IoGetDeviceObjectPointer,会打开设备,相当于调用ZwCreateFile
ntStatus = IoGetDeviceObjectPointer(&DeviceName,FILE_ALL_ACCESS,&FileObject,&DeviceObject);
KdPrint(("DriverB:FileObject:%x\n",FileObject));
KdPrint(("DriverB:DeviceObject:%x\n",DeviceObject));
// 判断是否成功打开设备
if (!NT_SUCCESS(ntStatus))
{
KdPrint(("DriverB:IoGetDeviceObjectPointer() 0x%x\n", ntStatus ));
ntStatus = STATUS_UNSUCCESSFUL;
// 设置IRP的完成状态
pIrp->IoStatus.Status = ntStatus;
// 设置IRP操作的字节数
pIrp->IoStatus.Information = 0; // bytes xfered
// 将IRP请求结束
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("DriverB:Leave B HelloDDKRead\n"));
return ntStatus;
}
KEVENT event;
// 初始化一个同步对象
KeInitializeEvent(&event,NotificationEvent,FALSE);
IO_STATUS_BLOCK status_block;
// 将32位整数转为64位的整数
LARGE_INTEGER offsert = RtlConvertLongToLargeInteger(0);
//创建同步IRP
PIRP pNewIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,
DeviceObject,
NULL,0,
&offsert,&event,&status_block);
KdPrint(("DriverB:pNewIrp:%x\n",pNewIrp));
// 得到下一层的I/O堆栈
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(pNewIrp);
// 设置I/O堆栈的文件对象指针
stack->FileObject = FileObject;
//调用DriverA,会一直调用到DriverA的派遣函数
NTSTATUS status = IoCallDriver(DeviceObject,pNewIrp);
// 判断操作是否被挂起
if (status == STATUS_PENDING)
{
//如果DriverA的派遣函数没有完成IRP,则等待IRP完成
status = KeWaitForSingleObject(
&event,
Executive,
KernelMode,
FALSE, // Not alertable
NULL);
status = status_block.Status;
}
//将引用计数减1,如果此时计数器减为0,
//则将关闭设备,相当于调用ZwClose
ObDereferenceObject( FileObject );
ntStatus = STATUS_SUCCESS;
// 设置IRP的完成状态
pIrp->IoStatus.Status = ntStatus;
// 设置IRP的操作字节数
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("DriverB:Leave B HelloDDKRead\n"));
return ntStatus;
}
测试方法:
(1) 标准驱动DriverA的设计与文章(Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式))一样,请参考代码。
(2) DriverB 的设计请参考代码。
(3) 安装HelloDDKA.sys、HelloDDKB.sys(安装方法与Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式)一样)
通过DebugView看到的打印信息如下:
1、4 用IoBuildAsynchronousFsdRequest创建IRP
这个内核函数比IoBuildSynchronousFsdRequest内核函数少一个事件参数。
1、5 用IoAllocate创建IRP