You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
414 lines
13 KiB
414 lines
13 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
excptdbg.c
|
|
|
|
Abstract:
|
|
|
|
This module implements an exception dispatcher logging facility.
|
|
|
|
Author:
|
|
|
|
Kent Forschmiedt (kentf) 05-Oct-1995
|
|
|
|
Revision History:
|
|
|
|
Jonathan Schwartz (jschwart) 16-Jun-2000
|
|
Added RtlUnhandledExceptionFilter
|
|
|
|
Jay Krell (a-JayK) November 2000
|
|
Added RtlUnhandledExceptionFilter2, takes __FUNCTION__ parameter
|
|
|
|
--*/
|
|
|
|
#include "ntrtlp.h"
|
|
|
|
PLAST_EXCEPTION_LOG RtlpExceptionLog;
|
|
ULONG RtlpExceptionLogCount;
|
|
ULONG RtlpExceptionLogSize;
|
|
|
|
|
|
VOID
|
|
RtlInitializeExceptionLog(
|
|
IN ULONG Entries
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates space for the exception dispatcher logging
|
|
facility, and records the address and size of the log area in globals
|
|
where they can be found by the debugger.
|
|
|
|
If memory is not available, the table pointer will remain NULL
|
|
and the logging functions will do nothing.
|
|
|
|
Arguments:
|
|
|
|
Entries - Supplies the number of entries to allocate for
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
#if defined(NTOS_KERNEL_RUNTIME)
|
|
RtlpExceptionLog = (PLAST_EXCEPTION_LOG)ExAllocatePoolWithTag( NonPagedPool, sizeof(LAST_EXCEPTION_LOG) * Entries, 'gbdE' );
|
|
#else
|
|
//RtlpExceptionLog = (PLAST_EXCEPTION_LOG)RtlAllocateHeap( RtlProcessHeap(), 0, sizeof(LAST_EXCEPTION_LOG) * Entries );
|
|
#endif
|
|
if (RtlpExceptionLog) {
|
|
RtlpExceptionLogSize = Entries;
|
|
}
|
|
}
|
|
|
|
|
|
ULONG
|
|
RtlpLogExceptionHandler(
|
|
IN PEXCEPTION_RECORD ExceptionRecord,
|
|
IN PCONTEXT ContextRecord,
|
|
IN ULONG_PTR ControlPc,
|
|
IN PVOID HandlerData,
|
|
IN ULONG Size
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Records the dispatching of exceptions to frame-based handlers.
|
|
The debugger may inspect the table later and interpret the data
|
|
to discover the address of the filters and handlers.
|
|
|
|
Arguments:
|
|
|
|
ExceptionRecord - Supplies an exception record
|
|
|
|
ContextRecord - Supplies the context at the exception
|
|
|
|
ControlPc - Supplies the PC where control left the frame being
|
|
dispatched to.
|
|
|
|
HandlerData - Supplies a pointer to the host-dependent exception
|
|
data. On the RISC machines this is a RUNTIME_FUNCTION record;
|
|
on x86 it is the registration record from the stack frame.
|
|
|
|
Size - Supplies the size of HandlerData
|
|
|
|
Returns:
|
|
|
|
The index to the log entry used, so that if the handler returns
|
|
a disposition it may be recorded.
|
|
|
|
--*/
|
|
{
|
|
#if !defined(NTOS_KERNEL_RUNTIME)
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
ULONG LogIndex;
|
|
|
|
if (!RtlpExceptionLog) {
|
|
return 0;
|
|
}
|
|
|
|
ASSERT(Size <= MAX_EXCEPTION_LOG_DATA_SIZE * sizeof(ULONG));
|
|
|
|
do {
|
|
LogIndex = RtlpExceptionLogCount;
|
|
} while (LogIndex != (ULONG)InterlockedCompareExchange(
|
|
(PLONG)&RtlpExceptionLogCount,
|
|
((LogIndex + 1) % MAX_EXCEPTION_LOG),
|
|
LogIndex));
|
|
|
|
//
|
|
// the debugger will have to interpret the exception handler
|
|
// data, because it cannot be done safely here.
|
|
//
|
|
|
|
RtlCopyMemory(RtlpExceptionLog[LogIndex].HandlerData,
|
|
HandlerData,
|
|
Size);
|
|
RtlpExceptionLog[LogIndex].ExceptionRecord = *ExceptionRecord;
|
|
RtlpExceptionLog[LogIndex].ContextRecord = *ContextRecord;
|
|
RtlpExceptionLog[LogIndex].Disposition = -1;
|
|
|
|
return LogIndex;
|
|
#endif // !NTOS_KERNEL_RUNTIME
|
|
}
|
|
|
|
|
|
VOID
|
|
RtlpLogLastExceptionDisposition(
|
|
ULONG LogIndex,
|
|
EXCEPTION_DISPOSITION Disposition
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Records the disposition from an exception handler.
|
|
|
|
Arguments:
|
|
|
|
LogIndex - Supplies the entry number of the exception log record.
|
|
|
|
Disposition - Supplies the disposition code
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
// If MAX_EXCEPTION_LOG or more exceptions were dispatched while
|
|
// this one was being handled, this disposition will get written
|
|
// on the wrong record. Oh well.
|
|
if (RtlpExceptionLog) {
|
|
RtlpExceptionLog[LogIndex].Disposition = Disposition;
|
|
}
|
|
}
|
|
|
|
LONG
|
|
NTAPI
|
|
RtlUnhandledExceptionFilter(
|
|
IN struct _EXCEPTION_POINTERS *ExceptionInfo
|
|
)
|
|
{
|
|
return RtlUnhandledExceptionFilter2(ExceptionInfo, "");
|
|
}
|
|
|
|
LONG
|
|
NTAPI
|
|
RtlUnhandledExceptionFilter2(
|
|
IN struct _EXCEPTION_POINTERS *ExceptionInfo,
|
|
IN CONST CHAR* Function
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Default exception handler that prints info and does a DbgBreak if
|
|
a debugger is attached to the machine.
|
|
|
|
Arguments:
|
|
|
|
ExceptionInfo - Structure containing the exception and context records
|
|
|
|
Function - the function containing the __except, such as returned by __FUNCTION__
|
|
|
|
Returns:
|
|
|
|
EXCEPTION_CONTINUE_EXECUTION or EXCEPTION_CONTINUE_SEARCH
|
|
|
|
--*/
|
|
|
|
{
|
|
LPCWSTR lpProcessName = NtCurrentPeb()->ProcessParameters->CommandLine.Buffer;
|
|
BOOLEAN DebuggerPresent = NtCurrentPeb()->BeingDebugged;
|
|
|
|
if (!DebuggerPresent)
|
|
{
|
|
SYSTEM_KERNEL_DEBUGGER_INFORMATION KdInfo = { 0 };
|
|
|
|
NtQuerySystemInformation(
|
|
SystemKernelDebuggerInformation,
|
|
&KdInfo,
|
|
sizeof(KdInfo),
|
|
NULL);
|
|
DebuggerPresent = KdInfo.KernelDebuggerEnabled;
|
|
}
|
|
|
|
if (DebuggerPresent)
|
|
{
|
|
switch ( ExceptionInfo->ExceptionRecord->ExceptionCode )
|
|
{
|
|
case STATUS_POSSIBLE_DEADLOCK:
|
|
{
|
|
PRTL_CRITICAL_SECTION CritSec;
|
|
PRTL_CRITICAL_SECTION_DEBUG CritSecDebug;
|
|
|
|
CritSec = (PRTL_CRITICAL_SECTION)
|
|
ExceptionInfo->ExceptionRecord->ExceptionInformation[ 0 ];
|
|
|
|
if ( CritSec )
|
|
{
|
|
try
|
|
{
|
|
CritSecDebug = CritSec->DebugInfo ;
|
|
|
|
if ( CritSecDebug->Type == RTL_RESOURCE_TYPE )
|
|
{
|
|
PRTL_RESOURCE Resource = (PRTL_RESOURCE) CritSec;
|
|
|
|
DbgPrint("\n\n *** Resource timeout (%p) in %ws:%s\n\n",
|
|
Resource, lpProcessName, Function );
|
|
|
|
if ( Resource->NumberOfActive < 0 )
|
|
{
|
|
DbgPrint("The resource is owned exclusively by thread %x\n",
|
|
Resource->ExclusiveOwnerThread);
|
|
}
|
|
else if ( Resource->NumberOfActive > 0 )
|
|
{
|
|
DbgPrint("The resource is owned shared by %d threads\n",
|
|
Resource->NumberOfActive);
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("The resource is unowned. This usually implies a "
|
|
"slow-moving machine due to memory pressure\n\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("\n\n *** Critical Section Timeout (%p) in %ws:%s\n\n",
|
|
CritSec, lpProcessName, Function );
|
|
|
|
if (CritSec->OwningThread != 0)
|
|
{
|
|
DbgPrint("The critical section is owned by thread %x.\n",
|
|
CritSec->OwningThread );
|
|
DbgPrint("Go determine why that thread has not released "
|
|
"the critical section.\n\n" );
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("The critical section is unowned. This "
|
|
"usually implies a slow-moving machine "
|
|
"due to memory pressure\n\n");
|
|
}
|
|
}
|
|
}
|
|
except( EXCEPTION_EXECUTE_HANDLER )
|
|
{
|
|
NOTHING ;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case STATUS_IN_PAGE_ERROR:
|
|
|
|
DbgPrint("\n\n *** Inpage error in %ws:%s\n\n", lpProcessName, Function );
|
|
DbgPrint("The instruction at %p referenced memory at %p.\n",
|
|
ExceptionInfo->ExceptionRecord->ExceptionAddress,
|
|
ExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
|
|
DbgPrint("This failed because of error %x.\n\n",
|
|
ExceptionInfo->ExceptionRecord->ExceptionInformation[2]);
|
|
|
|
|
|
switch (ExceptionInfo->ExceptionRecord->ExceptionInformation[2])
|
|
{
|
|
case STATUS_INSUFFICIENT_RESOURCES:
|
|
|
|
DbgPrint("This means the machine is out of memory. Use !vm "
|
|
"to see where all the memory is being used.\n\n");
|
|
|
|
break;
|
|
|
|
case STATUS_DEVICE_DATA_ERROR:
|
|
case STATUS_DISK_OPERATION_FAILED:
|
|
|
|
DbgPrint("This means the data could not be read, typically because "
|
|
"of a bad block on the disk. Check your hardware.\n\n");
|
|
|
|
|
|
break;
|
|
|
|
case STATUS_IO_DEVICE_ERROR:
|
|
|
|
DbgPrint("This means that the I/O device reported an I/O error. "
|
|
"Check your hardware.");
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATUS_ACCESS_VIOLATION:
|
|
|
|
DbgPrint("\n\n *** An Access Violation occurred in %ws:%s\n\n", lpProcessName, Function );
|
|
DbgPrint("The instruction at %p tried to %s ",
|
|
ExceptionInfo->ExceptionRecord->ExceptionAddress,
|
|
ExceptionInfo->ExceptionRecord->ExceptionInformation[0] ?
|
|
"write to" : "read from" );
|
|
|
|
if ( ExceptionInfo->ExceptionRecord->ExceptionInformation[1] )
|
|
{
|
|
DbgPrint("an invalid address, %p\n\n",
|
|
ExceptionInfo->ExceptionRecord->ExceptionInformation[1] );
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("a NULL pointer\n\n" );
|
|
}
|
|
|
|
break;
|
|
|
|
case STATUS_STACK_BUFFER_OVERRUN:
|
|
|
|
DbgPrint("\n\n *** A stack buffer overrun occurred in %ws:%s\n\n",
|
|
lpProcessName,
|
|
Function);
|
|
DbgPrint("This is usually the result of a memory copy to a local buffer "
|
|
"or structure where the size is not properly calculated/checked.\n");
|
|
DbgPrint("If this bug ends up in the shipping product, it could be a severe "
|
|
"security hole.\n");
|
|
DbgPrint("The stack trace should show the guilty function (the function "
|
|
"directly above __report_gsfailure).\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DbgPrint("\n\n *** Unhandled exception 0x%08lx, hit in %ws:%s\n\n", ExceptionInfo->ExceptionRecord->ExceptionCode, lpProcessName, Function);
|
|
}
|
|
|
|
DbgPrint(" *** enter .exr %p for the exception record\n",
|
|
ExceptionInfo->ExceptionRecord);
|
|
|
|
//
|
|
// STATUS_STACK_BUFFER_OVERRUN is a fake exception (since we
|
|
// can't trust exception handling once we detect a stack overrun)
|
|
// so the context record is empty.
|
|
//
|
|
|
|
if (ExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_STACK_BUFFER_OVERRUN)
|
|
{
|
|
DbgPrint(" *** enter .cxr %p for the context\n",
|
|
ExceptionInfo->ContextRecord);
|
|
}
|
|
|
|
//
|
|
// .cxr <foo> now changes the debugger state so kb
|
|
// will do the trick (vs. !kb previously)
|
|
//
|
|
|
|
DbgPrint(" *** then kb to get the faulting stack\n\n");
|
|
|
|
DbgBreakPoint();
|
|
}
|
|
|
|
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_POSSIBLE_DEADLOCK)
|
|
{
|
|
if (DebuggerPresent)
|
|
{
|
|
DbgPrint(" *** Restarting wait on critsec or resource at %p (in %ws:%s)\n\n",
|
|
ExceptionInfo->ExceptionRecord->ExceptionInformation[0],
|
|
lpProcessName,
|
|
Function);
|
|
}
|
|
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|