Driver post-Developement Tech

編譯時檢查

使用 /Wall及/WX參數編譯

/Wall:打開所有的警告

/WX:將警告視為錯誤

但是DDK本身的頭文件中還包括警告信息,可使用關閉警告的功能來克服這個問題,這些警告大多都是由微軟對標準語言的擴展引起的,不影響可靠性,我們可以在源文件中關閉這些警告:

MSC_WARNING_LEVEL=/Wall /W4 /WX /wd4115 /wd4127 /wd4200 /wd4201 /wd4214 /wd4255 /wd4514 /wd4619

/wd4668 /wd4820

更好的方法是僅在引起這些警告的MS包含文件部分關閉這些警告,如下:

Source文件中為:

MSC_WARNING_LEVEL=/Wall /WX

使用如下語句包圍引起警告的MS包含文件:

#pragma warning (disable: 4115 4127 4200 4201 4214 4255 4619 4668 4820)

...

#pragma warning (default: 4115 4200 4214 4255 4619 4668 4820)

注意:不同DDK版本出現的警告不同

使用C++編譯器

在驅動開發中使用C++會帶來一些問題,但使用C++編譯器將有一些額外的好處,因為C++編譯器會做更多的檢查,使用C開始同時使用C++編譯器編譯的方法如下:

使用/TP編譯開關,這個開關讓編譯器將.c文件作為.cpp文件來編譯,這種技術通常在check版本中使用,在source文件中需要加入如下代碼:

!if !$(FREEBUILD)

USER_C_FLAGS= /TP

!endif

同時,需要對DriverEntry進行標記:

#ifdef __cplusplus

extern "C"

#endif

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath);

如果我們使用了WPP,還需要標記WPP的包含文件。

PREfast

PREfast按照顯示規則對代碼進行靜態分析(DDK自帶)

定義DEPRECATE_DDK_FUNCTIONS

在source 文件的C_DEFINES定義DEPRECATE_DDK_FUNCTIONS,將使編譯器在編譯是檢查我們的代碼是否使用了DDK已經過時的例程。但是 DEPRECATE_DDK_FUNCTIONS并不能檢查出所有的過時例程,唯一的辦法是檢查DDK文檔。在驅動中使用 MmGetSystemRoutineAddress例程可以用來判斷我們使用的例程是否還被支持。

使用編譯時處理

C_ASSERT

C_ASSERT是一個編譯時斷言

條件測試

使用#error MSG可以產生一個比C_ASSERT可讀性更好的編譯時錯誤:

#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位驅動版本

編譯64位版本可發現如下的問題:

指針問題

使用32位值存儲指針、數據結構因為包含指針而改變大小

內聯匯編

在驅動中不應該使用內聯匯編

編譯器差異

64位結構上的代碼兼容性

ChkINF

安裝前使用ChkINF檢查INF文件(DDK自帶)

調試及運行時檢查

1、使用所有的檢查選項調試

2、逐個關閉檢查選項調試

3、關閉所有調試選項,在目標機運行,使用性能檢測軟件監視

內核調試器

KD:命令行調試器

WinDbg:帶有圖形前端的調試器

Check版Windows

使用Check版Window調試

驅動驗證(Driver Verifier)

主動測試工具(Windows自帶),同常設置如下檢查:

  • 自動檢查(必選)
  • 特殊內存檢查
  • IRQL檢查
  • 內存池跟蹤
  • I/O驗證
  • DMA驗證
  • 死鎖檢測

其中有一項低資源模擬測試將在驅動中注入一些資源請求失敗錯誤,這項檢查應該在排除了其它錯誤后,最后來測試

調用驗證(CUV)

使用調用驗證需要更改source文件并重新編譯驅動:

VERIFIER_DDK_EXTENSIONS=1

CUV支持如下類型的檢查:

  • 初始化檢查:使用一個內核類型前對其進行驗證
  • 一致性檢查:檢查內核例程調用的一致性
  • 分頁內存檢查:檢查不正確的分頁內存使用
  • IRP堆棧檢查:驗證IRP堆棧的使用

使用CUV支持編譯后的驅動在啟動后將向調試器報告錯誤信息

緩沖池標記

緩沖池標記可以在分配緩沖區前加上4個字符的標記,Windows2000及XP僅在check版本上打開了緩沖池標記,free版本可使用gflags /r +ptg命令打開,2003所有的版本都打開了緩沖池標記。可以通過3種途徑來查看這些緩沖池標記:

  • 調試器
  • Poolmon(Windows自帶)
  • Pooltag(DDK自帶)

從XP以后,我們都應該使用PROTECTED_POOL標記調用ExFreePoolWithTag來釋放我們分配的內存,當驅動驗證的特殊內存檢查開啟時,緩沖池標記不能被開啟

代碼覆蓋

代碼覆蓋包括行覆蓋及路徑覆蓋,可通過如下途徑來提高代碼覆蓋率:

  • 測試所有有效的請求
  • 測試所有不正確的輸入
  • 使用驅動驗證中的低資源模擬
  • 隨機的注入錯誤

性能監測

使用KrView及KernRate可以檢測驅動的CPU占用率,我們應該通過兩種方法來測試驅動的負載:

  • 在整個系統上運行性能監測,即使驅動負載較大,在整個系統負載中,它應該只占一個比較小的比例
  • 在驅動上運行性能監測,分析驅動中最花時間的部分

內核日志

通過Trace View(DDK自帶),我們可以記錄9種類型的數據:

  • 進程創建及終止
  • 線程創建及終止
  • 文件I/O
  • 磁盤I/O
  • 映象文件加載及卸載
  • 注冊表訪問

安裝日志

通過使能安裝日志,我們可以跟蹤驅動在安裝時的動作,在注冊表種設置如下鍵值:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurentVersion\Setup

為0x3000FFFF可以使能詳細安裝日志,在完成調試后,需要關閉這個日志以保證性能,日志文件在%systemroot%\setupapi.log

雜項工具

  • DriverQuery(Windows自帶):使用這個工具可以查詢系統中驅動的相關信息
  • DeviceTree:顯示設備及驅動的相關數據
  • DevCon:命令行工具,提供所有設備管理的功能,部分DriberQuery、DeviceTree功能,DDK的src\setup\devcon有源碼,可用于編寫驅動控制工具
  • RegEdit32:注冊表編輯工具

診斷

斷言

通常使用的斷言宏有:

ASSERT ( expression )

ASSERTMSG ( message, expression )

在Win2000以前的系統中,這兩個宏不返回任何值,以后的系統中,這兩個宏返回expression的值,這兩個斷言僅在check版本中有效,通常在如下情況下使用斷言:

  • 在進入和退出非靜態函數時要對靜態數據成員進行檢查
  • 在進行一個處理前,校驗輸入參數是否正確
  • 在進入和退出循環前, 校驗輸入參數是否正確
  • 在使用指針時,對指針及指針指向的數據進行校驗;在使用全局變量前,對其進行檢驗

校驗的方式:

  • 檢查指針是否有效

除了檢查指針是否為NULL外,我們還可以做進一步檢查.例如檢查數據的類型:

ASSERTMSG ( “Invalid IRP pointer”,

irp != NULL && irp->Type == IO_TYPE_IRP)

  • 檢查獲得的值是否在范圍以內
  • 檢查雙向鏈表的一致性

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 ))

  • 檢查函數執行時的IRQL
  • 避免檢查的側效

ASSERT ( --CurrentCount > 0 )

  • 注意區分斷言檢查和錯誤檢查的區別

p = ExAllocatePoolWithTag ( NonPagedPool, BLK_SIZE,
BLK_TAG );
ASSERT ( p != NULL )
在分配內存時,因為系統資源不足而引起的分配失敗是很正常的事情,這里不應該使用斷言

調試打印

通常使用的調試打印有如下四種:

KdPrint ( format, ... );

KdPrintEx ( componentId, level, format, ... );

DbgPrint ( format, ... );

DbgPrintEx ( componentId, level, format, ... );

頭兩個調用僅在check版本有效,后兩個在所有版本都有效, componentId 和 level用來過濾調試輸出

通常應該打印輸出的信息如下:

  • 打印文字而不是數字

例如用IRP_MJ_CLOSE代替0x2

  • 打印描述數據結構的信息,而不是指向數據結構的指針

例如打印出IRP包的主功能碼

  • 使用標準格式打印
  • 打印調用棧
  • 更多的輸出信息將有助于調試,例如文件名,行號,當前執行線程,IRQL

格式化的規則:

  • 如果要打印指針,使用%p格式符,這樣可以使程序在32位及64位平臺上均可運行
  • 如果打印一個計數的字符串,使用%Z (ANSI)或者%wZ (Unicode),它可以正確打印出非空結束的字符串
  • 如果需要打印Unicode值,必須確認程序運行在DISPATCH_LEVEL級別之下
  • 每個輸出調用限制在512字節內

WPP軟件跟蹤

WPP軟件提供了一種低開銷的日志數據機制,它創建一個二進制數據日志文件存放信息,它的顯著優點是可以從驅動收集最終用戶不可理解的數據。

使用WPP的最簡單方法是在SOURCE文件中添加如下語句:

RUN_WPP=$(SOURCES) –km

此外,還需設置一些定義及包含文件,WPP的用法如下:

DoTraceMessage(IN TraceFlag, IN Format, ...)

我們也可以通過如下定義將WPP轉變為調試打印:

#define WPP_DEBUG(args) DbgPrint args;

WPP支持一些DbgPrint沒有提供的格式符宏。

由于WPP目的為低開銷,所以于調試打印的規則略有不同:

  • 無需將數字轉換成有意義的字符串
  • 無需輸出文件及行號數據
  • ……

事件日志

系統事件日志用來報告驅動運行過程中發生的事情,主要是發生的問題,每個日志項的大小限制為256字節,使用系統事件日志主要考慮以下四點:

  • 事件日志的大小有限

系統日志的大小及覆蓋規則有管理員來指定,驅動加入的每一個事件日志都應當有充分的理由,管理員并不關系驅動什么時候啟動或者關閉,而僅關系驅動發生了什么問題

  • 每個錯誤指定一個唯一的錯誤代碼,每個事件僅包含錯誤,便于管理員查詢
  • 不要全部使用一樣的錯誤信息,對每個錯誤使用實際的錯誤信息
  • 注意消息分類文件的處理

性能監測

一個好的驅動應該給用戶提供性能信息,通常通過WMI來提供這些性能數據,通常我們可以提供如下數據來衡量驅動的性能:

  • IRP計數
  • 處理數據計數
  • 出錯率或者出錯個數
  • 安全事件計數

WMI的例子見src\wdm\wmi\wmifilt

自定義信息轉儲

驅動可以通過自定義信息轉儲來提高驅動的調試效率,在Win2000中使用KeRegisterBugCheckCallback實現自定義信息轉儲,在XP及以后的系統中使用KeRegisterBugCheckReasonCallback實現自定義信息轉儲。

版本塊

使用版本信息將有利于用戶報告驅動的問題,在資源描述文件中,使用VERSIONINFO定義來描述驅動的版本信息。

測試工具

測試的基本原則:

  • 易于復現
  • 易于確認測試結果
  • 使用不同的平臺測試
  • 修復BUG后做回歸測試

Device Path Exerciser

Device Path Exerciser(dc2.exe,DDK自帶)使用不同的設備控制I/O調用來檢測驅動的穩定性。這個工具的調用格式如下:

dc2 [options] /dr driver [driver …] (XP and later, support to 10 drivers)

dc2 [options] device (all, support one dirver)

日志部分

dc2.exe共產生四個日志文件:DC2.log,Diags.log,CrashN.log,Crash.log

打開設備部分

dc2.exe試圖使用多種手段來打開設備進行測試。

測試部分

dc2.exe包括很多測試的內容,常用的如下:

  • 雜項測試(讀/寫/取消請求)
  • 零長度緩沖區測試
  • 隨機測試

PNP驅動測試

PNP測試(pnpdtest.exe,DDK自帶)主要包括如下部分:

  • 可移除性
  • 重新分配資源
  • 意外移除設備
  • 壓力測試(包括5分鐘的重新分配資源及移除測試,最后一個意外移除)

休眠及ACPI測試

使用sleeper.exe(DDK自帶)測試驅動的電源管理與系統的電源管理的配合情況,使用pmte.exe(DDK自帶)測試驅動的電源管理功能。

硬件兼容性測試(HCT)

HCT是一系列Windows Logo Program指定的必須通過的測試,它的主要目的是測試硬件,但有助于驅動開發,主要包括如下幾類測試:

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

總結

通常,驅動開發應該遵守以下規則:

  • 使用最新的DDK編譯驅動
  • 在編譯是發現問題

使用/Wall, C++編譯,使用PREfast,使用DEPRECATE_DDK_FUNCTIONS 及C_ASSERT宏,使用ChkINF檢查INF文件

  • 使用運行時檢查及日志手段
  • 提供調試診斷信息來快速定位錯誤
  • 使用HCT及DDK中提供的工具進行測試,使用自定義的test case進行測試