共用方式為


Surface Team Driver 開發最佳做法

簡介

這些驅動程式開發指導方針已由 Microsoft 的驅動程式開發人員開發多年。 隨著時間的推移,當司機錯誤和吸取教訓時,這些課程被擷取並演變成這一組指導。 Microsoft Surface 硬體小組會使用這些最佳做法來開發和維護支援獨特 Surface 硬體體驗的裝置驅動程式程式代碼。

和任何一組指導方針一樣,會有合法的例外狀況和替代方法同樣有效。 請考慮將這些指導方針納入您的開發標準,或使用它們來啟動開發環境的特定指導方針,以及您獨特的需求。

驅動程式開發人員犯的常見錯誤

處理 I/O

  1. 存取從 IOCTLs 擷取的緩衝區,而不驗證長度。 請參閱 無法檢查緩衝區的大小。
  2. 在使用者線程或隨機線程內容的內容中執行封鎖 I/O。 請參閱 核心發送器對象的簡介。
  3. 將同步 I/O 傳送至另一個驅動程式,而不會逾時。 請參閱 同步傳送 I/O 要求。
  4. 在不瞭解安全性影響的情況下,使用無ioIOCTL。 請參閱 不使用緩衝處理或直接 I/O
  5. 未檢查 WdfRequestForwardToIoQueue 的傳回狀態,或未正確處理失敗,並導致放棄的 WDFREQUEST。
  6. 將 WDFREQUEST 保留在佇列外部處於無法取消的狀態。 請參閱 管理 I/O 佇列完成 I/O 要求取消 I/O 要求
  7. 嘗試使用 Mark/UnmarkCancelable 函式來管理取消,而不是使用 IoQueues。 請參閱 Framework Queue 物件
  8. 不知道檔句柄清除和關閉作業之間的差異。 請參閱 處理清除和關閉作業中的錯誤。
  9. 忽略 I/O 完成和從完成例程重新提交的潛在遞歸。
  10. 未明確說明 WDFQUEUE 的電源管理屬性。 未清楚地記錄電源管理選擇。 這是錯誤檢查0x9F的主要原因 :WDF 驅動程式中的DRIVER_POWER_STATE_FAILURE 。 拿掉裝置時,架構會在移除程式的不同階段,從電源受控佇列和非電源受控佇列清除 IO。 收到最終IRP_MN_REMOVE_DEVICE時,會清除非電源受控佇列。 因此,如果您在非電源受控佇列中保存 I/O,最好在 EvtDeviceSelfManagedIoFlush 的內容中明確清除 I/O,以避免死結。
  11. 不遵循處理 IRP 的規則。 請參閱 處理清除和關閉作業中的錯誤。

同步處理

  1. 針對不需要保護的程式代碼保留鎖定。 當只有少數作業需要保護時,請勿保留整個函式的鎖定。
  2. 叫出鎖定的驅動程式。 這是死結的主要原因。
  3. 使用聯結基本類型來建立鎖定配置,而不是使用適當的系統提供鎖定基本類型,例如 mutex、旗號和微調鎖定。 請參閱 Mutex 物件簡介、旗號物件微調鎖定簡介。
  4. 使用同步鎖定,其中某些類型的被動鎖定會更合適。 請參閱 Fast Mutexes和 Guarded Mutexes事件物件。 如需鎖定的其他觀點,請檢閱OSR文章 - 同步處理的狀態。
  5. 選擇加入 WDF 同步處理和執行層級模型,而不需要完全瞭解含意。 請參閱 使用架構鎖定。 除非您的驅動程式是整合式最上層驅動程式直接與硬體互動,否則請避免選擇使用 WDF 同步處理,因為它可能會導致因遞歸而導致死結。
  6. 在多個線程的內容中取得KEVENT、Semaphore、ERESOURCE、UnsafeFastMutex,而不需要進入重要區域。 這樣做可能會導致 DOS 攻擊,因為持有其中一個鎖定的線程可以暫停。 請參閱 核心發送器對象的簡介。
  7. 在線程堆疊上配置KEVENT,並在EVENT仍在使用時返回呼叫端。 通常搭配 IoBuildSyncronousFsdRequestIoBuildDeviceIoControlRequest使用時完成。 這些呼叫的呼叫端應該確定它們不會從堆疊回溯,直到I/O 管理員在 IRP 完成時發出事件訊號為止。
  8. 無限期地在分派例程中等候。 一般而言,分派例程中的任何等候都是一種不良的做法。
  9. 在刪除物件之前,不適當地檢查物件的有效性(如果 blah == NULL)。 這通常表示作者對控制物件存留期的程式代碼沒有完整瞭解。

物件管理

  1. 未明確父系 WDF 物件。 請參閱 Framework 物件簡介。
  2. 將WDF物件父系至WDFDRIVER,而不是將父系至提供更佳存留期管理和優化記憶體使用量的物件。 例如,將 WDFREQUEST 父代為 WDFDEVICE,而不是 IOTARGET。 請參閱 使用一般 Framework 物件Framework 物件生命週期Framework 物件的摘要。
  3. 不會對跨驅動程式存取的共用記憶體資源執行取消保護。 請參閱 ExInitializeRundownProtection 函式
  4. 在上一個工作專案已經在佇列中或已經執行時,錯誤地將相同的工作專案排入佇列。 如果客戶端假設每個已排入佇列的工作專案都會執行,就會發生此問題。 請參閱 使用 Framework WorkItems。 如需佇列 WorkItems 的詳細資訊,請參閱 驅動程式模組架構 (DMF) 專案中的 DMF_QueuedWorkitem 模組 - https://github.com/Microsoft/DMF
  5. 在張貼定時器預期要處理的訊息之前,佇列定時器。 請參閱 使用定時器
  6. 在工作專案中執行作業,可以封鎖或無限期地完成。
  7. 設計會導致大量工作專案排入佇列的解決方案。 如果壞人可以控制動作,可能會導致沒有回應的系統或 DOS 攻擊(例如,將 I/O 抽入至排入每個 I/O 的新工作專案的驅動程式)。 請參閱 使用 Framework 工作專案
  8. 刪除物件之前,不會再執行工作專案 DPC 回呼以完成。 請參閱撰寫 DPC 例程和 WdfDpcCancel 函式的指導方針。
  9. 建立線程,而不是在短期/非輪詢工作中使用工作專案。 請參閱 系統背景工作線程
  10. 在刪除或卸除驅動程式之前,不保證線程已執行到完成。 如需線程執行同步處理的詳細資訊,請參閱驅動程式模塊架構 (DMF) 專案中與 DMF_Thread 模組相關聯的程式代碼 - https://github.com/Microsoft/DMF
  11. 使用單一驅動程式來管理不同但相互依賴的裝置,以及使用全域變數來共用資訊。

記憶體

  1. 盡可能不要將被動執行程式代碼標示為PAGEABLE。 分頁驅動程式程式代碼可以減少驅動程式程式代碼使用量的大小,從而釋放系統空間以供其他用途使用。 請謹慎標記可設定代碼頁,以引發 IRQL >= DISPATCH_LEVEL,或在引發 IRQL 時呼叫。 請參閱 何時應該讓程式代碼和數據可 分頁,並 讓驅動程式可分頁偵測可分頁的程序代碼。
  2. 在堆疊上宣告大型結構,應該使用堆積/poolinstead。 請參閱 使用 KernelStack配置系統空間記憶體
  3. 不必要的零 WDF 對象內容。 這表示記憶體何時會自動清零時,表示缺乏明確性。

一般驅動程式指導方針

  1. 混合 WDM 和 WDF 基本類型。 使用可使用 WDF 基本類型的 WDM 基本類型。 使用 WDF 基本類型可保護您免於 gotchas、改善偵錯,更重要的是讓您的驅動程式可移植到 usermode。
  2. 視需要命名 FDO 並建立符號連結。 請參閱 管理驅動程式訪問控制
  3. 從範例驅動程式複製貼上和使用 GUID 和其他常數值。
  4. 請考慮在驅動程式專案中使用驅動程式模組架構 (DMF) 開放原始碼 程式代碼。 DMF 是 WDF 的延伸模組,可為 WDF 驅動程式開發人員啟用額外的功能。 請參閱 驅動程式模組架構簡介。
  5. 使用登錄做為進程間通知機制或信箱。 如需替代方案,請參閱 DMF 專案中可用的DMF_NotifyUserWithEventDMF_NotifyUserWithRequest 模組 - https://github.com/Microsoft/DMF
  6. 假設登錄的所有部分在系統早期開機階段都可供存取。
  7. 相依於另一個驅動程式或服務的載入順序。 由於載入順序可以在驅動程式的控制之外變更,這可能會導致一開始運作的驅動程式,但稍後會以無法預測的模式失敗。
  8. 重新建立已可用的驅動程序連結庫,例如 WDF 提供 PnP,如支援驅動程式中的 PnP 和電源管理中所述,或在總線介面中提供的連結庫,如使用適用於驅動程式對驅動程式通訊的總線介面一文中所述。

PnP/Power

  1. 以非 pnp 易記的方式與另一個驅動程式互動 - 未註冊 pnp 裝置變更通知。 請參閱 註冊裝置介面變更通知
  2. 建立 ACPI 節點來列舉裝置,並在其中建立電源相依性,而不是使用總線驅動程式或系統,以簡潔的方式提供軟體裝置建立介面給 PNP 和電源相依性。 請參閱 在函式驅動程式中支援 PnP 和電源管理。
  3. 標示裝置不可停用 - 強制在驅動程式更新時重新啟動。
  4. 將裝置隱藏在設備管理器中。 請參閱從 裝置管理員 隱藏裝置。
  5. 假設驅動程式只會用於裝置的一個實例。
  6. 假設驅動程序永遠不會卸除。 請參閱 PnP 驅動程式的卸除例程
  7. 未處理假介面抵達通知。 這可能會發生,且驅動程式預期會安全地處理此狀況。
  8. 未實作 S0 閑置電源原則,這對 DRIPS 條件約束或子系的裝置很重要。 請參閱 支援閑置電源關閉
  9. 未檢查 WdfDeviceStopIdle 傳回狀態會導致因 WdfDeviceStopIdle/ResumeIdle 不平衡而導致電源參考流失,最終會檢查 9F 錯誤。
  10. 由於資源重新平衡,不知道 PrepareHardware/ReleaseHardware 可以多次呼叫。 這些回呼應限制為初始化硬體資源。 請參閱 EVT_WDF_DEVICE_PREPARE_HARDWARE
  11. 使用 PrepareHardware/ReleaseHardware 來配置軟體資源。 如果資源配置需要與硬體互動,則應在 AddDevice 或 SelfManagedIoInit 中完成裝置的軟體資源配置。 請參閱 EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT

程式代碼撰寫指導方針

  1. 不使用安全字串和整數函式。 請參閱使用 保管庫 字串函式和使用 保管庫 整數函式
  2. 不使用 typedefs 來定義常數。
  3. 使用全域和靜態變數。 避免將每個裝置內容儲存在全域。 全域是用來在多個裝置實例之間共享資訊。 或者,請考慮使用 WDFDRIVER 對象內容,在多個裝置實例之間共用資訊。
  4. 不使用變數的描述性名稱。
  5. 在命名變數中不一致 - 大小寫一致性。 對現有程式代碼進行更新時,不要遵循現有的程式代碼撰寫樣式。 例如,針對不同函式中的通用結構使用不同的變數名稱。
  6. 未批註重要的設計選擇 - 電源管理、鎖定、狀態管理、使用工作專案、DPC、定時器、全域資源使用量、資源預先配置、複雜運算式/條件語句。
  7. 從所呼叫的 API 名稱中,對明顯項目進行批注。 將批注設為相當於函式名稱的英文語言(例如在呼叫 WdfDeviceCreate 時撰寫批注「建立裝置物件」)。
  8. 請勿建立具有傳回呼叫的宏。 請參閱函式 (C++)。
  9. 沒有或不完整的原始程式碼註釋(SAL)。 請參閱 適用於 Windows 驅動程式的 SAL 2.0 註釋。
  10. 使用宏而非內嵌函式。
  11. 使用 C++ 時,針對常數使用宏取代 constexpr
  12. 使用 C 編譯程式編譯驅動程式,而不是 C++ 編譯程式,以確保您能進行強型別檢查。

錯誤處理

  1. 未報告重大驅動程式錯誤,並正常標記裝置無法運作。
  2. 未傳迴轉譯為有意義的 WIN32 錯誤狀態的適當 NT 錯誤狀態。 請參閱 使用NTSTATUS值
  3. 不使用NTSTATUS宏來檢查系統函式的傳回狀態。
  4. 視需要不要判斷狀態變數或旗標。
  5. 檢查指標是否有效,再存取指標以因應競爭條件。
  6. NULL 指標的 ASSERTING。 如果您嘗試使用 NULL 指標來存取記憶體 Windows,將會檢查錯誤。 錯誤檢查的參數將提供修正 Null 指標的必要資訊。 加班時,當許多不需要的 ASSERT 語句新增至程式代碼時,它們會耗用記憶體並讓系統變慢。
  7. 對象內容指標上的 ASSERTING。 驅動程式架構保證物件一律會以內容配置。

追蹤

  1. 未定義 WPP 自定義類型,並在追蹤呼叫中使用它來取得人類可讀取的追蹤訊息。 請參閱 將 WPP 軟體追蹤新增至 Windows 驅動程式
  2. 不使用 IFR 追蹤。 請參閱 在 KMDF 和 UMDF 2 驅動程式中使用 Inflight Trace Recorder (IFR)。
  3. 在 WPP 追蹤呼叫中呼叫函式名稱。 WPP 已經追蹤函式名稱和行號。
  4. 不使用 ETW 事件來測量影響事件的效能和其他重要用戶體驗。 請參閱 將事件追蹤新增至內核模式驅動程式
  5. 未在事件記錄檔中報告重大錯誤,並正常標記裝置無法運作。

驗證

  1. 在開發和測試期間,未同時執行標準和進階設定的驅動程序驗證器。 請參閱 驅動程式驗證器。 在進階設定中,建議啟用所有規則,但與低資源模擬相關的規則除外。 最好以隔離方式執行低資源模擬測試,以便更輕鬆地偵錯問題。
  2. 在驅動程式或驅動程序類別上未執行 DevFund 測試,驅動程式是啟用進階驗證程式設定的一部分。 請參閱 如何透過命令行執行DevFund測試。
  3. 未驗證驅動程式是否符合 HVCI 規範。 請參閱 實作 HVCI 相容性程式代碼
  4. 在開發及測試使用者模式驅動程序期間,未在WUDFhost.exe上執行 AppVerifier。 請參閱 應用程式驗證器
  5. 在運行時間不使用 !wdfpoolusage 調試程序擴充功能檢查記憶體使用量,以確保不會放棄 WDF 物件。 記憶體、要求和工作專案是這些問題的共同受害者。
  6. 不使用 !wdfkd 調試程式延伸模組來檢查物件樹狀結構,以確保對象已正確父代,並檢查主要對象的屬性,例如 WDFDRIVER、WDFDEVICE、IO。