深度剖析WinPcap之(三)――所涉及的Windows驱动基础知识(1)

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)声明如下:
    NTSTATUS
    
(*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 的声明如下:
VOID IoCompleteRequest(
IN PIRP   Irp,
IN CCHAR   PriorityBoost
);
参数 Irp 代表需要被结束的 IRP 。参数 PriorityBoost 代表线程恢复时的优先级别,指的是被阻塞的线程以何种优先级恢复运行。一般情况下,优先级设置为 IO_NO_INCREMENT


 

你可能感兴趣的:(windows,基础,深度,知识,winpcap)