64位内核开发第一讲,驱动框架.
驱动框架介绍
1.应用程序3环到0环的框架
1.1 3环到0环的驱动框架.
首先是我们的3环API
API -> 封装数据跟命令 ->调用kerner32或者ntdll的函数 ->进行封装,传送给IRP结构体 ->调用驱动
这里接触了一个新的概念.IRP .IRP结构体其实是3环的数据以及命令.进行封装传送到0环的时候.保存在这个结构体里面. 0环通过读取进而调用0环的 NT函数来执行.
如我们调用ReadFile.那么会直接调用我们写的驱动的派遣函数
DispathRead
其中有0x1B(27)个分发派遣函数. 以及一个DriverUnLoad函数.
我们的数据都存放在 IRP中.我们如果要完成例程,那么就设置IRP中的.
IOstatus即可.我们的驱动是分层驱动.如果不设置.他还会调用其它的驱动.
1.2 NT驱动框架
上面我们说了,3环的API会调用0环.其中数据以及命令信息会放在IRP结构体中.
那么如果我们调用 CreateFile. 那么则会产生一个IRP_MJ_CREATE
我们内核层则会调用DispathCreate()来进行设置.
如下:
Nt模型,函数 | 消息 |
---|---|
DriverEntry | 单线程环境,程序入口点. |
DispatchCreate | IRP_MJ_CREATE |
DispatchRead | IRP_MJ_READ |
DispatchWrite | IRP_MJ_WRITE |
DisPatchchClose | IRP_MJ_CLOSE FileObject内核对象 |
DispatchClean | IRP_MJ_CLEANUP HANDLE为句柄 |
DisPatchControl | irp_mj_device_control |
DriverUnLoad | 单线程环境,程序卸载. |
文件句柄为0.那么系统就会发送IRP_MJ_CLEANUP
FileOBject内核对象.如果对文件的内核对象没有在操作了(包括内核)
则会发送IRP_MJ_CLOSE. 大部分情况这两种都会同时发生的.
WDM模型
WDM是网卡等.它引入了两个新的函数
WDMAddDevice()
wdmpnp()
链接即可.
应用框架
Sfilter/Minifilter 文件过滤框架.可以使用Nt模型.
TDI/NDIS/WFP 基于NT模型加的新的框架.防火墙用的
DISPERF 磁盘基于Nt模型.产生的磁盘过滤框架
HOOK
二丶编写自己的最简单的 NT模型驱动.
#include <ntddk.h> //很多驱动的结构体函数的声明呀.都包含在这里面 #define DEVICE_NAME L"\\device\\IBinaryFirst" // 驱动的设备的名字 格式为 \device\你自定义的名字. \\是代表转义 在source中要一样. //#define LINK_NAME L"\\DosDevices\IBinaryFirst" // 驱动的符号连接名 格式\dosdevices\自定义的名字 也可以\\??\\自定义的名字 #define LINK_NAME L"\\DosDevices\\IBinaryFirst" /* 控制码,应用层,内核层通用. */ #define IOCTRL_BASE 0x800 #define MYIOCTRL_CODE(i)\ CTL_CODE(FILE_DEVICE_UNKNOWN,IOCTRL_BASE+i,METHOD_BUFFERED,FILE_ANY_ACCESS) //驱动设备类型 设备控制码数值 定义R3跟R0的通讯方式.是指定Device. 我们的权限. /* METHOD_BUFFERED 以缓存方式读取 METHOD_IN_DIRECT 只读,只有打开设备的时候 IoControl将会成功 METHOD_OUT_DIRECT 则会失败 METHOD_OUT_DIRECT 读写方式的时候.两种方式都会成功 都在MDL中拿数据 METHOD_NEITHER 在 type3InputBuffer拿数据 IN的数据. stack->Parameters.DeviceIoControl.Type3InputBuffer 发送给R3 在 pIrp->UserBuffer里面. 3中通讯方式. */ #define CTL_HELLO MYIOCTRL_CODE(0) //控制码为0则是HELLO. NTSTATUS DispatchCommon(PDEVICE_OBJECT pDeviceObject,PIRP pIrp); NTSTATUS DispatchCreate(PDEVICE_OBJECT pDeviceObject,PIRP pIrp); NTSTATUS DispatchRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp); NTSTATUS DispatchWrite(PDEVICE_OBJECT pDeviceObject,PIRP pIrp); NTSTATUS DispatchClose(PDEVICE_OBJECT pDeviceObject,PIRP pIrp); NTSTATUS DispatchClean(PDEVICE_OBJECT pDeviceObject,PIRP pIrp); NTSTATUS DispatchControl(PDEVICE_OBJECT pDeviceObject,PIRP pIrp); VOID DriverUnLoad(PDRIVER_OBJECT pDriverObject); NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegPath) { UNICODE_STRING uDeviceName = {0}; UNICODE_STRING uLinkName = {0}; NTSTATUS ntStatus = 0; PDEVICE_OBJECT pDeviceObject = NULL; ULONG i = 0; pDriverObject->DriverUnload = DriverUnLoad; DbgPrint("Load Driver Sucess"); RtlInitUnicodeString(&uDeviceName,DEVICE_NAME); //初始化驱动设备名字 RtlInitUnicodeString(&uLinkName,LINK_NAME); //初始化3环宇0环通信的设备名字 ntStatus = IoCreateDevice(pDriverObject,0,&uDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,&pDeviceObject);//创建设备对象 /* 参数1: 驱动对象 参数2: 设备扩展,创建完设备对象之后,申请的一段额外内存.可以保存设备对象的上下文的一些数据 参数3: 设备名字,传入函数,需要传地址 参数4: 设备类型.普通的驱动设置为FILE_DEVICE_UNKNOWN 参数5: 设备的属性 参数6: 设备对象是用来传入IRP请求的.是让我们应用层打开它. R3 发送IRP -> 设备对象(我们自己创建的) 参数6的意思就是 如果为TRUE 只能一个进程打开,独占打开.FALSE是可以多个进程打开的. 参数7: 创建好的设备对象通过最后一个参数传出. 注意是2级指针. */ DbgPrint("IoCreateDevice load.....\r\n"); if (!NT_SUCCESS(ntStatus)) { //判断是否设置成功 DbgPrint(L"IoCreateDevice Failed \r\n"); return 0; } //设置通讯的方式 pDeviceObject->Flags |= DO_BUFFERED_IO; /* R3 -> IRP ->内核. 通过IRP发送给内核层. 三种通讯方式 1.缓存方式: DO_BUFFERED_IO 最安全的一个通讯方式.(数据的交换)基于缓存 内核中会专门会分配跟R3的 Buffer一样的缓存. 内核层从这个空间读取 这个就是 DO_BUFFERED. 处理完毕之后.在放到分配的缓存区中.那么IO管理器 在拷拷贝给应用层.完成数据交互. 2.直接IO方式 DO_DIRECT_IO R3 有一块数据. 会使用MDL方式. 会将R3发送的数据.映射到物理内存中. 并且锁住. 就相当于 R3的数据地址 映射到内核中物理地址. R3往内核中写数据其实也是 往内核数据读取. 这个通讯完全就是在内核中映射的物理内存中进行的. 3.虚拟地址直接发送到R0 第三种方式是虚拟地址 直接发送到R0. 前提条件.进程不能切换.必须处在 同一个线程上下文. 这样不安全所以我们要对这块内存进行检查才可以. ProbeFroWrite ProbeFroRead */ DbgPrint("IoCreateSymbolicLink load.... \r\n"); ntStatus = IoCreateSymbolicLink(&uLinkName,&uDeviceName); //创建符号链接名字. if (!NT_SUCCESS(ntStatus)) { //创建失败,我们就要删除 IoDeleteDevice(pDeviceObject); DbgPrint("IoCreateSymbolicLink Error"); return 0; } DbgPrint("IoCreateSymbolicLink Sucess"); //初始化分发派遣函数. for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION +1;i++) { //分发函数有0x1b个(27)我们不注意的可以进行设置通用的分发函数.分发函数都是一样的. pDriverObject->MajorFunction[i] = DispatchCommon; } pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; pDriverObject->MajorFunction[IRP_MJ_WRITE]= DispatchWrite; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl; pDriverObject->MajorFunction[IRP_MJ_CLEANUP]=DispatchClean; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; DbgPrint("驱动安装成功IBinary \r\n"); //设置驱动卸载 return STATUS_SUCCESS; } NTSTATUS DispatchCommon(PDEVICE_OBJECT pDeviceObject,PIRP pIrp) { pIrp->IoStatus.Status = STATUS_SUCCESS; //IRP记录这次操作与否的. pIrp->IoStatus.Information = 0; //Information用来记录实际传输的字节数的. //提交请求. IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; //上面的 STATUS_SUCCESS是给R3看的.现在的返回时给IO管理器系统的 } NTSTATUS DispatchCreate(PDEVICE_OBJECT pDeviceObject,PIRP pIrp) { pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; //提交请求. IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp) { PVOID pReadBuffer = NULL; ULONG uReadLength = 0; PIO_STACK_LOCATION pStack = NULL; ULONG uMin = 0; ULONG uHelloStr = 0; uHelloStr = (wcslen(L"Hello World") + 1) * sizeof(WCHAR); pReadBuffer = pIrp->AssociatedIrp.SystemBuffer; //缓冲区通讯方式.则是这个值 //获取IRP堆栈.我们说过3环调用0环.需要封装在IRP结构中.windows是分层驱动.所以IRP头部是共用的.其余的是栈传递. pStack = IoGetCurrentIrpStackLocation(pIrp); uReadLength = pStack->Parameters.Read.Length; uMin = uReadLength > uHelloStr ? uHelloStr : uReadLength; RtlCopyMemory(pReadBuffer,L"Hello World",uMin); //拷贝到缓冲区中给3环. pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = uMin; //提交请求. IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchWrite(PDEVICE_OBJECT pDeviceObject,PIRP pIrp) { PVOID pWriteBuffer = NULL; ULONG uWriteLength = 0; PIO_STACK_LOCATION pIrpStack = NULL; PVOID pBuffer = NULL; //获取IRP堆栈 pIrpStack = IoGetCurrentIrpStackLocation(pIrp); //获取写的长度. uWriteLength = pIrpStack->Parameters.Write.Length; pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; //申请内存. pBuffer = ExAllocatePoolWithTag(PagedPool,uWriteLength,'TSET'); /* PagedPool 在分页中分配内存 CPU无分页才能在分页中分配. Dispathch级别则不能使用分页内存. NoPagePool非分页中分配. 优先级最低的才能使用分页内存. 参数2: 长度 参数3: 标记. 不能超过4个字节. 单引号引起来. 参数3是用来跟踪我们分配的内存的. 注意是低位优先, 内存中看到的是 TEST. */ if (NULL == pBuffer) { pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_INSUFFICIENT_RESOURCES; } //提交请求. memset(pBuffer,0,uWriteLength); //拷贝到0环缓冲区 RtlCopyMemory(pBuffer,pWriteBuffer,uWriteLength); ExFreePool(pBuffer); pBuffer = NULL; pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = uWriteLength; IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchClose(PDEVICE_OBJECT pDeviceObject,PIRP pIrp) { //控制 其它交互都通过控制码传送. pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; //提交请求. IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchClean(PDEVICE_OBJECT pDeviceObject,PIRP pIrp) { pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; //提交请求. IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchControl(PDEVICE_OBJECT pDeviceObject,PIRP pIrp) { //内核中共享 SystemBuffer 有时间差.先读在写. PIO_STACK_LOCATION pIrpStack; PVOID InPutBuffer = NULL; PVOID OutPutBuffer = NULL; ULONG uInPutLength = 0; ULONG uOutPutBufferLength = 0; ULONG IoCtrl = 0; InPutBuffer = OutPutBuffer = pIrp->AssociatedIrp.SystemBuffer; pIrpStack = IoGetCurrentIrpStackLocation(pIrp); //uOutPutBufferLength = pIrpStack->Parameters.DeviceIoControl.OutPutBufferLength; //uInPutLength = pIrpStack->Parameters.DeviceIoControl.InPutBufferLength; IoCtrl = pIrpStack->Parameters.DeviceIoControl.IoControlCode; //获取控制码. /* switch(IoCtrl) { case CTL_HELLO: KdPrint("Hello World"); break; default: break; } */ pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; //提交请求. IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; } //驱动卸载 VOID DriverUnLoad(PDRIVER_OBJECT pDriverObject) { DbgPrint("Unload MyDrive\n"); }
根据上面代码我们可以做个解析
1.DriverEntry();这个是NT驱动的入口点.两个参数. 驱动对象.以及注册表路劲.
2.使用IoCreateDevice函数创建了一个驱动设备对象.这样当r3使用ReadFile等函数会传送给设备对象.
3.使用IoCreateSymbolicLink();创建符号链接.此时我们R3调用CreateFile则可以进行链接了.
4.最后注册派遣函数即可.
5.在派遣函数中写入你的操作.如读取操作.我们将数据返还给R3.
1.3 IRP 结构
上面我们看的IRP有头部.
可以看到 IOSTATUS .里面保存了状态.以及实际Buffer字节.
SystemBuffer.这个是缓存IO.就是我们现在用的. 内核中开辟空间保存3环.再从里面读取.最后再给这个缓冲区设置.进行输出.
MdlAddress 这个则是直接IO.我们上面代码的注释中说了.直接IO是
3环的缓冲区地址,映射到0环的物理聂村. 进而0环读取物理内存进行操作.
UserBuffer
UserBuffer是自定义的.其中UserBuffer是传出的.而内部还有一个Buffer是用来读取的.
n以后就是IRP的栈. 在我们文件驱动与磁盘驱动.那么共享IRP头部.
磁盘设备则会使用0层的.
因为驱动是分层的.
而在栈中有一个很重要的联合体.
Read Write DeviceControl...等等.不同结构体对应不同的IRP请求.
所以在Read派遣函数中.获取ReadIrp的堆栈.
二丶编译驱动.
我用的是WDK7600.可以使用XP进行测试.
编译的时候需要使用WDK的 命令行.
当你安装WDK7600之后再开始菜单中则会看到.
打开之后切换到你的编写代码的目录.直接输入build进行编译即可.
注意你的驱动代码后缀名要为.c的文件.这样不会编译错误.
cpp有名字粉碎.你需要使用 extern C 表示这个函数名不会名称粉碎.
在编译的时候我们还需要提供一个sources 文件.
内容为:
TARGETNAME= IBinaryFirst //编译的驱动名字. TARGETTYPE=DRIVER //编译的类型为驱动 SOURCES= IBinaryFirst.c //你驱动的代码文件 这是我的: TARGETNAME=IBinaryFirst TARGETTYPE=DRIVER SOURCES=IBinaryFirst.c
编译之后如下.
3.加载驱动.
加载驱动有专门的的API进行操作.我以前写过.
可以看之前的文章.
https://www.geek-share.com/detail/2726858901.html
现在我们直接用工具加载了.
可以看到加载成功.宽字符打印出错.不影响.
4.ring3操作内核.进行读取.
可以看到我们的 HelloWorld已经正常读取了.
ring3下完整代码.
// Ring3.cpp : Defines the entry point for the console application. // #include <windows.H> #include <stdio.h> int main(int argc, char* argv[]) { HANDLE hFile = CreateFile(TEXT("\\\\?\\IBinaryFirst"), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("CreateFile ErrorCode:%d\n", GetLastError()); return 0; } system("pause"); TCHAR szBuff[0X100]; DWORD dwBytes = 0; if (!ReadFile(hFile, szBuff, sizeof(szBuff)/sizeof(szBuff[0]), &dwBytes, NULL)) { CloseHandle(hFile); printf("ReadFile ErrorCode:%d\n", GetLastError()); return 0; } printf("bytes:%d data:%ls\n", dwBytes, szBuff); system("pause"); //WriteFile(); //DeviceIoControl CloseHandle(hFile); system("pause"); return 0; }
- 驱动开发框架 -------内核模块结构|source insight 工程创建|模块的属性
- linux 驱动开发第三方学习框架资料整理
- Window XP驱动开发(十九) windows内核高级调试技巧(双机调试)
- MacOS内核扩展驱动开发
- Linux 网络设备驱动开发(一) —— linux内核网络分层结构
- 嵌入式软件开发之------浅析inux驱动模型(六)input框架
- 4000 Linux3.x 内核驱动框架的变动(ing)
- linux驱动开发--内核模块参数
- linux驱动开发--内核空间中内存的申请与释放
- linux驱动开发--内核定时器
- Windows内核驱动开发入门学习资料
- 浅谈 Linux 内核开发之网络设备驱动
- [Mac OS X] 内核、驱动开发资料汇总
- Android平台下驱动的开发及测试框架概述(一)
- mini2440 pwm蜂鸣器设备驱动开发源代码(宋宝华框架)
- 浅谈 Linux 内核开发之网络设备驱动
- [国嵌攻略][102][内核驱动开发环境搭建]
- Android平台下驱动的开发及测试框架概述(二)
- 开发驱动时用到的内核打印函数KdPrint 的使用方法