第一个驱动程序:Hello World 驱动编写
说实话,每个驱动开发者的起点都一样——写一个Hello World。我当年入行时,师傅丢给我一句话:“先让系统把你的代码跑起来,再说别的。”今天我们就来走完这第一步。
DriverEntry 与 DriverUnload 例程
驱动程序的入口点,不是main函数,而是DriverEntry。它就像一扇门,系统加载驱动时第一个敲的就是它。
#include <ntddk.h>
NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
DbgPrint("Hello World! 驱动已加载\n");
// 设置卸载例程
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
VOID DriverUnload(
_In_ PDRIVER_OBJECT DriverObject
)
{
DbgPrint("驱动已卸载,再见!\n");
}
这里有个细节我想强调一下:DbgPrint 的输出,你用 DebugView 才能看到。我第一次写驱动时,傻傻盯着控制台等了半天……嗯,后来才知道要看调试输出。
关键点:DriverEntry 必须返回 STATUS_SUCCESS,否则系统认为加载失败。DriverUnload 是必须注册的,不然驱动卸载时会蓝屏。
INF 文件结构解析
INF 文件是驱动的身份证。没有它,Windows 不认识你的驱动。我见过不少新手,代码写得挺好,结果 INF 写错了,安装死活不成功。
一个最简的 INF 文件长这样:
[Version]
Signature="$WINDOWS NT$"
Class=Sample
ClassGuid={78A1C341-4539-11D3-B98D-00C04F8EFB5C}
Provider=%ManufacturerName%
DriverVer=09/21/2024,1.0.0.0
[Manufacturer]
%ManufacturerName%=Standard,NTamd64
[Standard.NTamd64]
%DeviceDesc%=MyDevice_Inst,*PNP0000
[MyDevice_Inst]
CopyFiles=MyDevice.CopyFiles
[MyDevice.CopyFiles]
MyDriver.sys
[Strings]
ManufacturerName="MyCompany"
DeviceDesc="My Hello World Driver"
结构其实不复杂,我拆开给你看:
| 节 | 作用 |
|---|---|
| [Version] | 声明驱动版本、签名信息 |
| [Manufacturer] | 指定厂商和平台架构 |
| [Standard.NTamd64] | 硬件ID与驱动安装节的映射 |
| [MyDevice_Inst] | 安装指令,比如复制文件 |
| [Strings] | 字符串常量定义,避免硬编码 |
我的习惯:每次写 INF 文件,我都会先复制一个能用的模板,再改参数。从头写容易漏掉 ClassGuid 这种细节。
驱动签名与测试模式
64位 Windows 强制要求驱动签名。不签名的驱动,系统直接拒绝加载。我在项目中遇到过这种情况:调试好的驱动,换到客户机器上死活装不上,最后发现是签名问题。
开发阶段怎么办?开启测试模式:
bcdedit /set testsigning on
重启后,桌面右下角会出现“测试模式”水印。这时候你就可以加载未签名的驱动了。
注意:测试模式仅用于开发机。生产环境必须使用正式签名。我曾经见过有人把测试模式的驱动部署到服务器上,结果安全策略直接拦截,系统日志里全是错误。
签名的流程大致是:
- 生成证书(测试用自签名,正式用CA签发)
- 使用 SignTool 对 .sys 文件签名
- 在 INF 文件中添加签名信息
- 使用 Catalog 文件打包签名
说白了,签名就是给驱动盖个章,告诉系统“这东西是安全的”。
知识体系结构图
下面这张图,帮你理清本章的核心脉络:
避坑指南:我曾经在 INF 文件中写错了 ClassGuid,结果设备管理器里驱动显示黄色感叹号。排查了半天才发现是 GUID 不对。建议你直接从 WDK 示例中复制 GUID,别自己编。
好了,第一个驱动就这么简单。代码不到20行,INF文件不到30行。你想想看,驱动开发其实没那么神秘,对吧?