Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Opcionalmente, um driver pode fornecer uma função de retorno de chamada KBUGCHECK_REASON_CALLBACK_ROUTINE, que o sistema chama depois que um arquivo de crash dump é gravado.
Nota
Este artigo descreve a rotina de retorno de chamada do motivo do bug check e não a função de retorno de chamada KBUGCHECK_CALLBACK_ROUTINE.
Neste callback, o condutor pode:
Adicionar dados específicos do driver ao arquivo de despejo de memória
Redefinir o dispositivo para um estado conhecido
Use as seguintes rotinas para registar e remover o callback:
KeRegisterBugCheckReasonCallback
#B0 #A1 KeDeregisterBugCheckReasonCallback #A2 #C3
Esse tipo de retorno de chamada está sobrecarregado, com mudança de comportamento com base no valor constante de KBUGCHECK_CALLBACK_REASON fornecido no registro. Este artigo descreve os diferentes cenários de uso.
Para obter informações gerais sobre dados de verificação de bugs, consulte Lendo dados de retorno de chamada de verificação de bugs.
Uma rotina de retorno de chamada de verificação de bugs executa-se em IRQL = HIGH_LEVEL, o que impõe grandes limitações ao que ela pode realizar.
Uma rotina de retorno de chamada de verificação de bugs não pode:
Alocar memória
Aceda à memória paginável
Use quaisquer mecanismos de sincronização
Chame qualquer rotina que deva ser executada no nível IRQL = DISPATCH_LEVEL ou abaixo.
As rotinas de retificação de erros são executadas sem interrupção, portanto, não é necessária sincronização. (Se a rotina de verificação de bugs tentar adquirir bloqueios usando quaisquer mecanismos de sincronização, o sistema será bloqueado.) Tenha em mente que as estruturas ou listas de dados podem estar em um estado inconsistente no momento da verificação de bugs, portanto, deve-se ter cuidado ao acessar estruturas de dados protegidas por bloqueios. Por exemplo, deve adicionar a verificação de limites superiores ao percorrer listas e verificar se os links estão a apontar para a memória válida, no caso de haver uma lista circular ou de um link estar a apontar para um endereço inválido.
O MmIsAddressValid pode ser usado por uma rotina de retorno de chamada de verificação de bugs para verificar se o acesso a um endereço causará uma falha de página. Como a rotina é executada sem interrupção e outros núcleos são congelados, isso satisfaz os requisitos de sincronização dessa função. Os endereços do kernel que podem ser paginados ou inválidos devem ser sempre verificados com MmIsAddressValid antes de serem desreferenciados num callback de Bug Check, uma vez que uma falha de página causará uma falha dupla e pode impedir a gravação do despejo.
A rotina de retorno de chamada de verificação de bugs de um driver pode usar com segurança as rotinas READ_PORT_XXX, READ_REGISTER_XXX, WRITE_PORT_XXX e WRITE_REGISTER_XXX para se comunicar com o dispositivo do controlador. (Para obter informações sobre essas rotinas, consulte Rotinas de camada de abstração de hardware.)
Um driver de modo kernel pode implementar uma rotina de retorno de chamada KBUGCHECK_REASON_CALLBACK_ROUTINE do tipo KbCallbackAddPages para adicionar uma ou mais páginas de dados a um ficheiro de despejo de memória quando ocorre uma verificação de falha. Para registrar essa rotina no sistema operacional, o driver chama a rotina KeRegisterBugCheckReasonCallback . Antes de o driver descarregar, ele deve chamar a rotina KeDeregisterBugCheckReasonCallback para remover o registo.
A partir do Windows 8, uma rotina KbCallbackAddPages registrada é chamada durante um despejo de memória do kernel ou um despejo de memória completo. Em versões anteriores do Windows, uma rotina KbCallbackAddPages registrada é chamada durante um despejo de memória do kernel, mas não durante um despejo de memória completo. Por padrão, um despejo de memória do kernel inclui apenas as páginas físicas que estão sendo usadas pelo kernel do Windows no momento em que a verificação de bug ocorre, enquanto um despejo de memória completo inclui toda a memória física usada pelo Windows. Por padrão, um despejo completo de memória não inclui a memória física utilizada pelo firmware da plataforma.
Sua rotina KbCallbackAddPages pode fornecer dados específicos do driver para adicionar ao arquivo de despejo. Por exemplo, para um despejo de memória do kernel, esses dados adicionais podem incluir páginas físicas que não são mapeadas para o intervalo de endereços do sistema na memória virtual, mas que contêm informações que podem ajudá-lo a depurar seu driver. A rotina KbCallbackAddPages pode adicionar ao arquivo de despejo qualquer página física de propriedade do driver que não esteja mapeada ou que esteja mapeada para endereços de modo de usuário na memória virtual.
Quando ocorre uma verificação de erro, o sistema operativo chama todas as rotinas KbCallbackAddPages registadas para consultar os controladores em busca de dados para adicionar ao ficheiro de despejo de falhas. Cada chamada adiciona uma ou mais páginas de dados contíguos ao ficheiro de despejo de erro. Uma rotina KbCallbackAddPages pode fornecer um endereço virtual ou um endereço físico para a página inicial. Se mais de uma página for fornecida durante uma chamada, as páginas serão contíguas na memória virtual ou física, dependendo se o endereço inicial é virtual ou físico. Para fornecer páginas não contíguas, a rotina KbCallbackAddPages pode definir um sinalizador na estrutura KBUGCHECK_ADD_PAGES para indicar que tem dados adicionais e precisa ser chamado novamente.
Ao contrário de uma rotina KbCallbackSecondaryDumpData , que acrescenta dados à região de despejo de memória secundária, uma rotina KbCallbackAddPages adiciona páginas de dados à região de despejo de memória primária. Durante a depuração, os dados de despejo de memória primários são mais fáceis de acessar do que os dados de despejo de memória secundários.
O sistema operativo preenche o membro BugCheckCode da estrutura KBUGCHECK_ADD_PAGES para a qual ReasonSpecificData aponta. A rotina KbCallbackAddPages deve definir os valores dos membros Flags, Address e Count dessa estrutura.
Antes da primeira chamada para KbCallbackAddPages, o sistema operacional inicializa Context para NULL. Se a rotina KbCallbackAddPages for chamada mais de uma vez, o sistema operacional preservará o valor que a rotina de retorno de chamada escreveu para o membro Context na chamada anterior.
Uma rotina KbCallbackAddPages é muito restrita nas ações que pode tomar. Para obter mais informações, consulte Restrições da Rotina de Callback de Verificação de Bug.
Um driver de modo kernel pode implementar uma função de retorno de chamada KBUGCHECK_REASON_CALLBACK_ROUTINE do tipo KbCallbackDumpIo para executar trabalho sempre que os dados são escritos no arquivo de despejo de falha. O sistema passa, no parâmetro ReasonSpecificData, um ponteiro para uma estrutura KBUGCHECK_DUMP_IO. O membro Buffer aponta para os dados atuais e o membro BufferLength especifica seu comprimento. O membro Type indica o tipo de dados que estão sendo gravados no momento, como informações de cabeçalho do arquivo de despejo, estado da memória ou dados fornecidos por um driver. Para obter uma descrição dos possíveis tipos de informações, consulte a enumeração KBUGCHECK_DUMP_IO_TYPE.
O sistema pode gravar o arquivo de despejo de memória sequencialmente ou fora de ordem. Se o sistema estiver a gravar o arquivo de despejo de memória sequencialmente, o membro Offset de ReasonSpecificData será -1; caso contrário, Offset será definido como o deslocamento atual, em bytes, no arquivo de despejo de memória.
Quando o sistema grava o ficheiro sequencialmente, chama a rotina KbCallbackDumpIo uma ou mais vezes ao gravar as informações do cabeçalho (Type = KbDumpIoHeader), uma ou mais vezes ao gravar o corpo principal do ficheiro de despejo (Type = KbDumpIoBody) e uma ou mais vezes ao gravar os dados de despejo secundários (Type = KbDumpIoSecondaryDumpData). Uma vez que o sistema tenha concluído a gravação do ficheiro de despejo de falha, ele chama a função de retorno com Buffer = NULL, BufferLength = 0 e Type = KbDumpIoComplete.
O principal objetivo de uma rotina KbCallbackDumpIo é permitir que os dados de despejo de falha do sistema sejam gravados em dispositivos diferentes do disco. Por exemplo, um dispositivo que monitora o estado do sistema pode usar o callback para relatar que o sistema emitiu uma verificação de erros e para fornecer um crash dump para análise.
Utilize KeRegisterBugCheckReasonCallback para registrar uma rotina KbCallbackDumpIo. Um controlador pode posteriormente remover o retorno de chamada usando a rotina KeDeregisterBugCheckReasonCallback. Se o driver puder ser descarregado, deve remover qualquer callback registado na sua função de callback DRIVER_UNLOAD.
Uma rotina KbCallbackDumpIo é fortemente restrita nas ações que pode tomar. Para obter mais informações, consulte Bug Check Callback Routine Restrictions.
Um driver em modo kernel pode implementar uma rotina de retorno de chamada KBUGCHECK_REASON_CALLBACK_ROUTINE do tipo KbCallbackSecondaryDumpData para fornecer dados para anexar ao arquivo de despejo de falha.
O sistema define os membros InBuffer, InBufferLength, OutBuffer e MaximumAllowed da estrutura KBUGCHECK_SECONDARY_DUMP_DATA para a qual ReasonSpecificData aponta. O membro MaximumAllowed especifica a quantidade máxima de dados de despejo que a rotina pode fornecer.
O valor do membro OutBuffer determina se o sistema está solicitando o tamanho dos dados de despejo do driver ou os próprios dados, da seguinte maneira:
Se o membro OutBuffer do KBUGCHECK_SECONDARY_DUMP_DATA estiver NULL, o sistema apenas solicita informações de tamanho. A rotina KbCallbackSecondaryDumpData preenche os membros OutBuffer e OutBufferLength .
Se o membro OutBuffer de KBUGCHECK_SECONDARY_DUMP_DATA for igual ao membro InBuffer , o sistema está solicitando os dados de despejo secundário do driver. A rotina KbCallbackSecondaryDumpData preenche os membros OutBuffer e OutBufferLength e grava os dados no buffer especificado por OutBuffer.
O membro InBuffer de KBUGCHECK_SECONDARY_DUMP_DATA aponta para um pequeno buffer utilizado pela rotina. O membro InBufferLength especifica o tamanho do buffer. Se a quantidade de dados a serem escritos for menor que InBufferLength, a rotina de callback poderá usar esse buffer para fornecer os dados de despejo de memória ao sistema. Em seguida, a rotina de retorno de chamada define OutBuffer como InBuffer e OutBufferLength como a quantidade real de dados gravados no buffer.
Um driver que deve gravar uma quantidade de dados que é maior do que InBufferLength pode usar seu próprio buffer para fornecer os dados. Esse buffer deve ter sido alocado antes da rotina de chamada de retorno ser executada e deve residir na memória residente (como pool não paginado). Em seguida, a rotina de retorno de chamada define OutBuffer para apontar para o buffer do driver e OutBufferLength para a quantidade de dados no buffer a ser gravado no arquivo de despejo de falha.
Cada bloco de dados que será gravado no ficheiro de despejo de memória é marcado com o valor do membro Guid da estrutura KBUGCHECK_SECONDARY_DUMP_DATA. O GUID usado deve ser exclusivo para o driver. Para exibir os dados de despejo secundários correspondentes a esse GUID, você pode usar o comando .enumtag ou o método IDebugDataSpaces3::ReadTagged em uma extensão de depurador. Para obter informações sobre depuradores e extensões de depurador, consulte Depuração do Windows.
Um driver pode gravar vários blocos com o mesmo GUID no ficheiro de crash dump, mas isso é uma prática muito má, porque apenas o primeiro bloco será acessível ao debugger. Os drivers que registam várias rotinas KbCallbackSecondaryDumpData devem alocar um GUID único para cada callback.
Use KeRegisterBugCheckReasonCallback para registrar uma rotina KbCallbackSecondaryDumpData . Um controlador pode posteriormente remover a rotina de callback usando a rotina KeDeregisterBugCheckReasonCallback. Se o driver puder ser descarregado, ele deverá remover todas as rotinas de retorno de chamada registradas em sua função de retorno de chamada DRIVER_UNLOAD .
Uma rotina KbCallbackSecondaryDumpData é muito restrita nas ações que pode tomar. Para obter mais informações, consulte Bug Check Callback Routine Restrictions.
A partir do Windows 10, versão 1809 e Windows Server 2019, um driver de modo kernel pode implementar uma função de retorno de chamada KBUGCHECK_REASON_CALLBACK_ROUTINE do tipo KbCallbackTriageDumpData para marcar intervalos de memória virtual para inclusão em um minidump de kernel esculpido. Isso garante que um minidump conterá os intervalos especificados, para que eles possam ser acessados usando os mesmos comandos do depurador que funcionariam em um dump do kernel. Esta funcionalidade está atualmente implementada para minidumps 'esculpidos', o que significa que um kernel ou um dump maior foi capturado, sendo então criado um minidump a partir do dump maior. A maioria dos sistemas são configurados para despejos automáticos/kernel por padrão, e o sistema cria automaticamente um minidump na próxima inicialização após a falha.
O sistema passa, no parâmetro ReasonSpecificData , um ponteiro para uma estrutura KBUGCHECK_TRIAGE_DUMP_DATA que contém informações sobre a verificação de bugs, bem como um parâmetro OUT que é usado pelo driver para retornar sua matriz de dados inicializada e preenchida.
No exemplo a seguir, o driver configura um array de despejo de triagem e, em seguida, regista uma implementação mínima da função de callback. O driver usará a matriz para adicionar duas variáveis globais ao 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(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;
}
}
Somente endereços de modo kernel não paginados devem ser usados com esse método de retorno de chamada.
Uma rotina KbCallbackTriageDumpData é muito restrita nas ações que pode tomar. Para obter mais informações, consulte Bug Check Callback Routine Restrictions.
A função MmIsAddressValid só deve ser usada a partir de uma rotina KbCallbackTriageDumpData depois de validar que o KB_TRIAGE_DUMP_DATA_FLAG_BUGCHECK_ATIVE Flag está definido. Atualmente, esse sinalizador sempre deve ser definido, mas não é seguro chamar a rotina caso ela não seja definida sem sincronização adicional.