Windows 驅動程式開發(KMDF/WDM)需要特殊的編譯鏈接選項:輸出 .sys 格式、指定基址、對齊、子系統為 NATIVE、連結 ntoskrnl.lib 等。KMDKIT (Kernel Mode Driver Kit) 提供了一種巧妙的「批處理 + 組合語言」混合腳本技巧,將編譯命令嵌入 `.bat` 檔案註釋中,雙擊即可完成編譯連結,極大簡化了開發流程。本文以經典的 ring0.sys 範例為例詳細說明。
Windows 批處理執行時會忽略以 `;` 開頭的行(視為註釋),而 MASM/TASM 組譯器則將 `;` 視為註釋標記。利用這一點,可在同一個檔案中同時包含:
組譯器會跳過批處理命令行(因為它們以 `;` 開頭),批處理會跳過組合語言指令行(因為它們不以 `:` 開頭且非有效命令)。雙擊 `.bat` 即觸發編譯。
;@echo off
;goto make
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.586P ; 保護模式指令集
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include \masm32\include\w2k\ntddk.inc
; 中斷相關資料結構
IDT_REG STRUCT
limit WORD ?
base DWORD ?
IDT_REG ENDS
; 中斷描述符
INT_DESCRIPTOR STRUCT
offs0_15 WORD ?
sel WORD ?
paramcnt BYTE ?
attrs BYTE ?
offs16_31 WORD ?
INT_DESCRIPTOR ENDS
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
szBuffer db 16 dup(0)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
MyIntFunc proc
push edx
call eax
iretd
MyIntFunc endp
;====================================================================
AddMyInt proc uses edi
local @IDT
sidt szBuffer
mov edi,(IDT_REG ptr [szBuffer]).base
add edi,21h*8 ; IDT 向量 0x21
; 使用 Int 0x21 中斷,該中斷在 Win2k 下未被使用
; cli
mov eax,offset MyIntFunc
mov [edi],ax
shr eax,16
mov [edi+6],ax ; 設定入口位址高 16 位
mov [edi+2],cs ; 設定段選擇子
mov WORD ptr [edi+4],0EE00h ; 設定 DPL=3 (Ring 3 可呼叫), Present=1, Type=0Eh (32-bit Interrupt Gate)
; sti
ret
AddMyInt endp
;====================================================================
WdmUnload proc DriverObject:DWORD
local @IDT
sidt szBuffer
mov edi,(IDT_REG ptr [szBuffer]).base
add edi,21h*8
xor eax,eax
mov [edi],ax
mov [edi+6],ax
mov [edi+2],ax
mov WORD ptr [edi+4],ax ; 清除 IDT 條目
ret
WdmUnload endp
;====================================================================
DriverEntry proc DriverObj:DWORD, RegistryPath:DWORD
mov eax,DriverObj
assume eax:ptr DRIVER_OBJECT
mov [eax].DriverUnload,offset WdmUnload
assume eax:nothing
invoke AddMyInt
xor eax,eax
ret
DriverEntry endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end DriverEntry
:make
set drv=ring0
\masm32\bin\ml /nologo /c /coff %drv%.bat
\masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj
del %drv%.obj
echo.
pause
rem =====以上為 ring0.bat 的內容=============
| 工具 | 參數 | 說明 |
|---|---|---|
| ML.EXE | /nologo | 隱藏版權資訊 |
| /c | 僅編譯不連結 | |
| /coff | 輸出 COFF 格式 .obj (Win32/Win64 驅動必需) | |
| LINK.EXE | /nologo | 隱藏版權資訊 |
| /driver | 建立驅動程式 (.sys),設定 subsystem=NATIVE, 入口點=DriverEntry | |
| /base:0x10000 | 指定映像基址 (驅動常用 0x10000 避開用戶模式衝突) | |
| /align:32 | 檔案區段對齊 32 位元組 (節省空間,驅動常用) | |
| /out:xxx.sys | 輸出檔名 | |
| /subsystem:native | 原生子系統 (無 Win32 依賴) |
AddMyInt:讀取 IDT 基址(SIDT),修改向量 0x21 的描述符,將處理器指向 MyIntFunc,設置 DPL=3 允許 Ring 3 通過 INT 21h 調用(Win2k 下此向量未使用)。MyIntFunc:簡易存儲器,調用 EAX 指向的函數並 IRETD 返回。WdmUnload:卸載時清除 IDT 條目,防止系統崩潰。DriverEntry:註冊卸載例程,安裝中斷 Hook,返回成功。bcdedit /set testsigning on)或使用正式簽章。KMDKIT 的批處理混合腳本技巧是早期驅動開發者智慧的結晶,將「編輯-編譯-測試」循環壓縮到雙擊一個檔案。雖然工具鏈已過時,但其核心思想——**將構建腳本與源代碼共存、零配置啟動**——在現代 `CMakeLists.txt`、`Makefile`、`package.json`、`build.ps1` 等構建腳本中依然閃耀著實用主義的光芒。