1.1 Windows驱动的基础知识
本节主要描述在
WinPcap
的
NPF
中经常使用一些编写
Windows
驱动程序所需掌握的部分基础知识,以便于后面的理解。
1.1.1 驱动对象(DRIVER_OBJECT)
每个驱动程序都有唯一的驱动对象与之对应,该驱动对象在驱动程序被加载时由内核的对象管理程序所创建。
驱动对象用
DRIVER_OBJECT数据结构表示,它作为驱动程序的一个实例被内核加载,对一个驱动程序内核I/O管理器只加载一个实例。
驱动对象数据结构在
wdm.h文件中的定义如下。
typedef
struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
/*
*DeviceObject为每个驱动程序所创建的一个或多个设备对象链表,
*Flags提供一个扩展的标识定位驱动对象
*/
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
/*下列各成员字段描述驱动程序从哪儿被加载*/
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
/*
*DriverName成员被错误日志线程用来
*确定一个I/O请求越界的驱动名称
*/
UNICODE_STRING DriverName;
/*指向注册表中硬件信息的路径*/
PUNICODE_STRING HardwareDatabase;
/*
*如果驱动支持“fast I/O”,
*就指向一个“fast I/O”的派遣函数数组
*/
PFAST_IO_DISPATCH FastIoDispatch;
/*
*描述该特定驱动的入口点。
*主函数(major function)派遣函数表必须是对象最后的成员,
*因此它仍然是可扩展的
*/
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
typedef
struct _DRIVER_OBJECT *PDRIVER_OBJECT;
下面分别描述驱动对象中驱动程序可访问的成员。
PDEVICE_OBJECT DeviceObject
每个驱动对象会有一个或多个设备对象。每个设备对象都有一个指针(NextDevice)指向下一个驱动对象,最后一个设备对象指向空。此处的DeviceObject指向驱动对象的第一个设备对象。该成员在成功调用IoCreateDevice后自动更新。一个驱动程序使用该程成员与设备对象(DEVICE_OBJECT)的NextDevice可遍历给驱动对象的所有设备对象。在驱动被卸载的时候,需要遍历每个设备对象,并将其删除。
PUNICODE_STRING HardwareDatabase
指向注册表中硬件配置信息的路径,用UNICODE字符串表示。该字符串一般为
\
REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM。
PFAST_IO_DISPATCH FastIoDispatch
指向一个定义驱动
快速 I/O结构体的入口点,该成员只用于文件系统驱动与网络传输驱动。
PDRIVER_INITIALIZE DriverInit
是DriverEntry例程的入口点,由I/O管理器设置。
PDRIVER_STARTIO DriverStartIo
是
Startl0
例程的
的入口点
,如果需要,由
DriverEntry
例程设置
,否则为
NULL。
PDRIVER_UNLOAD DriverUnload
指向驱动卸载时所用回调函数的
入口点
。
PDRIVER_DISPATCH MajorFunction[
IRP_MJ_MAXIMUM_FUNCTION+1]
一个函数指针数组
,
数组
MajorFunction
中的每个成员保存着一个指针
,
每一个指针指向一个处理对应IRP(
IRP_MJ_XXX
)的派遣函数(
DispatchXxx)
。
每个
派遣函数
(DispatchXxx)声明如下:
(*PDRIVER_DISPATCH) (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
1.1.2 设备对象(DEVICE_OBJECT)
设备对象保存设备特征和状态的信息。一个设备对象表示一个逻辑的、虚拟的或物理的设备,由一个驱动对象操控设备对象的I/O请求。每一个内核模式的驱动必须创建设备对象,通过调用IoCreateDevice一次或多次。
每个驱动程序会创建一个或多个设备对象,用DEVICE_OBJECT数据结构表示。每个设备对象有一个指针(
NextDevice
)指向下一个设备对象,从而形成一个设备链表。设备链表第一个设备是由驱动对象结构体中
DeviceObject
指明的。
设备对象
数据结构在
wdm.h
文件中的定义如下。
typedef
struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT
{
CSHORT Type;
USHORT Size;
LONG ReferenceCount;
struct _DRIVER_OBJECT *DriverObject;
struct _DEVICE_OBJECT *NextDevice;
struct _DEVICE_OBJECT *AttachedDevice;
struct _IRP *CurrentIrp;
PIO_TIMER Timer;
ULONG Flags;
ULONG Characteristics;
__volatile PVPB Vpb;
PVOID DeviceExtension;
DEVICE_TYPE DeviceType;
CCHAR StackSize;
union {
LIST_ENTRY ListEntry;
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement;
KDEVICE_QUEUE DeviceQueue;
KDPC Dpc;
/*
*下列成员是为支持文件系统的互斥操作,
*为了对文件系统处理线程使用设备的计数保持跟踪
*/
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
KEVENT DeviceLock;
USHORT SectorSize;
USHORT Spare1;
struct _DEVOBJ_EXTENSION *DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT;
typedef
struct _DEVICE_OBJECT *PDEVICE_OBJECT;
下面分别描述设备对象中驱动程序可访问的成员。
PDRIVER_OBJECT DriverObject
指向驱动程序中的驱动对象。同属于一个驱动程序的驱动对象指向的是同一个驱动对象。
PDEVICE_OBJECT NextDevice
指向下一个设备对象。这里的下一个设备对象是同一个驱动程序创建的若干设备对象。每个设备对象根据
NextDevice
域形成链表,从而可以遍历每个设备对象。
在每次成功调用IoCreateDevice 后I/O管理器更新该链表。
在驱动被卸载的时候,需要遍历该链表,删除每个设备对象。
PIRP CurrentIrp
如果驱动使用
Startl0
例程时
,
此成员指向当前
IRP
结构。否则为NULL。
ULONG Flags
此成员是一个
32
位昀无符号整型,每个位有不同的含义。通过位或操作为新创建的设备对象设置不同的特性。
ULONG Characteristics
当驱动程序调用IoCreateDevice时,设置下列一个合适的值:
FILE_REMOVABLE_MEDIA
FILE_READ_ONLY_DEVICE
FILE_FLOPPY_DISKETTE
FILE_WRITE_ONCE_MEDIA
FILE_DEVICE_SECURE_OPEN
PVOID DeviceExtension
指向设备扩展对象。设备扩展对象是由程序员在驱动中自行定义的结构体,结构体的大小在调用
IoCreateDevice
时设置。
每个设备都会指定一个设备扩展对象,设备扩展对象记录的是特别定义的结构体。在驱动程序中应该尽量避免全局变量的使用,因为全局变量涉及不容易同步的问题,解决的办法可将全局变量存储在设备扩展中。
DEVICE_TYPE DeviceType
指明设备的类型
,
由
IoCreateDevice
设置
。根据设备需要填写相应的设备类型。
.
CCHAR StackSize
在多层驱动的情况下,驱动与驱动之间形成类似堆栈的结构。
IRP
会依次从最高层传递到最底层。
StackSize
就是
指定发送到该驱动的IRP在堆栈位置的最小层数
。
IoCreateDevice在一个新创建的设备对象中设置该成员。
ULONG AlignmentRequirement
设备在大容量传输的时候,为了保证传输速度需要内存对齐。
每个设备对象在它新创建的设备对象中设置该成员。
1.1.3 设备扩展(_DEVICE_EXTENSION)
设备对象记录设备的“通用”信息,而另外一些“特殊”信息记录在设备扩展中。设备扩展由程序员自行定义,指定内容与大小,由
I/O
管理器创建,保存在非分页内存中。
在驱动程序中,尽量避免使用全局函数,因为全局函数往往导致函数的不可重入性。一个解决办法就是将全局变量以设备扩展的形式存储,并加以适当的同步保护措施。
WinPcap
中
NPF
的设备扩展结构体,主要用于存储每个被
NPF
绑定的适配器的一些信息,结构体定义如下:
typedef
struct _DEVICE_EXTENSION {
//适配器名称
NDIS_STRING AdapterName;
//设备导出的名称,也就是通过WinPcap应用程序使用该名称来打开该适配器
PWSTR ExportString;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
1.1.4 IRP与派遣函数
驱动程序的主要功能是负责处理
l/O
请求,大部分
1/0
请求是在派遣函数中处理的。
IRP
的处理机制类似
Windows
应用程序中的“消息处理”机制。用户空间对驱动程序的所有
1/0
请求,全部由操作系统转化为一个
IRP
数据结构,不同的
IRP
数据会被“派遣”到不同的派遣函数中,在派遣函数中处理
IRP
。
IRP
是
Windows
内核中输入输出请求包
(I/O Request Package
,
IRP)
的缩写。
IRP
具有两个基本属性:
MajorFunction
与
MinorFunction
,分别记录
IRP
的主功能和子功能。操作系统根据
MajorFunction
将
IRP
“派遣”到不同的派遣函数中,在派遣函数中还可以根据
MinorFunction
继续判断该
IRP
属于哪种子功能。
一般来说驱动程序都是在
DriverEntry
函数中注册派遣函数的。在
DriverEntry
的驱动对象
pDriverObject
中,有个函数指针数组
MajorFunction
,每个数组元素都记录着一个派遣函数的地址。通过设置该数组,可以将不同类型的
IRP
和对应的派遣函数关联起来。
大部分的
IRP
都源于文件
I/O
处理的
API
,如
CreateFile
、
ReadFile
、
WriteFile
、
CloseHandle
等函数会使操作系统产生
IRP_MJ_CREATE
、
IRP_MJ_READ
、
IRP_ MJ_WRITE
、
IRP_MJ_CLOSE
等不同类型的
IRP
,这些
IRP
会被传送到驱动程序中,调用对应的派遣函数。此外,内核中的文件
I/O
处理函数,如
ZwCreateFile
、
ZwReadFile
、
ZwWriteFile
、
ZwClose
也同样会创建
IRP_MJ_CREATE
、
IRP_MJ_READ
、
lRP_MJ_WRITE
、
IRP_MJ_CLOSE
等
IRP
,并将
lRP
传送到驱动程序中,调用对应的派遣函数。
处理
IRP
最简单的方法就是在相应的派遣函数中,将
IRP
的状态设置为成功,然后结束
IRP
的请求,并让派遣函数返回成功。结束
IRP
的请求使用函数
IoCompleteRequest
。下面为
NPF
中
NPF_Close
的代码,为处理
IRP_MJ_CLOSE
类型
IRP
的派遣函数。
NTSTATUS NPF_Close(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
函数
NPF_Close
设置
IRP
的完成状态为
STATUS_SUCCESS
。这样发起请求的
API
(如
CloseHandle
)将会返回
TRUE
。相反,如果将
IRP
的完成状态设置为不成功,发起
I/O
请求的
API
(如
CloseHandle
)将会返回
FALSE
。出现该情况时,可以使用
GetLastError API
获得错误代码。所得的错误代码会和
IRP
设置的状态一致。
除了设置
IRP
的完成状态,函数还要设置这个
IRP
请求操作了多少字节。在本代码中,将操作字节数设置成了
0
。如果是
ReadFile
产生的
IRP
,这个字节数代表从设备读了多少字节。如果是
WriteFile
产生的
IRP
,这个字节数代表对设备写了多少字节。最后函数通过
IoCompleteR0quest
函数将
IRP
请求结束。
IoCompleteRequest
的声明如下:
IN PIRP
Irp,
IN CCHAR
PriorityBoost
参数
Irp
代表需要被结束的
IRP
。参数
PriorityBoost
代表线程恢复时的优先级别,指的是被阻塞的线程以何种优先级恢复运行。一般情况下,优先级设置为
IO_NO_INCREMENT
。