Skip to the content.

Windows 驱动开发环境

安装

统一安装入口:

pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass `
  -File "$HOME\.dotfiles\scripts\windows-driver-setup.ps1"

全新 Windows 只有 Windows PowerShell 和 WinGet 时,先安装脚本依赖,再重新打开终端:

winget install --id Microsoft.PowerShell --exact `
  --accept-package-agreements --accept-source-agreements
winget install --id Git.Git --exact `
  --accept-package-agreements --accept-source-agreements

不使用 dotfiles 脚本时,等价的官方 WDK 环境下载入口是:

$configurationUri = 'https://raw.githubusercontent.com/microsoft/Windows-driver-samples/main/_wdk_utils/winget/configs/wdk-vscommunity.dsc.yaml'

# 只在 WinGet Configuration 尚未启用时执行一次。
winget configure --enable

winget configure -f $configurationUri `
  --accept-configuration-agreements `
  --disable-interactivity

winget install --id Microsoft.WinDbg --exact `
  --accept-package-agreements `
  --accept-source-agreements

New-Item -ItemType Directory -Force -Path "$HOME\data" | Out-Null
git clone --depth 1 `
  https://github.com/microsoft/Windows-driver-samples.git `
  "$HOME\data\Windows-driver-samples"

官方 WinGet 配置会跟随 Microsoft 更新。安装完成后,应记录实际 SDK、WDK 和 Visual Studio 版本,不要只依赖文档中的历史版本号。

脚本使用 Microsoft Windows-driver-samples 仓库提供的官方 WinGet 配置,安装:

官方说明:

SDK 和 WDK 的 build number 必须一致,QFE 可以不同。当前环境是 SDK 10.0.28000.2114 和 WDK 10.1.28000.2526,共同使用 10.0.28000.0 工具目录。

构建 KMDF Echo 示例

pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass `
  -File "$HOME\.dotfiles\scripts\windows-driver-build.ps1"

默认构建:

~/data/Windows-driver-samples/general/echo/kmdf/kmdfecho.sln
Debug | x64 | Rebuild

输出包括:

构建过程完成 INF verification、API validation、catalog generation 和测试签名。

WDK 28000 必须使用 64 位 MSBuild

不要使用 vswhere -find 'MSBuild\**\Bin\MSBuild.exe' 返回的第一个结果,它通常是 32 位:

MSBuild\Current\Bin\MSBuild.exe

WDK 28000 已没有完整的 x86 内核驱动验证链。32 位 MSBuild 会出现:

INF verification exception: 无法加载 DLL“x86\InfVerif.dll”
ApiValidation: aitstatic is returned exit code 193

必须显式使用:

MSBuild\Current\Bin\amd64\MSBuild.exe

构建脚本不会回退到 32 位 MSBuild。

INF verification 1199

使用 Driver Store DIRID 13 时,INF 必须把 DDInstall section 限制到 Windows 10 build 16299 或更高。下面的 decoration 仍然过宽:

%ManufacturerName%=Standard,NTamd64.10.0

WDK 会报告:

error 1199: The syntax 'DIRID 13 (CopyFiles)' was introduced in OS version 10.0.16299

应写成:

%ManufacturerName%=Standard,NTamd64.10.0...16299

[Standard.NTamd64.10.0...16299]

WDK 28000 工具位置

常用路径:

C:\Program Files (x86)\Windows Kits\10\bin\10.0.28000.0\x64\signtool.exe
C:\Program Files (x86)\Windows Kits\10\bin\10.0.28000.0\x64\stampinf.exe
C:\Program Files (x86)\Windows Kits\10\bin\10.0.28000.0\x86\Inf2Cat.exe
C:\Program Files (x86)\Windows Kits\10\Tools\10.0.28000.0\x64\infverif.exe
C:\Program Files (x86)\Windows Kits\10\build\10.0.28000.0\WindowsDriver.Common.targets

Inf2Cat.exe 虽然位于 x86 目录,但二进制本身是 x64。不要根据目录名猜测 PE 架构。

加载和调试边界

编译和测试签名可以在开发机完成。驱动加载、TESTSIGNING、Driver Verifier 和内核调试应放在 可回滚快照的独立测试 VM 中;不要直接在日常使用的 Windows 开发机加载示例内核驱动。

MSBuild 会创建 WDKTestCert 并签署 SYS/CAT,但开发机没有信任这个自签名根证书。因此 SignTool verify /kp 能显示签名链,同时返回 root certificate which is not trusted。这是当前 安全边界的预期结果;不要为了让验证命令变绿而把测试证书加入日常系统的 Trusted Root。

下一步是准备独立 target VM,通过 WinDbg 进行 host/target 内核调试:

当前 Windows 开发机本身是 QEMU Standard PC (Q35 + ICH9, 2009) guest,并且 HypervisorPresent=True。不要默认依赖 guest 内再运行 Hyper-V;优先从外层虚拟化环境创建 第二台 Windows target VM。

将 KmdfHello 安装到测试 VM

目前完成的是“构建并由 WDK 测试签名”,不是“驱动已安装”。测试签名的内核驱动还需要目标机 信任测试证书、启用 TESTSIGNING、重启并创建匹配 INF hardware ID 的设备实例。它们会改变 系统信任、启动配置和内核状态,所以只应在有控制台和可回滚快照的测试 VM 中执行。

推荐顺序:

构建 -> 创建 VM 快照 -> 导出并复制证书和驱动包 -> 信任证书
     -> 启用 TESTSIGNING -> 重启 -> 连接 WinDbg -> 创建设备并安装
     -> 验证 -> 删除设备和驱动包,或恢复快照

如果当前 QEMU Windows guest 已经是一次性测试 VM,并且能从外层创建快照和访问控制台,也可以 把它直接作为 target;不必为了形式再嵌套一台 VM。

1. 构建驱动和 DevCon

在 build machine 上构建 KmdfHello:

pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass `
  -File "$HOME\.dotfiles\scripts\windows-driver-build.ps1" `
  -Solution "$HOME\data\kmdf-hello\kmdf-hello.vcxproj"

驱动包目录是:

C:\Users\97936\data\kmdf-hello\build\x64\Debug\kmdf-hello

其中必须同时存在 KmdfHello.sys、kmdf-hello.inf 和 KmdfHello.cat。不要只复制 SYS。

当前 WDK 28000 没有预装 devcon.exe,需要从已经下载的官方 samples 构建:

pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass `
  -File "$HOME\.dotfiles\scripts\windows-driver-build.ps1" `
  -Solution "$HOME\data\Windows-driver-samples\setup\devcon\devcon.vcxproj" `
  -Configuration Debug `
  -Platform x64

$devcon = Get-ChildItem `
  "$HOME\data\Windows-driver-samples\setup\devcon" `
  -Filter devcon.exe -Recurse |
  Where-Object FullName -Match '\\x64\\' |
  Sort-Object FullName -Descending |
  Select-Object -First 1

if (-not $devcon) {
  throw 'x64 devcon.exe was not found after the build'
}

$devcon.FullName

已验证的输出路径是 C:\Users\97936\data\Windows-driver-samples\setup\devcon\x64\Debug\devcon.exe。

Microsoft 通常建议使用 PnPUtil 管理驱动包。不过 pnputil /add-driver 不会凭空创建这里所需的 root-enumerated devnode;devcon install 会同时创建根设备并安装匹配的驱动,因此本例需要 DevCon。后续删除 Driver Store 中的包仍使用 PnPUtil。

2. 导出 WDKTestCert 并复制完整测试包

在 build machine 上从 catalog 签名中动态取得证书,不要把某一次构建生成的 thumbprint 硬编码到脚本:

$package = "$HOME\data\kmdf-hello\build\x64\Debug\kmdf-hello"
$catalog = Join-Path $package 'KmdfHello.cat'
$certificatePath = Join-Path $package 'WDKTestCert.cer'
$signature = Get-AuthenticodeSignature -FilePath $catalog

if (-not $signature.SignerCertificate) {
  throw "No signer certificate was found in $catalog"
}

Export-Certificate `
  -Cert $signature.SignerCertificate `
  -FilePath $certificatePath `
  -Force

$signature.SignerCertificate |
  Format-List Subject, Thumbprint, NotBefore, NotAfter

把整个 $package 目录复制到 target,例如 C:\driver-test\kmdf-hello。如果 WinDbg 运行在 另一台机器上,还要把 build 输出中的 KmdfHello.pdb 复制到 debugger host 的私有符号目录。

3. 修改 target 前的检查

下面开始的命令都必须在 target 的管理员 PowerShell 中执行。执行前先:

只读检查:

Confirm-SecureBootUEFI
Get-BitLockerVolume -MountPoint 'C:'
bcdedit /enum '{current}'

Confirm-SecureBootUEFI 在传统 BIOS 环境可能报告不支持;这不等于检查失败。若 BCDEdit 返回 The value is protected by Secure Boot policy,应回到 VM 固件设置处理 Secure Boot,而不是继续 尝试安装。

4. 在 target 信任测试证书

$package = 'C:\driver-test\kmdf-hello'
$certificatePath = Join-Path $package 'WDKTestCert.cer'
$certificate = Get-PfxCertificate -FilePath $certificatePath
$thumbprint = $certificate.Thumbprint

Import-Certificate `
  -FilePath $certificatePath `
  -CertStoreLocation 'Cert:\LocalMachine\Root'

Import-Certificate `
  -FilePath $certificatePath `
  -CertStoreLocation 'Cert:\LocalMachine\TrustedPublisher'

foreach ($store in 'Root', 'TrustedPublisher') {
  Get-Item "Cert:\LocalMachine\$store\$thumbprint" |
    Format-List Subject, Thumbprint, NotAfter
}

PnP 驱动安装读取 Local Machine certificate stores。测试证书需要同时进入 Trusted Root Certification Authorities 和 Trusted Publishers;只导入当前用户证书库不够。

5. 启用 TESTSIGNING 并重启 target

bcdedit /set testsigning on
bcdedit /enum '{current}' | Select-String -Pattern 'testsigning'
Restart-Computer

重新登录后再次确认:

bcdedit /enum '{current}' | Select-String -Pattern 'testsigning'

只有 BCDEdit 成功并且 target 已经重启,新的代码完整性策略才会生效。桌面通常也会显示 Test Mode 水印。

6. 先连接 WinDbg,再安装驱动

若要捕获 DriverEntry(),必须先完成 target 的 kernel debugging 配置并让 debugger host 上的 WinDbg 连入,再创建设备。WinDbg 命令示例:

.symfix
.sympath+ D:\symbols\kmdf-hello
.reload
ed nt!Kd_IHVDRIVER_Mask 0xffffffff
bu KmdfHello!DriverEntry
bu KmdfHello!KmdfHelloEvtDeviceAdd
g

D:\symbols\kmdf-hello 应替换为 debugger host 上包含 KmdfHello.pdb 的目录。源码使用 DPFLTR_IHVDRIVER_ID 和 KdPrintEx(),所以上面的 mask 会显示:

KmdfHello: DriverEntry
KmdfHello: EvtDeviceAdd

如果只想观察打印而不下断点,可在 WinDbg 中使用 !dbgprint。网络内核调试的 host/target 配置见本节开头的 Microsoft 文档。

7. 创建 ROOT 设备并安装驱动

在 target 的管理员 PowerShell 中定位或复制 x64 DevCon,然后执行:

$package = 'C:\driver-test\kmdf-hello'
$inf = Join-Path $package 'kmdf-hello.inf'
$devcon = 'C:\driver-test\tools\devcon.exe'

if (-not (Test-Path -LiteralPath $inf)) {
  throw "INF was not found: $inf"
}
if (-not (Test-Path -LiteralPath $devcon)) {
  throw "DevCon was not found: $devcon"
}

& $devcon install $inf 'ROOT\MARTINS3_KMDF_HELLO'
if ($LASTEXITCODE -ne 0) {
  throw "DevCon failed with exit code $LASTEXITCODE"
}

如果 build machine 和 target 是同一台 VM,可直接把 $devcon 改成第 1 步找到的路径。安装 成功后 INF 中的服务名是 Martins3KmdfHello,内核模块名是 KmdfHello.sys。

8. 验证设备、服务、签名和调试输出

$package = 'C:\driver-test\kmdf-hello'
$device = Get-PnpDevice |
  Where-Object InstanceId -Like 'ROOT\MARTINS3_KMDF_HELLO*'

if (-not $device) {
  throw 'ROOT\MARTINS3_KMDF_HELLO device was not found'
}

$device | Format-List Status, Class, FriendlyName, InstanceId
$instanceId = $device.InstanceId
pnputil /enum-devices /instanceid $instanceId /drivers /services
sc.exe query Martins3KmdfHello
sc.exe qc Martins3KmdfHello
Get-CimInstance Win32_SystemDriver `
  -Filter "Name='Martins3KmdfHello'" |
  Format-List Name, State, StartMode, PathName
driverquery /v /fo list |
  Select-String -Pattern 'KmdfHello|Martins3KmdfHello'

Get-AuthenticodeSignature "$package\KmdfHello.sys" |
  Format-List Status, StatusMessage, SignerCertificate
Get-AuthenticodeSignature "$package\KmdfHello.cat" |
  Format-List Status, StatusMessage, SignerCertificate

如果 target 安装了 WDK,还可以使用 kernel-policy verification:

$signtool = Get-ChildItem `
  "${env:ProgramFiles(x86)}\Windows Kits\10\bin" `
  -Filter signtool.exe -Recurse |
  Where-Object FullName -Match '\\x64\\signtool.exe$' |
  Sort-Object FullName -Descending |
  Select-Object -First 1

if (-not $signtool) {
  throw 'x64 signtool.exe was not found'
}

$signtoolPath = $signtool.FullName
& $signtoolPath verify /kp /v "$package\KmdfHello.sys"
& $signtoolPath verify /kp /v "$package\KmdfHello.cat"

WinDbg 中进一步检查:

lm m KmdfHello
!drvobj Martins3KmdfHello 7
!dbgprint

成功标准是:设备状态正常、服务/driver object 存在、模块已加载、签名链可信,并且 WinDbg 能看到 DriverEntry() 与 KmdfHelloEvtDeviceAdd() 的断点或打印。

9. 清理和回滚

最干净的做法是关闭 target 并恢复安装前快照。若要手工清理,先删除精确设备实例:

$devcon = 'C:\driver-test\tools\devcon.exe'
$device = Get-PnpDevice |
  Where-Object InstanceId -Like 'ROOT\MARTINS3_KMDF_HELLO*' |
  Select-Object -First 1

if ($device) {
  & $devcon remove "@$($device.InstanceId)"
}

然后运行 pnputil /enum-drivers /class System /files,按 original name kmdf-hello.inf 和 provider Martins3 确认其 published name,只删除精确匹配的 oemNN.inf:

pnputil /delete-driver oemNN.inf /uninstall /force

最后删除本次测试证书、关闭测试签名并重启:

$certificatePath = 'C:\driver-test\kmdf-hello\WDKTestCert.cer'
$thumbprint = (Get-PfxCertificate -FilePath $certificatePath).Thumbprint

foreach ($store in 'Root', 'TrustedPublisher') {
  Get-Item "Cert:\LocalMachine\$store\$thumbprint" `
    -ErrorAction SilentlyContinue |
    Remove-Item
}

bcdedit /set testsigning off
Restart-Computer

oemNN.inf 和 $thumbprint 必须来自前面步骤的实际查询结果,不能照抄占位符。重启后检查 bcdedit /enum '{current}'、设备列表和证书库;生产环境应使用符合 Windows 发布策略的正式 签名,不应分发或长期信任 WDKTestCert。