Schreiben einer Fehlerüberprüfungs-Grund-Rückrufroutine

Ein Treiber kann optional eine KBUGCHECK_REASON_CALLBACK_ROUTINE Rückruffunktion bereitstellen, die das System aufruft, nachdem eine Absturzabbilddatei geschrieben wurde.

Hinweis

In diesem Artikel wird die Rückrufroutine " Fehlerüberprüfungsursache " und nicht die KBUGCHECK_CALLBACK_ROUTINE Rückruffunktion beschrieben.

In diesem Rückruf kann der Treiber Folgendes ausführen:

  • Hinzufügen treiberspezifischer Daten zur Absturzabbilddatei

  • Zurücksetzen des Geräts in einen bekannten Zustand

Verwenden Sie die folgenden Routinen, um den Rückruf zu registrieren und zu entfernen:

Dieser Rückruftyp ist überladen, wobei sich das Verhalten basierend auf dem bei der Registrierung angegebenen konstanten Wert KBUGCHECK_CALLBACK_REASON ändert. In diesem Artikel werden die verschiedenen Nutzungsszenarien beschrieben.

Allgemeine Informationen zu Daten zur Fehlerüberprüfung finden Sie unter Lesen von Rückrufdaten zur Fehlerüberprüfung.

Fehlerüberprüfung: Rückrufroutineeinschränkungen

Eine Fehlerüberprüfungs-Rückrufroutine wird bei IRQL = HIGH_LEVEL ausgeführt, was starke Einschränkungen für ihre Möglichkeiten erzwingt.

Eine Rückrufroutine für die Fehlerüberprüfung kann folgendes nicht ausführen:

  • Zuweisen von Arbeitsspeicher

  • Zugreifen auf Auslagerungsspeicher

  • Verwenden von Synchronisierungsmechanismen

  • Aufrufen einer Beliebigen Routine, die unter IRQL = DISPATCH_LEVEL oder darunter ausgeführt werden muss

Rückrufroutinen zur Fehlerüberprüfung werden garantiert ohne Unterbrechung ausgeführt, sodass keine Synchronisierung erforderlich ist. (Wenn die Fehlerüberprüfungsroutine versucht, Sperren mithilfe von Synchronisierungsmechanismen abzurufen, wird das System deadlock.) Beachten Sie, dass sich Datenstrukturen oder -listen zum Zeitpunkt der Fehlerüberprüfung möglicherweise inkonsistent befinden. Daher sollte beim Zugriff auf durch Sperren geschützte Datenstrukturen Vorsicht betrieben werden. Sie sollten z. B. Obergrenzen für die Überprüfung hinzufügen, wenn Listen ausgeführt werden, und überprüfen Sie, ob die Links auf gültigen Arbeitsspeicher verweisen, falls eine Kreisliste vorhanden ist oder ein Link auf eine ungültige Adresse verweist.

Die MmIsAddressValid kann von einer Bug Check-Rückrufroutine verwendet werden, um zu überprüfen, ob der Zugriff auf eine Adresse einen Seitenfehler verursacht. Da die Routine ohne Unterbrechung ausgeführt wird und andere Kerne eingefroren werden, erfüllt dies die Synchronisierungsanforderungen dieser Funktion. Kerneladressen, die möglicherweise ausgelagert oder ungültig sind, sollten immer mit MmIsAddressValid überprüft werden, bevor sie in einem Fehlerüberprüfungsrückruf zurückgehalten werden, da ein Seitenfehler einen doppelten Fehler verursacht und das Schreiben des Dumps möglicherweise verhindert.

Die Rückrufroutine für die Fehlerüberprüfung eines Treibers kann sicher die Routinen READ_PORT_XXX, READ_REGISTER_XXX, WRITE_PORT_XXX und WRITE_REGISTER_XXX verwenden, um mit dem Gerät des Treibers zu kommunizieren. (Informationen zu diesen Routinen finden Sie unter Hardware-Abstraktionsebenenroutinen.)

Implementieren einer KbCallbackAddPages-Rückrufroutine

Ein Kernelmodustreiber kann eine KBUGCHECK_REASON_CALLBACK_ROUTINE Rückruffunktion vom Typ KbCallbackAddPages implementieren, um einer Absturzabbilddatei bei einer Fehlerüberprüfung eine oder mehrere Datenseiten hinzuzufügen. Um diese Routine beim Betriebssystem zu registrieren, ruft der Treiber die KeRegisterBugCheckReasonCallback-Routine auf. Bevor der Treiber entladen wird, muss er die KeDeregisterBugCheckReasonCallback-Routine aufrufen, um die Registrierung zu entfernen.

Ab Windows 8 wird eine registrierte KbCallbackAddPages-Routine während eines Kernelspeicherabbilds oder eines vollständigen Speicherabbilds aufgerufen. In früheren Versionen von Windows wird eine registrierte KbCallbackAddPages-Routine während eines Kernelspeicherabbilds aufgerufen, aber nicht während eines vollständigen Speicherabbilds. Standardmäßig enthält ein Kernelspeicherabbild nur die physischen Seiten, die zum Zeitpunkt der Fehlerüberprüfung vom Windows-Kernel verwendet werden, während ein vollständiges Speicherabbild den gesamten physischen Arbeitsspeicher enthält, der von Windows verwendet wird. Ein vollständiges Speicherabbild enthält standardmäßig keinen physischen Speicher, der von der Plattformfirmware verwendet wird.

Ihre KbCallbackAddPages-Routine kann treiberspezifische Daten bereitstellen, die der Dumpdatei hinzugefügt werden können. Für ein Kernelspeicherabbild können diese zusätzlichen Daten beispielsweise physische Seiten enthalten, die nicht dem Systemadressbereich im virtuellen Speicher zugeordnet sind, aber Informationen enthalten, die Ihnen beim Debuggen des Treibers helfen können. Die KbCallbackAddPages-Routine kann der Dumpdatei alle treibereigenen physischen Seiten hinzufügen, die nicht zugeordnet sind oder die Benutzermodusadressen im virtuellen Arbeitsspeicher zugeordnet sind.

Wenn eine Fehlerüberprüfung durchgeführt wird, ruft das Betriebssystem alle registrierten KbCallbackAddPages-Routinen auf, um Treiber nach Daten abzufragen, die der Absturzabbilddatei hinzugefügt werden sollen. Jeder Aufruf fügt der Absturzabbilddatei eine oder mehrere Seiten zusammenhängender Daten hinzu. Eine KbCallbackAddPages-Routine kann entweder eine virtuelle oder eine physische Adresse für die Startseite angeben. Wenn während eines Aufrufs mehrere Seiten angegeben werden, sind die Seiten entweder im virtuellen oder physischen Arbeitsspeicher zusammenhängend, je nachdem, ob die Startadresse virtuell oder physisch ist. Um nicht zusammenhängende Seiten zur Verfügung zu stellen, kann die KbCallbackAddPages-Routine ein Flag in der KBUGCHECK_ADD_PAGES-Struktur festlegen, um anzugeben, dass sie zusätzliche Daten enthält und erneut aufgerufen werden muss.

Im Gegensatz zu einer KbCallbackSecondaryDumpData-Routine , die Daten an die sekundäre Absturzabbildregion anfügt, fügt eine KbCallbackAddPages-Routine Datenseiten zur primären Absturzabbildregion hinzu. Während des Debuggens ist der Zugriff auf primäre Absturzabbilddaten einfacher als auf sekundäre Absturzabbilddaten.

Das Betriebssystem füllt das BugCheckCode-Element der KBUGCHECK_ADD_PAGES-Struktur aus, auf die ReasonSpecificData verweist. Die KbCallbackAddPages-Routine muss die Werte der Member Flags, Address und Count dieser Struktur festlegen.

Vor dem ersten Aufruf von KbCallbackAddPages initialisiert das Betriebssystem Context in NULL. Wenn die KbCallbackAddPages-Routine mehrmals aufgerufen wird, behält das Betriebssystem den Wert bei, den die Rückrufroutine im vorherigen Aufruf in den Context-Member geschrieben hat.

Eine KbCallbackAddPages-Routine ist in den Aktionen, die sie ausführen kann, sehr eingeschränkt. Weitere Informationen finden Sie unter Bug Check Callback-Routineeinschränkungen.

Implementieren einer KbCallbackDumpIo-Rückrufroutine

Ein Kernelmodustreiber kann eine KBUGCHECK_REASON_CALLBACK_ROUTINE Rückruffunktion vom Typ KbCallbackDumpIo implementieren, um bei jedem Schreiben von Daten in die Absturzabbilddatei Arbeit auszuführen. Das System übergibt im Parameter ReasonSpecificData einen Zeiger auf eine KBUGCHECK_DUMP_IO-Struktur . Das Buffer-Element zeigt auf die aktuellen Daten, und das BufferLength-Element gibt seine Länge an. Das Type-Element gibt den Typ der daten an, die derzeit geschrieben werden, z. B. Dumpdateiheaderinformationen, Speicherstatus oder von einem Treiber bereitgestellte Daten. Eine Beschreibung der möglichen Informationstypen finden Sie in der KBUGCHECK_DUMP_IO_TYPE-Enumeration .

Das System kann die Absturzabbilddatei entweder sequenziell oder ungeordnet schreiben. Wenn das System die Absturzabbilddatei sequenziell schreibt, ist der Offset-Member von ReasonSpecificData -1; Andernfalls wird Offset in der Absturzabbilddatei auf den aktuellen Offset in Bytes festgelegt.

Wenn das System die Datei sequenziell schreibt, ruft es jede KbCallbackDumpIo-Routine beim Schreiben der Headerinformationen (Typ = KbDumpIoHeader), ein oder mehrere Male beim Schreiben des Standard Textkörpers der Absturzabbilddatei (Type = KbDumpIoBody) und ein oder mehrere Male beim Schreiben der sekundären Dumpdaten (Type = KbDumpIoSecondaryDumpData) auf. Nachdem das System das Schreiben der Absturzabbilddatei abgeschlossen hat, ruft es den Rückruf mit Buffer = NULL, BufferLength = 0 und Type = KbDumpIoComplete auf.

Der Standard Zweck einer KbCallbackDumpIo-Routine besteht darin, das Schreiben von Systemabbilddaten auf andere Geräte als den Datenträger zu ermöglichen. Beispielsweise kann ein Gerät, das den Systemstatus überwacht, den Rückruf verwenden, um zu melden, dass das System eine Fehlerüberprüfung ausgestellt hat, und um ein Absturzabbild für die Analyse bereitzustellen.

Verwenden Sie KeRegisterBugCheckReasonCallback , um eine KbCallbackDumpIo-Routine zu registrieren. Ein Treiber kann den Rückruf anschließend mithilfe der KeDeregisterBugCheckReasonCallback-Routine entfernen. Wenn der Treiber entladen werden kann, müssen alle registrierten Rückrufe in seiner DRIVER_UNLOAD Rückruffunktion entfernt werden.

Eine KbCallbackDumpIo-Routine ist in den Aktionen, die sie ausführen kann, stark eingeschränkt. Weitere Informationen finden Sie unter Bug Check Callback-Routineeinschränkungen.

Implementieren einer KbCallbackSecondaryDumpData-Rückrufroutine

Ein Kernelmodustreiber kann eine KBUGCHECK_REASON_CALLBACK_ROUTINE Rückruffunktion vom Typ KbCallbackSecondaryDumpData implementieren, um Daten bereitzustellen, die an die Absturzabbilddatei angefügt werden können.

Das System legt die Member InBuffer, InBufferLength, OutBuffer und MaximumAllowed der KBUGCHECK_SECONDARY_DUMP_DATA-Struktur fest, auf die ReasonSpecificData verweist. Das MaximumAllowed-Element gibt die maximale Menge an Dumpdaten an, die die Routine bereitstellen kann.

Der Wert des OutBuffer-Members bestimmt wie folgt, ob das System die Größe der Treiberabbilddaten oder die Daten selbst anfordert:

  • Wenn das OutBuffer-Element von KBUGCHECK_SECONDARY_DUMP_DATA NULL ist, fordert das System nur Größeninformationen an. Die Routine KbCallbackSecondaryDumpData füllt die Elemente OutBuffer und OutBufferLength aus.

  • Wenn das OutBuffer-Element von KBUGCHECK_SECONDARY_DUMP_DATA dem InBuffer-Member entspricht, fordert das System die sekundären Dumpdaten des Treibers an. Die KbCallbackSecondaryDumpData-Routine füllt die Elemente OutBuffer und OutBufferLength aus und schreibt die Daten in den von OutBuffer angegebenen Puffer.

Das InBuffer-Element von KBUGCHECK_SECONDARY_DUMP_DATA verweist auf einen kleinen Puffer für die Verwendung der Routine. Das InBufferLength-Element gibt die Größe des Puffers an. Wenn die Menge der zu schreibenden Daten kleiner als InBufferLength ist, kann die Rückrufroutine diesen Puffer verwenden, um die Absturzabbilddaten an das System zu liefern. Die Rückrufroutine legt dann OutBuffer auf InBuffer und OutBufferLength auf die tatsächliche Menge der in den Puffer geschriebenen Daten fest.

Ein Treiber, der eine größere Datenmenge als InBufferLength schreiben muss, kann einen eigenen Puffer verwenden, um die Daten bereitzustellen. Dieser Puffer muss zugewiesen worden sein, bevor die Rückrufroutine ausgeführt wird, und muss sich im residenten Arbeitsspeicher (z. B. einem nicht auslagerten Pool) befinden. Die Rückrufroutine legt dann OutBuffer so fest, dass auf den Puffer des Treibers und OutBufferLength auf die Datenmenge im Puffer verweist, die in die Absturzabbilddatei geschrieben werden soll.

Jeder Datenblock, der in die Absturzabbilddatei geschrieben werden soll, wird mit dem Wert des Guid-Members der KBUGCHECK_SECONDARY_DUMP_DATA-Struktur markiert. Die verwendete GUID muss für den Treiber eindeutig sein. Zum Anzeigen der sekundären Dumpdaten, die dieser GUID entsprechen, können Sie den Befehl .enumtag oder die IDebugDataSpaces3::ReadTagged-Methode in einer Debuggererweiterung verwenden. Informationen zu Debuggern und Debuggererweiterungen finden Sie unter Windows-Debuggen.

Ein Treiber kann mehrere Blöcke mit derselben GUID in die Absturzabbilddatei schreiben, aber dies ist sehr schlecht, da nur der erste Block für den Debugger zugänglich ist. Treiber, die mehrere KbCallbackSecondaryDumpData-Routinen registrieren, sollten für jeden Rückruf eine eindeutige GUID zuweisen.

Verwenden Sie KeRegisterBugCheckReasonCallback , um eine KbCallbackSecondaryDumpData-Routine zu registrieren. Ein Treiber kann die Rückrufroutine anschließend mithilfe der KeDeregisterBugCheckReasonCallback-Routine entfernen. Wenn der Treiber entladen werden kann, muss er alle registrierten Rückrufroutinen in seiner DRIVER_UNLOAD-Rückruffunktion entfernen.

Eine KbCallbackSecondaryDumpData-Routine ist in den Aktionen, die sie ausführen kann, sehr eingeschränkt. Weitere Informationen finden Sie unter Einschränkungen der Rückrufroutine für die Fehlerüberprüfung.

Implementieren einer KbCallbackTriageDumpData-Rückrufroutine

Ab Windows 10, Version 1809 und Windows Server 2019 kann ein Kernelmodustreiber eine KBUGCHECK_REASON_CALLBACK_ROUTINE Rückruffunktion vom Typ KbCallbackTriageDumpData implementieren, um virtuelle Speicherbereiche für die Aufnahme in einen geschnitzten Kernel-Minidump zu markieren. Dadurch wird sichergestellt, dass ein Minidump die angegebenen Bereiche enthält, sodass auf sie mit denselben Debuggerbefehlen zugegriffen werden kann, die in einem Kerneldump funktionieren würden. Dies ist derzeit für "geschnitzte" Minidumps implementiert, was bedeutet, dass ein Kernel oder ein größeres Dump erfasst wurde, dann wurde aus dem größeren Dump ein Minidump erstellt. Die meisten Systeme sind standardmäßig für automatische/Kernelabbilder konfiguriert, und das System erstellt beim nächsten Start nach dem Absturz automatisch einen Minidump.

Das System übergibt im ReasonSpecificData-Parameter einen Zeiger auf eine KBUGCHECK_TRIAGE_DUMP_DATA Struktur, die Informationen zur Fehlerüberprüfung enthält, sowie einen OUT-Parameter, der vom Treiber verwendet wird, um sein initialisiertes und aufgefülltes Datenarray zurückzugeben.

Im folgenden Beispiel konfiguriert der Treiber ein Selektierungsabbildarray und registriert dann eine minimale Implementierung des Rückrufs. Der Treiber verwendet das Array, um dem Minidump zwei globale Variablen hinzuzufügen.

#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;
    }

}

Mit dieser Rückrufmethode sollten nur Adressen im Kernelmodus verwendet werden, die nicht auslagert sind.

Eine KbCallbackTriageDumpData-Routine ist in den Aktionen, die sie ausführen kann, sehr eingeschränkt. Weitere Informationen finden Sie unter Einschränkungen der Rückrufroutine für die Fehlerüberprüfung.

Die MmIsAddressValid-Funktion sollte nur aus einer KbCallbackTriageDumpData-Routine verwendet werden, nachdem überprüft wurde, ob das KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE-Flag festgelegt ist. Es wird derzeit immer erwartet, dass dieses Flag festgelegt wird, aber es ist unsicher, die Routine aufzurufen, wenn es nicht ohne zusätzliche Synchronisierung festgelegt wird.