简介:本资源是一套面向Windows驱动开发初学者与进阶工程师的VC++底层驱动源码集合,聚焦内核模式编程实践,帮助开发者掌握设备驱动框架搭建、IRP处理、设备对象注册、中断服务例程及WDF模型等核心能力。压缩包共657个文件,涵盖304个头文件(h)用于接口定义与结构声明,149个lib库文件支持链接依赖,34个C源文件与12个CPP文件构成驱动主体逻辑,另有inf安装脚本、sys驱动二进制、rc资源描述及makefile构建配置等关键类型,整体7.73MB,结构完整、模块清晰,便于逐层剖析与调试复现。已有154人学习下载,资源包含多组命名规整的驱动工程(如GG.001~GG.013),覆盖DriverEntry初始化、IoCreateDevice调用、ISR注册、内存分配与代码签名示例,配套sources与build环境配置,可直接导入WDK编译验证,是理解Windows内核交互机制与开展真实驱动实验的实用参考。
1. VC++ 语言编程驱动源程序:不是写个 DLL 就叫驱动,它特指 Windows 内核模式下的 WDM/WDK 开发路径
“VC++ 语言编程驱动源程序”这个标题常被初学者误读为“用 Visual C++ 写个用户态 DLL”,但实际在 Windows 系统开发语境下,它指向一个明确、高门槛、强约束的技术方向:基于 Microsoft WDK(Windows Driver Kit)工具链,使用 C++(严格说是 C 风格子集 + 少量 C++ 特性)编写的内核模式设备驱动程序源码工程。这类源程序不跑在用户空间,不调用 Win32 API,不依赖 CRT,而是直面 HAL(硬件抽象层)、IRP(I/O 请求包)、对象管理器与内存管理器,其编译产物是.sys文件,加载后运行于 Ring 0,拥有对硬件和系统资源的完全控制权——也意味着一个指针越界就可能蓝屏。
它解决的是真实工业场景中的硬需求:USB 自定义设备通信、PCIe 加速卡控制、音视频采集卡帧同步、打印机固件交互、安全软件的进程/文件/注册表实时监控钩子等。适合已有 C/C++ 基础、熟悉 Windows 内存模型、愿意啃文档、能接受“改一行代码需重启验证”的开发者;不适合想快速出 GUI 工具或做 Web 后端的人。这不是语法练习,而是一套完整的系统级工程范式:从DriverEntry入口函数开始,到AddDevice、IRP_MJ_READ分发、IoCompleteRequest结束,每一步都绑定着 WDK 的约定、IRQL 级别规则与即插即用(PnP)状态机。你写的不是“程序”,是操作系统信任并委以重托的“内核扩展”。
2. 为什么必须用 WDK 而非普通 VC++?看清编译环境、语言限制与入口契约
2.1 WDK 是唯一合法的内核开发工具链:VC++ 只是前端编译器,WDK 提供全部底层支撑
普通 Visual Studio 安装的 MSVC 编译器(如cl.exe)本身不具备生成内核模块的能力。它需要 WDK 提供的三类核心组件才能工作:
- 内核专用头文件:
ntddk.h、wdm.h、ntifs.h等,定义了DRIVER_OBJECT、DEVICE_OBJECT、IRP等关键结构体及宏; - 内核链接库:
ntoskrnl.lib(导出符号表,非真正链接)、hal.lib,用于解析内核导出函数(如ExAllocatePoolWithTag); - 构建系统(Build Environment):WDK 自带的
build.exe(旧版)或msbuild+ WDK targets(新版),它会自动设置/kernel链接标志、禁用浮点指令、强制/SUBSYSTEM:NATIVE、移除 CRT 初始化代码,并注入正确的内核引导节(.text,.data,.rdata,.INIT)。
提示:Visual Studio 2019/2022 安装 WDK 后,新建项目类型中会出现 “Kernel Mode Driver” 模板,它本质是预配置好上述所有路径与属性的 MSBuild 工程。手动用纯 VC++ 创建
.vcxproj并硬塞 WDK 路径极易失败——因为 WDK targets 里还嵌入了 IRQL 检查、驱动签名策略、PDB 符号路径生成等隐式逻辑。
2.2 C++ 在驱动中是“受限子集”:禁止异常、RTTI、全局构造/析构、new/delete(默认)
WDK 驱动工程中声明的.cpp文件,表面是 C++,实则被编译器强制降级为 C 风格。原因很直接:内核没有异常处理框架(SEH 在内核中存在但不可靠),没有运行时类型信息(RTTI)支持,也没有全局对象生命周期管理机制。若你在驱动中写:
// ❌ 危险!编译可能通过,但运行时蓝屏 class MyDevice { public: MyDevice() { /* 构造函数 */ } // 全局对象构造 → 无调用时机,触发 IRQL 冲突 ~MyDevice() { /* 析构函数 */ } void* operator new(size_t sz) { return ExAllocatePoolWithTag(NonPagedPool, sz, 'TAG'); } };编译器会静默忽略构造/析构调用,operator new也不会被链接器识别——因为内核不提供operator new的实现入口。WDK 明确要求:所有内存分配必须显式调用ExAllocatePoolWithTag/ExFreePoolWithTag,所有对象生命周期由驱动自己管理(通常在AddDevice中new出来,Unload中delete掉)。
正确做法是用 C 风格结构体 + 初始化函数:
// ✅ 推荐:C 风格结构体 + 显式 Init/Cleanup typedef struct _MY_DEVICE_CONTEXT { PDEVICE_OBJECT DeviceObject; ULONG DeviceNumber; KEVENT CloseEvent; } MY_DEVICE_CONTEXT, *PMY_DEVICE_CONTEXT; NTSTATUS MyDeviceContextInit(PDEVICE_OBJECT DeviceObject, PMY_DEVICE_CONTEXT* Context) { *Context = (PMY_DEVICE_CONTEXT)ExAllocatePoolWithTag(NonPagedPool, sizeof(MY_DEVICE_CONTEXT), 'CTX'); if (!*Context) return STATUS_INSUFFICIENT_RESOURCES; RtlZeroMemory(*Context, sizeof(MY_DEVICE_CONTEXT)); (*Context)->DeviceObject = DeviceObject; KeInitializeEvent(&(*Context)->CloseEvent, NotificationEvent, FALSE); return STATUS_SUCCESS; } VOID MyDeviceContextCleanup(PMY_DEVICE_CONTEXT Context) { if (Context) ExFreePoolWithTag(Context, 'CTX'); }2.3 入口函数DriverEntry是铁律:参数、返回值、调用时机全由内核规定
所有 WDM 驱动必须导出且仅导出一个DriverEntry函数,其签名被 WDK 头文件严格锁定:
// 必须如此声明,不能加 __stdcall / __cdecl,不能改名,不能少参数 extern "C" NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath );DriverObject:内核创建的驱动对象,包含DriverObject->MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]数组,你必须为关心的 IRP 类型(如IRP_MJ_CREATE,IRP_MJ_READ)填入自己的分发函数地址;RegistryPath:注册表路径\\Registry\\Machine\\System\\CurrentControlSet\\Services\\YourDriverName,用于读取 INF 安装时写入的参数;- 返回值:仅
STATUS_SUCCESS表示加载成功;其他值(如STATUS_INSUFFICIENT_RESOURCES)将导致驱动加载失败,内核不会调用AddDevice。
常见错误是试图在此函数中创建设备对象——这是错的。DriverEntry只负责初始化驱动对象、设置分发例程、注册卸载函数;设备对象必须在AddDevice回调中创建(由 PnP 管理器在设备枚举时调用),否则无法参与即插即用状态机。
3. 从零搭建一个可编译的 WDM 驱动源程序:最小工程结构与关键文件清单
3.1 工程目录结构:4 个核心文件构成最小可运行单元
一个合法的 WDM 驱动源程序,不依赖任何第三方库,仅靠 WDK 自带组件,最小需包含以下 4 个文件(路径以MyWdmDriver为例):
| 文件路径 | 作用 | 关键内容说明 |
|---|---|---|
MyWdmDriver.vcxproj | MSBuild 工程文件 | 由 VS WDK 模板生成,已预设<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>、<ConfigurationType>Driver</ConfigurationType>、<DriverType>WDM</DriverType>等 WDK 特有属性 |
DriverEntry.cpp | 驱动入口实现 | 包含DriverEntry函数、AddDevice、Unload、各 IRP 分发函数(至少DispatchCreate,DispatchCleanup,DispatchClose) |
MyWdmDriver.h | 驱动私有头文件 | 定义设备名L"\\Device\\MyWdmDriver"、符号链接名L"\\DosDevices\\MyWdmDriver"、IOCTL 控制码宏(如IOCTL_MYDRIVER_DO_SOMETHING) |
sources(仅旧版 WDK)或MyWdmDriver.targets(新版) | 构建配置文件 | 旧版 WDK 用文本sources文件指定TARGETNAME=MyWdmDriver,TARGETTYPE=DRIVER,SOURCES=DriverEntry.cpp;新版 WDK 由.vcxproj内置 targets 替代 |
注意:新版 WDK(10.0.22621+)已弃用
build.exe和sources文件,全部迁移到 MSBuild + WDK targets。若你看到网上教程还在教build -Zc,说明它基于 WDK 7600 或更早版本,请立即切换至 WDK 22H2(22621)或更新版,否则无法通过 WHQL 认证且缺少 ARM64 支持。
3.2DriverEntry.cpp最小可编译骨架:12 行核心逻辑撑起整个驱动
以下代码是经过实测、可在 WDK 22621 + VS 2022 下直接编译通过的最小骨架(删除所有业务逻辑,仅保活):
// DriverEntry.cpp #include <ntddk.h> #include "MyWdmDriver.h" // 声明函数(WDK 要求所有函数必须先声明) DRIVER_INITIALIZE DriverEntry; DRIVER_ADD_DEVICE AddDevice; DRIVER_UNLOAD Unload; DRIVER_DISPATCH DispatchCreate; DRIVER_DISPATCH DispatchCleanup; DRIVER_DISPATCH DispatchClose; // DriverEntry: 驱动加载入口 extern "C" NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); // 设置卸载函数(可选,但强烈建议) DriverObject->DriverUnload = Unload; // 设置所有 IRP 分发函数为默认处理(拒绝所有请求) for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = DispatchCreate; // 默认指向 Create 处理 } // 覆盖关键分发函数 DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DispatchCleanup; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; return STATUS_SUCCESS; } // AddDevice: 设备添加回调(PnP 触发) extern "C" NTSTATUS AddDevice(_In_ PDRIVER_OBJECT DriverObject, _In_ PDEVICE_OBJECT PhysicalDeviceObject) { UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(PhysicalDeviceObject); return STATUS_SUCCESS; // 简单返回成功,不创建设备对象(仅演示) } // Unload: 驱动卸载回调(仅当无设备对象时才调用) extern "C" VOID Unload(_In_ PDRIVER_OBJECT DriverObject) { UNREFERENCED_PARAMETER(DriverObject); } // DispatchCreate: 处理 CreateFile 打开请求 extern "C" NTSTATUS DispatchCreate(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } // DispatchCleanup & DispatchClose: 清理与关闭 extern "C" NTSTATUS DispatchCleanup(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } extern "C" NTSTATUS DispatchClose(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }逻辑说明与参数要点:
UNREFERENCED_PARAMETER(x)是 WDK 宏,用于消除“未使用参数”编译警告,不可删除,否则cl.exe会报C4100错误;IoCompleteRequest(Irp, IO_NO_INCREMENT)是 IRP 完成的强制步骤:第一个参数是待完成的 IRP 指针,第二个参数是 I/O 优先级增量(内核驱动一律用IO_NO_INCREMENT,用户态驱动才用IO_NO_INCREMENT以外的值);Irp->IoStatus.Status必须显式赋值,否则为随机值,用户态CreateFile可能返回INVALID_HANDLE_VALUE但 GetLastError() 为 0,极难排查;- 此骨架不创建设备对象,因此无法被用户态打开(
CreateFile会失败)。真实驱动必须在AddDevice中调用IoCreateDeviceSecure创建设备,并在DriverEntry中设置DriverObject->DriverExtension->AddDevice = AddDevice(模板已自动设置)。
3.3MyWdmDriver.h:定义设备名、符号链接与 IOCTL 控制码的“宪法文件”
该头文件是驱动对外暴露的契约,用户态程序必须按此约定访问:
// MyWdmDriver.h #pragma once #include <ntddk.h> // 设备对象名称(内核可见,格式固定) #define DEVICE_NAME L"\\Device\\MyWdmDriver" // 符号链接名称(用户态 CreateFile 使用的路径) #define SYMBOLIC_LINK_NAME L"\\DosDevices\\MyWdmDriver" // 自定义 IOCTL 控制码:必须用 CTL_CODE 宏生成,否则用户态 DeviceIoControl 失败 // CTL_CODE(设备类型, 功能码, 方法, 访问权限) #define IOCTL_MYDRIVER_DO_SOMETHING CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) // 驱动内部使用的常量 #define MYDRIVER_POOL_TAG 'DWMY' // 4 字节 ASCII 标签,小端序,调试时用于 Pool 查找参数说明:
FILE_DEVICE_UNKNOWN:设备类型,可选FILE_DEVICE_DISK、FILE_DEVICE_NETWORK等,但自定义驱动建议用UNKNOWN,避免与系统设备冲突;0x800:功能码,范围0x800–0xFFF为厂商自定义区,严禁重复,建议用#define常量而非魔法数字;METHOD_BUFFERED:表示数据缓冲区由系统在内核中分配(Irp->AssociatedIrp.SystemBuffer),最简单安全;其他方法(METHOD_IN_DIRECT)需处理 MDL,新手慎用;FILE_ANY_ACCESS:允许任意访问权限打开设备;生产环境应设为FILE_READ_DATA | FILE_WRITE_DATA并在Create分发中校验。
4. 编译、签名、安装全流程:从源码到可加载 .sys 的 5 个必过关卡
4.1 编译:VS + WDK 组合必须匹配,否则出现“LNK2001 无法解析外部符号”
WDK 与 Visual Studio 版本存在严格兼容矩阵。截至 2024 年,主流组合为:
| WDK 版本 | 对应 VS 版本 | 编译器工具集 | 关键检查点 |
|---|---|---|---|
| WDK 22621 (22H2) | VS 2022 17.4+ | v143 | 必须安装 “C++ Universal Windows Platform tools” 工作负载 |
| WDK 22000 (21H2) | VS 2019 16.11+ | v142 | 若用 VS 2022 编译,需手动修改.vcxproj中<PlatformToolset>v142</PlatformToolset> |
| WDK 19041 (20H1) | VS 2019 16.4+ | v142 | 已不支持 ARM64,新项目勿选 |
编译命令(命令行方式,便于 CI/CD):
# 进入驱动工程目录 cd MyWdmDriver # 调用 VS 开发者命令提示符(确保 vcvarsall.bat 已执行) msbuild MyWdmDriver.vcxproj /p:Configuration="Win10 Release" /p:Platform="x64" /t:Rebuild/p:Configuration="Win10 Release":WDK 工程的配置名不是 “Release”,而是"Win10 Release"或"Win11 Release",取决于目标 Windows 版本;/p:Platform="x64":必须显式指定平台,WDK 不支持AnyCPU;/t:Rebuild:强制清理并重建,避免旧 PDB 干扰。
编译成功后,输出位于x64\Win10 Release\MyWdmDriver.sys,同时生成MyWdmDriver.pdb(调试符号)。
4.2 签名:未签名驱动在 Win10/11 上默认拒绝加载,必须走 EV 证书或测试签名
Windows 强制要求内核驱动必须签名,否则sc create会报1275错误(“驱动程序被策略阻止”)。两种合法路径:
- EV 代码签名证书(生产环境必需):向 DigiCert、Sectigo 等 CA 购买,价格 $500+/年,用于最终发布版签名;
- 测试签名(开发/测试阶段):免费,但需启用“测试模式”且每次重启后桌面右下角显示水印。
测试签名完整流程:
# 1. 生成测试证书(仅首次) makecert -r -pe -ss PrivateCertStore -n "CN=MyTestRoot" MyTestRoot.cer # 2. 将证书安装到“受信任的根证书颁发机构” certmgr -add MyTestRoot.cer -s -r localMachine root # 3. 用证书对 .sys 签名 signtool sign /v /s PrivateCertStore /n "MyTestRoot" /t http://timestamp.digicert.com MyWdmDriver.sys # 4. 启用测试模式(需管理员权限,重启生效) bcdedit /set testsigning on提示:
signtool需安装 Windows SDK,路径通常为C:\Program Files (x86)\Windows Kits\10\bin\<ver>\x64\signtool.exe。签名后可用signtool verify /v MyWdmDriver.sys验证。
4.3 安装:sc create+sc start是标准流程,但必须注意服务类型与启动类型
驱动在 Windows 中以“服务”形式管理,但类型为SERVICE_KERNEL_DRIVER(非SERVICE_WIN32_OWN_PROCESS):
# 以管理员身份运行 CMD sc create MyWdmDriver binPath= C:\path\to\MyWdmDriver.sys type= kernel start= demand error= normal sc start MyWdmDriverbinPath=后必须跟绝对路径,且路径中不能有空格(若有,用短路径名C:\PROGRA~1\...或引号包裹,但sc不支持引号,故推荐无空格路径);type= kernel:声明为内核驱动,start= demand表示手动启动(非系统启动时自动加载);error= normal:出错时记录到事件日志,便于排错。
启动后,若失败,立即查eventvwr.msc→ Windows 日志 → 系统,筛选来源为Service Control Manager,错误事件 ID 7000 或 7026 会明确提示失败原因(如“找不到指定模块”=签名失败,“拒绝访问”=测试模式未开)。
5. 驱动开发避坑指南:5 条血泪经验总结,每一条都曾让我蓝屏三次以上
5.1 现象:DriverEntry返回STATUS_SUCCESS,但AddDevice完全不被调用
原因:INF 文件未正确关联硬件 ID,或设备未被 PnP 枚举到。AddDevice仅在 PnP 管理器发现匹配的硬件(如 USB VID/PID)时调用,纯sc create/start不会触发它。
解决:
- 确认 INF 中
[SourceDisksFiles]指向正确的.sys文件; [Manufacturer]和[Models]段中硬件 ID(如USB\VID_045E&PID_00F0)必须与实际设备一致;- 运行
pnputil /add-driver MyDriver.inf /install后,在设备管理器中“操作→扫描检测硬件改动”。
5.2 现象:用户态CreateFile返回INVALID_HANDLE_VALUE,GetLastError()为 2(系统找不到指定文件)
原因:符号链接未创建,或设备对象名/符号链接名拼写错误(大小写敏感、反斜杠数量错误)。
解决:
- 在
AddDevice中确认调用了IoCreateSymbolicLink:RtlInitUnicodeString(&symbolicLinkName, SYMBOLIC_LINK_NAME); status = IoCreateSymbolicLink(&symbolicLinkName, &deviceName); // deviceName 是 \\Device\\xxx - 用
WinObj工具(Sysinternals 套件)查看\DosDevices\下是否存在MyWdmDriver符号链接。
5.3 现象:驱动加载后,系统立即蓝屏,错误代码IRQL_NOT_LESS_OR_EQUAL
原因:在高于DISPATCH_LEVEL的 IRQL(如DIRQL)下调用了分页内存函数(如ExAllocatePoolWithTag传入PagedPool),或访问了分页内存地址。
解决:
- 所有驱动代码必须使用
NonPagedPool(除非你明确知道何时用PagedPool且已提升 IRQL); - 在
DriverEntry开头添加KdBreakPoint(),用 WinDbg 连接内核调试,蓝屏时!analyze -v查看崩溃线程调用栈,定位非法内存访问点。
5.4 现象:DeviceIoControl调用成功,但Irp->AssociatedIrp.SystemBuffer为NULL
原因:IOCTL 控制码定义错误,METHOD_BUFFERED未生效,或用户态传入的lpInBuffer为NULL且nInBufferSize非 0。
解决:
- 用
!ioctlWinDbg 命令验证控制码是否被正确识别; - 用户态代码中确保:
DWORD bytesReturned; DeviceIoControl(hDevice, IOCTL_MYDRIVER_DO_SOMETHING, inBuffer, inSize, outBuffer, outSize, &bytesReturned, NULL);inBuffer可为NULL,但此时inSize必须为 0。
5.5 现象:驱动卸载后,设备管理器中设备仍显示“正在停止”,数分钟后才消失
原因:DriverObject->DriverUnload函数中未等待所有 IRP 完成,或设备对象引用计数未归零(如忘记调用IoDeleteDevice)。
解决:
- 在
Unload函数中,必须:- 调用
IoDeleteDevice(pDeviceObject)删除每个设备对象; - 调用
IoDeleteSymbolicLink(&symbolicLinkName)删除符号链接; - 释放所有池内存(
ExFreePoolWithTag);
- 调用
- 使用
!drvobj MyWdmDriver 2WinDbg 命令查看驱动对象引用计数,应为 0。
6. 验证驱动行为与调试技巧:用 WinDbg 实时观测 IRP 流、内存泄漏与即插即用状态
6.1 用 WinDbg 实时跟踪 IRP 生命周期:从CreateFile到IoCompleteRequest
内核调试是驱动开发的“后悔药”。配置 WinDbg(预览版)连接目标机后,下断点观察 IRP 流:
# 在 DriverEntry 设置断点,确认加载 bp MyWdmDriver!DriverEntry # 在 DispatchCreate 下断,观察 IRP 结构 bp MyWdmDriver!DispatchCreate # 断下后,用以下命令查看 IRP 详情 dt nt!_IRP @rdx # rdx 是 DispatchCreate 第二个参数(PIRP) dt nt!_IO_STACK_LOCATION poi(@rdx+0x40) # 获取当前栈位置 !irp @rdx # WinDbg 扩展命令,打印完整 IRP 状态树@rdx是 x64 调用约定中第二个参数寄存器,DispatchCreate的Irp参数即在此;!irp命令会显示 IRP 的CurrentLocation、StackCount、PendingReturned等关键字段,确认IoCompleteRequest是否被调用;- 若
!irp显示IRP_PENDING且长时间不结束,说明你的分发函数未调用IoCompleteRequest,用户态线程将永久阻塞。
6.2 检测内存泄漏:用!poolused和!vm定位未释放的 NonPagedPool
驱动中最隐蔽的 Bug 是内存泄漏——每次ExAllocatePoolWithTag后忘记ExFreePoolWithTag,系统运行数小时后耗尽 NonPagedPool 导致蓝屏。
诊断步骤:
# 1. 查看当前 NonPagedPool 使用量(单位 KB) !vm 1 # 2. 按 Tag 统计内存占用('DWMY' 是我们定义的 TAG) !poolused 2 # 3. 查看特定 Tag 的所有分配堆栈(需开启 Pool Tracking) !poolval DWMY!vm 1输出中NonPagedPool Usage若持续增长(> 200MB),高度可疑;!poolused 2会列出所有 Tag 及其字节数,找到DWMY行,记下字节数;!poolval DWMY需提前在目标机启用pool tracking(bcdedit /set {current} testsigning on后重启,再运行poolmon -i),它会显示每次ExAllocatePoolWithTag('DWMY')的调用栈,精准定位泄漏点。
6.3 监控即插即用状态机:用!pnp命令查看设备状态流转
WDM 驱动的核心是 PnP 状态机。设备插入/拔出时,内核会按顺序调用AddDevice→StartDevice→QueryStop→StopDevice→RemoveDevice。若某步卡住,设备管理器会显示“正在停止”。
实时观测命令:
# 查看所有 PnP 设备及其当前状态 !pnp 0 # 查看指定驱动(MyWdmDriver)的所有设备对象状态 !pnp 1 MyWdmDriver # 查看最近一次 PnP 请求(如 Remove)的详细过程 !pnp 3!pnp 0输出中,State字段为Started表示设备已就绪;Stopping表示正在卸载;Removed表示已移除;- 若某设备
State长期卡在Stopping,说明StopDevice或RemoveDevice回调中存在死锁(如等待自旋锁未释放); !pnp 3会打印 PnP 请求的完整调用栈,结合源码确认是否在StopDevice中遗漏了KeReleaseSpinLock。
我习惯在每个 PnP 回调函数开头加KdPrint(("MyDriver: %s called\n", __FUNCTION__));,再配合!logopen c:\debug.log抓取内核打印,这样即使不连 WinDbg,也能从DebugView(Sysinternals)看到状态流转全貌。这招救过我三次——一次是RemoveDevice中忘了IoDetachDevice,一次是StartDevice里IoConnectInterrupt失败未处理,还有一次是QueryStop返回了STATUS_UNSUCCESSFUL却没写日志。希望帮到你。
本文还有配套的精品资源,点击获取