Surface Team ドライバー開発ベスト プラクティス

はじめに

これらのドライバー開発ガイドラインは、Microsoft のドライバー開発者によって長年にわたって開発されました。 ドライバーが不適切な行動をし、レッスンが学習された時間の経過と同時に、これらのレッスンはキャプチャされ、この一連のガイダンスに進化しました。 これらのベスト プラクティスは、Microsoft Surface ハードウェア チームが、独自の Surface ハードウェア エクスペリエンスをサポートするデバイス ドライバー コードを開発および保守するために使用します。

ガイドラインのセットと同様に、正当な例外と同様に有効な代替アプローチがあります。 これらのガイドラインを開発標準に組み込むか、それらを使用して、開発環境と独自の要件に対するドメイン固有のガイドラインを開始することを検討してください。

ドライバー開発者が行った一般的な間違い

I/O の処理

  1. 長さを検証せずに IOCTL から取得されたバッファーにアクセスする。 「バッファーのサイズを確認できない」を参照してください
  2. ユーザー スレッドまたはランダム スレッド コンテキストのコンテキストでブロック I/O を実行する。 カーネル ディスパッチャー オブジェクトの概要に関するページを参照してください。
  3. タイムアウトなしで別のドライバーに同期 I/O を送信する。 「 I/O 要求を同期的に送信する」を参照してください。
  4. セキュリティへの影響を理解せずにどちらの IOCTL も使用する。 「バッファーされた I/O と直接 I/O の両方を使用しない」を参照してください。
  5. WdfRequestForwardToIoQueue の戻り状態を確認しないか、エラーを正しく処理せず、破棄された WDFREQUEST が発生します。
  6. WDFREQUEST をキューの外部に保持し、取り消し不可能な状態にします。 「I/O キューの管理」、「I/O 要求の完了」、および「I/O 要求の取り消し」を参照してください。
  7. IoQueues を使用する代わりに Mark/UnmarkCancelable 関数を使用してキャンセルを管理しようとしています。 「フレームワーク キュー オブジェクト」を参照してください。
  8. ファイル ハンドルのクリーンアップ操作と閉じる操作の違いがわからない。 「クリーンアップ操作と閉じる操作の処理中のエラー」を参照してください。
  9. I/O 完了を伴う潜在的な再帰を見落とし、完了ルーチンから再送信します。
  10. WDFQUEUEs の電源管理属性について明示的ではありません。 電源管理の選択を明確に文書化していません。 これは、 バグ チェック 0x9F の主な原因です。WDF ドライバーでのDRIVER_POWER_STATE_FAILURE。 デバイスが削除されると、フレームワークは、削除プロセスのさまざまな段階で、電源管理キューと非電源マネージド キューから IO を消去します。 電源管理されていないキューは、最終的なIRP_MN_REMOVE_DEVICEを受信すると消去されます。 そのため、電源管理されていないキューで I/O を保持している場合は、デッドロックを回避するために 、EvtDeviceSelfManagedIoFlush のコンテキストで I/O を明示的に消去することをお勧めします。
  11. IRP の処理規則に従っていない。 「クリーンアップ操作と閉じる操作の処理中のエラー」を参照してください。

Synchronization

  1. 保護を必要としないコードのロックを保持する。 少数の操作のみを保護する必要がある場合は、関数全体のロックを保持しないでください。
  2. ロックが保持されているドライバーの呼び出し。 これがデッドロックの主な原因です。
  3. ミューテックス、セマフォ、スピンロックなどの適切なシステム提供のロックプリミティブを使用する代わりに、インターロックプリミティブを使用してロックスキームを作成します。 「ミューテックス オブジェクトの概要」、「セマフォ オブジェクト」、「スピン ロックの概要」を参照してください。
  4. 何らかの種類のパッシブ ロックがより適切なスピンロックを使用する。 「高速ミューテックス」および「保護されたミューテックスイベント オブジェクト」を参照してください。 ロックに関するその他の観点については、OSR に関する記事 「 同期の状態」を参照してください。
  5. 影響を完全に理解せずに WDF 同期と実行レベル モデルを選択する。 「フレームワーク ロックの使用」を参照してください。 ドライバーがモノリシック トップレベル ドライバーでハードウェアと直接やり取りしている場合を除き、再帰によるデッドロックにつながる可能性があるため、WDF 同期を選択しないでください。
  6. 重要な領域に入ることなく、複数のスレッドのコンテキストで KEVENT、Semaphore、ERESOURCE、UnsafeFastMutex を取得します。 この操作を行うと、これらのロックのいずれかを保持しているスレッドを中断できるため、DOS 攻撃が発生する可能性があります。 カーネル ディスパッチャー オブジェクトの概要に関するページを参照してください。
  7. スレッド スタックに KEVENT を割り当て、EVENT がまだ使用されている間に呼び出し元に戻ります。 通常、 IoBuildSyncronousFsdRequest または IoBuildDeviceIoControlRequest で使用する場合に実行されます。 これらの呼び出しの呼び出し元は、IRP が完了したときに I/O マネージャーがイベントを通知するまで、スタックからアンワインドしないようにする必要があります。
  8. ディスパッチ ルーチンで無期限に待機しています。 一般に、ディスパッチ ルーチンでの待機の種類は不適切な方法です。
  9. オブジェクトを削除する前に、オブジェクトの有効性を不適切にチェックします (blah == NULL の場合)。 これは通常、作成者がオブジェクトの有効期間を制御するコードを完全に理解していないことを意味します。

オブジェクト管理

  1. WDF オブジェクトを明示的に親にしない。 「 Framework オブジェクトの概要」を参照してください。
  2. WDF オブジェクトを WDFDRIVER に親付けする代わりに、有効期間管理を向上させ、メモリ使用量を最適化するオブジェクトに親を設定します。 たとえば、WDFREQUEST を IOTARGET ではなく WDFDEVICE に親化します。 「一般的なフレームワーク オブジェクトの使用」、「フレームワーク オブジェクトのライフ サイクル」、「フレームワーク オブジェクトの概要」を参照してください。
  3. ドライバー間でアクセスされる共有メモリ リソースのランダウン保護を行っていない。 ExInitializeRundownProtection 関数に関するページを参照してください。
  4. 前の作業項目が既にキューに入っているか、既に実行されている間に、同じ作業項目を誤ってキューに入れます。 これは、キューに登録されているすべての作業項目が実行されるとクライアントが想定している場合に問題になる可能性があります。 「Framework WorkItems の使用」を参照してください。 WorkItems のキューの詳細については、「Driver Module Framework (DMF) プロジェクトの DMF_QueuedWorkitem モジュール - https://github.com/Microsoft/DMF」を参照してください。
  5. メッセージを投稿する前にタイマーをキューに入れ、タイマーが処理すると予想されます。 「タイマーの使用」を参照してください。
  6. 作業項目で操作を実行すると、完了するまでに無期限に長い時間がかかる可能性があります。
  7. 大量の作業項目がキューに入るソリューションを設計する。 悪者がアクションを制御できる場合は、システムが応答しなくなるか、DOS 攻撃を受ける可能性があります (たとえば、I/O ごとに新しい作業項目をキューに入れるドライバーに I/O を送り込むなど)。 「フレームワーク作業項目の使用」を参照してください。
  8. オブジェクトを削除する前に、作業項目 DPC コールバックが完了するまで実行されません。 DPC ルーチンの記述のガイドラインWdfDpcCancel 関数に関するページを参照してください。
  9. 短時間または非ポーリング タスクで作業項目を使用する代わりにスレッドを作成する。 「システム ワーカー スレッド」を参照してください。
  10. ドライバーを削除またはアンロードする前に、スレッドが完了するまで実行されていないこと。 スレッドランダウン同期の詳細については、Driver Module Framework (DMF) プロジェクトhttps://github.com/Microsoft/DMFDMF_Thread モジュールに関連付けられているコードを参照してください。
  11. 1 つのドライバーを使用して、異なるが相互に依存するデバイスを管理し、グローバル変数を使用して情報を共有する。

メモリ

  1. 可能な場合は、パッシブ実行コードを PAGEABLE としてマークしません。 ページング ドライバー コードを使用すると、ドライバーのコードフットプリントのサイズを小さくできるため、他の用途のシステム領域を解放できます。 IRQL = DISPATCH_LEVELを発生させる、または発生した IRQL >で呼び出すことができるコード ページング可能なマークは注意してください。 「コードとデータをページング可能にするタイミング」とドライバーをページング可能にする」と「ページング可能なコードを検出する」を参照してください。
  2. スタックで大きな構造体を宣言する場合は、代わりにヒープ/プールを使用する必要があります。 「KernelStack の使用」および「System-Space メモリの割り当て」を参照してください。
  3. WDF オブジェクト コンテキストを不必要にゼロにします。 これは、メモリが自動的にゼロになるタイミングが明確でなされていないことを示している可能性があります。

ドライバーの一般的なガイドライン

  1. WDM プリミティブと WDF プリミティブの混在。 WDF プリミティブを使用できる WDM プリミティブを使用する。 WDF プリミティブを使用すると、ユーザーをゴチャから保護し、デバッグを改善し、さらに重要なのは、ドライバーを usermode に移植できるようにします。
  2. 必要でない場合は、FDO に名前を付け、シンボリック リンクを作成します。 「ドライバーのアクセス制御を管理する」を参照してください。
  3. サンプル ドライバーから GUID やその他の定数値を貼り付け、使用してコピーします。
  4. ドライバー プロジェクトでドライバー モジュール フレームワーク (DMF) オープンソースコードを使用することを検討してください。 DMF は、WDF ドライバー開発者向けの追加機能を有効にする WDF の拡張機能です。 「ドライバー モジュール フレームワークの概要」を参照してください。
  5. プロセス間通知メカニズムまたはメールボックスとしてレジストリを使用する。 別の方法については、「DMF プロジェクトで使用できるモジュールのDMF_NotifyUserWithEventとDMF_NotifyUserWithRequest - https://github.com/Microsoft/DMF」を参照してください。
  6. システムの初期ブート フェーズ中に、レジストリのすべての部分がアクセス可能になると仮定します。
  7. 別のドライバーまたはサービスの読み込み順序に依存しています。 読み込み順序はドライバーの制御外で変更できるため、最初に動作するドライバーが発生する可能性がありますが、後で予期しないパターンで失敗します。
  8. WDF など、既に使用可能なドライバー ライブラリを再作成すると、「 ドライバーでの PnP と電源管理のサポート 」で説明されている PnP、または「ドライバー間通信にバス インターフェイスを使用する OSR」の記事で説明されているように 、バス インターフェイスで提供される PnP が提供されます。

PnP/Power

  1. pnp 以外のわかりやすい方法で別のドライバーとやり取りする - pnp デバイス変更通知に登録されていません。 「デバイス インターフェイス変更通知の登録」を参照してください
  2. ACPI ノードを作成してデバイスを列挙し、バス ドライバーまたはシステム提供のソフトウェア デバイス作成インターフェイスを PNP に使用する代わりに、それらの間に電源依存関係を作成し、エレガントな方法で電源依存関係を作成します。 「Function Drivers での PnP と電源管理のサポート」を参照してください。
  3. デバイスを無効にしないことをマークする - ドライバーの更新時に強制的に再起動します。
  4. デバイス マネージャーでデバイスを非表示にします。 「デバイス マネージャーからデバイスを非表示にする」を参照してください。
  5. ドライバーがデバイスの 1 つのインスタンスにのみ使用されることを前提としています。
  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. 定数を定義するために typedef を使用しません。
  3. グローバル変数と静的変数の使用。 デバイス ごとのコンテキストをグローバルに格納しないようにします。 グローバルは、デバイスの複数のインスタンス間で情報を共有するためのものです。 別の方法として、デバイスの複数のインスタンス間で情報を共有するために WDFDRIVER オブジェクト コンテキストを使用することを検討してください。
  4. 変数にわかりやすい名前を使用しません。
  5. 名前付け変数で一貫性がない - 大文字と小文字の整合性。 既存のコードを更新するときに、既存のコーディング スタイルに従わない。 たとえば、さまざまな関数で共通の構造体に異なる変数名を使用します。
  6. 電源管理、ロック、状態管理、作業項目の使用、DPC、タイマー、グローバル リソースの使用状況、リソースの事前割り当て、複雑な式/条件付きステートメントなど、重要な設計上の選択肢はコメントしていません。
  7. 呼び出される API の名前から明らかなことに関するコメント。 コメントを関数名と同等の英語にする (WdfDeviceCreate を呼び出すときにコメント "Create the Device Object" を記述するなど)。
  8. 戻り値呼び出しを持つマクロは作成しないでください。 「Functions (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 カスタム型を定義せず、トレース呼び出しで使用して人間が判読できるトレース メッセージを取得する。 「Windows ドライバーへの WPP ソフトウェア トレースの追加」を参照してください。
  2. IFR トレースを使用していません。 「KMDF ドライバーと UMDF 2 ドライバーでのインフライト トレース レコーダー (IFR) の使用」を参照してください。
  3. WPP トレース呼び出しで関数名を呼び出す。 WPP では、関数名と行番号が既に追跡されています。
  4. ETW イベントを使用して、イベントに影響を与えるパフォーマンスやその他の重要なユーザー エクスペリエンスを測定しません。 「Kernel-Mode ドライバーへのイベント トレースの追加」を参照してください。
  5. イベント ログに重大なエラーが報告されず、デバイスが正常に機能していないというマークが付けられます。

検証

  1. 開発とテスト中に、標準設定と詳細設定の両方でドライバー検証ツールを実行していません。 「ドライバー検証ツール」を参照してください。 詳細設定では、リソースの少ないシミュレーションに関連するルールを除くすべてのルールを有効にすることをお勧めします。 問題をデバッグしやすくするために、リソースの少ないシミュレーション テストを分離して実行することをお勧めします。
  2. ドライバーまたはドライバーが参加しているデバイス クラスで DevFund テストを実行せず、高度な検証ツール設定が有効になっています。 「コマンド ラインを使用して DevFund テストを実行する方法」を参照してください。
  3. ドライバーが HVCI に準拠していることを確認していません。 「HVCI 互換性コードを実装する」を参照してください。
  4. ユーザー モード ドライバーの開発とテスト中に、WUDFhost.exe で AppVerifier を実行していません。 「アプリケーション検証ツール」を参照してください。
  5. 実行時に !wdfpoolusage デバッガー拡張機能を使用してメモリの使用状況をチェックせず、WDF オブジェクトが破棄されていないことを確認します。 メモリ、要求、および作業項目は、これらの問題の一般的な対象です。
  6. !wdfkd デバッガー拡張機能を使用してオブジェクト ツリーを検査して、オブジェクトが正しく親されていることを確認し、WDFDRIVER、WDFDEVICE、IO などの主要なオブジェクトの属性をチェックしない。