
1. 为什么还要折腾Windows驱动开发这套环境很多人第一次听到“Windows驱动开发”这六个字脑子里冒出来的画面大概是蓝屏、内核崩溃、调试器里一堆看不懂的地址。说实话我刚开始接触这块的时候也是这个感觉。但实际做下来你会发现驱动开发并没有想象中那么玄乎它本质上就是写一个跑在操作系统内核态或者内核态边缘的模块让硬件或者系统功能按照你的意图工作。而搭建这套开发环境就是所有后续工作的地基。这篇文章要聊的就是在Windows 10上用Visual Studio 2019把驱动开发环境从零搭起来然后跑通一个最简的Hello World驱动。关键词很明确Windows 10、VS2019、驱动开发、环境搭建、Hello World。适合谁看适合那些已经会写C/C、对Windows用户态编程有一定了解、想往系统底层方向走一步的开发者。如果你连指针和结构体都还没搞明白那建议先把C语言基础打牢再回来不然内核里的坑会让你怀疑人生。为什么选VS2019而不是更新的版本原因很实际。VS2019对WDKWindows Driver Kit的集成已经非常成熟社区资料多遇到问题容易搜到答案。而且VS2019的安装器对WDK组件的管理比较清晰不像某些新版本刚出来时各种兼容性小毛病。当然如果你已经在用VS2022大部分步骤也是通的但本文以VS2019为准来写因为这是目前最稳的组合之一。整个环境搭建的核心逻辑其实就三件事装对工具、配对版本、跑通第一个驱动。听起来简单但每一步都有细节。比如WDK的版本必须和SDK匹配VS2019的工作负载必须勾选“使用C的桌面开发”加上“Windows驱动工具包”少一个后面就编译不过。再比如测试驱动的时候目标机器的测试签名模式必须打开否则你加载驱动的时候系统直接拒绝。这些坑我在后面会一个个拆开讲。2. 环境搭建前的整体思路与工具选型2.1 为什么是VS2019加WDK这套组合Windows驱动开发的工具链经过这么多年演变目前主流就两条路一条是纯命令行加WDK用MSBuild或者自己写构建脚本另一条是Visual Studio集成WDK图形化配置项目属性。对于刚入门的人来说VS2019加WDK的组合明显更友好因为项目模板、编译配置、调试附加这些操作都有界面引导不用一上来就面对一堆命令行参数。VS2019在这个组合里扮演的角色是“项目管理和构建入口”WDK提供的是驱动编译需要的头文件、库和工具。两者版本必须对应比如VS2019 16.x系列通常搭配WDK 2004或者2104这些版本。如果你装了一个太新的WDKVS2019可能识别不了装了一个太旧的又可能缺少某些新API的定义。所以我的建议是先确定VS2019的版本号然后去官方文档查对应的WDK版本别凭感觉乱装。2.2 虚拟机还是物理机测试环境怎么选驱动开发最怕的就是把宿主机搞崩。虽然Hello World驱动基本不会造成蓝屏但养成好习惯很重要。我强烈建议在虚拟机里做测试VMware或者Hyper-V都行。虚拟机的好处是快照随便打驱动加载失败导致系统起不来的时候回滚快照就是几秒钟的事。物理机虽然性能好但一旦内核出问题恢复起来麻烦得多。不过虚拟机也有个坑某些驱动需要访问真实硬件虚拟机里可能模拟不了。但Hello World这种纯软件驱动完全没问题它只是在内核里打印一句话不涉及硬件操作。所以入门阶段用虚拟机足够了。等后面要做设备驱动、过滤驱动的时候再考虑用物理测试机或者双机调试。2.3 测试签名模式绕不开的一道坎Windows 10 64位系统对驱动签名有强制要求。你编译出来的驱动如果没有经过微软的正式签名系统默认是不让加载的。对于开发阶段解决办法就是打开测试签名模式。具体操作是以管理员身份运行命令提示符执行两条命令bcdedit /set testsigning on和bcdedit /set nointegritychecks on然后重启。重启后桌面右下角会出现“测试模式”的水印这说明测试签名已经生效了。这里有个细节要注意有些主板开启了Secure Boot测试签名模式可能打不开。如果执行命令后重启发现水印没出来或者驱动还是加载失败那就需要进BIOS把Secure Boot关掉。这个操作因主板而异一般在“Security”或者“Boot”选项卡里。关掉Secure Boot之后测试签名模式就能正常工作了。3. 手把手搭建VS2019驱动开发环境3.1 VS2019安装时的关键勾选项下载VS2019社区版就行社区版对个人开发者和小团队是免费的功能上做驱动开发完全够用。安装器启动后在“工作负载”标签页里必须勾选“使用C的桌面开发”。这个工作负载包含了MSVC编译器、Windows SDK的基础部分是驱动编译的底层依赖。然后在“单个组件”标签页里搜索“Windows Driver Kit”或者“WDK”把跟驱动相关的组件都勾上。通常会有“Windows Driver Kit”和“Windows Driver Kit Visual Studio扩展”两个选项都要选。如果你不确定具体选哪个可以在搜索框里输入“driver”把所有跟驱动沾边的组件都勾上多装一点不影响少装了后面编译报错还得回来补。安装位置建议放在SSD上因为VS2019加上WDK占用的空间不小机械硬盘上编译驱动会明显慢。安装过程大概需要20到40分钟取决于网络和磁盘速度。装完之后别急着打开VS先确认WDK是否安装成功。3.2 验证WDK是否安装到位打开文件资源管理器进入WDK的默认安装目录通常在C:\Program Files (x86)\Windows Kits\10\下面。你会看到几个子目录比如Include、Lib、bin。Include里面应该有km内核模式和um用户模式这两个文件夹km就是驱动开发用的头文件目录。如果这个目录不存在说明WDK没装好需要重新运行VS安装器修复。另一个验证方法是打开VS2019新建项目的时候在搜索框输入“Driver”如果能看到“Empty WDM Driver”或者“Kernel Mode Driver”这类模板说明WDK的VS扩展已经生效了。如果搜不到可能是安装的时候漏了扩展组件回安装器里补上就行。3.3 创建第一个驱动项目在VS2019里新建项目选择“Empty WDM Driver”模板。项目名字随便起比如“HelloDriver”。创建完成后你会看到解决方案资源管理器里有一个.inf文件和一个.c文件。.inf文件是驱动安装信息文件后面加载驱动的时候会用到。.c文件就是驱动入口默认里面是空的。这里有个细节VS2019创建的WDM驱动项目默认是支持即插即用PnP的框架入口函数是DriverEntry。如果你想要更简单的纯内核驱动也可以手动改成不依赖PnP的形式但对于Hello World来说默认模板就够用了。我们只需要在DriverEntry里加一行打印语句然后让驱动能正常加载和卸载就行。4. Hello World驱动的代码实现与编译4.1 DriverEntry函数的基本结构驱动程序的入口点叫DriverEntry相当于用户态程序的main函数。它的签名是固定的NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)第一个参数是驱动对象指针系统在加载驱动的时候会创建这个对象后面很多操作都要通过它。第二个参数是注册表路径系统会把你驱动相关的注册表信息传进来。对于Hello World来说这两个参数暂时用不到但签名不能改。在函数体里我们要做两件事第一打印一句“Hello World”第二设置驱动的卸载函数这样驱动才能被正常卸载。打印用DbgPrintEx或者KdPrint这两个函数会把信息输出到调试器里。注意不是输出到控制台窗口而是输出到内核调试器。如果你没有配置双机调试可以用DebugView这个工具来捕获内核打印信息。4.2 完整代码与逐行解释#include ntddk.h VOID DriverUnload(PDRIVER_OBJECT DriverObject) { UNREFERENCED_PARAMETER(DriverObject); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, HelloDriver: Unloaded\n); } NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DriverObject-DriverUnload DriverUnload; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, HelloDriver: Hello World!\n); return STATUS_SUCCESS; }#include ntddk.h是驱动开发必须包含的头文件里面定义了内核API和基本数据类型。DriverUnload是我们自己写的卸载函数当驱动被卸载时系统会调用它。UNREFERENCED_PARAMETER宏是为了消除编译器对未使用参数的警告在内核开发里这个宏很常用。DriverObject-DriverUnload DriverUnload;这一行很关键。如果不设置这个驱动加载后就没法卸载只能重启系统。对于开发阶段来说这简直是噩梦因为每次改代码都要重启。设置卸载函数之后就可以用sc stop命令把驱动停掉然后重新编译加载。DbgPrintEx的第一个参数是组件ID用DPFLTR_IHVDRIVER_ID就行第二个参数是级别DPFLTR_INFO_LEVEL表示信息级别。最后一个参数是格式化字符串跟用户态的printf用法一样。4.3 编译配置的注意事项在VS2019里编译驱动默认配置是Debug加x64。这里有几个地方要检查第一平台必须是x64因为现在Windows 10基本都是64位32位驱动在64位系统上加载不了。第二配置属性里的“Driver Settings”下面Target OS Version要选Windows 10Target Platform选Desktop。这些默认一般是对的但如果你从别人那里拷来的项目就要确认一下。编译的时候可能会遇到一个常见错误找不到ntddk.h。这通常是因为WDK的包含路径没配好。解决办法是在项目属性里VC目录的包含目录中手动加上WDK的Include\10.x.x.x\km路径。不过正常情况下VS2019加WDK装好之后会自动配好这些路径不需要手动改。编译成功后在项目的x64\Debug目录下会生成一个.sys文件这就是驱动文件。同时还会生成一个.inf文件和.cat文件后面安装驱动的时候会用到。5. 驱动的加载、测试与调试5.1 用sc命令加载驱动加载驱动最简单的方式是用sc命令。先把编译好的.sys文件拷到一个固定目录比如C:\TestDriver\HelloDriver.sys。然后以管理员身份打开命令提示符依次执行sc create HelloDriver type kernel binPath C:\TestDriver\HelloDriver.sys sc start HelloDriver第一条命令创建了一个叫HelloDriver的服务类型是内核驱动二进制路径指向我们的.sys文件。第二条命令启动这个服务也就是加载驱动。如果一切正常你会看到服务状态变成RUNNING。这时候用DebugView就能看到“HelloDriver: Hello World!”的输出。停止和删除驱动的命令是sc stop HelloDriver sc delete HelloDriver注意sc create命令里等号后面必须有一个空格这是sc命令的语法要求写成typekernel会报错。这个坑我踩过好几次每次都要愣一下才想起来。5.2 用DebugView捕获内核打印信息DebugView是Sysinternals套件里的一个工具可以捕获内核态的DbgPrintEx输出。下载后以管理员身份运行在菜单里勾选“Capture Kernel”和“Capture Global Win32”然后你就能看到驱动打印的信息了。如果没有勾选“Capture Kernel”是看不到内核打印的这一点很容易忽略。DebugView的过滤功能很实用。你可以在“Filter”菜单里设置只显示包含“HelloDriver”的行这样就不会被系统其他组件的打印信息淹没。另外DebugView默认的缓冲区大小有限如果打印信息太多旧的会被冲掉。可以在“Options”里把缓冲区调大一些。5.3 双机调试的简要配置如果你不想用DebugView或者需要更强大的调试能力可以配置双机调试。一台机器作为调试机运行WinDbg另一台作为目标机加载驱动。两台机器之间用网络或者串口连接。配置过程稍微复杂一些需要在目标机上执行bcdedit /debug on和bcdedit /dbgsettings net hostip:xxx.xxx.xxx.xxx port:xxxxx然后在调试机上用WinDbg连接。对于Hello World这个级别的驱动DebugView完全够用了。双机调试更适合后面做复杂驱动的时候用因为那时候你需要断点、查看内存、单步执行这些功能。入门阶段先把DebugView用熟后面再折腾双机调试也不迟。6. 常见问题与排查技巧实录6.1 驱动加载失败的错误码对照错误码含义常见原因解决办法0x80070002找不到文件.sys路径写错检查binPath是否指向正确的文件0x80070005拒绝访问没用管理员权限用管理员身份运行cmd0x8007041D服务启动超时驱动入口函数卡住检查DriverEntry里是否有死循环0xC000036B驱动签名验证失败测试签名没开执行bcdedit /set testsigning on并重启0xC000010E驱动已被加载重复加载同一个驱动先sc stop再sc start这些错误码是我在实际操作中遇到过的尤其是0xC000036B这个签名错误新手很容易卡在这里。明明代码没问题编译也过了就是加载不上最后发现是测试签名没开。所以再强调一遍测试签名模式一定要开Secure Boot一定要关。6.2 编译时的典型报错与处理编译驱动时最常见的报错是“无法打开源文件 ntddk.h”。这个前面提过通常是WDK没装好或者包含路径不对。另一个常见报错是“未解析的外部符号”这通常是因为链接器找不到某个库。驱动开发需要链接ntoskrnl.lib、hal.lib这些库正常情况下VS2019会自动配好但如果项目属性被改过就可能出问题。还有一个坑是字符集问题。驱动开发建议用Unicode字符集如果项目属性里设成了多字节字符集某些API调用会报错。在项目属性的“常规”页面里把“字符集”改成“使用Unicode字符集”就行。6.3 驱动加载后系统变慢或卡顿Hello World驱动本身不会导致系统卡顿但如果你在DriverEntry里做了耗时操作比如等待某个事件、循环查询某个状态系统启动时就会卡住。内核代码里绝对不能有长时间的阻塞操作因为内核是全局的你卡住了整个系统都动不了。另外如果你在驱动里分配了内存但没释放卸载驱动后那块内存就泄漏了。虽然Hello World不涉及内存分配但后面写复杂驱动的时候ExAllocatePool和ExFreePool必须成对出现。内核内存泄漏比用户态内存泄漏严重得多因为内核内存池是有限的泄漏多了系统直接蓝屏。6.4 卸载驱动时的注意事项卸载驱动之前一定要确保没有其他程序在使用这个驱动。如果驱动被占用sc stop会失败。对于Hello World来说一般不会有占用问题但养成好习惯很重要。另外卸载驱动后最好用sc delete把服务删掉不然注册表里会残留服务项下次创建同名服务的时候会报错。如果你忘了设置DriverUnload函数驱动加载后就无法卸载只能重启。所以再强调一次DriverObject-DriverUnload DriverUnload;这一行必须写。我见过有人忘了写这行结果每次改代码都要重启虚拟机效率极低。7. 从Hello World到实际驱动开发的延伸跑通Hello World只是第一步后面要学的东西还很多。比如设备对象的创建、IRPI/O请求包的处理、即插即用和电源管理、DMA操作、过滤驱动等等。但不管多复杂的驱动基本框架都是一样的入口函数里创建设备对象设置分发函数处理各种IRP最后在卸载函数里清理资源。我的建议是跑通Hello World之后下一步可以试着写一个简单的过滤驱动拦截一下键盘或者鼠标的输入。这个过程中你会接触到设备栈、IRP拦截、完成例程这些概念对理解Windows驱动模型非常有帮助。再往后可以研究WDFWindows Driver Framework它把很多底层细节封装起来了写驱动会轻松不少但前提是你得先理解WDM的基本原理不然用WDF也是知其然不知其所以然。环境搭建这块一旦配好了后面就是反复编译、加载、调试的循环。VS2019加WDK这套组合的稳定性还是不错的偶尔遇到编译报错多半是配置问题而不是工具本身的问题。把本文的步骤走一遍你应该能在一个下午之内把Hello World驱动跑起来。后面遇到具体问题的时候再针对性地查资料效率会高很多。