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:之前说到过,设备对象存在附加的情况,附加时每个设备对象会存储它上层的设备对象的指针,这样就形成了类似堆栈的结构,而这个值就表示从该设备对象到栈底还有多少个设备对象

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏jeremy的技术点滴

SSM项目脚手架

5124
来自专栏我是攻城师

在Lucene或Solr中实现高亮的策略

3815
来自专栏MasiMaro 的技术博文

枚举进程中的模块

在Windows中枚举进程中的模块主要是其中加载的dll,在VC上主要有2种方式,一种是解析PE文件中导入表,从导入表中获取它将要静态加载的dll,一种是利用查...

1492
来自专栏Java3y

图书管理系统【部署开发环境、解决分类、图书、前台页面模块】

前言 巩固Servlet+JSP开发模式,做一个比较完整的小项目. 成果图 该项目包含了两个部分,前台和后台。 前台用于显示 ? 后台用于管理 ? 该项目可分为...

6554
来自专栏狂码一生

用MFC写一个聊天室程序 - 学习笔记

下面的服务器端与客户端的程序与步骤是我在学习MFC网络编程写一个聊天室程序所写的程序,在这里作一个笔记,也希望能帮到一部分刚刚学习的朋友,一起共勉,一起努力历进...

1.1K15
来自专栏向治洪

Android 应用安装过程分析

在之前的文章中,我们对PakageManagerService启动流程分析 做了简单的介绍,并对PMS系统的启动流程做了详细的解析。上面只是说到了Android...

7459
来自专栏Android 研究

APK安装流程详解14——PMS中的新安装流程上(拷贝)补充

mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACK...

3711
来自专栏有趣的django

Django REST framework+Vue 打造生鲜超市(四)

五、商品列表页 5.1.django的view实现商品列表页 (1)goods/view_base.py 在goods文件夹下面新建view_base.py,为...

2.2K9
来自专栏Jerry的SAP技术分享

ABAP和XML数据格式互相转换的两种方式

1. ABAP提供了一个工具类cl_proxy_xml_transform,通过它的两个方法abap_to_xml_xstring和xml_xstring_to...

2502
来自专栏lgp20151222

整理代码,将一些曾经用过的功能整合进一个spring-boot

由于本人的码云太多太乱了,于是决定一个一个的整合到一个springboot项目里面。

2373

扫码关注云+社区

领取腾讯云代金券