Поделиться через


Написание процедуры обратного вызова причины проверки ошибок

При необходимости драйвер может предоставить 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

Драйвер режима ядра может реализовать функцию обратного вызова KBUGCHECK_REASON_CALLBACK_ROUTINE типа KbCallbackAddPages, чтобы добавить одну или несколько страниц данных в файл аварийного дампа при возникновении проверка ошибок. Чтобы зарегистрировать эту подпрограмму в операционной системе, драйвер вызывает подпрограмму KeRegisterBugCheckReasonCallback . Прежде чем драйвер выгрузит, он должен вызвать подпрограмму KeDeregisterBugCheckReasonCallback , чтобы удалить регистрацию.

Начиная с Windows 8, зарегистрированная подпрограмма KbCallbackAddPages вызывается во время дампа памяти ядра или полного дампа памяти. В более ранних версиях Windows зарегистрированная подпрограмма KbCallbackAddPages вызывается во время дампа памяти ядра, но не во время полного дампа памяти. По умолчанию дамп памяти ядра включает только физические страницы, используемые ядром Windows в момент возникновения проверка ошибок, тогда как полный дамп памяти включает всю физическую память, используемую Windows. Полный аварийный дамп памяти по умолчанию не включает в себя физическую память, используемую встроенным ПО платформы.

Подпрограмма KbCallbackAddPages может предоставлять данные, относящиеся к драйверу, для добавления в файл дампа. Например, для дампа памяти ядра эти дополнительные данные могут включать физические страницы, которые не сопоставлены с системным диапазоном адресов в виртуальной памяти, но содержат сведения, которые могут помочь в отладке драйвера. Подпрограмма KbCallbackAddPages может добавить в файл дампа все физические страницы, принадлежащие драйверу, которые не сопоставлены или сопоставлены с адресами пользовательского режима в виртуальной памяти.

При возникновении проверка ошибок операционная система вызывает все зарегистрированные подпрограммы KbCallbackAddPages для опроса драйверов на наличие данных для добавления в файл аварийного дампа. Каждый вызов добавляет одну или несколько страниц смежных данных в файл аварийного дампа. Подпрограмма KbCallbackAddPages может предоставить виртуальный или физический адрес начальной страницы. Если во время вызова задано несколько страниц, страницы размещаются в виртуальной или физической памяти в зависимости от того, является ли начальный адрес виртуальным или физическим. Для предоставления несмежных страниц подпрограмма KbCallbackAddPages может установить флаг в структуре KBUGCHECK_ADD_PAGES , чтобы указать, что она содержит дополнительные данные и должна быть вызвана еще раз.

В отличие от подпрограммы KbCallbackSecondaryDumpData , которая добавляет данные в дополнительный регион аварийного дампа, подпрограмма KbCallbackAddPages добавляет страницы данных в основной регион аварийного дампа. Во время отладки доступ к данным первичного аварийного дампа проще, чем к данным вторичного аварийного дампа.

Операционная система заполняет элемент BugCheckCodeструктуры KBUGCHECK_ADD_PAGES , на которую указывает ReasonSpecificData . Подпрограмма KbCallbackAddPages должна задавать значения элементов Flags, Address и Count этой структуры.

Перед первым вызовом KbCallbackAddPages операционная система инициализирует Контекстзначением NULL. Если подпрограмма KbCallbackAddPages вызывается несколько раз, операционная система сохраняет значение, которое подпрограмма обратного вызова записала в член Context в предыдущем вызове.

Подпрограмма KbCallbackAddPages очень ограничена действиями, которые она может выполнять. Дополнительные сведения см. в разделе Ограничения подпрограмм обратного вызова проверки ошибок.

Реализация процедуры обратного вызова KbCallbackDumpIo

Драйвер режима ядра может реализовать функцию обратного вызова KBUGCHECK_REASON_CALLBACK_ROUTINE типа KbCallbackDumpIo для выполнения работы при каждой записи данных в файл аварийного дампа. Система передает в параметре ReasonSpecificData указатель на структуру KBUGCHECK_DUMP_IO . Элемент Buffer указывает на текущие данные, а член BufferLength указывает его длину. Элемент Type указывает тип записываемых в данный момент данных, например сведения о заголовке файла дампа, состояние памяти или данные, предоставляемые драйвером. Описание возможных типов информации см. в перечислении KBUGCHECK_DUMP_IO_TYPE .

Система может записывать файл аварийного дампа последовательно или не по порядку. Если система записывает файл аварийного дампа последовательно, то элемент Offsetобъекта ReasonSpecificData имеет значение -1; В противном случае для параметра Offset устанавливается текущее смещение в байтах в файле аварийного дампа.

Когда система записывает файл последовательно, она вызывает каждую подпрограмму KbCallbackDumpIo один или несколько раз при записи сведений о заголовке (тип = KbDumpIoHeader), один или несколько раз при записи main тела файла аварийного дампа (тип = KbDumpIoBody) и один или несколько раз при записи данных вторичного дампа (Тип = KbDumpIoSecondaryDumpData). После того как система завершит запись файла аварийного дампа, она вызывает обратный вызов с буфером = NULL, BufferLength = 0 и типом = KbDumpIoComplete.

Main назначение подпрограммы KbCallbackDumpIo — разрешить запись данных аварийного дампа системы на устройства, отличные от диска. Например, устройство, которое отслеживает состояние системы, может использовать обратный вызов, чтобы сообщить о том, что система выдала проверка ошибок, и предоставить аварийный дамп для анализа.

Используйте KeRegisterBugCheckReasonCallback , чтобы зарегистрировать подпрограмму KbCallbackDumpIo . Впоследствии драйвер может удалить обратный вызов с помощью процедуры KeDeregisterBugCheckReasonCallback . Если драйвер можно выгрузить, он должен удалить все зарегистрированные обратные вызовы в своей функции обратного вызова DRIVER_UNLOAD .

Подпрограмма KbCallbackDumpIo строго ограничена действиями, которые она может предпринять. Дополнительные сведения см. в разделе Ограничения подпрограмм обратного вызова проверки ошибок.

Реализация процедуры обратного вызова KbCallbackSecondaryDumpData

Драйвер режима ядра может реализовать функцию обратного вызова KBUGCHECK_REASON_CALLBACK_ROUTINE типа KbCallbackSecondaryDumpData для предоставления данных для добавления в файл аварийного дампа.

Система задает элементы InBuffer, InBufferLength, OutBuffer и MaximumAllowedструктуры KBUGCHECK_SECONDARY_DUMP_DATA , на которую указывает ReasonSpecificData . Элемент MaximumAllowed указывает максимальный объем данных дампа, которые может предоставить подпрограмма.

Значение элемента OutBuffer определяет, запрашивает ли система размер данных дампа драйвера или сами данные следующим образом:

  • Если элемент OutBuffer KBUGCHECK_SECONDARY_DUMP_DATA имеет значение NULL, система запрашивает только сведения о размере. Подпрограмма KbCallbackSecondaryDumpData заполняет элементы OutBuffer и OutBufferLength .

  • Если элемент OutBuffer KBUGCHECK_SECONDARY_DUMP_DATA равен элементу InBuffer , система запрашивает дополнительные данные дампа драйвера. Подпрограмма KbCallbackSecondaryDumpData заполняет элементы OutBuffer и OutBufferLength и записывает данные в буфер, заданный параметром OutBuffer.

Элемент InBuffer KBUGCHECK_SECONDARY_DUMP_DATA указывает на небольшой буфер для использования подпрограммой. Член InBufferLength указывает размер буфера. Если объем записываемых данных меньше, чем InBufferLength, подпрограмма обратного вызова может использовать этот буфер для предоставления данных аварийного дампа в систему. Затем подпрограмма обратного вызова задает для OutBufferзначение InBuffer , а OutBufferLength — фактический объем данных, записанных в буфер.

Драйвер, который должен записать объем данных, превышающий InBufferLength , может использовать собственный буфер для предоставления данных. Этот буфер должен быть выделен перед выполнением подпрограммы обратного вызова и находиться в резидентной памяти (например, в непагрегированных пулах). Затем подпрограмма обратного вызова задает OutBuffer для указания буфера драйвера, а OutBufferLength — объем данных в буфере для записи в файл аварийного дампа.

Каждый блок данных, записываемый в файл аварийного дампа, помечается значением элемента Guid структуры KBUGCHECK_SECONDARY_DUMP_DATA . Используемый ИДЕНТИФИКАТОР GUID должен быть уникальным для драйвера. Чтобы отобразить данные вторичного дампа, соответствующие этому GUID, можно использовать команду .enumtag или метод IDebugDataSpaces3::ReadTagged в расширении отладчика. Сведения об отладчиках и расширениях отладчика см. в разделе Отладка Windows.

Драйвер может записать несколько блоков с одинаковым идентификатором GUID в файл аварийного дампа, но это очень плохо, так как только первый блок будет доступен отладчику. Драйверы, которые регистрируют несколько подпрограмм KbCallbackSecondaryDumpData , должны выделять уникальный ИДЕНТИФИКАТОР для каждого обратного вызова.

Используйте KeRegisterBugCheckReasonCallback , чтобы зарегистрировать подпрограмму KbCallbackSecondaryDumpData . Впоследствии драйвер может удалить подпрограмму обратного вызова с помощью процедуры KeDeregisterBugCheckReasonCallback . Если драйвер можно выгрузить, он должен удалить все зарегистрированные подпрограммы обратного вызова в DRIVER_UNLOAD функции обратного вызова.

Подпрограмма KbCallbackSecondaryDumpData очень ограничена в действиях, которые она может выполнять. Дополнительные сведения см. в разделе Ограничения подпрограмм обратного вызова проверки ошибок.

Реализация подпрограммы обратного вызова KbCallbackTriageDumpData

Начиная с Windows 10, версия 1809 и Windows Server 2019, драйвер режима ядра может реализовать функцию обратного вызова KBUGCHECK_REASON_CALLBACK_ROUTINE типа KbCallbackTriageDumpData, чтобы пометить диапазоны виртуальной памяти для включения в вырезанный минидамп ядра. Это гарантирует, что минидамп будет содержать указанные диапазоны, чтобы к им можно было получить доступ с помощью команд отладчика, которые будут работать в дампе ядра. В настоящее время это реализовано для "резных" минидампов. Это означает, что ядро или более крупный дамп был захвачен, а затем минидамп был создан из более крупного дампа. Большинство систем по умолчанию настроены для автоматического дампа ядра, и система автоматически создает минидамп при следующей загрузке после сбоя.

Система передает в параметре 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 очень ограничена в действиях, которые она может выполнять. Дополнительные сведения см. в разделе Ограничения подпрограмм обратного вызова проверки ошибок.

Функцию MmIsAddressValid следует использовать только из подпрограммы KbCallbackTriageDumpData после проверки установки флага KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ACTIVE. В настоящее время этот флаг всегда должен быть установлен, но небезопасно вызывать подпрограмму в случае, если она не установлена без дополнительной синхронизации.