首页 > 技术文章 > 驱动学习4

hgy413 2014-01-01 21:31 原文

1.驱动对象.

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时,必须先通过它来遍历设备列表,并删除它们


4.2.1

驱动程序有个入口函数,这个函数通常被命名为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进程在创建上述驱动对象的同事,在
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\HelloDDK(也就是pRegistryPath指向)创建了注册表子项HellDDK(原来在注册表中是没有HelloDDK的),

导出这一项为txt:

值  3
  名称:            ImagePath
  类型:            REG_EXPAND_SZ
  数据:            \??\C:\Documents and Settings\Administrator\桌面\HelloDDK.sys

值  4
  名称:            DisplayName
  类型:            REG_SZ
  数据:            HelloDDK
可以看到,注册表信息中包含了sys的全路径,显示的名字等信息

一般来说,DriverEntry函数中,用于设置卸载函数和IRP的派遣函数,另外还有一部分代码负责创建设备对象


4.22

在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

创建符号链接的函数是

IoCreateSymbolicLink(别名,设备对象名)

在内核下,符号链接是以\??\开头(或\DosDevices\),如C盘是\??\C:,而在用户模式下,则是以\\.\开头的,如C盘就是\\.\C:
驱动肯定是内核,所以符号链接必须以\??\开头,不然这个函数会调用失败,drivermonitor会返回123错误:(语法不正确)

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.)


4.2.3

DriverUnLoad函数一般会在驱动卸载时被调用,一般负责删除在DriverEntry中创建的设备对象,并把设备对象所关联的符号链接删除

pDriverObject->DriverUnload = HelloDDKUnload;
typedef
VOID
DRIVER_UNLOAD (
    __in struct _DRIVER_OBJECT *DriverObject
    );

typedef DRIVER_UNLOAD *PDRIVER_UNLOAD;



4.2.4

可以通过winobj来查看驱动对象和设备对象.


4.2.5

同样可以通过DeviceTree来查看.


4.3 WDM式驱动的基本结构

4.3.1

在WDM模型中,完成一个设备的操作,至少需要有两个设备对象共同完成,一个是物理设备对象,简称PDO,另一个是功能设备对象FDO

当PC插入某个设备时,PDO就会自动创建,系统会提示检测到新的设备,如下图:


意思要求安装WDM驱动程序,要安装的WDM驱动程序负责创建FDO,并附加在PDO上,当一个FDO附加到PDO上时,PDO设备对象的子域AttachedDevice会记录FDO的位置,PDO被称为底层驱动,而FDO被称为高层驱动,如下图:


WDM提示用户加载FDO,如果此设备已由微软提供,则会自动进行安装,


4.3.2

WDM的入口同样是DriverEntry,而是被放在AddDevice函数中,同时,需要设置对IRP_MJ_PNP处理的派遣函数


不同点:

1.增加了对AddDevice函数的设置,NT驱动是主动加载设备的,即驱动一旦加载就创建设备,而WDM驱动是被动加载设备的,操作系统必须加载PDO以后,调用驱动的AddDevice函数,AddDevice函数负责创建FDO,并附加到PDO之上

2.设备对象在AddDevice函数中创建

3.必须加入IRP_MJ_PNP的派遣回调函数,主要是负责计算机中的即插即用的处理


4.3.3

和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


4.4.1

驱动设备的创建顺序是,先创建底层PDO,再创建高层的FDO,PDO和FDO之间可能夹杂着各种过滤驱动,每次的设备对象由不同驱动程序所创建,有的驱动程序是系统自带的,有的需要程序员来编写,底层设备对象寻找上层的设备对象,是依靠底层设备对象的AttachedDevice寻找的,如果某一设备的AttachedDevice为空,说明已经到了设备堆栈的顶部,而高层设备找低一层的设备对象,只能能过设备扩展来记录,如下图:



4.4.2












 

 

 

 


 

推荐阅读