撰寫錯誤檢查原因回呼常式
驅動程式可以選擇性地提供 KBUGCHECK_REASON_CALLBACK_ROUTINE 回呼函式,此函式會在寫入損毀傾印檔案之後呼叫。
注意
本文說明錯誤檢查 原因 回呼常式,而不是 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 回呼常式
核心模式驅動程式可以實作KbCallbackAddPages類型的KBUGCHECK_REASON_CALLBACK_ROUTINE回呼函式,以在錯誤檢查發生時,將資料一或多個頁面新增至損毀傾印檔案。 若要向作業系統註冊此常式,驅動程式會呼叫 KeRegisterBugCheckReasonCallback 常式。 在驅動程式卸載之前,它必須呼叫 KeDeregisterBugCheckReasonCallback 常式來移除註冊。
從 Windows 8 開始,在核心記憶體傾印或完整記憶體傾印期間會呼叫已註冊的KbCallbackAddPages常式。 在舊版 Windows 中,在核心記憶體傾印期間會呼叫已註冊的 KbCallbackAddPages 常式,但在完整記憶體傾印期間不會呼叫。 根據預設,核心記憶體傾印只會包含 Windows 核心在錯誤檢查發生時所使用的實體頁面,而完整記憶體傾印則包含 Windows 所使用的所有實體記憶體。 根據預設,完整的記憶體傾印不包含平台韌體使用的實體記憶體。
您的 KbCallbackAddPages 常式可以提供驅動程式特定的資料,以新增至傾印檔案。 例如,針對核心記憶體傾印,此額外資料可以包含實體頁面,這些頁面未對應至虛擬記憶體中的系統位址範圍,但包含可協助您偵錯驅動程式的資訊。 KbCallbackAddPages常式可能會新增至傾印檔案,任何未對應或對應至虛擬記憶體中使用者模式位址的驅動程式擁有實體頁面。
發生錯誤檢查時,作業系統會呼叫所有已註冊的 KbCallbackAddPages 常式,以輪詢驅動程式以新增至損毀傾印檔案。 每個呼叫都會將一或多個連續資料的分頁新增至損毀傾印檔案。 KbCallbackAddPages常式可以提供起始頁面的虛擬位址或實體位址。 如果在呼叫期間提供多個頁面,則頁面會連續在虛擬或實體記憶體中,視起始位址為虛擬或實體而定。 若要提供非連續的頁面, KbCallbackAddPages 常式可以在 KBUGCHECK_ADD_PAGES 結構中設定旗標,以指出它有額外的資料,而且必須再次呼叫。
不同于將資料附加至次要損毀傾印區域的 KbCallbackSecondaryDumpData 常式, KbCallbackAddPages 常式會將資料分頁新增至主要損毀傾印區域。 在偵錯期間,主要損毀傾印資料比次要損毀傾印資料更容易存取。
作業系統會填入ReasonSpecificData指向之KBUGCHECK_ADD_PAGES結構的BugCheckCode成員。 KbCallbackAddPages常式必須設定這個結構的Flags、Address和Count成員的值。
在第一次呼叫 KbCallbackAddPages之前,作業系統會將 CoNtext 初始化為 Null。 如果 KbCallbackAddPages 常式多次呼叫,作業系統會保留回呼常式在先前呼叫中寫入 至 CoNtext 成員的值。
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) 、寫入損毀傾印檔案的主本文時, (TypeKbDumpIoBody) ,以及寫入次要傾印資料 (Type = = KbDumpIoSecondaryDumpData) 時多次。 系統完成寫入損毀傾印檔案之後,它會使用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 來註冊 KbCallbackSecondaryDumpData 常式。 驅動程式接著可以使用 KeDeregisterBugCheckReasonCallback 常式來移除回呼常式。 如果驅動程式可以卸載,則必須在其DRIVER_UNLOAD回呼函式中移除任何已註冊 的 回呼常式。
KbCallbackSecondaryDumpData常式在可以採取的動作中非常受限。 如需詳細資訊,請參閱 錯誤檢查回呼常式限制。
實作 KbCallbackTriageDumpData 回呼常式
從 Windows 10 版本 1809 和 Windows Server 2019 開始,核心模式驅動程式可以實作KbCallbackTriageDumpData類型的KBUGCHECK_REASON_CALLBACK_ROUTINE回呼函式,以標記虛擬記憶體範圍,以包含在浮動的核心迷你傾印中。 這可確保迷你傾印將包含指定的範圍,因此可以使用可在核心傾印中運作的相同偵錯工具命令來存取它們。 目前已針對「已浮動」的迷你傾印實作,這表示已擷取核心或較大的傾印,然後從較大的傾印建立迷你傾印。 大部分系統預設會設定為自動/核心傾印,而系統會在當機後於下一次開機時自動建立迷你傾印。
系統會在 ReasonSpecificData 參數中傳遞 KBUGCHECK_TRIAGE_DUMP_DATA結構的指標 ,其中包含錯誤檢查的相關資訊,以及驅動程式用來傳回其初始化和填入資料陣列的 OUT 參數。
在下列範例中,驅動程式會設定分級傾印陣列,然後註冊回呼的最小實作。 驅動程式會使用 陣列,將兩個全域變數新增至迷你傾印。
#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常式在可以採取的動作中非常受限。 如需詳細資訊,請參閱 錯誤檢查回呼常式限制。
驗證已設定KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE旗標之後,才應該從KbCallbackTriageDumpData常式使用MmIsAddressValid函式。 此旗標目前一律會設定,但在未設定其他同步處理的情況下呼叫常式並不安全。