windows-nt/Source/XPSP1/NT/base/ntos/verifier/vfbugcheck.c
2020-09-26 16:20:57 +08:00

973 lines
23 KiB
C

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
vfbugcheck.c
Abstract:
This module implements support for verifier bugchecks.
Author:
Adrian J. Oney (adriao) 20-Apr-1998
Environment:
Kernel mode
Revision History:
AdriaO 02/21/2000 - Moved from ntos\io\ioassert.c
--*/
//
// Disable W4 level warnings generated by public headers.
//
#include "vfpragma.h"
#include "..\io\iop.h" // Includes vfdef.h
#include "vibugcheck.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, VfBugcheckInit)
#pragma alloc_text(PAGEVRFY, VfBugcheckThrowIoException)
#pragma alloc_text(PAGEVRFY, VfBugcheckThrowException)
#pragma alloc_text(PAGEVRFY, ViBucheckProcessParams)
#pragma alloc_text(PAGEVRFY, ViBugcheckProcessMessageText)
#pragma alloc_text(PAGEVRFY, ViBugcheckApplyControl)
#pragma alloc_text(PAGEVRFY, ViBugcheckHalt)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrintBuffer)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrintParamData)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrintUrl)
#pragma alloc_text(PAGEVRFY, ViBugcheckPrompt)
#endif // ALLOC_PRAGMA
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEVRFD")
#endif
ULONG ViBugCheckInitialControl;
ULONG ViBugCheckControlOverride;
UNICODE_STRING ViBugCheckEmptyString;
#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg("PAGEVRFC")
#endif
//
// When invoking the driver check macro's, pass Irps first, Routines second,
// DevObj's third, and any Status's last...
//
const DCPARAM_TYPE_ENTRY ViBugCheckParamTable[] = {
{ DCPARAM_ROUTINE, "Routine" },
{ DCPARAM_IRP, "Irp" },
{ DCPARAM_IRPSNAP, "Snapshot" },
{ DCPARAM_DEVOBJ, "DevObj" },
{ DCPARAM_STATUS, "Status" },
{ DCPARAM_ULONG, "Ulong" },
{ DCPARAM_PVOID, "Pvoid" }
};
VOID
FASTCALL
VfBugcheckInit(
VOID
)
/*++
Routine Description:
This routine initializes the verifier bugcheck support routines.
Arguments:
None.
Return Value:
None.
--*/
{
ViBugCheckInitialControl = 0;
ViBugCheckControlOverride = 0;
RtlInitUnicodeString(&ViBugCheckEmptyString, NULL);
}
NTSTATUS
VfBugcheckThrowIoException(
IN DCERROR_ID MessageID,
IN ULONG MessageParameterMask,
...
)
/*++
Description:
This routine processes an assert and provides options for removing the
breakpoint, changing to just a text-out, etc.
DCPARAM_IRP*(count)+DCPARAM_ROUTINE*(count)+DCPARAM_DEVOBJ*(count),
irp1,
irp2,
irp3,
routine1,
..,
..,
devobj1,
count can be a max of 3.
Notes:
The text will automagically be formatted and printed as such:
ASSERTION CLASS: ASSERTION TEXT ASSERTION TEXT ASSERTION
TEXT ASSERTION TEXT ...
--*/
{
PVFMESSAGE_TEMPLATE_TABLE ioVerifierTable;
UCHAR paramFormat[9*3*ARRAY_COUNT(ViBugCheckParamTable)+1];
ULONG paramType, paramMask, curMask;
NTSTATUS status;
va_list arglist;
curMask = MessageParameterMask;
paramFormat[0] = '\0';
for(paramType=0; paramType<ARRAY_COUNT(ViBugCheckParamTable); paramType++) {
paramMask = ViBugCheckParamTable[paramType].DcParamMask;
while(curMask & (paramMask*3)) {
strcat(
(char *) paramFormat,
ViBugCheckParamTable[paramType].DcParamName
);
curMask -= paramMask;
}
}
VfMessageRetrieveInternalTable(
VFMESSAGE_TABLE_IOVERIFIER,
&ioVerifierTable
);
va_start(arglist, MessageParameterMask);
status = VfBugcheckThrowException(
ioVerifierTable,
(VFMESSAGE_ERRORID) MessageID,
(PCSTR) paramFormat,
&arglist
);
va_end(arglist);
return status;
}
NTSTATUS
VfBugcheckThrowException(
IN PVFMESSAGE_TEMPLATE_TABLE MessageTable OPTIONAL,
IN VFMESSAGE_ERRORID MessageID,
IN PCSTR MessageParamFormat,
IN va_list * MessageParameters
)
/*++
Description:
This routine displays an assert and provides options for
removing the breakpoint, changing to just a text-out, etc.
Arguments:
Notes:
The text will automagically be formatted and printed as such:
ASSERTION CLASS: ASSERTION TEXT ASSERTION TEXT ASSERTION
TEXT ASSERTION TEXT ...
--*/
{
UCHAR finalBuffer[512];
NTSTATUS status;
DC_CHECK_DATA dcCheckData;
PVOID dcParamArray[3*ARRAY_COUNT(ViBugCheckParamTable)];
BOOLEAN exitAssertion;
//
// Preinit
//
RtlZeroMemory(dcParamArray, sizeof(dcParamArray));
//
// Determine what our basic policy towards this check will be and fill out
// the dcCheckData structure as well as we can.
//
ViBucheckProcessParams(
MessageTable,
MessageID,
MessageParamFormat,
MessageParameters,
dcParamArray,
&dcCheckData
);
if (!ViBugcheckApplyControl(&dcCheckData)) {
//
// Nothing to see here, just ignore the assert...
//
return STATUS_SUCCESS;
}
//
// We are going to express our disatifaction somehow. Expand out the
// message we've prepared for this scenario.
//
status = ViBugcheckProcessMessageText(
sizeof(finalBuffer),
(PSTR)finalBuffer,
&dcCheckData
);
if (!NT_SUCCESS(status)) {
ASSERT(0);
//
// Something went wrong with the index lookup!
//
return status;
}
do {
ViBugcheckPrintBuffer(&dcCheckData);
ViBugcheckPrintParamData(&dcCheckData);
ViBugcheckPrintUrl(&dcCheckData);
ViBugcheckHalt(&dcCheckData);
ViBugcheckPrompt(&dcCheckData, &exitAssertion);
} while (!exitAssertion);
return status;
}
VOID
ViBucheckProcessParams(
IN PVFMESSAGE_TEMPLATE_TABLE MessageTable OPTIONAL,
IN VFMESSAGE_ERRORID MessageID,
IN PCSTR MessageParamFormat,
IN va_list * MessageParameters,
IN PVOID * DcParamArray,
OUT PDC_CHECK_DATA DcCheckData
)
{
PVOID culpritAddress;
ULONG i, paramType, paramLen;
char ansiDriverName[81];
NTSTATUS status;
ULONG paramIndices[ARRAY_COUNT(ViBugCheckParamTable)];
PCSTR format;
//
// First we grab parameter off the stack and slot them appropriately into
// our array of "things".
//
// The array is in groups of three for each possible member of a given type
// (three irps, three routines, three device objects, etc). Items not
// referenced in are set to NULL.
//
RtlZeroMemory(paramIndices, sizeof(paramIndices));
format = MessageParamFormat;
while(*format) {
if ((format[0] == ' ')||(format[0] == '%')) {
format++;
continue;
}
for(paramType = 0;
paramType < ARRAY_COUNT(ViBugCheckParamTable);
paramType++) {
paramLen = (ULONG)strlen(ViBugCheckParamTable[paramType].DcParamName);
if (!_strnicmp(ViBugCheckParamTable[paramType].DcParamName, format, paramLen)) {
//
// Match! Advance the pointer...
//
format += paramLen;
//
// If the caller specified an index, grab it. Otherwise infer.
//
if ((format[0] >= '1') && (format[0] <= '3')) {
i = format[0] - '1';
format++;
} else {
i = paramIndices[paramType];
ASSERT(i < 3);
}
if (i < 3) {
//
// Param number is within bounds.
//
DcParamArray[paramType*3+i] = va_arg(*MessageParameters, PVOID);
}
//
// Update the current parameter index for the given type.
//
paramIndices[paramType] = i+1;
//
// Get out early
//
break;
}
}
if (paramType == ARRAY_COUNT(ViBugCheckParamTable)) {
//
// We could'nt find an entry matching the format text. Bail.
//
ASSERT(paramType != ARRAY_COUNT(ViBugCheckParamTable));
break;
}
}
//
// Pre-init unhelpful answers...
//
DcCheckData->DriverName = &ViBugCheckEmptyString;
DcCheckData->OffsetIntoImage = 0;
DcCheckData->InVerifierList = TRUE;
culpritAddress = DcParamArray[0];
//
// Extract the culprit's name if possible...
//
if (culpritAddress) {
status = KevUtilAddressToFileHeader(
(PVOID) culpritAddress,
(PUINT_PTR)(&DcCheckData->OffsetIntoImage),
&DcCheckData->DriverName,
&DcCheckData->InVerifierList
);
if (!NT_SUCCESS(status)) {
//
// IF we don't know who it is, assert anyway.
//
DcCheckData->InVerifierList = TRUE;
}
}
//
// Record
//
DcCheckData->CulpritAddress = culpritAddress;
DcCheckData->DcParamArray = DcParamArray;
DcCheckData->MessageID = MessageID;
//
// Get an ANSI version of the driver name.
//
KeBugCheckUnicodeToAnsi(
DcCheckData->DriverName,
ansiDriverName,
sizeof(ansiDriverName)
);
//
// Retrieve a pointer to the appropriate message data.
//
VfMessageRetrieveErrorData(
MessageTable,
MessageID,
ansiDriverName,
&DcCheckData->BugCheckMajor,
&DcCheckData->AssertionClass,
&DcCheckData->MessageTextTemplate,
&DcCheckData->Control
);
}
NTSTATUS
FASTCALL
ViBugcheckProcessMessageText(
IN ULONG MaxOutputBufferSize,
OUT PSTR OutputBuffer,
IN OUT PDC_CHECK_DATA DcCheckData
)
{
ULONG paramType, maxParameterTypes;
ULONG arrayIndex, paramLength;
char const* messageHead;
PSTR newMessage;
LONG charsRemaining, length;
//
// Get the message text.
//
messageHead = DcCheckData->MessageTextTemplate;
//
// Now manually build out the message.
//
newMessage = OutputBuffer;
charsRemaining = (MaxOutputBufferSize/sizeof(UCHAR))-1;
maxParameterTypes = ARRAY_COUNT(ViBugCheckParamTable);
while(*messageHead != '\0') {
if (charsRemaining <= 0) {
return STATUS_BUFFER_OVERFLOW;
}
if (*messageHead != '%') {
*newMessage = *messageHead;
newMessage++;
messageHead++;
charsRemaining--;
} else {
for(paramType = 0; paramType < maxParameterTypes; paramType++) {
paramLength = (ULONG)strlen(ViBugCheckParamTable[paramType].DcParamName);
//
// Do we have a match?
//
// N.B. - We don't do any case 'de-sensitizing' anywhere, so
// everything's cases must match!
//
if (RtlCompareMemory(
messageHead+1,
ViBugCheckParamTable[paramType].DcParamName,
paramLength*sizeof(UCHAR)) == paramLength*sizeof(UCHAR)) {
arrayIndex = paramType*3;
messageHead += (paramLength+1);
//
// Was an index passed in (ie, "3rd" irp requested)?
//
if ((*messageHead >= '1') && (*messageHead <= '3')) {
//
// Adjust table index appropriately.
//
arrayIndex += (*messageHead - '1') ;
messageHead++;
}
if ((arrayIndex < 6) || (arrayIndex >=9)) {
//
// Normal param, print the pointer
//
length = _snprintf(
newMessage,
charsRemaining+1,
"%p",
DcCheckData->DcParamArray[arrayIndex]
);
} else {
//
// IRP Snapshot, extract the IRP and print that
//
length = _snprintf(
newMessage,
charsRemaining+1,
"%p",
((PIRP_SNAPSHOT) DcCheckData->DcParamArray[arrayIndex])->Irp
);
}
if (length == -1) {
return STATUS_BUFFER_OVERFLOW;
}
charsRemaining -= length;
newMessage += length;
break;
}
}
if (paramType == maxParameterTypes) {
//
// Either the message we looked up is malformed, we don't recognize
// the %thing it is talking about, or this is %%!
//
*newMessage = *messageHead;
messageHead++;
newMessage++;
charsRemaining--;
if (*messageHead == '%') {
messageHead++;
}
}
}
}
//
// Null-terminate it (we have room because we took one off the buffer size
// above).
//
*newMessage = '\0';
DcCheckData->ClassText = DcCheckData->AssertionClass->MessageClassText;
DcCheckData->AssertionText = OutputBuffer;
return STATUS_SUCCESS;
}
BOOLEAN
FASTCALL
ViBugcheckApplyControl(
IN OUT PDC_CHECK_DATA DcCheckData
)
{
ULONG assertionControl;
if (ViBugCheckControlOverride) {
assertionControl = ViBugCheckControlOverride;
} else if (DcCheckData->Control) {
//
// Initialize the control if appropo
//
if (!((*DcCheckData->Control) & VFM_FLAG_INITIALIZED)) {
*DcCheckData->Control |= (
VFM_FLAG_INITIALIZED | ViBugCheckInitialControl |
DcCheckData->AssertionClass->ClassFlags );
}
assertionControl = *DcCheckData->Control;
} else {
assertionControl =
( ViBugCheckInitialControl |
DcCheckData->AssertionClass->ClassFlags );
}
if (assertionControl & VFM_FLAG_CLEARED) {
//
// If the breakpoint was cleared, then return, print/rip not.
//
return FALSE;
}
if ((!(assertionControl & VFM_IGNORE_DRIVER_LIST)) &&
(!DcCheckData->InVerifierList)) {
//
// Not of interest, skip this one.
//
return FALSE;
}
//
// If there is no debugger, don't halt the machine. We are probably
// ripping like mad and the user just wants to be able to boot.
// The one exception is if VFM_DEPLOYMENT_FAILURE is set. Then we shall
// invoke the driver bugcheck...
//
if ((!KdDebuggerEnabled) && (!(assertionControl & VFM_DEPLOYMENT_FAILURE))) {
return FALSE;
}
//
// Record our intentions and continue.
//
DcCheckData->AssertionControl = assertionControl;
return TRUE;
}
VOID
FASTCALL
ViBugcheckHalt(
IN PDC_CHECK_DATA DcCheckData
)
{
PVOID parameterArray[4];
char captionBuffer[256];
char ansiDriverName[81];
//
// Do not bugcheck if a kernel debugger is attached, nor if this isn't a
// fatal error.
//
if (KdDebuggerEnabled ||
(!(DcCheckData->AssertionControl & VFM_DEPLOYMENT_FAILURE))) {
return;
}
//
// We are here because VFM_DEPLOYMENT_FAILURE is set. We use
// FATAL_UNHANDLED_HARD_ERROR so that we can give a
// descriptive text string for the problem.
//
parameterArray[0] = (PVOID)(ULONG_PTR)(DcCheckData->MessageID);
parameterArray[1] = DcCheckData->CulpritAddress;
parameterArray[2] = DcCheckData->DcParamArray[3];
parameterArray[3] = DcCheckData->DcParamArray[9];
if (DcCheckData->BugCheckMajor == DRIVER_VERIFIER_IOMANAGER_VIOLATION) {
KeBugCheckUnicodeToAnsi(
DcCheckData->DriverName,
ansiDriverName,
sizeof(ansiDriverName)
);
_snprintf(
captionBuffer,
sizeof(captionBuffer),
"IO SYSTEM VERIFICATION ERROR in %s (%s %x)\n[%s+%x at %p]\n",
ansiDriverName,
DcCheckData->ClassText,
DcCheckData->MessageID,
ansiDriverName,
DcCheckData->OffsetIntoImage,
DcCheckData->CulpritAddress
);
KeBugCheckEx(
FATAL_UNHANDLED_HARD_ERROR,
DcCheckData->BugCheckMajor,
(ULONG_PTR) parameterArray,
(ULONG_PTR) captionBuffer,
(ULONG_PTR) "" // DcCheckData->AssertionText is too technical
);
} else {
KeBugCheckEx(
DcCheckData->BugCheckMajor,
DcCheckData->MessageID,
(ULONG_PTR) DcCheckData->DcParamArray[9],
(ULONG_PTR) DcCheckData->DcParamArray[15],
(ULONG_PTR) DcCheckData->DcParamArray[16]
);
}
}
VOID
FASTCALL
ViBugcheckPrintBuffer(
IN PDC_CHECK_DATA DcCheckData
)
{
UCHAR buffer[82];
UCHAR classBuf[81];
UCHAR callerBuf[81+40];
UCHAR ansiDriverName[81];
LONG lMargin, i, lMarginCur, rMargin=78;
PSTR lineStart, lastWord, current, lMarginText;
//
// Put down a carriage return
//
DbgPrint("\n") ;
//
// Drop a banner if this is a fatal assert or a logo failure.
//
if (DcCheckData->AssertionControl &
(VFM_DEPLOYMENT_FAILURE | VFM_LOGO_FAILURE)) {
DbgPrint(
"***********************************************************************\n"
"* THIS VALIDATION BUG IS FATAL AND WILL CAUSE THE VERIFIER TO HALT *\n"
"* WINDOWS (BUGCHECK) WHEN THE MACHINE IS NOT UNDER A KERNEL DEBUGGER! *\n"
"***********************************************************************\n"
"\n"
);
}
//
// Prepare left margin (ClassText)
//
if (DcCheckData->ClassText != NULL) {
lMargin = (LONG)strlen(DcCheckData->ClassText)+2;
DbgPrint("%s: ", DcCheckData->ClassText);
} else {
lMargin = 0;
}
if (lMargin+1>=rMargin) {
lMargin=0;
}
for(i=0; i<lMargin; i++) classBuf[i] = ' ';
classBuf[lMargin] = '\0';
lMarginText = (PSTR)(classBuf+lMargin);
lMarginCur = lMargin;
lineStart = lastWord = current = DcCheckData->AssertionText;
//
// Print out culprit if we have him...
//
if (DcCheckData->CulpritAddress) {
if (DcCheckData->DriverName->Length) {
KeBugCheckUnicodeToAnsi(
DcCheckData->DriverName,
(PSTR)ansiDriverName,
sizeof(ansiDriverName)
);
sprintf((PCHAR)callerBuf, "[%s @ 0x%p] ",
ansiDriverName,
DcCheckData->CulpritAddress
);
} else {
sprintf((PCHAR)callerBuf, "[0x%p] ", DcCheckData->CulpritAddress);
}
DbgPrint("%s", callerBuf);
lMarginCur += (LONG)strlen((PCHAR)callerBuf);
}
//
// Format and print our assertion text
//
while(*current) {
if (*current == ' ') {
if ((current - lineStart) >= (rMargin-lMarginCur-1)) {
DbgPrint("%s", lMarginText);
lMarginText = (PSTR)classBuf;
lMarginCur = lMargin;
if ((lastWord-lineStart)<rMargin) {
memcpy(buffer, lineStart, (ULONG)(lastWord-lineStart)*sizeof(UCHAR));
buffer[lastWord-lineStart] = '\0';
DbgPrint("%s\n", buffer);
}
lineStart = lastWord+1;
}
lastWord = current;
}
current++;
}
if ((current - lineStart) >= (rMargin-lMarginCur-1)) {
DbgPrint("%s", lMarginText);
lMarginText = (PSTR)classBuf;
if ((lastWord-lineStart)<rMargin) {
memcpy(buffer, lineStart, (ULONG)(lastWord-lineStart)*sizeof(UCHAR));
buffer[lastWord-lineStart] = '\0';
DbgPrint("%s\n", buffer);
}
lineStart = lastWord+1;
}
if (lineStart<current) {
DbgPrint("%s%s\n", lMarginText, lineStart);
}
}
VOID
FASTCALL
ViBugcheckPrintParamData(
IN PDC_CHECK_DATA DcCheckData
)
{
if (DcCheckData->DcParamArray[3]) {
VfPrintDumpIrp((PIRP) DcCheckData->DcParamArray[3]);
}
if (DcCheckData->DcParamArray[6]) {
VfPrintDumpIrpStack(
&((PIRP_SNAPSHOT) DcCheckData->DcParamArray[6])->IoStackLocation
);
}
}
VOID
FASTCALL
ViBugcheckPrintUrl(
IN PDC_CHECK_DATA DcCheckData
)
{
DbgPrint(
"http://www.microsoft.com/hwdq/bc/default.asp?os=%d.%d.%d&major=0x%x&minor=0x%x&lang=0x%x\n",
VER_PRODUCTMAJORVERSION,
VER_PRODUCTMINORVERSION,
VER_PRODUCTBUILD,
DcCheckData->BugCheckMajor,
DcCheckData->MessageID,
9 // English
);
}
VOID
FASTCALL
ViBugcheckPrompt(
IN PDC_CHECK_DATA DcCheckData,
OUT PBOOLEAN ExitAssertion
)
{
char response[2];
ULONG assertionControl;
BOOLEAN waitForInput;
assertionControl = DcCheckData->AssertionControl;
*ExitAssertion = TRUE;
//
// Vocalize if so ordered.
//
if (assertionControl & VFM_FLAG_BEEP) {
DbgPrint("%c", 7);
}
if (assertionControl & VFM_FLAG_ZAPPED) {
return;
}
//
// Wait for input...
//
waitForInput = TRUE;
while(waitForInput) {
if (DcCheckData->Control) {
DbgPrompt( "Break, Ignore, Zap, Remove, Disable all (bizrd)? ", response, sizeof( response ));
} else {
DbgPrompt( "Break, Ignore, Disable all (bid)? ", response, sizeof( response ));
}
switch (response[0]) {
case 'B':
case 'b':
DbgPrint("Breaking in... (press g<enter> to return to assert menu)\n");
DbgBreakPoint();
waitForInput = FALSE;
*ExitAssertion = FALSE;
break;
case 'I':
case 'i':
waitForInput = FALSE;
break;
case 'Z':
case 'z':
if (DcCheckData->Control) {
DbgPrint("Breakpoint zapped (OS will print text and return)\n");
assertionControl |= VFM_FLAG_ZAPPED;
assertionControl &=~ VFM_FLAG_BEEP;
waitForInput = FALSE;
}
break;
case 'D':
case 'd':
ViBugCheckControlOverride = VFM_FLAG_CLEARED;
DbgPrint("Verification asserts disabled.\n");
waitForInput = FALSE;
break;
case 'R':
case 'r':
if (DcCheckData->Control) {
DbgPrint("Breakpoint removed\n") ;
assertionControl |= VFM_FLAG_CLEARED;
waitForInput = FALSE;
}
break;
}
}
if (DcCheckData->Control) {
*DcCheckData->Control = assertionControl;
}
}