バグ チェック理由コールバック ルーチンの記述
ドライバーは必要に応じて、クラッシュ ダンプ ファイルの書き込み後にシステムが呼び出す KBUGCHECK_REASON_CALLBACK_ROUTINE コールバック関数を提供できます。
Note
この記事では、バグ チェック 理由 コールバック ルーチンについて説明し、 KBUGCHECK_CALLBACK_ROUTINE コールバック関数については説明しません。
このコールバックでは、ドライバーは次のことができます。
ドライバー固有のデータをクラッシュ ダンプ ファイルに追加する
デバイスを既知の状態にリセットする
コールバックを登録および削除するには、次のルーチンを使用します。
このコールバック型はオーバーロードされ、登録時に指定された KBUGCHECK_CALLBACK_REASON に基づいて動作が変化します。 この記事では、さまざまな使用シナリオについて説明します。
バグ チェック データの一般的な情報については、 「バグ チェック コールバック データの読み取り」を参照してください。
バグ チェック コールバック ルーチンの制限
バグ チェック コールバック ルーチンは IRQL = HIGH_LEVEL で実行され、実行できる操作に強い制限を課します。
バグ チェック コールバック ルーチンでは、次のことはできません。
メモリの割り当て
ページング可能なメモリにアクセスする
任意の同期メカニズムを使用する
IRQL = DISPATCH_LEVEL 以下で実行する必要があるルーチンを呼び出す
バグ チェック コールバック ルーチンは、中断なしで実行することが保証されているため、同期は不要です。 (バグ チェック ルーチンが同期メカニズムを使用してロックを取得しようとすると、システムはデッドロックします)。データ構造またはリストはバグ チェックの時点で不整合な状態になる可能性があるため、ロックによって保護されたデータ構造にアクセスするときは注意が必要です。 たとえば、リストを取得するときにチェック上限を追加し、循環リストがある場合やリンクが無効なアドレスを指している場合に備えて、リンクが有効なメモリを指していることを確認する必要があります。
MmIsAddressValid は、バグ チェック コールバック ルーチンで、アドレスにアクセスするとページ エラーが発生するかどうかをチェックするために使用できます。 ルーチンは中断されずに実行され、他のコアはフリーズするため、これはその関数の同期要件を満たします。 ページング対象か、または無効なカーネル アドレスは、バグ チェック コールバックで延期する前に、常に MmIsAddressValid でチェックする必要があります。これは、ページ フォールトによって二重エラーが発生し、ダンプが書き込まれなくなる可能性があるためです。
ドライバーのバグ チェック コールバック ルーチンは、ドライバーのデバイスと通信するために、 READ_PORT_XXX、 READ_REGISTER_XXX、 WRITE_PORT_XXX、および WRITE_REGISTER_XXX ルーチンを安全に使用できます。 (これらのルーチンの詳細については、 「ハードウェア抽象化レイヤー ルーチン」を参照してください。)
KbCallbackAddPages コールバック ルーチンの実装
カーネル モード ドライバーは、 KBUGCHECK_REASON_CALLBACK_ROUTINE コールバック関数 ( KbCallbackAddPages 型) を実装して、バグチェックが発生したときにクラッシュ ダンプ ファイルに 1 つ以上のデータ ページを追加できます。 このルーチンをオペレーティング システムに登録するために、ドライバーは KeRegisterBugCheckReasonCallback ルーチンを呼び出します。 ドライバーをアンロードする前に、 KeDeregisterBugCheckReasonCallback ルーチンを呼び出して登録を削除する必要があります。
Windows 8 以降では、登録済みの KbCallbackAddPages ルーチンが カーネル メモリ ダンプ または 完全なメモリ ダンプ中に呼び出されます。 以前のバージョンの Windows では、登録済みの KbCallbackAddPages ルーチンはカーネル メモリ ダンプ中に呼び出されますが、完全なメモリ ダンプ中には呼び出されません。 既定では、カーネル メモリ ダンプには チェック、バグ チェックが発生した時点で Windows カーネルによって使用されている物理ページのみが含まれますが、完全なメモリ ダンプには、Windows によって使用されるすべての物理メモリが含まれます。 完全なメモリ ダンプには、既定では、プラットフォーム ファームウェアで使用される物理メモリは含まれません。
KbCallbackAddPages ルーチンでは、ダンプ ファイルに追加するドライバー固有のデータを指定できます。 たとえば、カーネル メモリ ダンプの場合、仮想メモリ内のシステム アドレス範囲にマップされていないが、ドライバーのデバッグに役立つ情報が含まれる物理ページを、この追加データに含めることができます。 KbCallbackAddPages ルーチンでは、マップされていないドライバー固有の任意の物理ページ、または仮想メモリ内のユーザー モード アドレスにマップされているドライバー固有の任意の物理ページが、ダンプ ファイルに追加されることがあります。
バグ チェックが発生すると、オペレーティング システムは、登録されているすべての KbCallbackAddPages ルーチンを呼び出して、クラッシュ ダンプ ファイルに追加するデータのドライバーをポーリングします。 各呼び出しでは、1 つ以上の連続したデータのページがクラッシュ ダンプ ファイルに追加されます。 KbCallbackAddPages ルーチンは、開始ページの仮想アドレスまたは物理アドレスを指定できます。 呼び出し中に複数のページが指定された場合、開始アドレスが仮想または物理のいずれであるかに応じて、ページは仮想メモリまたは物理メモリ内で連続しています。 連続しないページを提供するために、 KbCallbackAddPages ルーチンは、 KBUGCHECK_ADD_PAGES 構造体にフラグを設定して、追加のデータがあり、再度呼び出す必要があることを示すことができます。
セカンダリ クラッシュ ダンプ領域にデータを追加する KbCallbackSecondaryDumpData ルーチンとは異なり、 KbCallbackAddPages ルーチンは、プライマリ クラッシュ ダンプ領域にデータのページを追加します。 デバッグ中、プライマリ クラッシュ ダンプ データは、セカンダリ クラッシュ ダンプ データよりも簡単にアクセスできます。
オペレーティング システムは、ReasonSpecificData が指す KBUGCHECK_ADD_PAGES 構造体の BugCheckCode メンバー を入力します。 KbCallbackAddPages ルーチンは、この構造体の Flags、 Address、 および Count メンバーの値を設定する必要があります。
KbCallbackAddPagesの最初の呼び出しの前に、オペレーティング システムは コンテキスト を NULLに初期化します。 KbCallbackAddPages ルーチンが複数回呼び出された場合、オペレーティング システムは、コールバック ルーチンが前の呼び出しで コンテキスト メンバーに書き込んだ値を保持します。
KbCallbackAddPages ルーチンは、実行できるアクションで非常に制限されています。 詳細については、 「バグ チェック コールバック ルーチンの制限」を参照してください。
KbCallbackDumpIo コールバック ルーチンの実装
カーネル モード ドライバーは、データがクラッシュ ダンプ ファイルに書き込まれるたびに作業を実行する KbCallbackDumpIo 型の KBUGCHECK_REASON_CALLBACK_ROUTINE コールバック関数を実装できます。 システムは ReasonSpecificData パラメーターで、 KBUGCHECK_DUMP_IO 構造体へポインターを渡します。 Buffer メンバーは現在のデータを指し、 BufferLength メンバーはその長さを指定します。 Type メンバーは、ダンプ ファイル ヘッダー情報、メモリの状態、ドライバーによって提供されるデータなど、現在書き込まれているデータの種類を示します。 使用可能な情報の種類の説明については、 KBUGCHECK_DUMP_IO_TYPE 列挙体を参照してください。
システムは、クラッシュ ダンプ ファイルを順番に書き込むか、順序を狂わせて書き込むことができます。 システムがクラッシュ ダンプ ファイルを順番に書き込む場合、 ReasonSpecificData の Offset メンバーは -1 です。それ以外の場合、 Offset はクラッシュ ダンプ ファイル内の現在のオフセット (バイト単位) に設定されます。
システムは、ファイルを順番に書き込むときに、各 KbCallbackDumpIo ルーチンを、ヘッダー情報 (Type = KbDumpIoHeader) を書き込むときに 1 回以上呼び出し、クラッシュ ダンプ ファイルの本文 (Type = KbDumpIoBody) を書き込むときに 1 回以上、セカンダリ ダンプ データ (Type = KbDumpIoSecondaryDumpData) を書き込むときに 1 回以上呼び出します。 クラッシュ ダンプ ファイルの書き込みが完了すると、システムは Buffer = NULL、 BufferLength = 0、および Type = KbDumpIoComplete を使用してコールバックを呼び出します。
KbCallbackDumpIo ルーチンのメインの目的は、システム クラッシュ ダンプ データがディスク以外のデバイスに書き込まれるのを許可することです。 たとえば、システムの状態を監視するデバイスは、コールバックを使用して、システムがバグ チェックを発行したことを報告したり、分析のためにクラッシュ ダンプを提供することができます。
KeRegisterBugCheckReasonCallback は KbCallbackDumpIo ルーチンの登録に使用します。 ドライバーは、その後、 KeDeregisterBugCheckReasonCallback を使用してコールバックを削除できます。 ドライバーをアンロードできる場合は、その DRIVER_UNLOAD コールバック関数に登録されているコールバックを削除する必要があります。
KbCallbackDumpIo ルーチンは、実行できるアクションが厳密に制限されます。 詳細については、 「バグ チェック コールバック ルーチンの制限」を参照してください。
KbCallbackSecondaryDumpData コールバック ルーチンの実装
カーネル モード ドライバーは、 KbCallbackSecondaryDumpData 型の KBUGCHECK_REASON_CALLBACK_ROUTINE コールバック関数を実装して、クラッシュ ダンプ ファイルに追加するデータを提供できます。
システムは、ReasonSpecificData が指す KBUGCHECK_SECONDARY_DUMP_DATA構造体の InBuffer、 InBufferLength、 OutBuffer、および MaximumAllowed メンバーを設定します。 MaximumAllowed メンバーは、ルーチンが提供できるダンプ データの最大量を指定します。
OutBuffer メンバーの値は、次のように、システムがドライバーのダンプ データのサイズまたはデータ自体のサイズを要求しているかどうかを決定します。
KBUGCHECK_SECONDARY_DUMP_DATA の OutBuffer メンバーが NULL の場合、システムはサイズ情報のみを要求します。 KbCallbackSecondaryDumpData ルーチンは、 OutBuffer メンバーと OutBufferLength メンバーを格納します。
KBUGCHECK_SECONDARY_DUMP_DATA の OutBuffer メンバーが InBuffer メンバーと等しい場合、システムはドライバーのセカンダリ ダンプ データを要求しています。 KbCallbackSecondaryDumpData ルーチンは、 OutBuffer メンバーと OutBufferLength メンバーを格納し、 OutBuffer で指定されたバッファーにデータを書き込みます。
KBUGCHECK_SECONDARY_DUMP_DATA の InBuffer メンバーは、ルーチンで使用する小さなバッファーを指します。 InBufferLength メンバーは、バッファーのサイズを指定します。 書き込むデータの量が InBufferLength 未満の場合、コールバック ルーチンはこのバッファーを使用して、クラッシュ ダンプ データをシステムに提供できます。 その後、コールバック ルーチンは、 OutBuffer を InBuffer に、 OutBufferLength をバッファーに書き込まれた実際のデータ量に設定します。
InBufferLength より大きい量のデータを書き込む必要があるドライバーは、独自のバッファーを使用してデータを提供できます。 このバッファーは、コールバック ルーチンを実行する前に割り当てられている必要があり、常駐メモリ (非ページ プールなど) に存在する必要があります。 次に、コールバック ルーチンは、ドライバーのバッファーを指すように OutBuffer を設定し、 OutBufferLength にクラッシュ ダンプ ファイルに書き込むバッファー内のデータ量を設定します。
クラッシュ ダンプ ファイルに書き込まれるデータの各ブロックには、KBUGCHECK_SECONDARY_DUMP_DATA構造体の Guid メンバーの値がタグ付けされます。 使用される GUID は、ドライバーに固有である必要があります。 この GUID に対応するセカンダリ ダンプ データを表示するには、デバッガー拡張機能で .enumtag コマンドまたは IDebugDataSpaces3::ReadTagged メソッドを使用できます。 デバッガーおよびデバッガー拡張機能の詳細については、 「Windows デバッグ」」を照してください。
ドライバーは、同じ GUID を持つ複数のブロックをクラッシュ ダンプ ファイルに書き込むことができますが、これは非常に不適切な方法です。デバッガーからアクセスできるのは最初のブロックのみであるためです。 複数の KbCallbackSecondaryDumpData ルーチンを登録するドライバーは、コールバックごとに一意の GUID を割り当てる必要があります。
KeRegisterBugCheckReasonCallback を使用して KbCallbackDumpIo ルーチンを登録します。 ドライバーは、その後、 KeDeregisterBugCheckReasonCallback を使用してコールバック ルーチンを削除できます。 ドライバーをアンロードできる場合は、その DRIVER_UNLOAD コールバック関数に登録されているコールバック ルーチンを削除する必要があります。
KbCallbackSecondaryDumpData ルーチンは、実行できるアクションが非常に制限されています。 詳細については、 「バグ チェック コールバック ルーチンの制限」を参照してください。
KbCallbackTriageDumpData コールバック ルーチンの実装
Windows 10 バージョン 1809 および Windows Server 2019 以降では、カーネル モード ドライバーは KbCallbackTriageDumpData 型の KBUGCHECK_REASON_CALLBACK_ROUTINE コールバック関数を実装して、彫刻されたカーネル ミニダンプに含める仮想メモリ範囲をマークできます。 これにより、指定された範囲が確実にミニダンプに含まれるので、カーネル ダンプで動作するのと同じデバッガー コマンドを使用してアクセスできます。 これは現在、"彫刻された" ミニダンプに対して実装されています。つまり、カーネル以上のダンプがキャプチャされ、その後、より大きなダンプからミニダンプが作成されました。 ほとんどのシステムは既定で自動/カーネル ダンプ用に構成され、クラッシュ後の次回起動時にミニダンプが自動的に作成されます。
システムは ReasonSpecificData パラメーターで、バグ チェックに関する情報を含む KBUGCHECK_TRIAGE_DUMP_DATA 構造体へのポインターと、初期化され設定されたデータ配列を返すためにドライバーによって使用される OUT パラメーターを渡します。
次の例では、ドライバーはトリアージ ダンプ配列を構成し、コールバックの最小限の実装を登録します。 ドライバーは、配列を使用して、ミニダンプに 2 つのグローバル変数を追加します。
#include <ntosp.h>
// Header definitions
//
// The maximum count of ranges the driver will add to the array.
// This example is only adding max 3 ranges with some extra.
//
#define MAX_RANGES 10
//
// This should be large enough to hold the maximum number of KADDRESS_RANGE
// which the driver expects to add to the array.
//
#define ARRAY_SIZE ((FIELD_OFFSET(KTRIAGE_DUMP_DATA_ARRAY, Blocks)) + (sizeof(KADDRESS_RANGE) * MAX_RANGES))
// Globals
static PKBUGCHECK_REASON_CALLBACK_RECORD gBugcheckTriageCallbackRecord;
static PKTRIAGE_DUMP_DATA_ARRAY gTriageDumpDataArray;
//
// This is a global variable which the driver wants to be available in
// the kernel minidump. A real driver may add more address ranges.
//
ULONG64 gDriverData1 = 0xAAAAAAAA;
PULONG64 gpDriverData2;
// Functions
VOID
ExampleBugCheckCallbackRoutine(
KBUGCHECK_CALLBACK_REASON Reason,
PKBUGCHECK_REASON_CALLBACK_RECORD Record,
PVOID Data,
ULONG Length
)
{
PKBUGCHECK_TRIAGE_DUMP_DATA DumpData;
UNREFERENCED_PARAMETER(Reason);
UNREFERENCED_PARAMETER(Record);
UNREFERENCED_PARAMETER(Length);
DumpData = (PKBUGCHECK_TRIAGE_DUMP_DATA) Data;
if ((DumpData->Flags & KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE) == 0) {
return;
}
if (gTriageDumpDataArray == NULL)
{
return;
}
//
// Add the dynamically allocated global pointer and buffer once validated.
//
if ((gpDriverData2 != NULL) && (MmIsAddressValid(gpDriverData2))) {
//
// Add the address of the global itself a well as the pointed data
// so you can use the global to access the data in the debugger
// by running a command like "dt example!gpDriverData2"
//
KeAddTriageDumpDataBlock(gTriageDumpDataArray, &gpDriverData2, sizeof(PULONG64));
KeAddTriageDumpDataBlock(gTriageDumpDataArray, gpDriverData2, sizeof(ULONG64));
}
//
// Pass the array back for processing.
//
DumpData->DataArray = gTriageDumpDataArray;
return;
}
// Setup Function
NTSTATUS
SetupTriageDataCallback(VOID)
{
PVOID pBuffer;
NTSTATUS Status;
BOOLEAN bSuccess;
//
// Call this function from DriverEntry.
//
// Allocate a buffer to hold a callback record and triage dump data array
// in the non-paged pool.
//
pBuffer = ExAllocatePoolWithTag(NonPagedPoolNx,
sizeof(KBUGCHECK_REASON_CALLBACK_RECORD) + ARRAY_SIZE,
'Xmpl');
if (pBuffer == NULL) {
return STATUS_NO_MEMORY;
}
RtlZeroMemory(pBuffer, sizeof(KBUGCHECK_REASON_CALLBACK_RECORD));
gBugcheckTriageCallbackRecord = (PKBUGCHECK_REASON_CALLBACK_RECORD) pBuffer;
KeInitializeCallbackRecord(gBugcheckTriageCallbackRecord);
gTriageDumpDataArray =
(PKTRIAGE_DUMP_DATA_ARRAY) ((PUCHAR) pBuffer + sizeof(KBUGCHECK_REASON_CALLBACK_RECORD));
//
// Initialize the dump data block array.
//
Status = KeInitializeTriageDumpDataArray(gTriageDumpDataArray, ARRAY_SIZE);
if (!NT_SUCCESS(Status)) {
ExFreePoolWithTag(pBuffer, 'Xmpl');
gTriageDumpDataArray = NULL;
gBugcheckTriageCallbackRecord = NULL;
return Status;
}
//
// Set up a callback record
//
bSuccess = KeRegisterBugCheckReasonCallback(gBugcheckTriageCallbackRecord,
ExampleBugCheckCallbackRoutine,
KbCallbackTriageDumpData,
(PUCHAR)"Example");
if ( !bSuccess ) {
ExFreePoolWithTag(pBuffer, 'Xmpl');
gTriageDumpDataArray = NULL;
gBugcheckTriageCallbackRecord = NULL;
return STATUS_UNSUCCESSFUL;
}
//
// It is possible to add a range to the array before bugcheck if it is
// guaranteed to remain valid for the lifetime of the driver.
// The value could change before bug check, but the address and size
// must remain valid.
//
KeAddTriageDumpDataBlock(gTriageDumpDataArray, &gDriverData1, sizeof(gDriverData1));
//
// For an example, allocate another buffer here for later addition tp the array.
//
gpDriverData2 = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(ULONG64), 'Xmpl');
if (gpDriverData2 != NULL) {
*gpDriverData2 = 0xBBBBBBBB;
}
return STATUS_SUCCESS;
}
// Deregister function
VOID CleanupTriageDataCallbacks()
{
//
// Call this routine from DriverUnload
//
if (gBugcheckTriageCallbackRecord != NULL) {
KeDeregisterBugCheckReasonCallback( gBugcheckTriageCallbackRecord );
ExFreePoolWithTag( gBugcheckTriageCallbackRecord, 'Xmpl' );
gTriageDumpDataArray = NULL;
}
}
このコールバック メソッドでは、非ページ カーネル モード アドレスのみを使用する必要があります。
KbCallbackTriageDumpData ルーチンは、実行できるアクションが非常に制限されています。 詳細については、 「バグ チェック コールバック ルーチンの制限」を参照してください。
MmIsAddressValid 関数は、KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE フラグが設定されていることを検証した後にのみ、 KbCallbackTriageDumpData ルーチンから使用する必要があります。 このフラグは現在、常に設定されることが想定されていますが、追加の同期なしで設定されていない場合、安全にルーチンを呼び出すことができません。