共用方式為


撰寫錯誤檢查原因回呼例程

驅動程式可以選擇提供KBUGCHECK_REASON_CALLBACK_ROUTINE回呼函式,系統會在損毀傾印檔案寫入後呼叫此函式。

備註

本文說明的是錯誤檢查 原因 的回呼例程,而不是 KBUGCHECK_CALLBACK_ROUTINE 的回呼函式。

在這個回呼中,驅動程式可以:

  • 將驅動程式特定數據新增至當機傾印檔案

  • 將裝置重設為已知狀態

使用下列常式來註冊和移除回調函數:

此回呼類型已多載,行為會根據註冊時提供的 KBUGCHECK_CALLBACK_REASON 常數值 變更。 本文說明不同的使用案例。

如需錯誤檢查數據的一般資訊,請參閱 讀取錯誤檢查回呼數據

錯誤檢查回呼例程限制

錯誤檢查回呼例程會在 IRQL = HIGH_LEVEL執行,這會對其可執行的工作施加強式限制。

錯誤檢查回呼例程無法:

  • 配置記憶體

  • 存取可分頁記憶體

  • 使用任何同步處理機制

  • 調用必須在 IRQL = DISPATCH_LEVEL 或以下執行的任何例程

錯誤檢查回呼例程保證不會中斷執行,因此不需要同步處理。 (如果錯誤檢查例程嘗試使用任何同步處理機制取得鎖定,系統將會死結。請記住,在錯誤檢查時,數據結構或清單可能處於不一致的狀態,因此在存取受鎖定保護的數據結構時,應該小心。 例如,您應該在遍歷清單時新增上限檢查,並確認連結指向有效的記憶體,以防出現循環清單或連結指向無效的位址。

MmIsAddressValid 可由錯誤檢查回呼例程使用,以檢查存取位址是否會導致頁面錯誤。 由於例程執行時不會中斷,且其他核心已凍結,因此這符合該函式的同步處理需求。 可能分頁或無效的核心地址應該一律使用 MmIsAddressValid 檢查,然後再延遲錯誤檢查回呼,因為頁面錯誤會導致雙重錯誤,而且可能會防止傾印寫入。

驅動程式的錯誤檢查回呼例程可以安全地使用 READ_PORT_XXXREAD_REGISTER_XXXWRITE_PORT_XXXWRITE_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 例程必須設定此結構之 FlagsAddressCount 成員的值。

第一次呼叫 KbCallbackAddPages 之前,作系統會將 Context 初始化為 NULL。 如果多次呼叫 KbCallbackAddPages 例程,則作系統會保留回呼例程在先前呼叫中寫入 Context 成員的值。

KbCallbackAddPages 例程在可以採取的動作中非常受限。 如需詳細資訊,請參閱 Bug 檢查回呼例程限制

實作 KbCallbackDumpIo 回調例程

核心模式驅動程式可以實作 KbCallbackDumpIo 類型的KBUGCHECK_REASON_CALLBACK_ROUTINE回呼函式,以在每次將數據寫入損毀傾印檔案時執行工作。 系統會在 ReasonSpecificData 參數中傳遞 KBUGCHECK_DUMP_IO 結構的指標。 Buffer 成員會指向目前的數據,而 BufferLength 成員會指定其長度。 Type 成員表示目前正在寫入的數據類型,例如傾印檔頭資訊、記憶體狀態或驅動程式提供的數據。 如需可能的信息類型描述,請參閱 KBUGCHECK_DUMP_IO_TYPE 列舉。

系統可以循序或非循序地寫入崩潰傾印檔案。 如果系統會循序寫入損毀傾印檔案,則 ReasonSpecificDataOffset 成員為 -1;否則,Offset 會設定為損毀傾印檔案中的目前位移,以位元組為單位。

當系統循序寫入檔案時,它會在寫入標頭資訊(Type = KbDumpIoHeader)時,呼叫每個 KbCallbackDumpIo 例程一或多次,在寫入損毀傾印檔案的主體(Type = KbDumpIoBody)時一或多次,以及在寫入次要傾印數據(Type = KbDumpIoSecondaryDumpData)時一或多次。 系統完成寫入當機轉儲檔案之後,會使用 Buffer = NULLBufferLength = 0 和 Type = KbDumpIoComplete 來呼叫回呼函式。

KbCallbackDumpIo 例程的主要目的是允許系統損毀傾印數據寫入磁碟以外的裝置。 例如,監視系統狀態的裝置可以使用回呼來報告系統已發出錯誤檢查,並提供損毀傾印進行分析。

使用 KeRegisterBugCheckReasonCallback 註冊 KbCallbackDumpIo 例程。 驅動程式接著可以使用 KeDeregisterBugCheckReasonCallback 例程來移除回呼。 如果可以卸除驅動程式,它必須移除其DRIVER_UNLOAD回呼函式中任何已註冊 回呼。

KbCallbackDumpIo 例程在可以採取的動作中受到強烈限制。 如需詳細資訊,請參閱 Bug 檢查回呼例程限制

實現 KbCallbackSecondaryDumpData 回呼例程

內核模式驅動程式可以實作類型為 KbCallbackSecondaryDumpDataKBUGCHECK_REASON_CALLBACK_ROUTINE 回呼函式,以提供要附加至損毀傾印檔案的數據。

系統會設定 ReasonSpecificData 所指向的 KBUGCHECK_SECONDARY_DUMP_DATA 結構中的 InBufferInBufferLengthOutBufferMaximumAllowed 成員。 MaximumAllowed 成員會指定例程可以提供的最大傾印數據量。

OutBuffer 成員的值會判斷系統是否要求驅動程序傾印數據的大小,或數據本身,如下所示:

  • 如果KBUGCHECK_SECONDARY_DUMP_DATA的 OutBuffer 成員為 NULL,則系統只會要求大小資訊。 KbCallbackSecondaryDumpData 例程會填入 OutBufferOutBufferLength 成員。

  • 如果 KBUGCHECK_SECONDARY_DUMP_DATA 的 OutBuffer 成分與 InBuffer 成分相等,系統會要求驅動程式的次要傾印數據。 KbCallbackSecondaryDumpData 例程會填入 OutBufferOutBufferLength 成員,並將數據寫入 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 例程在可以採取的動作中非常受限。 如需詳細資訊,請參閱 Bug 檢查回呼例程限制

實作 KbCallbackTriageDumpData 回呼例程

從 Windows 10 版本 1809 和 Windows Server 2019 開始,內核模式驅動程式可以實作一個類型為KbCallbackTriageDumpDataKBUGCHECK_REASON_CALLBACK_ROUTINE回呼函式,以標記虛擬記憶體範圍,並將其納入雕刻的核心迷你傾印中。 這可確保小型傾印將包含指定的範圍,因此可以使用可在核心傾印中運作的相同調試程式命令來存取它們。 目前已針對「提取出的」迷你傾印進行實作,這表示已擷取核心或較大的傾印,然後從較大的傾印建立小型傾印。 大部分系統預設都會設定為自動/核心傾印,而系統會在當機後於下一次開機時自動建立小型傾印。

系統會在 ReasonSpecificData 參數中傳遞一個指向 KBUGCHECK_TRIAGE_DUMP_DATA 結構的指標,其中包含 Bug 檢查的相關資訊,還有一個由驅動程式用來傳回其初始化並填充資料陣列的 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 例程在可採取的動作中非常受限。 如需詳細資訊,請參閱 Bug 檢查回呼例程限制

在驗證 KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE 旗標已設定之後,只應從 KbCallbackTriageDumpData 例程中使用 MmIsAddressValid 函式。 此旗標目前一律會設定,但在未設定其他同步處理的情況下呼叫例程並不安全。