typedef struct _DRIVER_OBJECT { CSHORT Type; CSHORT Size; // // The following links all of the devices created by a single driver // together on a list, and the Flags word provides an extensible flag // location for driver objects. // PDEVICE_OBJECT DeviceObject; ULONG Flags; // // The following section describes where the driver is loaded. The count // field is used to count the number of times the driver has had its // registered reinitialization routine invoked. // PVOID DriverStart; ULONG DriverSize; PVOID DriverSection; PDRIVER_EXTENSION DriverExtension; // // The driver name field is used by the error log thread // determine the name of the driver that an I/O request is/was bound. // UNICODE_STRING DriverName; // // The following section is for registry support. Thise is a pointer // to the path to the hardware information in the registry // PUNICODE_STRING HardwareDatabase; // // The following section contains the optional pointer to an array of // alternate entry points to a driver for "fast I/O" support. Fast I/O // is performed by invoking the driver routine directly with separate // parameters, rather than using the standard IRP call mechanism. Note // that these functions may only be used for synchronous I/O, and when // the file is cached. // PFAST_IO_DISPATCH FastIoDispatch; // // The following section describes the entry points to this particular // driver. Note that the major function dispatch table must be the last // field in the object so that it remains extensible. // PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; } DRIVER_OBJECT;
驱动对象用DRIVER_OBJECT数据结构表示,它做为驱动的一个实例被内核加载,并且内核对一个驱动只加载一个实例,也就是一个驱动最多只有一个驱动对象
更确切的说,是由内核中的I/O管理器负责加载的,由DriverEntry进行初始化
PDEVICE_OBJECT DeviceObject;
每个驱动程序都会有一个或多个设备对象,其中,每个设备对象都有一个指针指向下一个设备对象,最后一个设备对象指向空
UNICODE_STRING DriverName;
记录了驱动程序的名字,一般为\Driver\[驱动程序名称],
PDRIVER_STARTIO DriverStartIo;
记录StartIO的函数地址,用于串行化操作
PDRIVER_UNLOAD DriverUnload;
指定驱动卸载时所用的回调函数地址
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
MajorFunction记录的是一个函数指针数组,函数是处理IRP的派遣函数
PFAST_IO_DISPATCH FastIoDispatch;
文件驱动用到的派遣函数.
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; // See above: DO_... ULONG Characteristics; // See ntioapi: FILE_... __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; // // The following field is for exclusive use by the filesystem to keep // track of the number of Fsp threads currently using the device // ULONG ActiveThreadCount; PSECURITY_DESCRIPTOR SecurityDescriptor; KEVENT DeviceLock; USHORT SectorSize; USHORT Spare1; struct _DEVOBJ_EXTENSION *DeviceObjectExtension; PVOID Reserved; } DEVICE_OBJECT;
struct _DRIVER_OBJECT *DriverObject;
这个是驱动对象的指针,DriverEntry和AddDevice参数中都是同一个驱动对象指针
PDEVICE_OBJECT NextDevice
指向由同一个驱动创建的下个设备对象,驱动要unload时,必须先通过它来遍历设备列表,并删除它们
驱动程序有个入口函数,这个函数通常被命名为DriverEntry,它由系统进程(system)调用,负责对驱动程序的初始化工作,
extern "C" NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath )驱动被加载时,系统进程启动新的线程,调用对象管理器,创建一个驱动对象(DRIVER_OBJECT),另外,系统执行配置管理程序,查询此驱动程序对应的注册表中的项
extern "C" NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath ) { NTSTATUS status = STATUS_SUCCESS; KdPrint(("Enter DriverEntry\n")); KdPrint(("%S\n", pRegistryPath->Buffer)); //注册其他驱动调用函数入口 pDriverObject->DriverUnload = HelloDDKUnload; KdPrint(("DriverEntry end\n")); return status; }
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject) { KdPrint(("Enter DriverUnload\n")); }
以下为LOG:
00000000 0.00000000 Enter DriverEntry 00000001 0.00001047 \REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\HelloDDK 00000002 0.00001570 DriverEntry end 00000003 27.51406479 Enter DriverUnload更仔细的跟进,发现system进程在创建上述驱动对象的同时,在
导出这一项为txt:
值 3 名称: ImagePath 类型: REG_EXPAND_SZ 数据: \??\C:\Documents and Settings\Administrator\桌面\HelloDDK.sys 值 4 名称: DisplayName 类型: REG_SZ 数据: HelloDDK可以看到,注册表信息中包含了sys的全路径,显示的名字等信息
一般来说,DriverEntry函数中,用于设置卸载函数和IRP的派遣函数,另外还有一部分代码负责创建设备对象
在NT式驱动中,创建设备对象是由IoCreateDevice内核函数完成的
其中它的第三个参数DeviceName用于设定设备对象的名字,设备名称用UNICODE字符串指定,并且字符串必须是“\Device\[设备名]”这种形式,如果不以此命名,如:
\\Device1\\MyDDKDevice,则会返回错误:
// The path %hs does not exist. // #define STATUS_OBJECT_PATH_NOT_FOUND ((NTSTATUS)0xC000003AL)
在Windows下所有设备都是以类似名字命名的,如,C盘就是被命名为\Device\HarddiskVolume1,当然也可以不指定设备名字,这时I/O管理器会自动分配一个数字作为设备的设备名:如“/Device/00000001”。
但是如果指定了设备名,也只能被内核模式下的其他驱动所识别,在用户模式下的应用程序仍无法识别这个设备,
怎么让用户模式下的应用程序识别设备呢:
1.通过符号链接找到设备(通用),相当于给设备对象起个别名,别名就可以被用户模式下的应用程序识别了,如C盘,D盘都是符号链接
所谓C盘,指的是名为"C:\"的符号链接,其真正的设备对象名称是\Device\HarddiskVolume1
创建符号链接的函数是
2.通过设备接口找到设备(少用)
在使用IoCreateDevice创建设备对象时,一般指定设备类型为FILE_DEVICE_UNKNOWN,说明这个设备是常用设备以外的设备,一般虚拟设备常用这个类型
(MSDN:If a type of hardware does not match any of the defined types, specify a value of either FILE_DEVICE_UNKNOWN, or a value within the range of 32768 through 65535.)
DriverUnLoad函数一般会在驱动卸载时被调用,一般负责删除在DriverEntry中创建的设备对象,并把设备对象所关联的符号链接删除
pDriverObject->DriverUnload = HelloDDKUnload;
typedef VOID DRIVER_UNLOAD ( __in struct _DRIVER_OBJECT *DriverObject ); typedef DRIVER_UNLOAD *PDRIVER_UNLOAD;
可以通过winobj来查看驱动对象和设备对象.
同样可以通过DeviceTree来查看.
在WDM模型中,完成一个设备的操作,至少需要有两个设备对象共同完成,一个是物理设备对象,简称PDO,另一个是功能设备对象FDO
当PC插入某个设备时,PDO就会自动创建,系统会提示检测到新的设备,如下图:
意思要求安装WDM驱动程序,要安装的WDM驱动程序负责创建FDO,并附加在PDO上,当一个FDO附加到PDO上时,PDO设备对象的子域AttachedDevice会记录FDO的位置,PDO被称为底层驱动,而FDO被称为高层驱动,如下图:
WDM提示用户加载FDO,如果此设备已由微软提供,则会自动进行安装,
WDM的入口同样是DriverEntry,而是被放在AddDevice函数中,同时,需要设置对IRP_MJ_PNP处理的派遣函数
不同点:
1.增加了对AddDevice函数的设置,NT驱动是主动加载设备的,即驱动一旦加载就创建设备,而WDM驱动是被动加载设备的,操作系统必须加载PDO以后,调用驱动的AddDevice函数,AddDevice函数负责创建FDO,并附加到PDO之上
2.设备对象在AddDevice函数中创建
3.必须加入IRP_MJ_PNP的派遣回调函数,主要是负责计算机中的即插即用的处理
和DriverEntry不同,AddDevice函数名字可以任意命名,分以下步骤:
1.通过IoCreateDevice函数,创建FDO设备对象,保存FDO的地址。
2.FDO地址保存在设备扩展中
3.驱动程序把创建的FDO通过IoAttachDeviceToDeviceStack附加到PDO上
PDEVICE_OBJECT IoAttachDeviceToDeviceStack( IN PDEVICE_OBJECT SourceDevice, IN PDEVICE_OBJECT TargetDevice );SourceDevice:要附加在别的设备之上的设备,这里填的是FDO地址
TargetDevice:被附加的设备,这里指的是PDO地址
返回值:返回附加设备的下层设备,如果中间没有过滤驱动,返回值就是PDO,如果有过滤驱动,返回的是过滤驱动
4.在附加操作完成后,需要设定符号链接,以便用户应用程序可以访问该设备
5.设置fdo的Flags子域,定义为“缓冲内存设备”,把DO_DEVICE_INITIALIZING位清零,表示设备初始化完成,这步是必需的
fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE; fdo->Flags &= ~DO_DEVICE_INITIALIZING;IRP一般由两个号码指定该IRP的具体意义,一个主IRP,一个辅IRP
当设备需要被卸载时,如先后发出多个IRP_MJ_PNP,这个IRP的辅IRP号会有所不同,其中对设备的卸载是IRP_MN_REMOVE_DEVICE
驱动设备的创建顺序是,先创建底层PDO,再创建高层的FDO,PDO和FDO之间可能夹杂着各种过滤驱动,每次的设备对象由不同驱动程序所创建,有的驱动程序是系统自带的,有的需要程序员来编写,底层设备对象寻找上层的设备对象,是依靠底层设备对象的AttachedDevice寻找的,如果某一设备的AttachedDevice为空,说明已经到了设备堆栈的顶部,而高层设备找低一层的设备对象,只能能过设备扩展来记录,如下图: