重要概念
- 每个驱动程序都会有一个或多个设备对象
- 驱动程序中的每个设备对象都有一个指针指向下一个设备对象(也属于当前驱动),最后一个设备对象指向空
- 驱动程序中尽量不要使用全局变量,而是将全局变量定义在设备扩展里
- 设备对象记录通用设备的信息,另外一些特殊信息记录在设备扩展(由程序员设计)里
- 驱动程序中,字符串都使用UNICODE编码(16位为一个字符)
- 可以使用NT_SUCCESS宏来判断NTSTATUS是否为成功
typedef struct _UNICODE_STRING { USHORT Length; //该字符串共占用多少字节(非字符数) USHORT MaximumLength; //Buffer的大小,最大能记录的字符数,大于等于Length PWSTR Buffer; //字符串的指针 } UNICODE_STRING, *PUNICODE_STRING;
- 在Windows下所有设备的命名方式为:
\Device\[设备名]
如C盘:\Device\Harddisk Volume1
,D盘:\Device\Harddisk Volume2
- 用户模式下,想要识别设备,①通过设备的链接符号②通过设备接口
- 设备的符号链接是设备对象的一个别名,如符号链接
C:
,对应于设备名称\Device\Harddisk Volume1
- 在系统中,有很多总线,不同设备的PDO由不同总线驱动所创建,虚拟设备的PDO由
\Driver\PnpManger
所创建(即插即用管理器)
重要数据结构
1. 驱动对象(DRIVER_OBJECT)
驱动对象作为驱动的一个实例,内核对一个驱动只加载一次实例
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;
DeviceObject
:指向当前驱动程序中包含的第一个设备对象
DriverName
:驱动程序的名字,UNICODE,一般为\Driver\[驱动程序名称]
HardwareDatabase
:记录设备的硬件数据库键名,UNICODE,一般为\REGISTAY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM
DriverStarIo
:记录StarIO例程的函数地址,用于串行化操作
DriverUnload
:记录驱动卸载时所用的回调函数地址
MajorFunction
:一个数组,数组中的每个成员是一个指针,指向一个处理IRP的派遣函数
FastIoDispatch
:文件驱动用的派遣函数
2. 设备对象(DEVICE_OBJECT)
typedef struct _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, *PDEVICE_OBJECT;
DriverObject
:指向驱动程序中的驱动对象
NextDevice
:指向下一个设备对象(当前驱动中的)
AttachDevice
:指向更高一层附加在此驱动上的驱动
CurrentIrp
:在使用StarIO例程时,指向的是当前的IRP结构
Flags
:一个32位无符号整型,每一位的含义为
标志 | 描述 |
---|---|
DO_BUFFERED_IO | 读写操作使用缓冲方式(系统复制缓冲区)访问用户模式数据 |
DO_EXCLUSIVE | 一次只允许一个线程打开设备句柄 |
DO_DIRECT_IO | 读写操作使用直接方式(内存描述符表)访问用户模式数据 |
DO_DEVICE_INITIALIZING | 设备对象正在初始化 |
DO_POWER_PAGABLE | 必须在PASSIVE_LEVEL级上处理IRP_MJ_PNP请求 |
DO_POWER_INRUSH | 设备上电期间需要大电流 |
DeviceExtension :指向设备的扩展对象,由程序员自己定义的结构体(由IO管理器创建,保存在非分页内存中)通常包含:①设备对象的反向指针②设备状态或驱动环境信息③中断对象指针④控制器对象指针 | |
DeviceType :设备的类型,常用类型为(虚拟设备选择FILE_DEVICE_UNKNOW): |
设备类型 | 描述 |
---|---|
#define FILE_DEVICE_BEEP | 蜂鸣器设备 |
#define FILE_DEVICE_CD_ROM | CD光驱设备 |
#define FILE_DEVICE_CD_ROM_FILE_SYSTEM | 光驱文件系统设备 |
#define FILE_DEVICE_CONTROLLER | 控制器设备 |
#define FILE_DEVICE_DATALINK | 数据链设备 |
#define FILE_DEVICE_DFS | DFS设备 |
#define FILE_DEVICE_DISK | 磁盘设备 |
#define FILE_DEVICE_DISK_FILE_SYSTEM | 磁盘文件系统设备 |
#define FILE_DEVICE_FILE_SYSTEM | 文件系统设备 |
#define FILE_DEVICE_INPORT_PORT | 输入端口设备 |
#define FILE_DEVICE_KEYBOARD | 键盘设备 |
#define FILE_DEVICE_MAILSLOT | 邮槽设备 |
#define FILE_DEVICE_MIDI_IN | MIDI输入设备 |
#define FILE_DEVICE_MIDI_OUT | MIDI输出设备 |
#define FILE_DEVICE_MOUSE | 鼠标设备 |
#define FILE_DEVICE_MULTI_UNC_PROVIDER | 多UNC设备 |
#define FILE_DEVICE_NAMED_PIPE | 命名管道设备 |
#define FILE_DEVICE_NETWORK | 网络设备 |
#define FILE_DEVICE_NETWORK_BROWSER | 网络浏览器设备 |
#define FILE_DEVICE_NETWORK_FILE_SYSTEM | 网络文件系统设备 |
#define FILE_DEVICE_NULL | 空设备 |
#define FILE_DEVICE_PARALLEL_PORT | 并口设备 |
#define FILE_DEVICE_PHYSICAL_NETCARD | 物理网卡设备 |
#define FILE_DEVICE_PRINTER | 打印机设备 |
#define FILE_DEVICE_SCANNER | 扫描仪设备 |
#define FILE_DEVICE_SERIAL_MOUSE_PORT | 串口鼠标设备 |
#define FILE_DEVICE_SERIAL_PORT | 串口设备 |
#define FILE_DEVICE_SCREEN | 屏幕设备 |
#define FILE_DEVICE_SOUND | 声音设备 |
#define FILE_DEVICE_STREAMS | 流设备 |
#define FILE_DEVICE_TAPE | 磁带设备 |
#define FILE_DEVICE_TAPE_FILE_SYSTEM | 磁带文件系统设备 |
#define FILE_DEVICE_TRANSPORT | 传输设备 |
#define FILE_DEVICE_UNKNOWN | 未知设备 |
#define FILE_DEVICE_VIDEO | 视频设备 |
#define FILE_DEVICE_VIRTUAL_DISK | 虚拟磁盘设备 |
#define FILE_DEVICE_WAVE_IN | 声音输入设备 |
#define FILE_DEVICE_WAVE_OUT | 声音输出设备 |
#define FILE_DEVICE_8042_PORT | 8402端口设备 |
#define FILE_DEVICE_NETWORK_REDIRECTOR | 网卡设备 |
#define FILE_DEVICE_BATTERY | 电池设备 |
#define FILE_DEVICE_BUS_EXTENDER | 总线扩展设备 |
#define FILE_DEVICE_MODEM | 调制解调器设备 |
#define FILE_DEVICE_VDM | VDM设备 |
#define FILE_DEVICE_MASS_STORAGE | 大容量存储设备 |
#define FILE_DEVICE_SMB | SMB设备 |
#define FILE_DEVICE_KS | 内核流设备 |
#define FILE_DEVICE_CHANGER | 充电设备 |
#define FILE_DEVICE_SMARTCARD | 智能卡设备 |
#define FILE_DEVICE_ACPI | ACPI设备 |
#define FILE_DEVICE_DVD | DVD设备 |
#define FILE_DEVICE_FULLSCREEN_VIDEO | 全屏视频设备 |
#define FILE_DEVICE_DFS_FILE_SYSTEM | DFS文件系统设备 |
#define FILE_DEVICE_DFS_VOLUME | DFS卷设备 |
StackSize :多层驱动情况下的层数 | |
AlignmentRequirement :设备大容量传输时,需要内存对齐,保证传输速度 |
1. NT式驱动的基本结构
NT驱动主要包括:DriverEntry例程,卸载例程,各IRP派遣例程
NT驱动的DriverEntry介绍
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
);
DriverEntry由系统进程调用(任务管理器中显示的System进程)
- 驱动加载时,系统进程启动新线程,调用执行体组件中的对象管理器,创建一个驱动对象(一个DRIVER_OBJECT结构体)
- 另外系统进程调用
执行体组件
中的配置管理程序
,查询此驱动程序对应注册表中的项 - 新创建的系统线程调用驱动程序的DriverEntry例程,传入两个参数,pDriverObject(指向刚才创建的驱动对象)和pRegistryPath(指向设备服务键的键名字符串,一般为
\REGISTRY\MACHINE\SYSTEM\ControlSet\Services\[服务名]
) - DriverEntry中,主要执行对传递进来的pDriverObject参数进行初始化,保存pReistryPath(因为这是一个临时的数据,可能DriverEntry结束就会被销毁掉)
- DriverEntry返回值为NTSTATUS类型(一个32位无符号长整型),其中0~0x7FFFFFFF约定为正确的状态,0x80000000~0xFFFFFFFF约定为错误的状态
- DriverEntry如果返回成功意味着驱动加载成功,否则,驱动加载失败,调用对象管程序销毁驱动对象
创建设备对象(IoCreateDevice)
NTSTATUS IoCreateDevice(
[in] PDRIVER_OBJECT DriverObject,
[in] ULONG DeviceExtensionSize,
[in, optional] PUNICODE_STRING DeviceName,
[in] DEVICE_TYPE DeviceType,
[in] ULONG DeviceCharacteristics,
[in] BOOLEAN Exclusive,
[out] PDEVICE_OBJECT *DeviceObject
);
DriverObject
:指向驱动对象的指针(一个驱动文件对应一个驱动对象)
DriverExtensionSize
:指定设备扩展的大小,IO管理器根据这个大小,创建设备扩展与驱动关联
DeviceName
:要创建的设备对象的名称,字符串必须是\Device\[设备名]
的形式,如果不指定名称,IO管理器将自动分配一个数字作为设备名称,如\Device\00000001
DeviceCharacteristics
:设备对象的特征
Exclusive
:设置设备对象是否在内核模式下使用,一般为TRUE
DeviceObject
:输出参数,IO管理器创建这个设备,返回设备的地址
创建设备的符号链接(IoCreateSymbolicLink)
NTSTATUS IoCreateSymbolicLink(
[in] PUNICODE_STRING SymbolicLinkName,
[in] PUNICODE_STRING DeviceName
);
SymbolicLinkName
:要创建的符号链接的字符串
DeviceName
:设备对象名的字符串
在内核模式下,符号链接以\??\
或者\DosDevices\
开头,如C盘在内核模式下符号链接为\??\C:
或者\DosDevices\C:
在用户模式下,符号链接以\\.\
开头,如C盘在用户模式下符号链接为\\.\C:
驱动的卸载(DriverUnload)
驱动的卸载例程一般包括删除在DriverEntry中创建的设备对象,并将与之关联的符号链接删除,回收一些资源
void IoDeleteDevice(
[in] PDEVICE_OBJECT DeviceObject
);
NTSTATUS IoDeleteSymbolicLink(
[in] PUNICODE_STRING SymbolicLinkName
);
DeviceObject
:要删除的设备对象指针
SymbolicLinkName
:要删除的符号链接
2. WDM驱动的基本机构
WDM模型是建立在NT模型之上的
WDM模型中完成一个设备的操作至少需要两个驱动:
1. 物理设备对象(PDO,上层驱动/高层驱动)
2. 功能设备对象(FDO,下层驱动/底层驱动)
当PC插入某个设备时,PDO会由总线驱动自动创建,PDO需要配合FDO一起使用
当PC插入某个设备时,系统提示检测到新设备,并要求安装驱动程序(WDM驱动,负责创建FDO并附加到PDO之上)
当一个FDO附加在PDO之上时,PDO设备对象的子域AttachedDevice会记录FDO的位置
FDO上层的过滤驱动称作:上层过滤驱动
FDO下层的过滤驱动乘坐:下层过滤驱动
过滤驱动可以嵌套,不是必须存在的,但PDO和FDO是必须的
每个设备对象中的StackSize子域,表面该设备对象需要几层才能到达最下面的物理设备
NT设备插入PC时,系统不会有提示,需要用户自行安装驱动
WDM设备插入PC时,系统自动创建PDO,请求用户安装FDO(如果已经有了FDO则会自动安装,如鼠标键盘等)
WDM驱动的DriverEntry介绍
函数原型同NT一样
WDM和NT驱动的DriverEntry
不同点:
- WDM模型增加了AddDevice函数的设备(最主要不同点),NT模型是主动加载设备驱动的,所以直接在DriverEntry中创建了设备;而WDM模型由于是被动的,不会直接创建设备,而是等到设备插入PC,总线驱动创建PDO后,才会调用WDM驱动的AddDevice函数创建设备FDO,并附加在PDO上
- WDM模型必须加入
IRP_MJ_PNP
的派遣回调函数,负责计算机中的即插即用处理
WDM驱动的AddDevice例程
AddDevice由WDM模型独有,NT模型没有
设置方法:驱动对象中的DriverExtension
子域中的AddDevice
子域指向回调函数地址
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
函数原型:
NTSTATUS HelloWDMAddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
DriverObject
:IO管理器创建的驱动对象
PhysicalDeviceObject
:底层总线驱动创建的PDO设备对象
AddDevice中需要执行的步骤:
① 通过IoCreateDevice
等函数创建设备对象(即FDO),地址保存在设备扩展中
③ 通过IoAttachDeviceToDeviceStack
将创建的FDO附加在PDO上(虽然指定了附加在PDO上,但是如果有过滤驱动的存在,则实际上附加在了过滤驱动之上,返回值为实际附加的下层设备,可能为PDO也可能为过滤驱动)
一个FDO驱动的上下层:上层在AttachDevice域中,下层在调用
IoAttachDeviceToDeviceStack
后将返回值保存在设备扩展中进行记录
④ 设置刚才创建出来的FDO的Flags子域,指明该设备的属性,其中fdo->Flags &= ~DO_DEVICE_INITIALIZING
为必须的,意味着告诉外面设备初始化完毕
WDM驱动的DriverUnload例程
NT模型中DriverUnload负责删除设备和符号链接
WDM模型中删除设备和符号链接的工作交由IRP_MN_REMOVE_DEVICE
的IRP处理函数负责
WDM模型中的DriverUnload相对简单,主要是销毁程序员自己申请的内存等资源
WDM驱动IRP_MN_REMOVE_DEVICE的IRP处理
IRP_MN_REMOVE_DEVICE
的作用:当设备需要被卸载时,由即插即用管理器创建并发送到驱动程序中
对应的派遣函数在DriverEntry中指定
主要步骤:
- 作为一个IRP处理函数,先处理IRP本身(转发给下层驱动)
- 删除符号链接
- 将FDO设备从PDO的设备栈上摘下来(调用IoDetachDevice)
自此FDO从设备栈上被删除,但PDO仍然存在,删除PDO由操作系统负责,与程序员无关
驱动的层次结构
垂直层次结构
设备创建顺序为从下往上创建,先创建底层PDO,再创建高层的FDO
设备向上查找:通过设备对象的AttachedDevice
,如果某一设备为空,则到了顶部
设备向下查找:调用IoAttachDeviceToDeviceStack
后,将返回值保存在设备扩展中
水平层次结构
水平层次结构存在于同一驱动创建出来的设备对象之间的关系
例如:电脑中插入两块相同型号网卡,这两块网卡的PDO就是同一水平层次,都是由PCI总线驱动所创建,它们的FDO也在同一水平层次
复杂层次结构
举例:最初windows只有一根总线,根总线驱动对插在总线上的设备进行枚举,枚举到一个USB_HUB设备,安装这个USB_HUB设备的PDO和FDO;然后USB_HUB设备的FDO又可以作为一个总线,继续枚举插在USB_HUB设备上的设备,分别安装它们的驱动
总结:FDO也可以作为一个总线驱动,继续枚举该总线上的设备(如USB_HUB)