Escribir una rutina de devolución de llamada del motivo de comprobación de errores

Opcionalmente, un controlador puede proporcionar una función de devolución de llamada KBUGCHECK_REASON_CALLBACK_ROUTINE , a la que el sistema llama después de escribir un archivo de volcado de memoria.

Nota:

En este artículo se describe la rutina de devolución de llamada del motivo de comprobación de errores y no la KBUGCHECK_CALLBACK_ROUTINE función de devolución de llamada.

En esta devolución de llamada, el controlador puede:

  • Agregar datos específicos del controlador al archivo de volcado de memoria

  • Restablecer el dispositivo a un estado conocido

Use las siguientes rutinas para registrar y quitar la devolución de llamada:

Este tipo de devolución de llamada está sobrecargado, con un comportamiento que cambia en función del valor constante KBUGCHECK_CALLBACK_REASON proporcionado en el registro. En este artículo se describen los distintos escenarios de uso.

Para obtener información general sobre los datos de comprobación de errores, consulte Lectura de los datos de devolución de llamada de comprobación de errores.

Restricciones de rutina de devolución de llamada de comprobación de errores

Una rutina de devolución de llamada de comprobación de errores se ejecuta en IRQL = HIGH_LEVEL, lo que impone restricciones fuertes sobre lo que puede hacer.

Una rutina de devolución de llamada de comprobación de errores no puede:

  • Asignar memoria

  • Acceso a la memoria paginable

  • Uso de los mecanismos de sincronización

  • Llame a cualquier rutina que se debe ejecutar en IRQL = DISPATCH_LEVEL o inferior.

Se garantiza que las rutinas de devolución de llamada de comprobación de errores se ejecutan sin interrupción, por lo que no se requiere ninguna sincronización. (Si la rutina de comprobación de errores intenta adquirir bloqueos mediante mecanismos de sincronización, el sistema interbloqueo). Tenga en cuenta que las estructuras o listas de datos pueden estar en un estado incoherente en el momento de la comprobación de errores, por lo que se debe tener cuidado al acceder a estructuras de datos protegidas por bloqueos. Por ejemplo, debe agregar límites superiores comprobando al caminar listas y comprobar que los vínculos apuntan a memoria válida, en caso de que haya una lista circular o un vínculo apunte a una dirección no válida.

Una rutina de devolución de llamada Comprobación de errores puede usar MmIsAddressValid para comprobar si el acceso a una dirección provocará un error de página. Dado que la rutina se ejecuta sin interrupción y otros núcleos se inmovilizan, satisface los requisitos de sincronización de esa función. Las direcciones de kernel que pueden estar paginadas o no válidas siempre deben comprobarse con MmIsAddressValid antes de aplazarlas en una devolución de llamada comprobación de errores, ya que un error de página provocará un error doble y puede impedir que se escriba el volcado de memoria.

La rutina de devolución de llamada de comprobación de errores de un controlador puede usar de forma segura las rutinas READ_PORT_XXX, READ_REGISTER_XXX, WRITE_PORT_XXX y WRITE_REGISTER_XXX para comunicarse con el dispositivo del controlador. (Para obtener información sobre estas rutinas, consulte Rutinas de capa de abstracción de hardware).

Implementar una rutina de devolución de llamada KbCallbackAddPages

Un controlador en modo kernel puede implementar una función de devolución de llamada KBUGCHECK_REASON_CALLBACK_ROUTINE de tipo KbCallbackAddPages para agregar una o varias páginas de datos a un archivo de volcado de memoria cuando se produce una comprobación de errores. Para registrar esta rutina con el sistema operativo, el controlador llama a la rutina KeRegisterBugCheckReasonCallback . Antes de descargar el controlador, debe llamar a la rutina KeDeregisterBugCheckReasonCallback para quitar el registro.

A partir de Windows 8, se llama a una rutina KbCallbackAddPages registrada durante un volcado de memoria del kernel o un volcado de memoria completo. En versiones anteriores de Windows, se llama a una rutina KbCallbackAddPages registrada durante un volcado de memoria del kernel, pero no durante un volcado de memoria completo. De forma predeterminada, un volcado de memoria de kernel incluye solo las páginas físicas que usa el kernel de Windows en el momento en que se produce la comprobación de errores, mientras que un volcado de memoria completo incluye toda la memoria física que usa Windows. De forma predeterminada, un volcado de memoria completo no incluye la memoria física que usa el firmware de la plataforma.

La rutina KbCallbackAddPages puede proporcionar datos específicos del controlador para agregarlos al archivo de volcado. Por ejemplo, para un volcado de memoria del kernel, estos datos adicionales pueden incluir páginas físicas que no están asignadas al intervalo de direcciones del sistema en memoria virtual, pero que contienen información que puede ayudarle a depurar el controlador. La rutina KbCallbackAddPages puede agregar al archivo de volcado de memoria las páginas físicas propiedad del controlador que no estén asignadas o que se asignen a direcciones en modo de usuario en memoria virtual.

Cuando se produce una comprobación de errores, el sistema operativo llama a todas las rutinas KbCallbackAddPages registradas para sondear los controladores para que los datos se agreguen al archivo de volcado de memoria. Cada llamada agrega una o varias páginas de datos contiguos al archivo de volcado de memoria. Una rutina KbCallbackAddPages puede proporcionar una dirección virtual o una dirección física para la página de inicio. Si se proporciona más de una página durante una llamada, las páginas son contiguas en memoria virtual o física, en función de si la dirección inicial es virtual o física. Para proporcionar páginas no contiguas, la rutina KbCallbackAddPages puede establecer una marca en la estructura KBUGCHECK_ADD_PAGES para indicar que tiene datos adicionales y debe llamarse de nuevo.

A diferencia de una rutina KbCallbackSecondaryDumpData , que anexa datos a la región de volcado de memoria secundaria, una rutina KbCallbackAddPages agrega páginas de datos a la región de volcado de memoria principal. Durante la depuración, los datos del volcado de memoria principal son más fáciles de acceder que los datos de volcado de memoria secundarios.

El sistema operativo rellena el miembro BugCheckCode de la estructura de KBUGCHECK_ADD_PAGES a la que apunta ReasonSpecificData . La rutina KbCallbackAddPages debe establecer los valores de los miembros Flags, Address y Count de esta estructura.

Antes de la primera llamada a KbCallbackAddPages, el sistema operativo inicializa Context en NULL. Si se llama a la rutina KbCallbackAddPages más de una vez, el sistema operativo conserva el valor que la rutina de devolución de llamada escribió en el miembro Context de la llamada anterior.

Una rutina KbCallbackAddPages está muy restringida en las acciones que puede realizar. Para obtener más información, consulte Restricciones de rutina de devolución de llamada de comprobación de errores.

Implementación de una rutina de devolución de llamada KbCallbackDumpIo

Un controlador en modo kernel puede implementar una función de devolución de llamada KBUGCHECK_REASON_CALLBACK_ROUTINE de tipo KbCallbackDumpIo para realizar el trabajo cada vez que se escriben datos en el archivo de volcado de memoria. El sistema pasa, en el parámetro ReasonSpecificData , un puntero a una estructura KBUGCHECK_DUMP_IO . El miembro Buffer apunta a los datos actuales y el miembro BufferLength especifica su longitud. El miembro Type indica el tipo de datos que se están escribiendo actualmente, como la información del encabezado del archivo de volcado, el estado de memoria o los datos proporcionados por un controlador. Para obtener una descripción de los posibles tipos de información, vea la enumeración KBUGCHECK_DUMP_IO_TYPE .

El sistema puede escribir el archivo de volcado de memoria secuencial o desorden. Si el sistema está escribiendo el archivo de volcado de memoria secuencialmente, el miembro Offset de ReasonSpecificData es -1; de lo contrario, Offset se establece en el desplazamiento actual, en bytes, en el archivo de volcado de memoria.

Cuando el sistema escribe el archivo secuencialmente, llama a cada rutina KbCallbackDumpIo una o varias veces al escribir la información de encabezado (Tipo = KbDumpIoHeader), una o varias veces al escribir el cuerpo principal del archivo de volcado de memoria (Tipo = KbDumpIoBody) y una o varias veces al escribir los datos de volcado secundarios (Type = KbDumpIoSecondaryDumpData). Una vez que el sistema haya terminado de escribir el archivo de volcado de memoria, llama a la devolución de llamada con Buffer = NULL, BufferLength = 0 y Type = KbDumpIoComplete.

El propósito principal de una rutina KbCallbackDumpIo es permitir que los datos del volcado de memoria del sistema se escriban en dispositivos distintos del disco. Por ejemplo, un dispositivo que supervisa el estado del sistema puede usar la devolución de llamada para informar de que el sistema ha emitido una comprobación de errores y para proporcionar un volcado de memoria para el análisis.

Use KeRegisterBugCheckReasonCallback para registrar una rutina KbCallbackDumpIo . Un controlador puede quitar posteriormente la devolución de llamada mediante la rutina KeDeregisterBugCheckReasonCallback . Si el controlador se puede descargar, debe quitar las devoluciones de llamada registradas en su DRIVER_UNLOAD función de devolución de llamada.

Una rutina KbCallbackDumpIo está fuertemente restringida en las acciones que puede realizar. Para obtener más información, consulte Restricciones de rutina de devolución de llamada de comprobación de errores.

Implementar una rutina de devolución de llamada KbCallbackSecondaryDumpData

Un controlador en modo kernel puede implementar una función de devolución de llamada KBUGCHECK_REASON_CALLBACK_ROUTINE de tipo KbCallbackSecondaryDumpData para proporcionar datos para anexar al archivo de volcado de memoria.

El sistema establece los miembros InBuffer, InBufferLength, OutBuffer y MaximumAllowed de la estructura de KBUGCHECK_SECONDARY_DUMP_DATA a la que Apunta ReasonSpecificData . El miembro MaximumAllowed especifica la cantidad máxima de datos de volcado que puede proporcionar la rutina.

El valor del miembro OutBuffer determina si el sistema solicita el tamaño de los datos de volcado del controlador o los propios datos, como se indica a continuación:

  • Si el miembro OutBuffer de KBUGCHECK_SECONDARY_DUMP_DATA es NULL, el sistema solo solicita información de tamaño. La rutina KbCallbackSecondaryDumpData rellena los miembros OutBuffer y OutBufferLength .

  • Si el miembro OutBuffer de KBUGCHECK_SECONDARY_DUMP_DATA es igual al miembro InBuffer , el sistema solicita los datos de volcado secundarios del controlador. La rutina KbCallbackSecondaryDumpData rellena los miembros OutBuffer y OutBufferLength y escribe los datos en el búfer especificado por OutBuffer.

El miembro InBuffer de KBUGCHECK_SECONDARY_DUMP_DATA apunta a un pequeño búfer para el uso de la rutina. El miembro InBufferLength especifica el tamaño del búfer. Si la cantidad de datos que se van a escribir es menor que InBufferLength, la rutina de devolución de llamada puede usar este búfer para proporcionar los datos del volcado de memoria al sistema. A continuación, la rutina de devolución de llamada establece OutBufferen InBuffer y OutBufferLength en la cantidad real de datos escritos en el búfer.

Un controlador que debe escribir una cantidad de datos mayor que InBufferLength puede usar su propio búfer para proporcionar los datos. Este búfer debe haberse asignado antes de ejecutar la rutina de devolución de llamada y debe residir en la memoria residente (como el grupo no paginado). A continuación, la rutina de devolución de llamada establece OutBuffer para que apunte al búfer del controlador y OutBufferLength a la cantidad de datos del búfer que se van a escribir en el archivo de volcado de memoria.

Cada bloque de datos que se va a escribir en el archivo de volcado de memoria se etiqueta con el valor del miembro GUID de la estructura KBUGCHECK_SECONDARY_DUMP_DATA . El GUID usado debe ser único para el controlador. Para mostrar los datos de volcado secundarios correspondientes a este GUID, puede usar el comando .enumtag o el método IDebugDataSpaces3::ReadTagged en una extensión del depurador. Para obtener información sobre los depuradores y las extensiones del depurador, vea Depuración de Windows.

Un controlador puede escribir varios bloques con el mismo GUID en el archivo de volcado de memoria, pero esto es muy deficiente, ya que solo el primer bloque será accesible para el depurador. Los controladores que registran varias rutinas KbCallbackSecondaryDumpData deben asignar un GUID único para cada devolución de llamada.

Use KeRegisterBugCheckReasonCallback para registrar una rutina KbCallbackSecondaryDumpData . Posteriormente, un controlador puede quitar la rutina de devolución de llamada mediante la rutina KeDeregisterBugCheckReasonCallback . Si el controlador se puede descargar, debe quitar las rutinas de devolución de llamada registradas en su función de devolución de llamada DRIVER_UNLOAD .

Una rutina KbCallbackSecondaryDumpData está muy restringida en las acciones que puede realizar. Para obtener más información, consulte Restricciones de rutina de devolución de llamada de comprobación de errores.

Implementación de una rutina de devolución de llamada kbCallbackTriageDumpData

A partir de Windows 10, versión 1809 y Windows Server 2019, un controlador en modo kernel puede implementar una función de devolución de llamada KBUGCHECK_REASON_CALLBACK_ROUTINE de tipo KbCallbackTriageDumpData para marcar los intervalos de memoria virtual para su inclusión en un minivolcado de kernel tallado. Esto garantiza que un minivolcado contendrá los intervalos especificados, por lo que se puede acceder a ellos mediante los mismos comandos del depurador que funcionarían en un volcado de kernel. Esto se implementa actualmente para minivolcados "tallados", lo que significa que se capturó un volcado de memoria o un volcado de memoria más grande y, a continuación, se creó un minivolcado a partir del volcado de memoria más grande. La mayoría de los sistemas están configurados para volcados automáticos o de kernel de forma predeterminada, y el sistema crea automáticamente un minivolcado en el siguiente arranque después del bloqueo.

El sistema pasa, en el parámetro ReasonSpecificData , un puntero a una estructura de KBUGCHECK_TRIAGE_DUMP_DATA que contiene información sobre la comprobación de errores, así como un parámetro OUT que el controlador usa para devolver su matriz de datos inicializada y rellenada.

En el ejemplo siguiente, el controlador configura una matriz de volcado de evaluación de prioridades y, a continuación, registra una implementación mínima de la devolución de llamada. El controlador usará la matriz para agregar dos variables globales al minivolcado.

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

}

Solo se deben usar direcciones en modo kernel no paginadas con este método de devolución de llamada.

Una rutina KbCallbackTriageDumpData está muy restringida en las acciones que puede realizar. Para obtener más información, consulte Restricciones de rutina de devolución de llamada de comprobación de errores.

La función MmIsAddressValid solo debe usarse desde una rutina KbCallbackTriageDumpData después de validar que se establece la marca de KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE. Actualmente, se espera que esta marca se establezca, pero no es seguro llamar a la rutina en caso de que no se establezca sin sincronización adicional.