編譯時(shí)檢查
使用 /Wall及/WX參數(shù)編譯
/Wall:打開所有的警告
/WX:將警告視為錯(cuò)誤
但是DDK本身的頭文件中還包括警告信息,可使用關(guān)閉警告的功能來(lái)克服這個(gè)問(wèn)題,這些警告大多都是由微軟對(duì)標(biāo)準(zhǔn)語(yǔ)言的擴(kuò)展引起的,不影響可靠性,我們可以在源文件中關(guān)閉這些警告:
MSC_WARNING_LEVEL=/Wall /W4 /WX /wd4115 /wd4127 /wd4200 /wd4201 /wd4214 /wd4255 /wd4514 /wd4619
/wd4668 /wd4820
更好的方法是僅在引起這些警告的MS包含文件部分關(guān)閉這些警告,如下:
Source文件中為:
MSC_WARNING_LEVEL=/Wall /WX
使用如下語(yǔ)句包圍引起警告的MS包含文件:
#pragma warning (disable: 4115 4127 4200 4201 4214 4255 4619 4668 4820)
...
#pragma warning (default: 4115 4200 4214 4255 4619 4668 4820)
注意:不同DDK版本出現(xiàn)的警告不同
使用C++編譯器
在驅(qū)動(dòng)開發(fā)中使用C++會(huì)帶來(lái)一些問(wèn)題,但使用C++編譯器將有一些額外的好處,因?yàn)镃++編譯器會(huì)做更多的檢查,使用C開始同時(shí)使用C++編譯器編譯的方法如下:
使用/TP編譯開關(guān),這個(gè)開關(guān)讓編譯器將.c文件作為.cpp文件來(lái)編譯,這種技術(shù)通常在check版本中使用,在source文件中需要加入如下代碼:
!if !$(FREEBUILD)
USER_C_FLAGS= /TP
!endif
同時(shí),需要對(duì)DriverEntry進(jìn)行標(biāo)記:
#ifdef __cplusplus
extern "C"
#endif
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath);
如果我們使用了WPP,還需要標(biāo)記WPP的包含文件。
PREfast
PREfast按照顯示規(guī)則對(duì)代碼進(jìn)行靜態(tài)分析(DDK自帶)
定義DEPRECATE_DDK_FUNCTIONS
在source 文件的C_DEFINES定義DEPRECATE_DDK_FUNCTIONS,將使編譯器在編譯是檢查我們的代碼是否使用了DDK已經(jīng)過(guò)時(shí)的例程。但是 DEPRECATE_DDK_FUNCTIONS并不能檢查出所有的過(guò)時(shí)例程,唯一的辦法是檢查DDK文檔。在驅(qū)動(dòng)中使用 MmGetSystemRoutineAddress例程可以用來(lái)判斷我們使用的例程是否還被支持。
使用編譯時(shí)處理
C_ASSERT
C_ASSERT是一個(gè)編譯時(shí)斷言
條件測(cè)試
使用#error MSG可以產(chǎn)生一個(gè)比C_ASSERT可讀性更好的編譯時(shí)錯(cuò)誤:
#define BLK_SHIFT 9
#define BLK_SIZE 512 // Must be 1 << BLK_SHIFT
#if !(BLK_SIZE = (1 << BLK_SHIFT))
#error !(BLK_SIZE == (1 << BLK_SHIFT))
#endif
編譯32位&64位驅(qū)動(dòng)版本
編譯64位版本可發(fā)現(xiàn)如下的問(wèn)題:
指針問(wèn)題
使用32位值存儲(chǔ)指針、數(shù)據(jù)結(jié)構(gòu)因?yàn)榘羔樁淖兇笮?
內(nèi)聯(lián)匯編
在驅(qū)動(dòng)中不應(yīng)該使用內(nèi)聯(lián)匯編
編譯器差異
64位結(jié)構(gòu)上的代碼兼容性
ChkINF
安裝前使用ChkINF檢查INF文件(DDK自帶)
調(diào)試及運(yùn)行時(shí)檢查
1、使用所有的檢查選項(xiàng)調(diào)試
2、逐個(gè)關(guān)閉檢查選項(xiàng)調(diào)試
3、關(guān)閉所有調(diào)試選項(xiàng),在目標(biāo)機(jī)運(yùn)行,使用性能檢測(cè)軟件監(jiān)視
內(nèi)核調(diào)試器
KD:命令行調(diào)試器
WinDbg:帶有圖形前端的調(diào)試器
Check版Windows
使用Check版Window調(diào)試
驅(qū)動(dòng)驗(yàn)證(Driver Verifier)
主動(dòng)測(cè)試工具(Windows自帶),同常設(shè)置如下檢查:
- 自動(dòng)檢查(必選)
- 特殊內(nèi)存檢查
- IRQL檢查
- 內(nèi)存池跟蹤
- I/O驗(yàn)證
- DMA驗(yàn)證
- 死鎖檢測(cè)
其中有一項(xiàng)低資源模擬測(cè)試將在驅(qū)動(dòng)中注入一些資源請(qǐng)求失敗錯(cuò)誤,這項(xiàng)檢查應(yīng)該在排除了其它錯(cuò)誤后,最后來(lái)測(cè)試
調(diào)用驗(yàn)證(CUV)
使用調(diào)用驗(yàn)證需要更改source文件并重新編譯驅(qū)動(dòng):
VERIFIER_DDK_EXTENSIONS=1
CUV支持如下類型的檢查:
- 初始化檢查:使用一個(gè)內(nèi)核類型前對(duì)其進(jìn)行驗(yàn)證
- 一致性檢查:檢查內(nèi)核例程調(diào)用的一致性
- 分頁(yè)內(nèi)存檢查:檢查不正確的分頁(yè)內(nèi)存使用
- IRP堆棧檢查:驗(yàn)證IRP堆棧的使用
使用CUV支持編譯后的驅(qū)動(dòng)在啟動(dòng)后將向調(diào)試器報(bào)告錯(cuò)誤信息
緩沖池標(biāo)記
緩沖池標(biāo)記可以在分配緩沖區(qū)前加上4個(gè)字符的標(biāo)記,Windows2000及XP僅在check版本上打開了緩沖池標(biāo)記,free版本可使用gflags /r +ptg命令打開,2003所有的版本都打開了緩沖池標(biāo)記。可以通過(guò)3種途徑來(lái)查看這些緩沖池標(biāo)記:
- 調(diào)試器
- Poolmon(Windows自帶)
- Pooltag(DDK自帶)
從XP以后,我們都應(yīng)該使用PROTECTED_POOL標(biāo)記調(diào)用ExFreePoolWithTag來(lái)釋放我們分配的內(nèi)存,當(dāng)驅(qū)動(dòng)驗(yàn)證的特殊內(nèi)存檢查開啟時(shí),緩沖池標(biāo)記不能被開啟
代碼覆蓋
代碼覆蓋包括行覆蓋及路徑覆蓋,可通過(guò)如下途徑來(lái)提高代碼覆蓋率:
- 測(cè)試所有有效的請(qǐng)求
- 測(cè)試所有不正確的輸入
- 使用驅(qū)動(dòng)驗(yàn)證中的低資源模擬
- 隨機(jī)的注入錯(cuò)誤
性能監(jiān)測(cè)
使用KrView及KernRate可以檢測(cè)驅(qū)動(dòng)的CPU占用率,我們應(yīng)該通過(guò)兩種方法來(lái)測(cè)試驅(qū)動(dòng)的負(fù)載:
- 在整個(gè)系統(tǒng)上運(yùn)行性能監(jiān)測(cè),即使驅(qū)動(dòng)負(fù)載較大,在整個(gè)系統(tǒng)負(fù)載中,它應(yīng)該只占一個(gè)比較小的比例
- 在驅(qū)動(dòng)上運(yùn)行性能監(jiān)測(cè),分析驅(qū)動(dòng)中最花時(shí)間的部分
內(nèi)核日志
通過(guò)Trace View(DDK自帶),我們可以記錄9種類型的數(shù)據(jù):
- 進(jìn)程創(chuàng)建及終止
- 線程創(chuàng)建及終止
- 文件I/O
- 磁盤I/O
- 映象文件加載及卸載
- 注冊(cè)表訪問(wèn)
安裝日志
通過(guò)使能安裝日志,我們可以跟蹤驅(qū)動(dòng)在安裝時(shí)的動(dòng)作,在注冊(cè)表種設(shè)置如下鍵值:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurentVersion\Setup
為0x3000FFFF可以使能詳細(xì)安裝日志,在完成調(diào)試后,需要關(guān)閉這個(gè)日志以保證性能,日志文件在%systemroot%\setupapi.log
雜項(xiàng)工具
- DriverQuery(Windows自帶):使用這個(gè)工具可以查詢系統(tǒng)中驅(qū)動(dòng)的相關(guān)信息
- DeviceTree:顯示設(shè)備及驅(qū)動(dòng)的相關(guān)數(shù)據(jù)
- DevCon:命令行工具,提供所有設(shè)備管理的功能,部分DriberQuery、DeviceTree功能,DDK的src\setup\devcon有源碼,可用于編寫驅(qū)動(dòng)控制工具
- RegEdit32:注冊(cè)表編輯工具
診斷
斷言
通常使用的斷言宏有:
ASSERT ( expression )
ASSERTMSG ( message, expression )
在Win2000以前的系統(tǒng)中,這兩個(gè)宏不返回任何值,以后的系統(tǒng)中,這兩個(gè)宏返回expression的值,這兩個(gè)斷言僅在check版本中有效,通常在如下情況下使用斷言:
- 在進(jìn)入和退出非靜態(tài)函數(shù)時(shí)要對(duì)靜態(tài)數(shù)據(jù)成員進(jìn)行檢查
- 在進(jìn)行一個(gè)處理前,校驗(yàn)輸入?yún)?shù)是否正確
- 在進(jìn)入和退出循環(huán)前, 校驗(yàn)輸入?yún)?shù)是否正確
- 在使用指針時(shí),對(duì)指針及指針指向的數(shù)據(jù)進(jìn)行校驗(yàn);在使用全局變量前,對(duì)其進(jìn)行檢驗(yàn)
校驗(yàn)的方式:
除了檢查指針是否為NULL外,我們還可以做進(jìn)一步檢查.例如檢查數(shù)據(jù)的類型:
ASSERTMSG ( “Invalid IRP pointer”,
irp != NULL && irp->Type == IO_TYPE_IRP)
- 檢查獲得的值是否在范圍以內(nèi)
- 檢查雙向鏈表的一致性
ASSERTMSG ( “Corrupted List”,
listPtr != NULL && listPtr->Flink != NULL &&
listPtr->Flink->Blink == ListPtr )
ASSERTMSG ( “Invalid Counted String”, string != NULL &&
string->MaxLength > string->Length &&
string != NULL ? (string->Length != 0 ) : TRUE ))
- 檢查函數(shù)執(zhí)行時(shí)的IRQL
- 避免檢查的側(cè)效
ASSERT ( --CurrentCount > 0 )
- 注意區(qū)分?jǐn)嘌詸z查和錯(cuò)誤檢查的區(qū)別
p = ExAllocatePoolWithTag ( NonPagedPool, BLK_SIZE,
BLK_TAG );
ASSERT ( p != NULL )
在分配內(nèi)存時(shí),因?yàn)橄到y(tǒng)資源不足而引起的分配失敗是很正常的事情,這里不應(yīng)該使用斷言
調(diào)試打印
通常使用的調(diào)試打印有如下四種:
KdPrint ( format, ... );
KdPrintEx ( componentId, level, format, ... );
DbgPrint ( format, ... );
DbgPrintEx ( componentId, level, format, ... );
頭兩個(gè)調(diào)用僅在check版本有效,后兩個(gè)在所有版本都有效, componentId 和 level用來(lái)過(guò)濾調(diào)試輸出
通常應(yīng)該打印輸出的信息如下:
例如用IRP_MJ_CLOSE代替0x2
- 打印描述數(shù)據(jù)結(jié)構(gòu)的信息,而不是指向數(shù)據(jù)結(jié)構(gòu)的指針
例如打印出IRP包的主功能碼
- 使用標(biāo)準(zhǔn)格式打印
- 打印調(diào)用棧
- 更多的輸出信息將有助于調(diào)試,例如文件名,行號(hào),當(dāng)前執(zhí)行線程,IRQL
格式化的規(guī)則:
- 如果要打印指針,使用%p格式符,這樣可以使程序在32位及64位平臺(tái)上均可運(yùn)行
- 如果打印一個(gè)計(jì)數(shù)的字符串,使用%Z (ANSI)或者%wZ (Unicode),它可以正確打印出非空結(jié)束的字符串
- 如果需要打印Unicode值,必須確認(rèn)程序運(yùn)行在DISPATCH_LEVEL級(jí)別之下
- 每個(gè)輸出調(diào)用限制在512字節(jié)內(nèi)
WPP軟件跟蹤
WPP軟件提供了一種低開銷的日志數(shù)據(jù)機(jī)制,它創(chuàng)建一個(gè)二進(jìn)制數(shù)據(jù)日志文件存放信息,它的顯著優(yōu)點(diǎn)是可以從驅(qū)動(dòng)收集最終用戶不可理解的數(shù)據(jù)。
使用WPP的最簡(jiǎn)單方法是在SOURCE文件中添加如下語(yǔ)句:
RUN_WPP=$(SOURCES) –km
此外,還需設(shè)置一些定義及包含文件,WPP的用法如下:
DoTraceMessage(IN TraceFlag, IN Format, ...)
我們也可以通過(guò)如下定義將WPP轉(zhuǎn)變?yōu)檎{(diào)試打印:
#define WPP_DEBUG(args) DbgPrint args;
WPP支持一些DbgPrint沒(méi)有提供的格式符宏。
由于WPP目的為低開銷,所以于調(diào)試打印的規(guī)則略有不同:
- 無(wú)需將數(shù)字轉(zhuǎn)換成有意義的字符串
- 無(wú)需輸出文件及行號(hào)數(shù)據(jù)
- ……
事件日志
系統(tǒng)事件日志用來(lái)報(bào)告驅(qū)動(dòng)運(yùn)行過(guò)程中發(fā)生的事情,主要是發(fā)生的問(wèn)題,每個(gè)日志項(xiàng)的大小限制為256字節(jié),使用系統(tǒng)事件日志主要考慮以下四點(diǎn):
系統(tǒng)日志的大小及覆蓋規(guī)則有管理員來(lái)指定,驅(qū)動(dòng)加入的每一個(gè)事件日志都應(yīng)當(dāng)有充分的理由,管理員并不關(guān)系驅(qū)動(dòng)什么時(shí)候啟動(dòng)或者關(guān)閉,而僅關(guān)系驅(qū)動(dòng)發(fā)生了什么問(wèn)題
- 每個(gè)錯(cuò)誤指定一個(gè)唯一的錯(cuò)誤代碼,每個(gè)事件僅包含錯(cuò)誤,便于管理員查詢
- 不要全部使用一樣的錯(cuò)誤信息,對(duì)每個(gè)錯(cuò)誤使用實(shí)際的錯(cuò)誤信息
- 注意消息分類文件的處理
性能監(jiān)測(cè)
一個(gè)好的驅(qū)動(dòng)應(yīng)該給用戶提供性能信息,通常通過(guò)WMI來(lái)提供這些性能數(shù)據(jù),通常我們可以提供如下數(shù)據(jù)來(lái)衡量驅(qū)動(dòng)的性能:
- IRP計(jì)數(shù)
- 處理數(shù)據(jù)計(jì)數(shù)
- 出錯(cuò)率或者出錯(cuò)個(gè)數(shù)
- 安全事件計(jì)數(shù)
WMI的例子見(jiàn)src\wdm\wmi\wmifilt
自定義信息轉(zhuǎn)儲(chǔ)
驅(qū)動(dòng)可以通過(guò)自定義信息轉(zhuǎn)儲(chǔ)來(lái)提高驅(qū)動(dòng)的調(diào)試效率,在Win2000中使用KeRegisterBugCheckCallback實(shí)現(xiàn)自定義信息轉(zhuǎn)儲(chǔ),在XP及以后的系統(tǒng)中使用KeRegisterBugCheckReasonCallback實(shí)現(xiàn)自定義信息轉(zhuǎn)儲(chǔ)。
版本塊
使用版本信息將有利于用戶報(bào)告驅(qū)動(dòng)的問(wèn)題,在資源描述文件中,使用VERSIONINFO定義來(lái)描述驅(qū)動(dòng)的版本信息。
測(cè)試工具
測(cè)試的基本原則:
- 易于復(fù)現(xiàn)
- 易于確認(rèn)測(cè)試結(jié)果
- 使用不同的平臺(tái)測(cè)試
- 修復(fù)BUG后做回歸測(cè)試
Device Path Exerciser
Device Path Exerciser(dc2.exe,DDK自帶)使用不同的設(shè)備控制I/O調(diào)用來(lái)檢測(cè)驅(qū)動(dòng)的穩(wěn)定性。這個(gè)工具的調(diào)用格式如下:
dc2 [options] /dr driver [driver …] (XP and later, support to 10 drivers)
dc2 [options] device (all, support one dirver)
日志部分
dc2.exe共產(chǎn)生四個(gè)日志文件:DC2.log,Diags.log,CrashN.log,Crash.log
打開設(shè)備部分
dc2.exe試圖使用多種手段來(lái)打開設(shè)備進(jìn)行測(cè)試。
測(cè)試部分
dc2.exe包括很多測(cè)試的內(nèi)容,常用的如下:
- 雜項(xiàng)測(cè)試(讀/寫/取消請(qǐng)求)
- 零長(zhǎng)度緩沖區(qū)測(cè)試
- 隨機(jī)測(cè)試
PNP驅(qū)動(dòng)測(cè)試
PNP測(cè)試(pnpdtest.exe,DDK自帶)主要包括如下部分:
- 可移除性
- 重新分配資源
- 意外移除設(shè)備
- 壓力測(cè)試(包括5分鐘的重新分配資源及移除測(cè)試,最后一個(gè)意外移除)
休眠及ACPI測(cè)試
使用sleeper.exe(DDK自帶)測(cè)試驅(qū)動(dòng)的電源管理與系統(tǒng)的電源管理的配合情況,使用pmte.exe(DDK自帶)測(cè)試驅(qū)動(dòng)的電源管理功能。
硬件兼容性測(cè)試(HCT)
HCT是一系列Windows Logo Program指定的必須通過(guò)的測(cè)試,它的主要目的是測(cè)試硬件,但有助于驅(qū)動(dòng)開發(fā),主要包括如下幾類測(cè)試:
Audio
Bus Controllers
Display
Anti-Virus/File System Filter Drivers
Imaging
Input and HID
Modems
Network Devices
Storage Controllers and Devices
Streaming Media and Broadcast
Systems
Unclassified
總結(jié)
通常,驅(qū)動(dòng)開發(fā)應(yīng)該遵守以下規(guī)則:
- 使用最新的DDK編譯驅(qū)動(dòng)
- 在編譯是發(fā)現(xiàn)問(wèn)題
使用/Wall, C++編譯,使用PREfast,使用DEPRECATE_DDK_FUNCTIONS 及C_ASSERT宏,使用ChkINF檢查INF文件
- 使用運(yùn)行時(shí)檢查及日志手段
- 提供調(diào)試診斷信息來(lái)快速定位錯(cuò)誤
- 使用HCT及DDK中提供的工具進(jìn)行測(cè)試,使用自定義的test case進(jìn)行測(cè)試