学习笔记windows驱动开发技术详解.docx
《学习笔记windows驱动开发技术详解.docx》由会员分享,可在线阅读,更多相关《学习笔记windows驱动开发技术详解.docx(12页珍藏版)》请在冰豆网上搜索。
学习笔记windows驱动开发技术详解
<学习笔记>Windows驱动开发技术详解
派遣函数是Windows驱动程序中的重要概念。
驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的。
用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IRP数据结构,不同的IRP会被“派遣”到不同的派遣函数中。
IRP与派遣函数
IRP的处理机制类似于Windows应用程序中的“消息处理”,驱动程序接收到不同的IRP后,会进入不同的派遣函数,在派遣函数中IRP得到处理。
1.IRP
在Windows内核中,有一种数据结构叫做IRP(I/ORequestPackage),即输入输出请求包。
上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求。
操作系统将I/O请求转化为相应的IRP数据,不同类型的IRP会被传递到不同的派遣函数中。
IRP有两个基本的重要属性,一个是MajorFunction,另一个MinorFunction,分别记录IRP的主类型和子类型,操作系统根据MajorFunction将IRP“派遣”到不同的派遣函数中,在派遣函数中还可以继续判断这个IRP属于哪种MinorFunction。
下面是HelloDDK的DriverEntry中关于派遣函数的注册:
viewplaincopytoclipboardprint?
#pragmaINITCODE
extern"C"NTSTATUSDriverEntry(
INPDRIVER_OBJECTpDriverObject,
INPUNICODE_STRINGpRegisterPath
)
{
NTSTATUSstatus;
KdPrint(("EnterDriverEntry\n"));
//设置卸载函数
pDriverObject->DriverUnload=HelloDDKUnload;
//设置派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN]=HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]=HelloDDKDispatchRoutine;
//创建驱动设备对象
status=CreateDevice(pDriverObject);
KdPrint(("LeaveDriverEntry\n"));
returnstatus;
}2.IRP的类型
文件I/O的相关函数,如CreateFile,ReadFile,WriteFile,CloseHandle等函数会使操作系统产生出IRP_MJ_CREATE,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_CLOSE等不同的IRP。
另外,内核中的文件I/O处理函数,如ZwCreateFile,ZwReadFile,ZwWriteFile,ZwClose,他们同样会产以上IRP。
一下列出了IRP的类型,并对其产生的来源做了说明IRP类型来源
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_CREATE创建设备,CreateFile会产生此IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_CLOSE关闭设备,CloseHandle会产生此IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_CLEANUP清除工作,CloseHandle会产生此IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_DEVICE_CONTROLDeviceControl函数会产生此IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_PNP即插即用消息,NT驱动不支持次IRP,WDM驱动才支持次IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_POWER在操作系统处理电源消息时,产生次IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_QUERY_INFORMATION获取文件长度,GetFileSize会产生IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_READ读取设备内容,ReadFile会产生此IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_SET_INFORMATION设置文件长度,GetFileSize会产生IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_SHUTDOWN关闭系统前会产生此IRP
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_SYSTEM_CONTROL系统内部产生的控制信息,类似于内核调用DeviceControl函数
-----------------------------------------------------------------------------------------------------------------------------------------------
IRP_MJ_WRITE对设备进行WriteFile时会产生此IRP
-----------------------------------------------------------------------------------------------------------------------------------------------3.对派遣函数的简单处理
大部分的IRP都源于文件I/O处理Win32API,处理这些IRP最简单的方法就是在相应的派遣函数中,将IRP状态设置为成功,然后结束IRP的请求,并让派遣函数成功返回。
结束IRP的请求使用函数IoCompleteRequest.。
下面代码演示了一种最简单的处理IRP请求的派遣函数。
viewplaincopytoclipboardprint?
NTSTATUSHelloDDKDispatchRoutine(INPDEVICE_OBJECTpDevObj,INPIRPpIrp)
{
KdPrint(("EnterHelloDDKDispatchRoutine\n"));
//对一般IRP的简单操作
NTSTATUSstatus=STATUS_SUCCESS;
//设置IRP完成状态
pIrp->IoStatus=status;
//设置IRP操作了多少字节
pIrp->IoStatus.Information=0;
//处理IRP
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
KdPrint(("LeaveHelloDDKDispatchRputine"));
returnstatus;
}
本例中,派遣函数设置了IRP的完成状态为STATUS_SUCCESS。
这样,发起I/O操作请求的Win32API将会返回TRUE。
相反则会返回FALSE。
这种情况时,可以使用GetLastErrorWin32API得到错误代码,所得的错误代码会和IRP设置的状态一致。
除了设置IRP的完成状态,派遣函数还要设置这个IRP操作了多少字节。
派遣函数将IRP请求结束,这是通过IoCompleteRequest函数完成的。
4.通过设备链接打开设备
要打开设备,必须通过设备名字才能得到该设备的句柄。
前面介绍过,每个设备都有设备名称,如HelloDDK驱动程序的设备名称为“\\Device\\MyDDKDevice”,但是设备名称无法被用户模式下的应用程序查询到,设备名只能被内核模式下的程序查询到。
在应用程序中需要通过符号链接进行访问。
下面程序演示在用户模式下打开驱动设备:
viewplaincopytoclipboardprint?
#include<windows.h>
#include<stdio.h>
intmain()
{
HANDLEhDevice=
CreateFile("\\\\.\\HelloDDK",
GENERIC_READ|GENERIC_WRITE,
0,//sharemodenone
NULL,//nosecurity
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);//notemplate
if(hDevice==INVALID_HANDLE_VALUE)
{
printf("Failedtoobtainfilehandletodevice:
"
"%swithWin32errorcode:
%d\n",
"MyWDMDevice",GetLastError());
return1;
}
CloseHandle(hDevice);
return0;
}5.编写一个更通用的派遣函数
在Windows驱动开发中,有一个重要的内核数据结构,IO_STACK_LOCATION,即I/O堆栈,这个数据结构和IRP紧密相连。
驱动对象会创建一个个设备对象,并将这些设备对象“叠”成一个垂直结构,被称为“设备栈”。
IRP会被操作系统发送到设备栈顶层,如果顶层设备结束了本次IRP的请求,则I/O请求结束,如果不让I/O请求结束,可以将IRP继续转发到下一层设备。
因此,一个IRP可能会被转发多次。
为了记录IRP在每层设备中的操作,IRP会有一个IO_STACK_LOCATION数组,每个IO_STACK_LOCATION元素记录着对应设备中做的操作。
对于本层的IO_STACK_LOCATION,可以通过IoGetCurrentIrpStackLocation函数得到。
IO_STACK_LOCATION结构中会记录IRP的类型,即IO_STACK_LOCATION中的MajorFuncation子域。
下面代码增加了派遣函数的难度:
viewplaincopytoclipboardprint?
#pragmaPAGEDCODE
NTSTATUSHelloDDKDispatchRoutine(INPDEVICE_OBJECTpDevObj,INPIRPpIrp)
{
KdPrint(("EnterHelloDDKDispatchRoutine\n"));
PIO_STACK_LOCATIONstack=IoGetCurrentIrpStackLocation(pIrp);
//建立一个字符串数组与IRP类型对应起来
staticchar*irpname[]=
{
"IRP_MJ_CREATE",
"IRP_MJ_CREATE_NAMED_PIPE",
"IRP_MJ_CLOSE",
"IRP_MJ_READ",
"IRP_MJ_WRITE",
"IRP_MJ_QUERY_INFORMATION",
"IRP_MJ_SET_INFORMATION",
"IRP_MJ_QUERY_EA",
"IRP_MJ_SET_EA",
"IRP_MJ_FLUSH_BUFFERS",
"IRP_MJ_QUERY_VOLUME_INFORMATION",
"IRP_MJ_SET_VOLUME_INFORMATION",
"IRP_MJ_DIRECTORY_CONTROL",
"IRP_MJ_FILE_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CONTROL",
"IRP_MJ_INTERNAL_DEVICE_CONTROL",
"IRP_MJ_SHUTDOWN",
"IRP_MJ_LOCK_CONTROL",
"IRP_MJ_CLEANUP",
"IRP_MJ_CREATE_MAILSLOT",
"IRP_MJ_QUERY_SECURITY",
"IRP_MJ_SET_SECURITY",
"IRP_MJ_POWER",
"IRP_MJ_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CHANGE",
"IRP_MJ_QUERY_QUOTA",
"IRP_MJ_SET_QUOTA",
"IRP_MJ_PNP",
};
UCHARtype=stack->MajorFunction;
if(type>=arraysize(irpname))
{
KdPrint(("-UnknowIRP,majortype%X\n",type));
}
else
{
KdPrint(("\t%s\n",irpname[type]));
}
//对一般IRP的简单操作,后面会介绍对IRP更复杂的操作
NTSTATUSstatus=STATUS_SUCCESS;
//完成IRP
pIrp->IoStatus.Status=status;
pIrp->IoStatus.Information=0;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
KdPrint(("LeaveHelloDDKDispatchRoutine\n"));
returnstatus;
}缓冲区方式读写操作
驱动程序所创建的设备一般会有三种读写方式,一种是缓冲区方式,一种是直接方式,一种是其他方式。
1.缓冲区方式
IOCreateDevice创建完设备后,需要对设备对象的Flags子域进行设置,设置不同的Flags会导致以不同的方式操作设备。
设备对象一共可以有三种读写方式,这三种方式的Flags分别对应为DO_BUFFERED_ID,DO_DIRECT_IO和0,缓冲区方式读写相对简单。
读写操作一般是由ReadFile或者WriteFile函数引起的,这里以WriteFile函数为例进行介绍。
WriteFile要求用户提供一段缓冲区,并且说明缓冲区的大小,然后WriteFile将这段内存的数据传入到驱动程序中。
这段缓冲区内存是用户模式的内存地址,驱动程序如果直接引用这段内存是十分危险的。
如果以缓冲区方式读写,操作系统会将应该用程序提供缓冲区的数据复制到内核模式下的地址中,这样无论操作系统如何切换进程,内核模式的地址都不回改变。
IRP派遣函数真正操作的是内核模式下的缓冲区地址,而不是用户模式下的缓冲区地址。
但是这样做会有一定的效率影响。
2.缓冲区设备读写
以缓冲区方式写设备时,操作系统将WriteFile提供的用户模式的缓冲区复制到内核模式地址下,这个地址由WriteFile创建的IRP的AssociateIrp.SystemBuffer子域记录。
另外,在派遣函数中也可以通过IO_STACK_LOCATION中的Parameters.Read.Length子域知道ReadFile请求多少字节。
通过IO_STACK_LOCATION中的Parameters.Write.Length子域知道WriteFile请求多少字节。
然后,WriteFile和ReadFile指定对设备操作多少字节,并不真正意味着操作了这么多字节。
在派遣函数中,应该设置IRP的子域IoStatus.Information.这个子域记录设备实际操作了多少字节。
下面代码演示了如何利用“缓冲区”方式读设备:
viewplaincopytoclipboardprint?
NTSTATUSHelloDDKRead(INPDEVICE_OBJECTpDevObj,INPIRPpIrp)
{
KdPrint(("EnterHelloDDKRead\n"));
//对一般IRP进行处理,后面会介绍对IRP更复杂的处理
NTSTATUSstatus=STATUS_SUCCESS;
PIO_STACK_LOCATIONstack=IoGetCurrentIrpStackLocation(pIrp);
//获得需要读设备的字节数
ULONGulReadLength=stack->Parameters.Read.Length;
//完成IRP
//设置IRP完成状态
pIrp->IoStatus.Status=status;
//设置IRP操作了多少字节
pIrp->IoStatus.Information=ulReadLength;
memset(pIrp->AssociatedIrp.SystemBuffer,0XAA,ulReadLength);
//处理IRP
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
KdPrint(("LeaveHelloDDKRead\n"));
returnstatus;
}
ring3下的程序来读取数据:
viewplaincopytoclipboardprint?
#include<windows.h>
#include<stdio.h>
intmain()
{
HANDLEhDevice=
CreateFile("\\\\.\\HelloDDK",
GENERIC_READ|GENERIC_WRITE,
0,//sharemodenone
NULL,//nosecurity
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);//notemplate
if(hDevice==INVALID_HANDLE_VALUE)
{
printf("Failedtoobtainfilehandletodevice:
"
"%swithWin32errorcode:
%d\n",
"MyWDMDevice",GetLastError());
return1;
}
UCHARbuffer[10];
ULONGulRead;
BOOLbRet=ReadFile(hDe