664 lines
20 KiB
C
664 lines
20 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
exdsptch.c
|
||
|
||
Abstract:
|
||
|
||
This module implements the dispatching of exception and the unwinding of
|
||
procedure call frames.
|
||
|
||
Author:
|
||
|
||
David N. Cutler (davec) 13-Aug-1989
|
||
|
||
Environment:
|
||
|
||
Any mode.
|
||
|
||
Revision History:
|
||
|
||
10 april 90 bryanwi
|
||
|
||
Port to the 386.
|
||
|
||
--*/
|
||
|
||
#include "ntrtlp.h"
|
||
|
||
//
|
||
// Dispatcher context structure definition.
|
||
//
|
||
|
||
typedef struct _DISPATCHER_CONTEXT {
|
||
PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
|
||
} DISPATCHER_CONTEXT;
|
||
|
||
//
|
||
// Execute handler for exception function prototype.
|
||
//
|
||
|
||
EXCEPTION_DISPOSITION
|
||
RtlpExecuteHandlerForException (
|
||
IN PEXCEPTION_RECORD ExceptionRecord,
|
||
IN PVOID EstablisherFrame,
|
||
IN OUT PCONTEXT ContextRecord,
|
||
IN OUT PVOID DispatcherContext,
|
||
IN PEXCEPTION_ROUTINE ExceptionRoutine
|
||
);
|
||
|
||
//
|
||
// Execute handler for unwind function prototype.
|
||
//
|
||
|
||
EXCEPTION_DISPOSITION
|
||
RtlpExecuteHandlerForUnwind (
|
||
IN PEXCEPTION_RECORD ExceptionRecord,
|
||
IN PVOID EstablisherFrame,
|
||
IN OUT PCONTEXT ContextRecord,
|
||
IN OUT PVOID DispatcherContext,
|
||
IN PEXCEPTION_ROUTINE ExceptionRoutine
|
||
);
|
||
|
||
|
||
|
||
BOOLEAN
|
||
RtlDispatchException (
|
||
IN PEXCEPTION_RECORD ExceptionRecord,
|
||
IN PCONTEXT ContextRecord
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function attempts to dispatch an exception to a call frame based
|
||
handler by searching backwards through the stack based call frames. The
|
||
search begins with the frame specified in the context record and continues
|
||
backward until either a handler is found that handles the exception, the
|
||
stack is found to be invalid (i.e., out of limits or unaligned), or the end
|
||
of the call hierarchy is reached.
|
||
|
||
Arguments:
|
||
|
||
ExceptionRecord - Supplies a pointer to an exception record.
|
||
|
||
ContextRecord - Supplies a pointer to a context record.
|
||
|
||
Return Value:
|
||
|
||
If the exception is handled by one of the frame based handlers, then
|
||
a value of TRUE is returned. Otherwise a value of FALSE is returned.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
DISPATCHER_CONTEXT DispatcherContext;
|
||
EXCEPTION_DISPOSITION Disposition;
|
||
PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
|
||
PEXCEPTION_REGISTRATION_RECORD NestedRegistration;
|
||
ULONG HighAddress;
|
||
ULONG HighLimit;
|
||
ULONG LowLimit;
|
||
EXCEPTION_RECORD ExceptionRecord1;
|
||
|
||
#if !defined(WX86_i386)
|
||
|
||
ULONG Index;
|
||
|
||
#endif
|
||
|
||
#ifndef NTOS_KERNEL_RUNTIME
|
||
if (RtlCallVectoredExceptionHandlers(ExceptionRecord,ContextRecord)) {
|
||
return TRUE;
|
||
}
|
||
#endif // NTOS_KERNEL_RUNTIME
|
||
|
||
#if DBG && !defined(NTOS_KERNEL_RUNTIME)
|
||
|
||
//
|
||
// Check if conditions are ripe to catch an access violation before any
|
||
// custom try/except (page heap enabled, process under debugger, etc.).
|
||
//
|
||
|
||
{
|
||
extern ULONG RtlpDebugPageHeap;
|
||
|
||
if (RtlpDebugPageHeap) {
|
||
|
||
if (ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) {
|
||
|
||
if (NtCurrentPeb()->BeingDebugged) {
|
||
|
||
if (ExceptionRecord->NumberParameters > 1) {
|
||
|
||
if (ExceptionRecord->ExceptionInformation[1] > 0x10000) {
|
||
|
||
DbgPrint ("Page heap: first chance access violation (.exr %p, .cxr %p)\n",
|
||
ExceptionRecord,
|
||
ContextRecord);
|
||
DbgBreakPoint ();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Get current stack limits.
|
||
//
|
||
|
||
RtlpGetStackLimits(&LowLimit, &HighLimit);
|
||
|
||
//
|
||
// Start with the frame specified by the context record and search
|
||
// backwards through the call frame hierarchy attempting to find an
|
||
// exception handler that will handler the exception.
|
||
//
|
||
|
||
RegistrationPointer = RtlpGetRegistrationHead();
|
||
NestedRegistration = 0;
|
||
|
||
while (RegistrationPointer != EXCEPTION_CHAIN_END) {
|
||
|
||
//
|
||
// If the call frame is not within the specified stack limits or the
|
||
// call frame is unaligned, then set the stack invalid flag in the
|
||
// exception record and return FALSE. Else check to determine if the
|
||
// frame has an exception handler.
|
||
//
|
||
|
||
HighAddress = (ULONG)RegistrationPointer +
|
||
sizeof(EXCEPTION_REGISTRATION_RECORD);
|
||
|
||
if ( ((ULONG)RegistrationPointer < LowLimit) ||
|
||
(HighAddress > HighLimit) ||
|
||
(((ULONG)RegistrationPointer & 0x3) != 0)
|
||
#if !defined(NTOS_KERNEL_RUNTIME)
|
||
||
|
||
(((ULONG)RegistrationPointer->Handler >= LowLimit) && ((ULONG)RegistrationPointer->Handler < HighLimit))
|
||
#endif
|
||
) {
|
||
|
||
#if defined(NTOS_KERNEL_RUNTIME)
|
||
|
||
//
|
||
// Allow for the possibility that the problem occured on the
|
||
// DPC stack.
|
||
//
|
||
|
||
ULONG TestAddress = (ULONG)RegistrationPointer;
|
||
|
||
if (((TestAddress & 0x3) == 0) &&
|
||
KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
||
|
||
PKPRCB Prcb = KeGetCurrentPrcb();
|
||
ULONG DpcStack = (ULONG)Prcb->DpcStack;
|
||
|
||
if ((Prcb->DpcRoutineActive) &&
|
||
(HighAddress <= DpcStack) &&
|
||
(TestAddress >= DpcStack - KERNEL_STACK_SIZE)) {
|
||
|
||
//
|
||
// This error occured on the DPC stack, switch
|
||
// stack limits to the DPC stack and restart
|
||
// the loop.
|
||
//
|
||
|
||
HighLimit = DpcStack;
|
||
LowLimit = DpcStack - KERNEL_STACK_SIZE;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
|
||
return FALSE;
|
||
}
|
||
|
||
#if !defined(WX86_i386)
|
||
|
||
//
|
||
// The handler must be executed by calling another routine
|
||
// that is written in assembler. This is required because
|
||
// up level addressing of the handler information is required
|
||
// when a nested exception is encountered.
|
||
//
|
||
|
||
if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
|
||
Index = RtlpLogExceptionHandler(
|
||
ExceptionRecord,
|
||
ContextRecord,
|
||
0,
|
||
(PULONG)RegistrationPointer,
|
||
4 * sizeof(ULONG));
|
||
// can't use sizeof(EXCEPTION_REGISTRATION_RECORD
|
||
// because we need the 2 dwords above it.
|
||
}
|
||
#endif
|
||
|
||
Disposition = RtlpExecuteHandlerForException(
|
||
ExceptionRecord,
|
||
(PVOID)RegistrationPointer,
|
||
ContextRecord,
|
||
(PVOID)&DispatcherContext,
|
||
(PEXCEPTION_ROUTINE)RegistrationPointer->Handler);
|
||
|
||
#if !defined(WX86_i386)
|
||
if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
|
||
RtlpLogLastExceptionDisposition(Index, Disposition);
|
||
}
|
||
#endif
|
||
|
||
|
||
//
|
||
// If the current scan is within a nested context and the frame
|
||
// just examined is the end of the context region, then clear
|
||
// the nested context frame and the nested exception in the
|
||
// exception flags.
|
||
//
|
||
|
||
if (NestedRegistration == RegistrationPointer) {
|
||
ExceptionRecord->ExceptionFlags &= (~EXCEPTION_NESTED_CALL);
|
||
NestedRegistration = 0;
|
||
}
|
||
|
||
//
|
||
// Case on the handler disposition.
|
||
//
|
||
|
||
switch (Disposition) {
|
||
|
||
//
|
||
// The disposition is to continue execution. If the
|
||
// exception is not continuable, then raise the exception
|
||
// STATUS_NONCONTINUABLE_EXCEPTION. Otherwise return
|
||
// TRUE.
|
||
//
|
||
|
||
case ExceptionContinueExecution :
|
||
if ((ExceptionRecord->ExceptionFlags &
|
||
EXCEPTION_NONCONTINUABLE) != 0) {
|
||
ExceptionRecord1.ExceptionCode =
|
||
STATUS_NONCONTINUABLE_EXCEPTION;
|
||
ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
|
||
ExceptionRecord1.ExceptionRecord = ExceptionRecord;
|
||
ExceptionRecord1.NumberParameters = 0;
|
||
RtlRaiseException(&ExceptionRecord1);
|
||
} else {
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// The disposition is to continue the search. Get next
|
||
// frame address and continue the search.
|
||
//
|
||
|
||
case ExceptionContinueSearch :
|
||
break;
|
||
|
||
//
|
||
// The disposition is nested exception. Set the nested
|
||
// context frame to the establisher frame address and set
|
||
// nested exception in the exception flags.
|
||
//
|
||
|
||
case ExceptionNestedException :
|
||
ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
|
||
if (DispatcherContext.RegistrationPointer > NestedRegistration) {
|
||
NestedRegistration = DispatcherContext.RegistrationPointer;
|
||
}
|
||
break;
|
||
|
||
//
|
||
// All other disposition values are invalid. Raise
|
||
// invalid disposition exception.
|
||
//
|
||
|
||
default :
|
||
ExceptionRecord1.ExceptionCode = STATUS_INVALID_DISPOSITION;
|
||
ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
|
||
ExceptionRecord1.ExceptionRecord = ExceptionRecord;
|
||
ExceptionRecord1.NumberParameters = 0;
|
||
RtlRaiseException(&ExceptionRecord1);
|
||
break;
|
||
}
|
||
|
||
//
|
||
// If chain goes in wrong direction or loops, report an
|
||
// invalid exception stack, otherwise go on to the next one.
|
||
//
|
||
|
||
RegistrationPointer = RegistrationPointer->Next;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
#ifdef _X86_
|
||
#pragma optimize("y", off) // RtlCaptureContext needs EBP to be correct
|
||
#endif
|
||
|
||
VOID
|
||
RtlUnwind (
|
||
IN PVOID TargetFrame OPTIONAL,
|
||
IN PVOID TargetIp OPTIONAL,
|
||
IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
|
||
IN PVOID ReturnValue
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function initiates an unwind of procedure call frames. The machine
|
||
state at the time of the call to unwind is captured in a context record
|
||
and the unwinding flag is set in the exception flags of the exception
|
||
record. If the TargetFrame parameter is not specified, then the exit unwind
|
||
flag is also set in the exception flags of the exception record. A backward
|
||
walk through the procedure call frames is then performed to find the target
|
||
of the unwind operation.
|
||
|
||
N.B. The captured context passed to unwinding handlers will not be
|
||
a completely accurate context set for the 386. This is because
|
||
there isn't a standard stack frame in which registers are stored.
|
||
|
||
Only the integer registers are affected. The segement and
|
||
control registers (ebp, esp) will have correct values for
|
||
the flat 32 bit environment.
|
||
|
||
N.B. If you change the number of arguments, make sure you change the
|
||
adjustment of ESP after the call to RtlpCaptureContext (for
|
||
STDCALL calling convention)
|
||
|
||
Arguments:
|
||
|
||
TargetFrame - Supplies an optional pointer to the call frame that is the
|
||
target of the unwind. If this parameter is not specified, then an exit
|
||
unwind is performed.
|
||
|
||
TargetIp - Supplies an optional instruction address that specifies the
|
||
continuation address of the unwind. This address is ignored if the
|
||
target frame parameter is not specified.
|
||
|
||
ExceptionRecord - Supplies an optional pointer to an exception record.
|
||
|
||
ReturnValue - Supplies a value that is to be placed in the integer
|
||
function return register just before continuing execution.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PCONTEXT ContextRecord;
|
||
CONTEXT ContextRecord1;
|
||
DISPATCHER_CONTEXT DispatcherContext;
|
||
EXCEPTION_DISPOSITION Disposition;
|
||
PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
|
||
PEXCEPTION_REGISTRATION_RECORD PriorPointer;
|
||
ULONG HighAddress;
|
||
ULONG HighLimit;
|
||
ULONG LowLimit;
|
||
EXCEPTION_RECORD ExceptionRecord1;
|
||
EXCEPTION_RECORD ExceptionRecord2;
|
||
|
||
//
|
||
// Get current stack limits.
|
||
//
|
||
|
||
RtlpGetStackLimits(&LowLimit, &HighLimit);
|
||
|
||
//
|
||
// If an exception record is not specified, then build a local exception
|
||
// record for use in calling exception handlers during the unwind operation.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(ExceptionRecord) == FALSE) {
|
||
ExceptionRecord = &ExceptionRecord1;
|
||
ExceptionRecord1.ExceptionCode = STATUS_UNWIND;
|
||
ExceptionRecord1.ExceptionFlags = 0;
|
||
ExceptionRecord1.ExceptionRecord = NULL;
|
||
ExceptionRecord1.ExceptionAddress = _ReturnAddress();
|
||
ExceptionRecord1.NumberParameters = 0;
|
||
}
|
||
|
||
//
|
||
// If the target frame of the unwind is specified, then set EXCEPTION_UNWINDING
|
||
// flag in the exception flags. Otherwise set both EXCEPTION_EXIT_UNWIND and
|
||
// EXCEPTION_UNWINDING flags in the exception flags.
|
||
//
|
||
|
||
if (ARGUMENT_PRESENT(TargetFrame) == TRUE) {
|
||
ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING;
|
||
} else {
|
||
ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING |
|
||
EXCEPTION_EXIT_UNWIND);
|
||
}
|
||
|
||
//
|
||
// Capture the context.
|
||
//
|
||
|
||
ContextRecord = &ContextRecord1;
|
||
ContextRecord1.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_SEGMENTS;
|
||
RtlpCaptureContext(ContextRecord);
|
||
|
||
#ifdef STD_CALL
|
||
//
|
||
// Adjust captured context to pop our arguments off the stack
|
||
//
|
||
ContextRecord->Esp += sizeof(TargetFrame) +
|
||
sizeof(TargetIp) +
|
||
sizeof(ExceptionRecord) +
|
||
sizeof(ReturnValue);
|
||
#endif
|
||
ContextRecord->Eax = (ULONG)ReturnValue;
|
||
|
||
//
|
||
// Scan backward through the call frame hierarchy, calling exception
|
||
// handlers as they are encountered, until the target frame of the unwind
|
||
// is reached.
|
||
//
|
||
|
||
RegistrationPointer = RtlpGetRegistrationHead();
|
||
while (RegistrationPointer != EXCEPTION_CHAIN_END) {
|
||
|
||
//
|
||
// If this is the target of the unwind, then continue execution
|
||
// by calling the continue system service.
|
||
//
|
||
|
||
if ((ULONG)RegistrationPointer == (ULONG)TargetFrame) {
|
||
ZwContinue(ContextRecord, FALSE);
|
||
|
||
//
|
||
// If the target frame is lower in the stack than the current frame,
|
||
// then raise STATUS_INVALID_UNWIND exception.
|
||
//
|
||
|
||
} else if ( (ARGUMENT_PRESENT(TargetFrame) == TRUE) &&
|
||
((ULONG)TargetFrame < (ULONG)RegistrationPointer) ) {
|
||
ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;
|
||
ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
|
||
ExceptionRecord2.ExceptionRecord = ExceptionRecord;
|
||
ExceptionRecord2.NumberParameters = 0;
|
||
RtlRaiseException(&ExceptionRecord2);
|
||
}
|
||
|
||
//
|
||
// If the call frame is not within the specified stack limits or the
|
||
// call frame is unaligned, then raise the exception STATUS_BAD_STACK.
|
||
// Else restore the state from the specified frame to the context
|
||
// record.
|
||
//
|
||
|
||
HighAddress = (ULONG)RegistrationPointer +
|
||
sizeof(EXCEPTION_REGISTRATION_RECORD);
|
||
|
||
if ( ((ULONG)RegistrationPointer < LowLimit) ||
|
||
(HighAddress > HighLimit) ||
|
||
(((ULONG)RegistrationPointer & 0x3) != 0)
|
||
#if !defined(NTOS_KERNEL_RUNTIME)
|
||
||
|
||
(((ULONG)RegistrationPointer->Handler >= LowLimit) && ((ULONG)RegistrationPointer->Handler < HighLimit))
|
||
#endif
|
||
) {
|
||
|
||
#if defined(NTOS_KERNEL_RUNTIME)
|
||
|
||
//
|
||
// Allow for the possibility that the problem occured on the
|
||
// DPC stack.
|
||
//
|
||
|
||
ULONG TestAddress = (ULONG)RegistrationPointer;
|
||
|
||
if (((TestAddress & 0x3) == 0) &&
|
||
KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
||
|
||
PKPRCB Prcb = KeGetCurrentPrcb();
|
||
ULONG DpcStack = (ULONG)Prcb->DpcStack;
|
||
|
||
if ((Prcb->DpcRoutineActive) &&
|
||
(HighAddress <= DpcStack) &&
|
||
(TestAddress >= DpcStack - KERNEL_STACK_SIZE)) {
|
||
|
||
//
|
||
// This error occured on the DPC stack, switch
|
||
// stack limits to the DPC stack and restart
|
||
// the loop.
|
||
//
|
||
|
||
HighLimit = DpcStack;
|
||
LowLimit = DpcStack - KERNEL_STACK_SIZE;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK;
|
||
ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
|
||
ExceptionRecord2.ExceptionRecord = ExceptionRecord;
|
||
ExceptionRecord2.NumberParameters = 0;
|
||
RtlRaiseException(&ExceptionRecord2);
|
||
} else {
|
||
|
||
//
|
||
// The handler must be executed by calling another routine
|
||
// that is written in assembler. This is required because
|
||
// up level addressing of the handler information is required
|
||
// when a collided unwind is encountered.
|
||
//
|
||
|
||
Disposition = RtlpExecuteHandlerForUnwind(
|
||
ExceptionRecord,
|
||
(PVOID)RegistrationPointer,
|
||
ContextRecord,
|
||
(PVOID)&DispatcherContext,
|
||
RegistrationPointer->Handler);
|
||
|
||
//
|
||
// Case on the handler disposition.
|
||
//
|
||
|
||
switch (Disposition) {
|
||
|
||
//
|
||
// The disposition is to continue the search. Get next
|
||
// frame address and continue the search.
|
||
//
|
||
|
||
case ExceptionContinueSearch :
|
||
break;
|
||
|
||
//
|
||
// The disposition is colided unwind. Maximize the target
|
||
// of the unwind and change the context record pointer.
|
||
//
|
||
|
||
case ExceptionCollidedUnwind :
|
||
|
||
//
|
||
// Pick up the registration pointer that was active at
|
||
// the time of the unwind, and simply continue.
|
||
//
|
||
|
||
RegistrationPointer = DispatcherContext.RegistrationPointer;
|
||
break;
|
||
|
||
|
||
//
|
||
// All other disposition values are invalid. Raise
|
||
// invalid disposition exception.
|
||
//
|
||
|
||
default :
|
||
ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
|
||
ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
|
||
ExceptionRecord2.ExceptionRecord = ExceptionRecord;
|
||
ExceptionRecord2.NumberParameters = 0;
|
||
RtlRaiseException(&ExceptionRecord2);
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Step to next registration record
|
||
//
|
||
|
||
PriorPointer = RegistrationPointer;
|
||
RegistrationPointer = RegistrationPointer->Next;
|
||
|
||
//
|
||
// Unlink the unwind handler, since it's been called.
|
||
//
|
||
|
||
RtlpUnlinkHandler(PriorPointer);
|
||
|
||
//
|
||
// If chain goes in wrong direction or loops, raise an
|
||
// exception.
|
||
//
|
||
|
||
}
|
||
}
|
||
|
||
if (TargetFrame == EXCEPTION_CHAIN_END) {
|
||
|
||
//
|
||
// Caller simply wants to unwind all exception records.
|
||
// This differs from an exit_unwind in that no "exit" is desired.
|
||
// Do a normal continue, since we've effectively found the
|
||
// "target" the caller wanted.
|
||
//
|
||
|
||
ZwContinue(ContextRecord, FALSE);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Either (1) a real exit unwind was performed, or (2) the
|
||
// specified TargetFrame is not present in the exception handler
|
||
// list. In either case, give debugger and subsystem a chance
|
||
// to see the unwind.
|
||
//
|
||
|
||
ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
|
||
|
||
}
|
||
return;
|
||
}
|
||
|
||
#ifdef _X86_
|
||
#pragma optimize("", on)
|
||
#endif
|