Scrittura di una routine di callback del motivo del controllo bug

Un driver può facoltativamente fornire una funzione di callback KBUGCHECK_REASON_CALLBACK_ROUTINE , che il sistema chiama dopo la scrittura di un file di dump di arresto anomalo del sistema.

Nota

Questo articolo descrive la routine di callback del motivo del controllo dei bug e non la funzione di callback KBUGCHECK_CALLBACK_ROUTINE.

In questo callback, il driver può:

  • Aggiungere dati specifici del driver al file di dump di arresto anomalo del sistema

  • Reimpostare lo stato noto del dispositivo

Utilizzare le routine seguenti per registrare e rimuovere il callback:

Questo tipo di callback è sottoposto a overload, con la modifica del comportamento in base al valore costante KBUGCHECK_CALLBACK_REASON fornito durante la registrazione. Questo articolo descrive i diversi scenari di utilizzo.

Per informazioni generali sui dati di controllo dei bug, vedere Lettura dei dati di callback del controllo dei bug.

Limitazioni delle routine di callback di controllo bug

Una routine di callback di controllo dei bug viene eseguita in IRQL = HIGH_LEVEL, che impone restrizioni forti sulle operazioni che può eseguire.

Una routine di callback di controllo di bug non può:

  • Allocare memoria

  • Accedere alla memoria di paging

  • Usare qualsiasi meccanismo di sincronizzazione

  • Chiamare qualsiasi routine che deve essere eseguita in IRQL = DISPATCH_LEVEL o di seguito

Le routine di callback del controllo bug sono garantite per l'esecuzione senza interruzioni, pertanto non è necessaria alcuna sincronizzazione. Se la routine di controllo dei bug tenta di acquisire blocchi usando qualsiasi meccanismo di sincronizzazione, il sistema eseguirà il deadlock. Tenere presente che le strutture dei dati o gli elenchi potrebbero trovarsi in uno stato incoerente al momento del controllo dei bug, pertanto è consigliabile prestare attenzione quando si accede alle strutture di dati protette da blocchi. Ad esempio, è consigliabile aggiungere un controllo dei limiti superiori durante gli elenchi a piedi e verificare che i collegamenti puntino a una memoria valida, nel caso in cui sia presente un elenco circolare o che un collegamento punti a un indirizzo non valido.

La routine di callback MmIsAddressValid può essere utilizzata da una routine di callback controllo bug per verificare se l'accesso a un indirizzo causerà un errore di pagina. Poiché la routine viene eseguita senza interruzioni e altri core sono bloccati, questo soddisfa i requisiti di sincronizzazione di tale funzione. Gli indirizzi del kernel che possono essere sottoposti a paging o non validi devono essere sempre controllati con MmIsAddressValid prima di rinviarli in un callback di controllo bug, poiché un errore di pagina causerà un doppio errore e potrebbe impedire la scrittura del dump.

La routine di callback di controllo dei bug di un driver può usare in modo sicuro le routine di callback READ_PORT_XXX, READ_REGISTER_XXX, WRITE_PORT_XXX e WRITE_REGISTER_XXX per comunicare con il dispositivo del driver. Per informazioni su queste routine, vedere Routine del livello di astrazione hardware.

Implementazione di una routine di callback KbCallbackAddPages

Un driver in modalità kernel può implementare una funzione di callback KBUGCHECK_REASON_CALLBACK_ROUTINE di tipo KbCallbackAddPages per aggiungere una o più pagine di dati a un file di dump di arresto anomalo quando si verifica un controllo di bug. Per registrare questa routine con il sistema operativo, il driver chiama la routine KeRegisterBugCheckReasonCallback . Prima di scaricare il driver, deve chiamare la routine KeDeregisterBugCheckReasonCallback per rimuovere la registrazione.

A partire da Windows 8, viene chiamata una routine KbCallbackAddPages registrata durante un dump della memoria del kernel o un dump di memoria completo. Nelle versioni precedenti di Windows viene chiamata una routine KbCallbackAddPages registrata durante un dump della memoria del kernel, ma non durante un dump completo della memoria. Per impostazione predefinita, un dump della memoria del kernel include solo le pagine fisiche usate dal kernel di Windows al momento in cui si verifica il controllo dei bug, mentre un dump completo della memoria include tutta la memoria fisica usata da Windows. Per impostazione predefinita, un dump di memoria completo non include la memoria fisica usata dal firmware della piattaforma.

La routine KbCallbackAddPages può fornire dati specifici del driver da aggiungere al file dump. Ad esempio, per un dump della memoria del kernel, questi dati aggiuntivi possono includere pagine fisiche non mappate all'intervallo di indirizzi di sistema nella memoria virtuale, ma che contengono informazioni che consentono di eseguire il debug del driver. La routine KbCallbackAddPages può aggiungere al file di dump tutte le pagine fisiche di proprietà del driver non mappate o mappate agli indirizzi in modalità utente nella memoria virtuale.

Quando si verifica un controllo di bug, il sistema operativo chiama tutte le routine KbCallbackAddPages registrate per eseguire il polling dei driver per i dati da aggiungere al file di dump di arresto anomalo del sistema operativo. Ogni chiamata aggiunge una o più pagine di dati contigui al file di dump di arresto anomalo del sistema. Una routine KbCallbackAddPages può fornire un indirizzo virtuale o un indirizzo fisico per la pagina iniziale. Se durante una chiamata vengono fornite più pagine, le pagine sono contigue nella memoria virtuale o fisica, a seconda che l'indirizzo iniziale sia virtuale o fisico. Per fornire pagine non contigue, la routine KbCallbackAddPages può impostare un flag nella struttura KBUGCHECK_ADD_PAGES per indicare che contiene dati aggiuntivi e deve essere chiamato di nuovo.

A differenza di una routine KbCallbackSecondaryDumpData , che aggiunge dati all'area di dump di arresto anomalo secondario, una routine KbCallbackAddPages aggiunge pagine di dati all'area di dump di arresto anomalo primario. Durante il debug, i dati del dump di arresto anomalo primario sono più facili da accedere rispetto ai dati del dump di arresto anomalo secondario.

Il sistema operativo inserisce il membro BugCheckCode della struttura KBUGCHECK_ADD_PAGES a cui punta ReasonSpecificData . La routine KbCallbackAddPages deve impostare i valori dei membri Flags, Address e Count di questa struttura.

Prima della prima chiamata a KbCallbackAddPages, il sistema operativo inizializza Context su NULL. Se la routine KbCallbackAddPages viene chiamata più volte, il sistema operativo mantiene il valore scritto dalla routine di callback al membro Context nella chiamata precedente.

Una routine KbCallbackAddPages è molto limitata nelle azioni che può eseguire. Per altre informazioni, vedere Bug Check Callback Routine Restrictions.For more information, see Bug Check Callback Routine Restrictions.

Implementazione di una routine di callback KbCallbackDumpIo

Un driver in modalità kernel può implementare una funzione di callback KBUGCHECK_REASON_CALLBACK_ROUTINE di tipo KbCallbackDumpIo per eseguire il lavoro ogni volta che i dati vengono scritti nel file di dump di arresto anomalo del sistema. Il sistema passa, nel parametro ReasonSpecificData , un puntatore a una struttura KBUGCHECK_DUMP_IO . Il membro Buffer punta ai dati correnti e il membro BufferLength ne specifica la lunghezza. Il membro Type indica il tipo di dati attualmente in fase di scrittura, ad esempio le informazioni sull'intestazione del file di dump, lo stato della memoria o i dati forniti da un driver. Per una descrizione dei possibili tipi di informazioni, vedere l'enumerazione KBUGCHECK_DUMP_IO_TYPE .

Il sistema può scrivere il file dump di arresto anomalo del sistema in modo sequenziale o non in ordine. Se il sistema scrive il file dump di arresto anomalo in sequenza, il membro Offset di ReasonSpecificData è -1; in caso contrario, Offset è impostato sull'offset corrente, in byte, nel file di dump di arresto anomalo del sistema.

Quando il sistema scrive il file in sequenza, chiama ogni routine KbCallbackDumpIo una o più volte durante la scrittura delle informazioni di intestazione (Tipo = KbDumpIoHeader), una o più volte durante la scrittura del corpo principale del file di dump di arresto anomalo (Tipo = KbDumpIoBody) e una o più volte durante la scrittura dei dati di dump secondari (tipo = KbDumpIoSecondaryDumpData). Dopo aver completato la scrittura del file di dump di arresto anomalo del sistema, chiama il callback con Buffer = NULL, BufferLength = 0 e Type = KbDumpIoComplete.

Lo scopo principale di una routine KbCallbackDumpIo è consentire la scrittura dei dati di dump di arresto anomalo del sistema nei dispositivi diversi dal disco. Ad esempio, un dispositivo che monitora lo stato del sistema può usare il callback per segnalare che il sistema ha emesso un controllo di bug e per fornire un dump di arresto anomalo per l'analisi.

Usare KeRegisterBugCheckReasonCallback per registrare una routine KbCallbackDumpIo . Un driver può successivamente rimuovere il callback usando la routine KeDeregisterBugCheckReasonCallback . Se il driver può essere scaricato, deve rimuovere tutti i callback registrati nella relativa funzione di callback DRIVER_UNLOAD .

Una routine KbCallbackDumpIo è fortemente limitata nelle azioni che può eseguire. Per altre informazioni, vedere Bug Check Callback Routine Restrictions.For more information, see Bug Check Callback Routine Restrictions.

Implementazione di una routine di callback KbCallbackSecondaryDumpData

Un driver in modalità kernel può implementare una funzione di callback KBUGCHECK_REASON_CALLBACK_ROUTINE di tipo KbCallbackSecondaryDumpData per fornire dati da aggiungere al file di dump di arresto anomalo del sistema.

Il sistema imposta i membri InBuffer, InBufferLength, OutBuffer e MaximumAllowed della struttura KBUGCHECK_SECONDARY_DUMP_DATA a cui punta ReasonSpecificData . Il membro MaximumAllowed specifica la quantità massima di dati di dump che la routine può fornire.

Il valore del membro OutBuffer determina se il sistema richiede le dimensioni dei dati di dump del driver o i dati stessi, come indicato di seguito:

  • Se il membro OutBuffer di KBUGCHECK_SECONDARY_DUMP_DATA è NULL, il sistema richiede solo informazioni sulle dimensioni. La routine KbCallbackSecondaryDumpData viene compilata nei membri OutBuffer e OutBufferLength.

  • Se il membro OutBuffer di KBUGCHECK_SECONDARY_DUMP_DATA è uguale al membro InBuffer , il sistema richiede i dati di dump secondari del driver. La routine KbCallbackSecondaryDumpData inserisce i membri OutBuffer e OutBufferLength e scrive i dati nel buffer specificato da OutBuffer.

Il membro InBuffer di KBUGCHECK_SECONDARY_DUMP_DATA punta a un buffer piccolo per l'uso della routine. Il membro InBufferLength specifica le dimensioni del buffer. Se la quantità di dati da scrivere è minore di InBufferLength, la routine di callback può usare questo buffer per fornire i dati del dump di arresto anomalo al sistema. La routine di callback imposta quindi OutBuffer su InBuffer e OutBufferLength sulla quantità effettiva di dati scritti nel buffer.

Un driver che deve scrivere una quantità di dati maggiore di InBufferLength può usare il proprio buffer per fornire i dati. Questo buffer deve essere stato allocato prima dell'esecuzione della routine di callback e deve risiedere nella memoria residente , ad esempio un pool non di paging. La routine di callback imposta quindi OutBuffer in modo che punti al buffer del driver e OutBufferLength alla quantità di dati nel buffer da scrivere nel file di dump di arresto anomalo del sistema.

Ogni blocco di dati da scrivere nel file di dump di arresto anomalo del sistema viene contrassegnato con il valore del membro Guid della struttura KBUGCHECK_SECONDARY_DUMP_DATA . Il GUID usato deve essere univoco per il driver. Per visualizzare i dati di dump secondari corrispondenti a questo GUID, è possibile usare il comando .enumtag o il metodo IDebugDataSpaces3::ReadTagged in un'estensione del debugger. Per informazioni sui debugger e sulle estensioni del debugger, vedere Debug di Windows.

Un driver può scrivere più blocchi con lo stesso GUID nel file di dump di arresto anomalo del sistema, ma questa è una procedura molto scarsa, perché solo il primo blocco sarà accessibile al debugger. I driver che registrano più routine KbCallbackSecondaryDumpData devono allocare un GUID univoco per ogni callback.

Usare KeRegisterBugCheckReasonCallback per registrare una routine KbCallbackSecondaryDumpData . Un driver può successivamente rimuovere la routine di callback usando la routine KeDeregisterBugCheckReasonCallback . Se il driver può essere scaricato, è necessario rimuovere tutte le routine di callback registrate nella relativa funzione di callback DRIVER_UNLOAD .

Una routine KbCallbackSecondaryDumpData è molto limitata nelle azioni che può eseguire. Per altre informazioni, vedere Verifica bug check callback routine restrizioni.

Implementazione di una routine di callback KbCallbackTriageDumpData

A partire da Windows 10, versione 1809 e Windows Server 2019, un driver in modalità kernel può implementare una funzione di callback KBUGCHECK_REASON_CALLBACK_ROUTINE di tipo KbCallbackTriageDumpData per contrassegnare gli intervalli di memoria virtuale per l'inclusione in un minidump del kernel scolpito. Ciò garantisce che un minidump contenga gli intervalli specificati, in modo che possano essere accessibili usando gli stessi comandi del debugger che funzionerebbero in un dump del kernel. Questa operazione è attualmente implementata per i minidump 'scolpiti', ovvero che è stato acquisito un kernel o un dump più grande, quindi è stato creato un minidump dal dump più grande. La maggior parte dei sistemi è configurata per i dump automatici/kernel per impostazione predefinita e il sistema crea automaticamente un minidump nell'avvio successivo dopo l'arresto anomalo.

Il sistema passa, nel parametro ReasonSpecificData , un puntatore a una struttura KBUGCHECK_TRIAGE_DUMP_DATA che contiene informazioni sul controllo bug e un parametro OUT utilizzato dal driver per restituire la matrice di dati inizializzata e popolata.

Nell'esempio seguente il driver configura una matrice dump di triage e quindi registra un'implementazione minima del callback. Il driver userà la matrice per aggiungere due variabili globali al minidump.

#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(gTriageDumpDataArray, 'Xmpl');
         gTriageDumpDataArray = 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;
    }

}

È consigliabile usare solo gli indirizzi in modalità kernel non a pagina con questo metodo di callback.

Una routine KbCallbackTriageDumpData è molto limitata nelle azioni che può eseguire. Per altre informazioni, vedere Verifica bug check callback routine restrizioni.

La funzione MmIsAddressValid deve essere usata solo da una routine KbCallbackTrimpData dopo aver convalidato che il flag di KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE sia impostato. Questo flag è attualmente sempre previsto per essere impostato, ma non è sicuro chiamare la routine nel caso in cui non sia impostato senza sincronizzazione aggiuntiva.