应用程序和驱动程序的通信

一、基础介绍

1.1 设备与驱动的关系

设备由驱动去创建,访问一个设备,是首先得访问驱动。如果驱动在卸载的时候没有删除符号,r3下也是不能去访问设备的。

驱动程序和系统其他组件之间的交互是通过给设备发送或者接受发给设备的请求来交互的。换句话说,一个没有任何设备的驱动是不能按规范方式和系统交互的。当然也不会收到任何IRP,分发函数也失去了意义。如果驱动程序要和应用程序之间通信,则需要生成设备。此外还必须为设备生成应用程序可以访问的符号链接。

也就是说,应用程序与驱动的交互是通过设备来完成的,设备成了中间桥梁。下文中的符号链接又成了设备名的中间桥梁。记住,核心中介是设备。其他都是为他们的通信提供的机制。

1.2 设备符号链接名与设备名

Ring3不能直接访问设备,需要中间桥梁。这个中间桥梁是Ring0驱动为设备名注册的链接符号(Symbol Link)。

从图中可以看出,R3要访问设备,是通过设备符号名去访问设备的,而R3应用层下的设备符号名和设备管理器中的设备符号名有出入,必须,也只能写成\.\设备符号名 的方式 ,系统会自动转为??\设备符号名 ,紧接着通过设备符号名这个中间桥梁可以转化为真正的R0下的设备名。

也就是说,符号链接名在应用层只是简单的符号转化过程,有些类似宏定义,设备符号名主体是相同的。而设备名和设备符号名可以不相同。因为设备符号名仅仅是个中间桥梁。

总结:

  1. R0下的设备名格式为\Device\自定义设备名
  2. R0下的设备符号名格式为\??\自定义符号名,其中自定义符号名自定义设备名可以不一致。
  3. R3下不能直接访问设备,只能用设备符号名去访问设备,但是也不能直接使用\??\设备符号名去获得设备名
  4. R3要访问设备,必须使用\\.\设备符号名,这个设备符号名由R0给定。

1.3 通信机制

  1. 应用程序和驱动程序的通信是通过IRP (i/o request packet) (IO请求包) 来完成的。
  2. 利用其它进程通信方式,如socket、pip、共享内存等(注意r0的api调用方式不一致)

二、IRP方式

2.1 定义IO控制码

可以看做一种通信协议,宏定义:

#define CTL_CODE( DeviceType, Function, Method, Access ) (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))

可以看到,这个宏四个参数,自然是一个32位分成了4部分:

  • 高16位存储设备类型
  • 14~15位访问权限
  • 2~13位操作功能
  • 最后0/1两位就是确定缓冲区是如何与I/O和文件系统数据缓冲区进行数据传递方式

自定义CTL_CODE

#define IOCTL_TEST_BUFFERED  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
  • IOCTL_TEST_BUFFERED:生成的IRP的MinorFunction
  • FILE_DEVICE_UNKNOWN:设备对象的类型。设备类型可参考:http://blog.csdn.net/liyun123gx/article/details/38058965
  • 0x900 :自定义的IO控制码。自己定义时取0x800到0xFFF,因为0x0到0x7FF是微软保留的。
  • METHOD_BUFFERED:缓冲区进行数据传递方式
  • FILE_ANY_ACCESS :数据的操作模式。
内存访问方式

有四种方式:

#define METHOD_BUFFERED     0
#define METHOD_IN_DIRECT    1
#define METHOD_OUT_DIRECT 2
#define METHOD_NEITHER      3

METHOD_BUFFERED:表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer来缓冲,因此这种方式的通信比较安全。

METHOD_IN_DIRECTMETHOD_OUT_DIRECT:表示系统会将输入缓冲在pIrp->AssociatedIrp.SystemBuffer中,并将输出缓冲区锁定,然后在内核模式下重新映射一段地址,这样也是比较安全的。

METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。

METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别,仅在于打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT方式的IoControl将会成功,而METHOD_OUT_DIRECT方式将会失败。如果以读写权限打开设备,两种方式都会成功。

METHOD_NEITHER:“其他方式”,虽然通信的效率提高了,但是不够安全。驱动的派遣函数中输入缓冲区可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。由于驱动中的派遣函数不能保证传递进来的用户输入和输出地址,因此最好不要直接去读写这些地址的缓冲区。应该在读写前使用ProbeForRead和ProbeForWrite函数探测地址是否可读和可写。

METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址,驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。

由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。

2.2 定义驱动设备名,符号链接名

关于在Ring0层中要设置驱动设备名的同时还要设置符号链接名的原因,是因为只有符号链接名才可以被用户模式下的应用程序识别。

如果IoCreateDevice中没有指定设备名称,那么I/O管理器会自动分配一个数字作为设备的名称。

// 驱动设备名:设备与设备之间通信
#define DEVNAME L"\\Device\\kmdfDriver2"// 符号链接名:设备与Ring3之间通信
#define LNKNAME L"\\??\\kmdfDriver2"

2.3 设备同驱动通信

驱动程序要做的最后一步,先用IoCreateDevice函数创建设备对象,再用IoCreateSymbolicLink将符号链接名与设备对象名称关联,大功告成,等待IO控制码。


// 创建设备对象名称
RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
// 创建设备对象
Status = IoCreateDevice(DriverObject,NULL,&DeviceObjectName,FILE_DEVICE_UNKNOWN,0, FALSE,&DeviceObject);
if (!NT_SUCCESS(Status))
{return Status;
}// 创建设备连接名称
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
// 将设备连接名称与设备名称关联
Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);if (!NT_SUCCESS(Status))
{IoDeleteDevice(DeviceObject);return Status;
}

驱动程序铺垫打理好之后,应用程序就可以由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个DeviceHandle发送控制码了。

BOOL WINAPI DeviceIoControl(_In_         HANDLE hDevice,       // CreateFile函数打开的设备句柄_In_         DWORD dwIoControlCode,// 自定义的控制码_In_opt_     LPVOID lpInBuffer,    // 输入缓冲区_In_         DWORD nInBufferSize,  // 输入缓冲区的大小_Out_opt_    LPVOID lpOutBuffer,   // 输出缓冲区_In_         DWORD nOutBufferSize, // 输出缓冲区的大小_Out_opt_    LPDWORD lpBytesReturned, // 实际返回的字节数,对应驱动程序中pIrp->IoStatus.Information。_Inout_opt_  LPOVERLAPPED lpOverlapped // 重叠操作结构指针。同步设为NULL,DeviceIoControl将进行阻塞调用;否则,应在编程时按异步操作设计
);
HANDLE CreateFile(LPCTSTR lpFileName,                         // 打开的文件名DWORD dwDesiredAccess,                    // 访问权限DWORD dwShareMode,                      // 共享模式LPSECURITY_ATTRIBUTES lpSecurityAttributes,   // 安全属性DWORD dwCreationDisposition,               // 文件存在与不存在时的文件创建模式DWORD dwFlagsAndAttributes,                // 文件属性设定(隐藏、只读、压缩、指定为系统文件等)HANDLE hTemplateFile                       // 文件副本句柄
);

2.4 代码

驱动

#include <ntddk.h>#define DEVNAME L"\\Device\\kmdfDriver2"
#define LNKNAME L"\\??\\kmdfDriver2"
#define IOCTL_TEST_BUFFERED    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_TEST_IN_DIRECT   CTL_CODE(FILE_DEVICE_UNKNOWN, 0x901, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
#define IOCTL_TEST_OUT_DIRECT  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x902, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)typedef struct IRPData
{ULONG flag;ULONG addr;ULONG writebuffAddr;ULONG size;ULONG pid;
}_IRPData, * PIRPData;// IRP_MJ_CONTROL 处理例程
NTSTATUS DisPathRoutine(PDEVICE_OBJECT deviceObj, PIRP irp)
{NTSTATUS Status = STATUS_SUCCESS;ULONG_PTR informaiton = 0;PVOID inputData = NULL;ULONG inputDataLength = 0;PVOID outputData = NULL;ULONG outputDataLength = 0;// 得到IRP当前栈PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(irp);inputData = irp->AssociatedIrp.SystemBuffer;outputData = irp->AssociatedIrp.SystemBuffer;inputDataLength = pStack->Parameters.DeviceIoControl.InputBufferLength; // 输入缓冲大小outputDataLength = pStack->Parameters.DeviceIoControl.OutputBufferLength; // 输出缓冲大小// 获取控制码ULONG ulcode = (ULONG)pStack->Parameters.DeviceIoControl.IoControlCode;switch (ulcode){case IOCTL_TEST_BUFFERED:{DbgPrint("kmdfDriver2: recive message IOCTL_TEST_BUFFERED, outputDataLength: %d", outputDataLength);PIRPData data = (PIRPData)inputData;DbgPrint("kmdfDriver2:data->flag: %d", data->flag);DbgPrint("kmdfDriver2:data->addr: %x", data->addr);DbgPrint("kmdfDriver2:data->writebuffAddr: %d", data->writebuffAddr);DbgPrint("kmdfDriver2:data->size: %d", data->size);DbgPrint("kmdfDriver2: data->pid: %d", data->pid);// 通过内存返回数据.char* retBuffer = "hello lyshark";memcpy(outputData, retBuffer, strlen(retBuffer));informaiton = strlen(retBuffer) + 1;Status = STATUS_SUCCESS;DbgPrint("kmdfDriver2:outputData: %s | len: %d", retBuffer, informaiton);/*// 通过内存返回数据,另一种通信方式.PVOID addr = (PVOID)"ok";RtlCopyMemory(outputData, addr, 4);informaiton = 4;Status = STATUS_SUCCESS;*/break;}case IOCTL_TEST_IN_DIRECT:{DbgPrint("kmdfDriver2: recive message IOCTL_TEST_IN_DIRECT, outputDataLength: %d", outputDataLength);PIRPData data = (PIRPData)inputData;DbgPrint("kmdfDriver2:data->flag: %d", data->flag);DbgPrint("kmdfDriver2:data->addr: %x", data->addr);DbgPrint("kmdfDriver2:data->writebuffAddr: %d", data->writebuffAddr);DbgPrint("kmdfDriver2:data->size: %d", data->size);DbgPrint("kmdfDriver2: data->pid: %d", data->pid);// 获得用户层传入的 输出字符串指针, 就可以任意改变字符串内容outputData = MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);char* retBuffer = "hello lyshark";if (outputData) memcpy(outputData, retBuffer, strlen(retBuffer));informaiton = strlen(retBuffer) + 1;Status = STATUS_SUCCESS;DbgPrint("kmdfDriver2:outputData: %s | len: %d", retBuffer, informaiton);/*// 通过内存返回数据,另一种通信方式.PVOID addr = (PVOID)"ok";RtlCopyMemory(outputData, addr, 4);informaiton = 4;Status = STATUS_SUCCESS;*/break;}case IOCTL_TEST_OUT_DIRECT:{DbgPrint("kmdfDriver2: recive message IOCTL_TEST_OUT_DIRECT, outputDataLength: %d", outputDataLength);// 获得用户层传入的 输出字符串指针, 就可以任意改变字符串内容outputData = MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);// 通过内存返回数据.char* retBuffer = "hello lyshark";if (outputData) memcpy(outputData, retBuffer, strlen(retBuffer));informaiton = strlen(retBuffer) + 1;Status = STATUS_SUCCESS;DbgPrint("kmdfDriver2:outputData: %s | len: %d", retBuffer, informaiton);/*// 通过内存返回数据,另一种通信方式.PVOID addr = (PVOID)"ok";RtlCopyMemory(outputData, addr, 4);informaiton = 4;Status = STATUS_SUCCESS;*/break;}default:break;}// 注明已完成IRPirp->IoStatus.Status = Status; // 设置IRP完成状态,会设置用户模式下的GetLastErrorirp->IoStatus.Information = informaiton; // 设置操作的字节IoCompleteRequest(irp, IO_NO_INCREMENT); // 完成IRP,不增加优先级return Status;
}// IRP_MJ_Create 处理例程,返回成功时,r3才能获得句柄。
NTSTATUS CreateRoutine(PDEVICE_OBJECT deviceObj, PIRP irp)
{return STATUS_SUCCESS;
}NTSTATUS DispatchRead(PDEVICE_OBJECT deviceObj, PIRP irp)
{NTSTATUS Status = STATUS_SUCCESS;PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(irp);ULONG ulReadLength = Stack->Parameters.Read.Length;irp->IoStatus.Status = Status;irp->IoStatus.Information = ulReadLength;DbgPrint("kmdfDriver2 app want to read length:%d", ulReadLength);// 将内核中的缓冲区全部填充为0x68 方便演示读取的效果memset(irp->AssociatedIrp.SystemBuffer, 0x68, ulReadLength);IoCompleteRequest(irp, IO_NO_INCREMENT);return Status;
}// 提供一个Unload 函数只是为了让这个程序能够动态卸载,方便调试
extern "C" void DriverUnload(PDRIVER_OBJECT driver)
{// 结束入口DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "kmdfDriver2: DriverUnload in");// 删除设备,删除符号链接。PDEVICE_OBJECT pDev;pDev = driver->DeviceObject;IoDeleteDevice(pDev);UNICODE_STRING linkName;RtlInitUnicodeString(&linkName, LNKNAME);IoDeleteSymbolicLink(&linkName);DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "kmdfDriver2: DriverUnload out");
}// DriverEntry,入口函数。相当于main。
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING regPath)
{// 这是内核模块入口,可以在这里写入我们想写的东西DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "kmdfDriver2: DriverEntry");DbgPrint("kmdfDriver2: regPath: %S", (ULONG*)regPath->Buffer);// 设置一个卸载函数,便于这个函数退出driver->DriverUnload = DriverUnload;// 注册派遣函数driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DisPathRoutine;driver->MajorFunction[IRP_MJ_CREATE] = CreateRoutine;driver->MajorFunction[IRP_MJ_READ] = DispatchRead;// 创建设备UNICODE_STRING DevName;UNICODE_STRING LnkName;RtlInitUnicodeString(&DevName, DEVNAME);RtlInitUnicodeString(&LnkName, LNKNAME);NTSTATUS status = STATUS_SUCCESS; // STATUS_UNSUCCESSFULPDEVICE_OBJECT pDevObj = NULL;DbgPrint("kmdfDriver2: DevName: %S", DevName.Buffer);status = IoCreateDevice(driver,0,&DevName,FILE_DEVICE_UNKNOWN,0,TRUE,&pDevObj);DbgPrint("kmdfDriver2: IoCreateDevice status: %08x", status);if (!NT_SUCCESS(status)){DbgPrint("kmdfDriver2: Can't CreateDevice.");return status;}DbgPrint("kmdfDriver2: CreateDevice Success.");pDevObj->Flags |= DO_DIRECT_IO;pDevObj->Flags |= DO_BUFFERED_IO;// 注册符号链接status = IoCreateSymbolicLink(&LnkName, &DevName);if (!NT_SUCCESS(status)){DbgPrint("kmdfDriver2: Can't Create SymbolLink.");IoDeleteDevice(pDevObj);return status;}return status;
}

应用层

// communicate_client.cpp : 定义控制台应用程序的入口点。
//#include <windows.h>
#include <stdio.h>//  device name:
//  "\\.\MyDevice" ,so symbol link is "\??\MyDevice"
#define DEVICE_NAME L"\\\\.\\kmdfDriver2"#define IOCTL_TEST_BUFFERED    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_TEST_IN_DIRECT   CTL_CODE(FILE_DEVICE_UNKNOWN, 0x901, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
#define IOCTL_TEST_OUT_DIRECT  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x902, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)typedef struct IRPData
{ULONG flag;ULONG addr;ULONG writebuffAddr;ULONG size;ULONG pid;
}_IRPData, * PIRPData;int main(int argc, char* argv[])
{// 打开句柄,会触发IRP_MJ_CREATE// CreateFile可以访问文件,也可以访问设备。正是符号"\\.\"让系统得知要访问的是设备,而不是文件HANDLE hDevice = CreateFile(DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (INVALID_HANDLE_VALUE == hDevice){printf("Can't create this Device.\r\n");exit(-1);}// readUCHAR buffer[10];ULONG ulRead = 0;ReadFile(hDevice, buffer, 10, &ulRead, 0);for (int i = 0; i < (int)ulRead; i++){printf("[%02d-%02d] ReadFile数据:%02X\n", i, ulRead, buffer[i]);}// send & receiveprintf("输入以下命令进行测试: \n");printf("1. t - IOCTL_TEST_BUFFERED \n");printf("2. i - IOCTL_TEST_IN_DIRECT \n");printf("3. o - IOCTL_TEST_OUT_DIRECT \n");printf("4. q - exit \n");char signal = NULL;DWORD dwRet = NULL;while (signal = getchar()){switch (signal){case 't':{char outbuff[20] = { 0 };IRPData data;data.flag = 2;data.addr = 0x401234;data.writebuffAddr = 1024;data.size = 100;data.pid = 2566;DeviceIoControl(hDevice, IOCTL_TEST_BUFFERED, &data, sizeof(data), (LPVOID)outbuff, 20, &dwRet, NULL);  // message 1printf("返回的数据:%s, 总长度:%d\n", outbuff, dwRet);for (int i = 0; i < (int)dwRet; i++){printf("[%02d-%02d] DeviceIoControl 返回数据: %02X\n", i, dwRet, outbuff[i]);}break;}case 'i':{char outbuff[20] = { 0 };IRPData data;data.flag = 2;data.addr = 0x401234;data.writebuffAddr = 1024;data.size = 100;data.pid = 2566;DeviceIoControl(hDevice, IOCTL_TEST_IN_DIRECT, &data, sizeof(data), (LPVOID)outbuff, 20, &dwRet, NULL);  // message 1printf("返回的数据:%s, 总长度:%d\n", outbuff, dwRet);for (int i = 0; i < (int)dwRet; i++){printf("[%02d-%02d] DeviceIoControl 返回数据: %02X\n", i, dwRet, outbuff[i]);}break;}case 'o':{char outbuff[14] = { 0 };DeviceIoControl(hDevice, IOCTL_TEST_OUT_DIRECT, NULL, 0, (LPVOID)outbuff, 20, &dwRet, NULL);  // message 2printf("返回的数据:%s, 总长度:%d\n", outbuff, dwRet);for (int i = 0; i < (int)dwRet; i++){printf("[%02d-%02d] DeviceIoControl 返回数据: %02X\n", i, dwRet, outbuff[i]);}break;}case 'q':CloseHandle(hDevice); // 关闭句柄会触发 IRP_MJ_CLEANUP and IRP_MJ_CLOSEexit(-1);break;}}return 0;
}

演示效果:METHOD_BUFFERED
驱动打印:
应用打印:

参考

  • https://blog.csdn.net/bugmeout/article/details/45770855?utm_source=blogxgwz0
  • https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/reading-and-filtering-debugging-messages
  • http://www.javashuo.com/article/p-pkusjtkw-nu.html
  • https://blog.csdn.net/weixin_30790841/article/details/101848878
  • https://www.likecs.com/show-306775366.html?sc=3004
  • https://www.zhangshengrong.com/p/noXQbR671G/

windows驱动开发7:应用程序和驱动程序的通信相关推荐

  1. Windows驱动开发基础(五)驱动程序的数据结构

    Windows驱动开发基础:驱动程序的数据结构.转载请标明出处:http://blog.csdn.net/ikerpeng/article/details/38794405 I/O管理器定义了一些数据 ...

  2. Windows驱动开发第10课(R3与R0通信交换数据第一节)

    首先,先解释一下R3与R0是什么意思,R是Ring的首字母(中文:环).在计算机技术里把对CPU的访问控制的权限抽象成一环套着一环,分为4个级别,Ring0-Ring3.Ring0层拥有最高的权限,在 ...

  3. windows驱动开发第12课(R3与R0通信之写入数据)

    在上一节课我们使两边的控制码对应上了,这节课我们来实现向驱动层写入数据. 知识点:和IRP有关的系统缓冲区 pirp->AssociatedIrp.SystemBuffer; //和IRP有关的 ...

  4. Windows驱动开发学习笔记(一)—— 环境配置第一个驱动程序

    Windows驱动开发学习笔记(一)-- 环境配置&第一个驱动程序 环境配置 第一个驱动程序 环境配置 安装VS2010:https://pan.baidu.com/s/1uZWWxCtB60 ...

  5. 安信Windows驱动开发教程:什么是通用 Windows 平台 (UWP) 应用程序?有什么功能?

    安信Windows驱动开发教程:什么是通用 Windows 平台 (UWP) 应用程序?有什么功能? UWP 是为 Windows 创建客户端应用程序的众多方法之一.UWP 应用使用 WinRT AP ...

  6. Windows驱动开发学习笔记(四)—— 3环与0环通信(常规方式)

    Windows驱动开发学习笔记(四)-- 3环与0环通信(常规方式) 设备对象 创建设备对象 设置数据交互方式 创建符号链接 IRP与派遣函数 IRP的类型 其它类型的IRP 派遣函数 派遣函数注册位 ...

  7. Windows驱动开发学习笔记(二)—— 驱动调试内核编程基础

    Windows驱动开发学习笔记(二)-- 驱动调试&内核编程基础 基础知识 驱动调试 PDB(Program Debug Database) WinDbg 加载 PDB 实验:调试 .sys ...

  8. windows驱动开发学习

    序言]  很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资 料少有关系.大多学的驱动开发资料都以英文为主,这样让很多驱动初学者很头疼.本人从 事驱动开发时间不长也不短, ...

  9. 15、Windows驱动开发技术详解笔记(11) 基本概念

    9.Windows驱动程序的入口函数规定为_DriverEntry@8,所以用C++编写时要用extern. 驱动程序中,不能使用编译器运行时函数,甚至C语言中的malloc,C++的new函数都不能 ...

最新文章

  1. cocos2dx 调用oc java_cocos2dx 调用浏览器打开网址
  2. JQuery中.css()与.addClass()设置样式的区别
  3. mysql截取字符串最后两位_MySQL字符串函数substring:字符串截取
  4. aidl使用_Android进阶之AIDL如何使用自定义类型
  5. 机器字长、存储字长、指令字长
  6. db2的bufferpool不足报错的快速解决
  7. 安卓手机作为中继器-连接Wifi共享该Wifi给PC和手机
  8. clickhouse性能优化实践
  9. hdoj2044:一只小蜜蜂(递推)
  10. vue 项目使用通过经纬度显示地图
  11. 阿里云盘下载安装保存文件教程
  12. asp.net天轰穿视频学习总结
  13. IDEA中单词拼写错误
  14. L1-086 斯德哥尔摩火车上的题
  15. 程序员很少上《非诚勿扰》电视节目相亲之分析
  16. Django框架-Django视图(views)系统
  17. PWM脉冲宽度调制,实现呼吸灯_领航者开发板
  18. 移动调试工具weinre安装和使用
  19. 一个算法工程师的日常是怎样的?
  20. CHIL-SQL-DEFAULT 约束

热门文章

  1. 懒人反编译辅助脚本工具
  2. 微信小程序显示背景图片
  3. bui java_java springmvc+bui+bootstrap后台管理系统搭建
  4. Python实现高德POI点(GCJ-02)火星坐标批量转换为WGS84
  5. CSS 3D 书籍封面效果
  6. android腾讯一面,腾讯音乐Android工程师一面面试题记录
  7. SPDK SRIOV VIRTIO
  8. JSP密码不少于6位
  9. 五种网络IO模型详解
  10. 大意失荆州-记VBA+ACCESS通配符问题