Windowsのシステムクラッシュダンプ内の自身のデータ

私の活動の性質上(Windowsカーネル)、定期的にBSODダンプを解析する必要があります。 エンドユーザーがミニダンプのみを正常に書き込み、プロセッサレジスタとフォールスタックの値のみが保存されるケースは少なくありません。 また、クライアントマシンをデバッグする他の手段はありません。 しかし、スタックにドライバーがなく、顧客が製品のインストール後にクラッシュが始まり、この製品のドライバーがオフになった後に終了すると主張した場合はどうでしょうか? 私の場合、最近のイベントの小さなログを循環バッファーに保存するのが良い解決策であることがわかりました。 この循環バッファをダンプに保存するためだけに残ります。







カットの下で、ドライバーからダンプにデータを追加する方法を説明します。 そしてpykdを使用してそれらを抽出します。

pykd







Windows XP SP1および2003 Server以降、システムは、ドライバーが独自のデータをコアクラッシュダンプに追加する機会を提供します: セカンダリコールバックデータ 。 システムがドライバーからこのデータを要求するには、 KeRegisterBugCheckReasonCallbackを呼び出してコールバック関数を登録する必要があります。 登録するときは、カーネルがクラッシュしたときに呼び出される関数のアドレスを指定する必要があり、この場合( BugCheckSecondaryDumpDataCallback )、システムダンプで補足する必要があるデータを提供します。 指定されたコールバック関数は2回呼び出されます。







  1. システムが初めてドライバーを呼び出してバッファーのサイズを決定するとき。 この段階で、OSは入力データに最大データサイズ( KBUGCHECK_SECONDARY_DUMP_DATA .MaximumAllowed)を示しており、dapmaに保存できます。 このサイズは、生成されるシステムダンプのタイプによって異なります。 Windows XPでは、ミニダンプ記録設定が設定されている場合、システムは4096バイト(1ページのメモリ)を提供します。
  2. 2回目は、システムがデータ自体を要求します。


オペレーティングシステムのカーネルがクラッシュしたときにコールバック関数が呼び出されるという事実により、この関数のコードには重大な制限が課せられます。メモリ割り当てを使用しない(すべてが事前に割り当てられます)同期(デッドロックのリスク)。 詳細については、MSDNの記事「 バグチェックコールバックルーチンの作成」を参照してください。







奇妙なことですが、 KeRegisterBugCheckReasonCallback関数の使用例は、WDKの例集にはありません。 しかし、この例はオープンソースのMicrosoftのKMDF(カーネルモードドライバーフレームワーク) -fxbugcheckcallback.cppで見つかりました







ハンドラー登録:FxInitializeBugCheckDriverInfo関数の一部
// // The KeRegisterBugCheckReasonCallback exists for xp sp1 and above. So // check whether this function is defined on the current OS and register // for the bugcheck callback only if this function is defined. // RtlInitUnicodeString(&funcName, L"KeRegisterBugCheckReasonCallback"); funcPtr = (PFN_KE_REGISTER_BUGCHECK_REASON_CALLBACK) MmGetSystemRoutineAddress(&funcName); if (NULL == funcPtr) { goto Done; }
      
      





  // // Initialize the callback record. // KeInitializeCallbackRecord(callbackRecord); // // Register the bugcheck callback. // funcPtr(callbackRecord, FxpLibraryBugCheckCallback, KbCallbackSecondaryDumpData, (PUCHAR)WdfLdrType); ASSERT(callbackRecord->CallbackRoutine != NULL);
      
      





ハンドラー実装:FxpLibraryBugCheckCallback関数
 VOID FxpLibraryBugCheckCallback( __in KBUGCHECK_CALLBACK_REASON Reason, __in PKBUGCHECK_REASON_CALLBACK_RECORD /* Record */, __inout PVOID ReasonSpecificData, __in ULONG ReasonSpecificLength ) /*++ Routine Description: Global (framework-library) BugCheck callback routine for WDF Arguments: Reason - Must be KbCallbackSecondaryData Record - Supplies the bugcheck record previously registered ReasonSpecificData - Pointer to KBUGCHECK_SECONDARY_DUMP_DATA ReasonSpecificLength - Sizeof(ReasonSpecificData) Return Value: None Notes: When a bugcheck happens the kernel bugcheck processor will make two passes of all registered BugCheckCallbackRecord routines. The first pass, called the "sizing pass" essentially queries all the callbacks to collect the total size of the secondary dump data. In the second pass the actual data is captured to the dump. --*/ { PKBUGCHECK_SECONDARY_DUMP_DATA dumpData; ULONG dumpSize; UNREFERENCED_PARAMETER(Reason); UNREFERENCED_PARAMETER(ReasonSpecificLength); ASSERT(ReasonSpecificLength >= sizeof(KBUGCHECK_SECONDARY_DUMP_DATA)); ASSERT(Reason == KbCallbackSecondaryDumpData); dumpData = (PKBUGCHECK_SECONDARY_DUMP_DATA) ReasonSpecificData; dumpSize = FxLibraryGlobals.BugCheckDriverInfoIndex * sizeof(FX_DUMP_DRIVER_INFO_ENTRY); // // See if the bugcheck driver info is more than can fit in the dump // if (dumpData->MaximumAllowed < dumpSize) { dumpSize = EXP_ALIGN_DOWN_ON_BOUNDARY( dumpData->MaximumAllowed, sizeof(FX_DUMP_DRIVER_INFO_ENTRY)); } if (0 == dumpSize) { goto Done; } // // Ok, provide the info about the bugcheck data. // dumpData->OutBuffer = FxLibraryGlobals.BugCheckDriverInfo; dumpData->OutBufferLength = dumpSize; dumpData->Guid = WdfDumpGuid2; Done:; }
      
      





デモとして、ダンプから抽出するのはこれらのデータです。 データは構造FX_DUMP_DRIVER_INFO_ENTRYの配列であり 、各構造のフィールドにはバージョンとドライバー名があります。 ダンプ内のデータのキーは、書き込み時に指定されたGUIDです。この例では{F87E4A4C-C5A1-4d2f-BFF0-D5DE63A5E4C3}です。







ダンプに保存されたデータを表示するには、デバッグコマンド.enumtagがあります。 コマンドの結果として、生のメモリダンプが表示されます。 関心のあるデータの例を次に示します。







 1: kd> .enumtag {65755A40-F146-43EA-8C9136B85728FD35} - 0x0 bytes <...> {F87E4A4C-C5A1-4D2F-BFF0D5DE63A5E4C3} - 0x508 bytes 00 00 00 00 00 00 00 00 01 00 00 00 0D 00 00 00 ................ 00 00 00 00 57 64 66 30 31 30 30 30 00 00 00 00 ....Wdf01000.... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 90 AC 55 00 00 E0 FF FF ..........U..... 01 00 00 00 0B 00 00 00 00 00 00 00 61 63 70 69 ............acpi 65 78 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ex.............. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 30 81 F6 00 00 E0 FF FF 01 00 00 00 0B 00 00 00 0............... 00 00 00 00 6D 73 69 73 61 64 72 76 00 00 00 00 ....msisadrv.... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 A0 D3 EB 00 00 E0 FF FF ................ 01 00 00 00 0B 00 00 00 00 00 00 00 76 64 72 76 ............vdrv <...>
      
      





この形式で作業できますが、便利ではありません。 マイクロソフトは、その拡張機能をデバッガに書き込むことを提案しています







このデータをより実用的な方法で使用するには、独自のデバッガー拡張機能を作成することをお勧めします。

しかし、私はpykdプロジェクトの開発者の1人です。 pykdモジュールは、Pythonを使用してデバッグを自動化できるデバッガ拡張機能です。 したがって、それを使用してデータを抽出および視覚化する方法を示します。 セカンダリコールバックデータのリストと抽出が最新の(執筆時点で)リリース0.3.3.3に追加されたことを直ちに予約します。 したがって、古いバージョンがすでにインストールされている場合は、pykd( 最終リリース )を更新する必要があります。







テストダンプとして、pykdユニットテストに使用されるファイルwin8_x64_mem.cabを使用します







実際、データの読み取りおよびフォーマット用のスクリプト全体は次のとおりです。







kmdf_tagged.py
 import os import sys import pykd import struct def print_command(command): if pykd.getDebugOptions() & pykd.debugOptions.PreferDml: pykd.dprint( '<exec cmd="{}">{}</exec>'.format(command, command), dml = True ) else: pykd.dprint( command ) def parse(): buff = bytearray( pykd.loadTaggedBuffer("F87E4A4C-C5A1-4d2f-BFF0-D5DE63A5E4C3") ) entry_type = pykd.typeInfo("Wdf01000!_FX_DUMP_DRIVER_INFO_ENTRY") _struct = struct.Struct( "<{}III".format("Q" if pykd.is64bitSystem() else "L") ) name_offset = entry_type.fieldOffset("DriverName") name_size = entry_type.DriverName.size() entry_size = entry_type.size() if len(buff) % entry_size: raise RuntimeError( "The buffer size ({}) is not a multiple of entry size ({})".format(len(buff), entry_size) ) print("[FxLibraryGlobals.BugCheckDriverInfo]") while len(buff): ptr, mj, mn, build = _struct.unpack_from(buff) name = str(buff[name_offset : name_offset + name_size]).strip("\0") command = "!drvobj {} 7".format(name) print_command( command ) pykd.dprint( " " * (24 - len(name)) ) pykd.dprint( " {:12} ".format("({}.{}.{})".format(mj, mn, build)) ) if ptr: command = "dx ((Wdf01000!{})0x{:x})".format(entry_type.FxDriverGlobals.name(), ptr) print_command( command ) pykd.dprintln( "" ) buff = buff[entry_size:] if __name__ == "__main__": if len(sys.argv) == 1: parse() else: for file_name in sys.argv[1:]: print(file_name) dump_id = pykd.loadDump(file_name) parse() pykd.closeDump(dump_id)
      
      





私の意見では、スクリプトの内容は非常に単純です(解析関数)。









WinDbgデバッガーでスクリプトを実行します。

windbg_output







解析関数の後にスクリプトの内容を見ると、スクリプトが引数を取ることができることがわかります。 kmdf_tagged.pyスクリプトは、コマンドライン引数が指定された場合のオフライン(デバッガー外)での動作を示すために作成されています。 スクリプトは、渡された各引数をダンプファイルへのパスとして扱い、このダンプをロードして、そこからターゲットデータを抽出します。 特に、スクリプトはバッチモードでダンプファイルを処理できます。







 ~> for /R .\dumps %i in (*.*) do @python.exe kmdf_tagged.py %i ~\dumps\win8_x64_mem.cab [FxLibraryGlobals.BugCheckDriverInfo] !drvobj Wdf01000 7 (1.13.0) !drvobj acpiex 7 (1.11.0) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe0000055ac90) <...> !drvobj PEAUTH 7 (1.7.6001) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe000022081c0) ~\dumps\win8_x64_mem2.cab [FxLibraryGlobals.BugCheckDriverInfo] !drvobj Wdf01000 7 (1.13.0) !drvobj acpiex 7 (1.11.0) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe0000055ac90) <...> !drvobj PEAUTH 7 (1.7.6001) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe000022081c0)
      
      





私の経験(およびこの記事の内容)が誰かに役立つことを願っています。 また、BSODの数(理由が謎のまま)は0になる傾向があります。








All Articles