windows 驱动开发入门——驱动中的数据结构
最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书——《独钓寒江 windows安全编程》 和 《windows驱动开发技术详解》。
驱动开发过程中,主要使用的C语言,虽说C中定义了许多数据类型,但是一般来说在编码上还是习惯与使用WDK的规范,虽说这个不是必须的,比如有这样一句
unsigned long ul = 0;
这个数据的大小根据不同的机器不同的编译器环境略有不同,这样代码就产生了不可控的行为,但是WDK上专门定义了相关的宏,环境不同,只需要修改一下宏定义,这样就避免了这个问题。
在这列举一些常用的数据类型,以免以后在编写代码或者查看例子代码时犯迷糊:
普通数据类型
#define ULONG unsigned long
#define UCHAR unsigned char
#define UINT unsigned int
#define VOID void
#define PULONG unsigned *
#define PUCHAR unsigned char*
#define PUINT unsigned int*
#define PVOID void*
字符串类型
在驱动的编程中,为字符串操作专门定义了一个数据类型UNICODE_STRING ANSI_STRING,他们的定义大致相同,只是一个是表示UNICODE字符串,一个表示ANSI字符串,下面主要来说明一下UNICODE_STRING
typedef struct _UNICODE_STRING {USHORT Length; // 字符串的中字符所占的内存大小USHORT MaximumLength;//用来存储字符串缓冲的大小PWCHAR Buffer;//缓冲的地址
} UNICODE_STRING;
这个结构体在使用是需要注意的是上述两个大小单位是字节数而不是字符个数,另外在操作UNICODE_STRING 的时候只是简单的操作Buffer指向的内存,并不会特意的为其分配另外的空间,字符串处理函数主要有这样几个:
RtlInitUnicodeString(&uStr1, str1);
RtlCopyUnicodeString(&uStr1, &uStr2);
RtlAppendUnicodeToString(&uStr1, str1);
RtlAppendUnicodeStringToString(&uStr1, &uStr2);
RtlCompareUnicodeString(&uStr1, &uStr2, TRUE/FALSE);
RtlAnsiStringToUnicodeString(&uStr1, &aStr1, TRUE/FALSE);
RtlFreeUnicodeString(&uStr1);
这些函数从字面上就可以知道它们是干什么用的,需要注意的是,除了Init,这些函数只是简单的操作Buffer已指向的内存,并不会改变指针的指向。所以在使用时要特别注意不要试图改变静态常量区的内容,也要特别注意指向的内存是在栈中还是在堆中。下面是一个简单的例子:
UNICODE_STRING uStr1 = { 0 };UNICODE_STRING uStr2 = { 0 };UNICODE_STRING uStr3 = { 0 };ANSI_STRING aStr = { 0 };RtlInitUnicodeString(&uStr1, L"Hello");RtlInitUnicodeString(&uStr2, L"Goodbye");//打印字符串结构用%Z表示%wZ表示是宽字符DbgPrint("uStr1 = %wZ\n", &uStr1);DbgPrint("uStr2 = %wZ\n", &uStr2);RtlInitAnsiString(&aStr, "Hello World");DbgPrint("aStr = %Z\n", &aStr);/*这个操作是由于uStr3中的Buffer指向NULL,所以会失败*/RtlCopyUnicodeString(&uStr3, &uStr1);DbgPrint("uStr3 = %wZ\n", &uStr3); //失败/*下面两个失败是由于Str1 Str2 指向的是字符串常量区,不可修改*/RtlAppendUnicodeToString(&uStr1, &uStr2);DbgPrint("uStr1 = %wZ\n", &uStr1); //失败RtlAppendUnicodeStringToString(&uStr1, L"World");DbgPrint("uStr1 = %wZ\n", &uStr1); //失败
LARGE_INTEGER
这个结构就像它的名字一样,用来表示一个比较大的整数,它的定义如下:
typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; struct { DWORD LowPart; LONG HighPart; } u; LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;
这是一个公用体,可以认为它是由两部分组成高32位的HighPart和低32位的LowPart,它分了高位优先和低位优先两种情况,也可以认为它是一个64位的整形。在使用时根据需求来决定
NTSTATUS
绝大多数驱动函数都返回这个值,用来表示当前处理的状态,一般STATUS_SUCCESS表示成功,其余的都表示失败。微软根据不同情况定义了它的状态值,一般常用的有下面几个
值 | 含义 |
---|---|
STATUS_SUCCESS | 函数执行成功 |
STATUS_UNSUCCESSFUL | 函数执行不成功 |
STATUS_NOT_IMPLEMENTED | 函数违背实现 |
STATUS_INVALID_INFO_CLASS | 输入参数是无效的类别 |
STATUS_ACCESS_VIOLATION | 不允许访问 |
STATUS_IN_PAGE_ERROR | 发生页面故障 |
STATUS_INVALID_HANDLE | 输入的是无效的句柄 |
STATUS_INVALID_PARAMETER | 输入的是无效的参数 |
STATUS_NO_SUCH_DEVICE | 指定的设备不存在 |
STATUS_NO_SUCH_FILE | 指定的文件不存在 |
STATUS_INVALID_DEVICE_REQUEST | 无效的设备请求 |
STATUS_END_OF_FILE | 文件已到结尾 |
STATUS_INVALID_SYSTEM_SERVICE | 无效的系统调用 |
STATUS_ACCESS_DENIED | 访问被拒绝 |
STATUS_BUFFER_TOO_SMALL | 输入的缓冲区过小 |
STATUS_OBJECT_TYPE_MISMATCH | 输入的对象类型不匹配 |
STATUS_OBJECT_NAME_INVALIE | 输入的对象名无效 |
STATUS_OBJECT_NAME_NOT_FOUND | 输入的对象没有找到 |
STATUS_PORT_DISCONNNECTED | 需要连接的端口没有被连接 |
STATUS_OBJECT_PATH_INVALID | 输入的对象路径无效 |
另外在使用WinDbg进行调试的时候,一般都会得到函数调用的错误码,根据错误码可以找到对应的错误信息,微软提供了一种解决方案:
LPVOID lpMessageBuffer;
HMODULE Hand = LoadLibrary(_T("NTDLL.DLL"));
DWORD dwErrCode = 0;
//获取错误码FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE,Hand, dwErrCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR) &lpMessageBuffer, 0, NULL );// Now display the string.
// Free the buffer allocated by the system.
LocalFree( lpMessageBuffer );
FreeLibrary(Hand);
驱动对象
驱动程序的入口函数是DriverEntry,函数会传入一个驱动对象的指针——PDRIVER_OBJECT,每个驱动都有一个唯一的驱动对象,就好像每个Win32应用程序有一个唯一的实例句柄。它的定义如下:
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;
下面主要对几个重要的部分做介绍:
1. DeviceObject:保存的是驱动中设备对象的指针,另外每个设备对象又有一个指向下一个设备对象的指针,这样同一个驱动程序中的不同设备对象就构成了一个链表
2. DriverName:这个里面存储的是驱动程序的名称,该字符串一般为“\Driver\驱动名称”
3. HardwareDatabase:这里记录的是设备的硬件数据库键名,这个数据库一般是注册表,字符串一般为“REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM”
4. DriverStartIo:记录StartIo这个例程回调函数的地址
5. DriverUnload:当驱动卸载时会调用这个指针所指向的函数
6. MajorFunction,这是一个回调函数的指针数组,处理IRP包的不同请求,就好像应用层里面的消息处理函数,根据不同的请求,调用不同的函数。
设备对象
在windows平台将每个设备抽象为一个设备对象,驱动层一般通过设备对象来操作具体的设备,每个驱动可以有多个设备对象。
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {...struct _DRIVER_OBJECT *DriverObject;struct _DEVICE_OBJECT *NextDevice;struct _DEVICE_OBJECT *AttachedDevice;struct _IRP *CurrentIrp;ULONG Flags;PVOID DeviceExtension;DEVICE_TYPE DeviceType;CCHAR StackSize;...
} DEVICE_OBJECT;
设备对象本身定义是十分复杂的,在这我们只列举出部分,以后写程序会经常使用的部分,下面是对这些部分的说明:
1. DriverObject: 指向所属驱动的驱动对象的指针
2. NextDevice:指向下一个设备驱动的指针
3. AttachedDevice:指向它被附加的驱动的指针,设备对象之上还可以在附加上其他的设备对象,这样每当有消息传来时总会由附加在它之上的设备对象处理,然后才会交由它自身处理,这个指针就是指向附加在它之上的设备对象的指针
4. CurrentIrp:指向当前IRP域的指针
5. Flags:表名该设备的一些标志信息,主要有下面几个值:
标志 | 描述 |
---|---|
DO_BUFFERED_IO | 读写使用缓冲方式,内核层在使用用户缓冲区时会将用户分区中的数据拷贝到内核分区中 |
DO_EXCLUSIVE | 一次只允许一个线程使用这个设备对象 |
DO_DIRECT_IO | 读写直接方式,应用层将某块内存锁定在内存,然后将内存映射到内核空间中,这种方式是最快的方式 |
DO_DEVICE_INITIALIZING | 设备正在初始化 |
DO_POWER_PAGABLE | 设备必须在PASSIVE_LEVEL上处理IRP_MJ_PNP请求 |
DO_POWER_INRUSH | 设备上电期间需要大电流 |
6. DeviceExtension:指向一块扩展的内存,系统允许用户在创建设备对象时自定义一块区域用来保存结构体中没有但是用户自己感兴趣的内容。在驱动程序中需要尽量避免使用全局变量,所以可以通过使用这块扩展内存来传输全局变量
7. DeviceType:驱动的类型,主要有下面几个值
设备类型 | 描述 |
---|---|
FILE_DEVICE_BEEP | 该设备是一个蜂鸣器 |
FILE_DEVICE_CD_ROM | 该设备时一个CD光驱 |
FILE_DEVICE_CD_ROM_FILE_SYSTEM | CD光驱文件系统设备 |
FILE_DEVICE_CONTROLLER | 控制器设备 |
FILE_DEVICE_DATALINK | 数据链设备 |
FILE_DEVICE_DFS | DFS设备对象 |
FILE_DEVICE_DISK | 磁盘设备对象 |
FILE_DEVICE_DISK_FILE_SYSTEM | 磁盘文件系统设备对象 |
FILE_DEVICE_FILE_SYSTEM | 文件系统设备对象 |
FILE_DEVICE_INPORT_PORT | 输入端口设备对象 |
FILE_DEVICE_KEYBOARD | 键盘设备对象 |
FILE_DEVICE_MAILSLOT | 邮件曹设备对象 |
FILE_DEVICE_MIDI_IN | MIDI输入设备对象 |
FILE_DEVICE_MIDI_OUT | MIDI输出设备对象 |
FILE_DEVICE_MOUSE | 鼠标设备对象 |
FILE_DEVICE_MULTI_UNC_PROVIDER | 多UNC设备对象 |
FILE_DEVICE_NAMED_PIPE | 命名管道设备对象 |
FILE_DEVICE_NETWORK | 网络设备对象 |
FILE_DEVICE_NETWORK_BROWSER | 网络浏览器设备对象 |
FILE_DEVICE_NETWORK_FILE_SYSTEM | 网络文件系统设备对象 |
FILE_DEVICE_NULL | 空设备对象 |
FILE_DEVICE_PARALLEL_PORT | 并口设备对象 |
FILE_DEVICE_PHYSICAL_NETCARD | 物理网卡设备对象 |
FILE_DEVICE_PRINTER | 打印机设备对象 |
FILE_DEVICE_SCANNER | 扫描仪设备对象 |
FILE_DEVICE_SERIAL_MOUSE_PORT | 串口鼠标设备对象 |
LE_DEVICE_SERIAL_PORT | 串口设备对象 |
FILE_DEVICE_SCREEN | 屏幕设备对象 |
FILE_DEVICE_SOUND | 声音设备对象 |
FILE_DEVICE_STREAMS | 流设备对象 |
LE_DEVICE_TAPE | 磁带设备对象 |
FILE_DEVICE_TAPE_FILE_SYSTEM | 磁带文件系统设备对象 |
FILE_DEVICE_TRANSPORT | 传输设备对象 |
FILE_DEVICE_UNKNOWN | 未知设备对象 |
FILE_DEVICE_VIDEO | 视频设备对象 |
FILE_DEVICE_VIRTUAL_DISK | 虚拟磁盘设备对象 |
FILE_DEVICE_WAVE_IN | 声音输入设备对象 |
FILE_DEVICE_WAVE_OUT | 声音输出设备对象 |
在创建设备对象时如果不知道这个设备对象是何种类型,可以直接给FILE_DEVICE_UNKNOWN;
8. StackSize:之前说到过,设备对象存在附加的情况,附加时每个设备对象会存储它上层的设备对象的指针,这样就形成了类似堆栈的结构,而这个值就表示从该设备对象到栈底还有多少个设备对象
为了便于理解我们做了这样一个示意图:
windows 驱动开发入门——驱动中的数据结构相关推荐
- 竹林蹊径:深入浅出Windows驱动开发(china-pub预订中)
竹林蹊径:深入浅出Windows驱动开发(china-pub预订中) 基本信息 作者: 张佩 马勇 董鉴源 出版社:电子工业出版社 ISBN:9787121125553 内容简介 本书是作者根 ...
- 驱动开发:内核中实现Dump进程转储
多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导 ...
- Kinect for Windows SDK开发入门
Kinect for Windows SDK开发入门(一):开发环境配置 首先来看一下Kinect设备: 1. Kinect设备 黑色的Kinect设备如下图:基座和感应器之间有一个电动的马达,通过程 ...
- STM32MP157驱动开发——SPI驱动
STM32MP157驱动开发--SPI驱动 一.简介 1.SPI介绍 2.STM32MP1 SPI介绍 3. ICM-20608 简介 4.Linux下的SPI框架 二.驱动开发 1)IO 的 pin ...
- Linux驱动开发 -- touch驱动注册
Linux i2c驱动开发 – touch 驱动 文章目录 Linux i2c驱动开发 -- touch 驱动 前言 一.i2c 驱动框架 二.Linux的MODULE声明 1. MODULE相关声明 ...
- Linux驱动开发——串口设备驱动
Linux驱动开发--串口设备驱动 一.串口简介 串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单.使用两条线即可实现双向通信,一条用于发送,一条用于 ...
- Linux嵌入式驱动开发02——驱动编译到内核
文章目录 全系列传送门 make menuconfig图形化配置界面 1. 怎么进入到make menuconfig图形化界面? 2. make menuconfig图形化界面的操作 3. 退出 4. ...
- Windows驱动开发入门系列教程
从事驱动开发也有一段时间了,从最初的无头苍蝇到懵懵懂懂,到入门,直至今天,感觉一路走来,走了不少的弯路,只因为没有人引导.前几天,一个朋友问到我怎么学习Windows驱动开发,我就想到把我学习Wind ...
- Windows驱动开发入门 -- HelloWorld
一.驱动开发环境搭建 在Download the Windows Driver Kit (WDK)页面中下载最新版本的Visual Studio和WDK进行安装.如果要下载老版本可以到Other WD ...
最新文章
- 普度网络营销策划_普度网络营销策划-齐宁_新浪博客
- 程序员数学基础【七、等比数列 棋盘麦粒】
- 广州某IT公司HR招开发:“不加班的都是垃圾 ”、“考不上本科是智商有问题”,网友:听了想打人...
- python-socket作业
- 微信小程序布局技巧(一)
- 《计算机网络:自顶向下的方法》课后习题_第二章
- html 调高德地图 导航,在H5页面内通过地址调起高德地图实现导航
- Java毕设项目-社区居民健康档案管理系统
- 盘点Windows10系统的使用小技巧二 —— 磁贴
- uni-app个人中心页开发
- 血污夜之仪式秘密巫师实验室收集策略
- 使用sws_scale转换视频、使用swr_convert转换音频
- gitlab本地创建空文件,之后关联仓库提交文件,提交成功,但是gitlab网页中不会显示提交的文件
- 安装Go,配置Go环境(实力亲测)
- 《CLR via C#》读书笔记---11事件
- 身体质量指数BMI(Python123练习)
- 知乎美女挖掘指南--Python实现自动化图片抓取、颜值评分
- feof函数的滞后性
- linux时间8个小时差,Linux下Chrome浏览器时间不对,时区不对
- Ubuntu18.04鲁班猫ROS-melodic适配镭神智能C16多线激光雷达
热门文章
- Android 9.0 以上http链接提示无网络和加载失败
- 在人口老龄化和慢性病刺激下需求不断增加导尿管市场
- M8147:Account determination for entry T001 GBB ____ ZOB 3000 not possible
- 【定时同步系列8】QPSK基带调制+Gardener定时误差检测+解调误码率曲线之MATLAB并行仿真姊妹篇一
- 树莓派2 安装linux系统安装教程,树莓派raspi2-ubuntu meta安装配置指导
- Java和php访问数据库对比_在Web开发方面Java和PHP的比较
- 世界上最经典的二十五句话
- STM32F407 - 随机数发生器
- java调用函数_Java中如何调用函数和自定义函数
- 号外:数据中心跳大海事件