Windows2000/private/ntos/rtl/alpha/exdsptch.c
2020-09-30 17:12:32 +02:00

3044 lines
107 KiB
C

/*++
Copyright (c) 1990 Microsoft Corporation
Copyright (c) 1993 Digital Equipment Corporation
Module Name:
exdsptch.c
Abstract:
This module implements the dispatching of exceptions and the unwinding of procedure call frames.
Author:
David N. Cutler (davec) 11-Sep-1990
Environment:
Any mode.
Revision History:
Thomas Van Baak (tvb) 13-May-1992
Adapted for Alpha AXP.
Florence Lee (Digital) 10-Apr-1997
Add support for dynamic function tables (user mode only)
1) Modify RtlLookupFunctionEntry to search dynamic function tables
2) Add RtlAddFunctionTable: Add an array of RUNTIME_FUNCTION entries to the dynamic function table list.
3) Add RtlDeleteFunctionTable: Remove dynamic from table the dynamic function table list.
Monty VanderBilt (Digital) 16-Jun-1997
1) Use macros defined in ntalpha.h to access RUNTIME_FUNCTION fields without low order bits used for other information
2) Modify RtlLookupFunctionEntry() and RtlVirtualUnwind() to handle the variations of secondary function entry types.
Monty VanderBilt (Compaq) 10-Aug-1999
ECO (Engineering Change Order) numbers refer to Alpha NT calling standard changes
ECO 11: Support for compiler optimizations: tail calls, floating return sequences, shrinkwrap.
ECO 12: Minor change to ECO 11 moving StackAllocation field from the 3rd to 4th longword in the secondary function entry.
ECO 14: Fixed return function entries to support (among other uses) exception handling in instrumentation code.
--*/
#include "ntrtlp.h"
int __cdecl sprintf(char *, const char *, ...);
// Define local macros.
// Raise noncontinuable exception with associated exception record.
#define RAISE_EXCEPTION(Status, ExceptionRecordt) { \
EXCEPTION_RECORD ExceptionRecordn; \
\
ExceptionRecordn.ExceptionCode = Status; \
ExceptionRecordn.ExceptionFlags = EXCEPTION_NONCONTINUABLE; \
ExceptionRecordn.ExceptionRecord = ExceptionRecordt; \
ExceptionRecordn.NumberParameters = 0; \
RtlRaiseException(&ExceptionRecordn); \
}
// Determine if ExceptionHandler is defined
#define IS_HANDLER_DEFINED(FunctionEntry) \
(RF_EXCEPTION_HANDLER(FunctionEntry) != 0)
#if DBG
// Maintain a short history of PC's for malformed function table errors.
#define PC_HISTORY_DEPTH 4
// Definition of global flag to debug/validate exception handling.
// See ntrtlalp.h for the bit definitions in this flag word.
ULONG RtlDebugFlags = 0;
void ShowRuntimeFunction(PRUNTIME_FUNCTION FunctionEntry, PSTR Label);
#endif
#define Virtual VirtualFramePointer
#define Real RealFramePointer
// Define private function prototypes.
VOID RtlpRaiseException (IN PEXCEPTION_RECORD ExceptionRecord);
VOID RtlpRaiseStatus (IN NTSTATUS Status);
ULONG_PTR RtlpVirtualUnwind (IN ULONG_PTR ControlPc, IN PRUNTIME_FUNCTION FunctionEntry, IN PCONTEXT ContextRecord, OUT PBOOLEAN InFunction, OUT PFRAME_POINTERS EstablisherFrame);
#if !defined(NTOS_KERNEL_RUNTIME)
// List head for DYNAMIC_FUNCTION_TABLE entries.
LIST_ENTRY DynamicFunctionTable;
PRUNTIME_FUNCTION RtlLookupDynamicFunctionEntry(IN ULONG_PTR ControlPc);
#endif
PRUNTIME_FUNCTION RtlLookupStaticFunctionEntry(IN ULONG_PTR ControlPc, OUT PBOOLEAN InImage);
VOID RtlGetUnwindFunctionEntry(IN ULONG_PTR ControlPc, IN PRUNTIME_FUNCTION FunctionEntry, OUT PRUNTIME_FUNCTION UnwindFunctionEntry, OUT PULONG StackAdjust, OUT PULONG_PTR FixedReturn);
BOOLEAN RtlDispatchException (IN PEXCEPTION_RECORD ExceptionRecord, IN PCONTEXT ContextRecord)
/*++
Routine Description:
This function attempts to dispatch an exception to a 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.
As each frame is encountered, the PC where control left the corresponding
function is determined and used to lookup exception handler information
in the runtime function table built by the linker. If the respective
routine has an exception handler, then the handler is called. If the
handler does not handle the exception, then the prologue of the routine
is executed backwards to "unwind" the effect of the prologue and then
the next frame is examined.
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.
--*/
{
CONTEXT ContextRecord1;
ULONG_PTR ControlPc;
#if DBG
ULONG_PTR ControlPcHistory[PC_HISTORY_DEPTH];
ULONG ControlPcHistoryIndex = 0;
#endif
DISPATCHER_CONTEXT DispatcherContext;
EXCEPTION_DISPOSITION Disposition;
FRAME_POINTERS EstablisherFrame;
ULONG ExceptionFlags;
#if DBG
LONG FrameDepth = 0;
#endif
PRUNTIME_FUNCTION FunctionEntry;
ULONG_PTR HighLimit;
BOOLEAN InFunction;
ULONG_PTR LowLimit;
ULONG_PTR NestedFrame;
ULONG_PTR NextPc;
// Get current stack limits, copy the context record, get the initial
// PC value, capture the exception flags, and set the nested exception
// frame pointer.
// The initial PC value is obtained from ExceptionAddress rather than
// from ContextRecord.Fir since some Alpha exceptions are asynchronous.
RtlpGetStackLimits(&LowLimit, &HighLimit);
RtlMoveMemory(&ContextRecord1, ContextRecord, sizeof(CONTEXT));
ControlPc = (ULONG_PTR)ExceptionRecord->ExceptionAddress;
#if DBG
if ((ULONG_PTR)ExceptionRecord->ExceptionAddress != (ULONG_PTR)ContextRecord->Fir) {
DbgPrint("RtlDispatchException: ExceptionAddress = %p, Fir = %p\n",
ExceptionRecord->ExceptionAddress, (ULONG_PTR)ContextRecord->Fir);
}
#endif
ExceptionFlags = ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE;
NestedFrame = 0;
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION) {
DbgPrint("\nRtlDispatchException(ExceptionRecord = %p, ContextRecord = %p)\n",
ExceptionRecord, ContextRecord);
DbgPrint("RtlDispatchException: ControlPc = %p, ExceptionRecord->ExceptionCode = %lx\n",
ControlPc, ExceptionRecord->ExceptionCode);
}
#endif
// 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 handle the exception.
do {
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION_DETAIL) {
DbgPrint("RtlDispatchException: Loop: FrameDepth = %d, sp = %p, ControlPc = %p\n",
FrameDepth, (ULONG_PTR)ContextRecord1.IntSp, ControlPc);
FrameDepth -= 1;
}
#endif
// Lookup the function table entry using the point at which control
// left the procedure.
FunctionEntry = RtlLookupFunctionEntry(ControlPc);
// If there is a function table entry for the routine, then virtually
// unwind to the caller of the current routine to obtain the virtual
// frame pointer of the establisher and check if there is an exception
// handler for the frame.
if (FunctionEntry != NULL) {
NextPc = RtlVirtualUnwind(ControlPc,
FunctionEntry,
&ContextRecord1,
&InFunction,
&EstablisherFrame,
NULL);
// If the virtual frame pointer is not within the specified stack
// limits or the virtual frame pointer is unaligned, then set the
// stack invalid flag in the exception record and return exception
// not handled. Otherwise, check if the current routine has an
// exception handler.
if ((EstablisherFrame.Virtual < LowLimit) ||
(EstablisherFrame.Virtual > HighLimit) ||
((EstablisherFrame.Virtual & 0xF) != 0)) {
#if DBG
DbgPrint("\n****** Warning - stack invalid (exception).\n");
DbgPrint(" EstablisherFrame.Virtual = %p, EstablisherFrame.Real = %p\n",
EstablisherFrame.Virtual, EstablisherFrame.Real);
DbgPrint(" LowLimit = %p, HighLimit = %p\n",
LowLimit, HighLimit);
DbgPrint(" NextPc = %p, ControlPc = %p\n",
NextPc, ControlPc);
DbgPrint(" Now setting EXCEPTION_STACK_INVALID flag.\n");
#endif
ExceptionFlags |= EXCEPTION_STACK_INVALID;
break;
} else if (IS_HANDLER_DEFINED(FunctionEntry) && InFunction) {
ULONG Index;
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION_DETAIL) {
DbgPrint("RtlDispatchException: ExceptionHandler = %p, HandlerData = %p\n",
FunctionEntry->ExceptionHandler, FunctionEntry->HandlerData);
}
#endif
// The frame has an exception handler. 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.
DispatcherContext.ControlPc = ControlPc;
DispatcherContext.FunctionEntry = FunctionEntry;
DispatcherContext.EstablisherFrame = EstablisherFrame.Virtual;
DispatcherContext.ContextRecord = ContextRecord;
ExceptionRecord->ExceptionFlags = ExceptionFlags;
if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
Index = RtlpLogExceptionHandler(
ExceptionRecord,
ContextRecord,
ControlPc,
FunctionEntry,
sizeof(RUNTIME_FUNCTION));
}
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION_DETAIL) {
DbgPrint("RtlDispatchException: calling RtlpExecuteHandlerForException, ControlPc = %lx Handler = %lx\n",
ControlPc, RF_EXCEPTION_HANDLER(FunctionEntry) );
}
#endif
Disposition =
RtlpExecuteHandlerForException(ExceptionRecord,
EstablisherFrame.Virtual,
ContextRecord,
&DispatcherContext,
RF_EXCEPTION_HANDLER(FunctionEntry));
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION_DETAIL) {
DbgPrint("RtlDispatchException: RtlpExecuteHandlerForException returned Disposition = %lx\n", Disposition);
}
#endif
if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
RtlpLogLastExceptionDisposition(Index, Disposition);
}
ExceptionFlags |=
(ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE);
// If the current scan is within a nested context and the frame
// just examined is the end of the nested region, then clear
// the nested context frame and the nested exception flag in
// the exception flags.
if (NestedFrame == EstablisherFrame.Virtual) {
ExceptionFlags &= (~EXCEPTION_NESTED_CALL);
NestedFrame = 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 exception handled.
case ExceptionContinueExecution :
if ((ExceptionFlags & EXCEPTION_NONCONTINUABLE) != 0) {
RAISE_EXCEPTION(STATUS_NONCONTINUABLE_EXCEPTION, ExceptionRecord);
} else {
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION) {
DbgPrint("RtlDispatchException: returning TRUE\n");
}
#endif
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 the nested exception flag in the
// exception flags.
case ExceptionNestedException :
ExceptionFlags |= EXCEPTION_NESTED_CALL;
if (DispatcherContext.EstablisherFrame > NestedFrame) {
NestedFrame = DispatcherContext.EstablisherFrame;
}
break;
// All other disposition values are invalid.
// Raise invalid disposition exception.
default :
RAISE_EXCEPTION(STATUS_INVALID_DISPOSITION, ExceptionRecord);
}
}
} else {
// Set point at which control left the previous routine.
NextPc = (ULONG_PTR)ContextRecord1.IntRa - 4;
// If the next control PC is the same as the old control PC, then
// the function table is not correctly formed.
if (NextPc == ControlPc) {
#if DBG
ULONG Count;
DbgPrint("\n****** Warning - malformed function table (exception).\n");
DbgPrint("ControlPc = %p, NextPc %p", NextPc, ControlPc);
for (Count = 0; Count < PC_HISTORY_DEPTH; Count += 1) {
if (ControlPcHistoryIndex > 0) {
ControlPcHistoryIndex -= 1;
ControlPc = ControlPcHistory[ControlPcHistoryIndex % PC_HISTORY_DEPTH];
DbgPrint(", %p", ControlPc);
}
}
DbgPrint(ControlPcHistoryIndex == 0 ? ".\n" : ", ...\n");
#endif
break;
}
}
// Set point at which control left the previous routine.
#if DBG
ControlPcHistory[ControlPcHistoryIndex % PC_HISTORY_DEPTH] = ControlPc;
ControlPcHistoryIndex += 1;
#endif
ControlPc = NextPc;
} while ((ULONG_PTR)ContextRecord1.IntSp < HighLimit);
// Set final exception flags and return exception not handled.
ExceptionRecord->ExceptionFlags = ExceptionFlags;
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION) {
DbgPrint("RtlDispatchException: returning FALSE\n");
}
#endif
return FALSE;
}
PRUNTIME_FUNCTION RtlLookupFunctionEntry (IN ULONG_PTR ControlPc)
/*++
Routine Description:
This function searches the currently active function tables (static and dynamic) for an entry that corresponds to the specified PC value.
If the entry is for a secondary function entry then the primary function table entry is obtained via an indirection through the PrologEndAddress.
RtlLookupDirectFunctionEntry() performs the same function without the indirection to the primary function table.
Because RtlLookupFunctionEntry() always returns the primary function entry, it has the property such that
RtlLookupFunctionEntry(Pc1) == RtlLookupFunctionEntry(Pc2)
implies
Pc1 and Pc2 are in the same procedure.
Arguments:
ControlPc - Supplies the address of an instruction within the specified
function.
Return Value:
If there is no entry in the function table for the specified PC, then NULL is returned.
Otherwise, the address of the primary function table entry that corresponds to the specified PC is returned.
--*/
{
PRUNTIME_FUNCTION FunctionEntry;
// Look for a static or dynamic function entry
FunctionEntry = RtlLookupDirectFunctionEntry( ControlPc );
if (FunctionEntry != NULL) {
// The capability exists for more than one function entry to map to the same function. This permits a function to
// have discontiguous code segments described by separate function table entries. If the ending prologue address
// is not within the limits of the begining and ending address of the function able entry, then the prologue
// ending address is the address of the primary function table entry that accurately describes the ending prologue address.
if ((RF_PROLOG_END_ADDRESS(FunctionEntry) < RF_BEGIN_ADDRESS(FunctionEntry)) || (RF_PROLOG_END_ADDRESS(FunctionEntry) >= RF_END_ADDRESS(FunctionEntry))) {
#if DBG
ShowRuntimeFunction(FunctionEntry, "RtlLookupFunctionEntry: secondary entry" );
#endif
// Officially the PrologEndAddress field in secondary function entries
// doesn't have the exception mode bits there have been some versions
// of alpha tools that put them there. Strip them off to be safe.
FunctionEntry = (PRUNTIME_FUNCTION)RF_PROLOG_END_ADDRESS(FunctionEntry);
} else if (RF_IS_FIXED_RETURN(FunctionEntry)) {
ULONG_PTR FixedReturn = RF_FIXED_RETURN(FunctionEntry);
#if DBG
ShowRuntimeFunction(FunctionEntry, "LookupFunctionEntry: fixed return entry");
#endif
// Recursively call LookupFunctionEntry to ensure we get a primary function entry here.
// Check for incorrectly formed function entry where the fixed return points to itself.
if ((FixedReturn < RF_BEGIN_ADDRESS(FunctionEntry)) ||
(FixedReturn >= RF_END_ADDRESS(FunctionEntry))) {
FunctionEntry = RtlLookupFunctionEntry( RF_FIXED_RETURN(FunctionEntry) );
}
}
#if DBG
else {
ShowRuntimeFunction(FunctionEntry, "RtlLookupFunctionEntry: primary entry" );
}
#endif
}
#if DBG
if (RtlDebugFlags & RTL_DBG_FUNCTION_ENTRY) {
DbgPrint("RtlLookupFunctionEntry: returning FunctionEntry = %lx\n", FunctionEntry);
}
#endif
return FunctionEntry;
}
VOID RtlRaiseException (IN PEXCEPTION_RECORD ExceptionRecord)
/*++
Routine Description:
This function raises a software exception by building a context record and calling the raise exception system service.
N.B. This routine is a shell routine that simply calls another routine
to do the real work. The reason this is done is to avoid a problem
in try/finally scopes where the last statement in the scope is a call to raise an exception.
Arguments:
ExceptionRecord - Supplies a pointer to an exception record.
Return Value:
None.
--*/
{
#if DBG
if (RtlDebugFlags & RTL_DBG_RAISE_EXCEPTION) {
DbgPrint("RtlRaiseException(ExceptionRecord = %p) Status = %lx\n", ExceptionRecord, ExceptionRecord->ExceptionCode);
}
#endif
RtlpRaiseException(ExceptionRecord);
return;
}
VOID RtlpRaiseException (IN PEXCEPTION_RECORD ExceptionRecord)
/*++
Routine Description:
This function raises a software exception by building a context record and calling the raise exception system service.
Arguments:
ExceptionRecord - Supplies a pointer to an exception record.
Return Value:
None.
--*/
{
ULONG_PTR ControlPc;
CONTEXT ContextRecord;
FRAME_POINTERS EstablisherFrame;
PRUNTIME_FUNCTION FunctionEntry;
BOOLEAN InFunction;
ULONG_PTR NextPc;
NTSTATUS Status;
// Capture the current context, virtually unwind to the caller of this
// routine, set the fault instruction address to that of the caller, and
// call the raise exception system service.
RtlCaptureContext(&ContextRecord);
ControlPc = (ULONG_PTR)ContextRecord.IntRa - 4;
FunctionEntry = RtlLookupFunctionEntry(ControlPc);
NextPc = RtlVirtualUnwind(ControlPc,
FunctionEntry,
&ContextRecord,
&InFunction,
&EstablisherFrame,
NULL);
ContextRecord.Fir = (ULONGLONG)(LONG_PTR)NextPc + 4;
ExceptionRecord->ExceptionAddress = (PVOID)ContextRecord.Fir;
Status = ZwRaiseException(ExceptionRecord, &ContextRecord, TRUE);
// There should never be a return from this system service unless
// there is a problem with the argument list itself. Raise another
// exception specifying the status value returned.
RtlRaiseStatus(Status);
return;
}
VOID RtlRaiseStatus (IN NTSTATUS Status)
/*++
Routine Description:
This function raises an exception with the specified status value.
The exception is marked as noncontinuable with no parameters.
N.B. This routine is a shell routine that simply calls another routine
to do the real work. The reason this is done is to avoid a problem
in try/finally scopes where the last statement in the scope is a call to raise an exception.
Arguments:
Status - Supplies the status value to be used as the exception code for the exception that is to be raised.
--*/
{
#if DBG
if (RtlDebugFlags & RTL_DBG_RAISE_EXCEPTION) {
DbgPrint("RtlRaiseStatus(Status = %lx)\n", Status);
}
#endif
RtlpRaiseStatus(Status);
return;
}
VOID
RtlpRaiseStatus (
IN NTSTATUS Status
)
/*++
Routine Description:
This function raises an exception with the specified status value. The
exception is marked as noncontinuable with no parameters.
Arguments:
Status - Supplies the status value to be used as the exception code
for the exception that is to be raised.
Return Value:
None.
--*/
{
ULONG_PTR ControlPc;
CONTEXT ContextRecord;
FRAME_POINTERS EstablisherFrame;
EXCEPTION_RECORD ExceptionRecord;
PRUNTIME_FUNCTION FunctionEntry;
BOOLEAN InFunction;
ULONG_PTR NextPc;
// Construct an exception record.
ExceptionRecord.ExceptionCode = Status;
ExceptionRecord.ExceptionRecord = (PEXCEPTION_RECORD)NULL;
ExceptionRecord.NumberParameters = 0;
ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
// Capture the current context, virtually unwind to the caller of this
// routine, set the fault instruction address to that of the caller, and
// call the raise exception system service.
RtlCaptureContext(&ContextRecord);
ControlPc = (ULONG_PTR)ContextRecord.IntRa - 4;
FunctionEntry = RtlLookupFunctionEntry(ControlPc);
NextPc = RtlVirtualUnwind(ControlPc,
FunctionEntry,
&ContextRecord,
&InFunction,
&EstablisherFrame,
NULL);
ContextRecord.Fir = (ULONGLONG)(LONG_PTR)NextPc + 4;
ExceptionRecord.ExceptionAddress = (PVOID)ContextRecord.Fir;
Status = ZwRaiseException(&ExceptionRecord, &ContextRecord, TRUE);
// There should never be a return from this system service unless
// there is a problem with the argument list itself. Raise another
// exception specifying the status value returned.
RtlRaiseStatus(Status);
return;
}
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
scan through the procedure call frames is then performed to find the target
of the unwind operation.
As each frame is encounter, the PC where control left the corresponding
function is determined and used to lookup exception handler information
in the runtime function table built by the linker. If the respective
routine has an exception handler, then the handler is called.
N.B. This routine is provided for backward compatibility with release 1.
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.
--*/
{
CONTEXT ContextRecord;
// Call real unwind routine specifying a context record as an
// extra argument.
RtlUnwind2(TargetFrame,
TargetIp,
ExceptionRecord,
ReturnValue,
&ContextRecord);
return;
}
VOID
RtlUnwind2 (
IN PVOID TargetFrame OPTIONAL,
IN PVOID TargetIp OPTIONAL,
IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
IN PVOID ReturnValue,
IN PCONTEXT ContextRecord
)
/*++
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
scan through the procedure call frames is then performed to find the target
of the unwind operation.
As each frame is encounter, the PC where control left the corresponding
function is determined and used to lookup exception handler information
in the runtime function table built by the linker. If the respective
routine has an exception handler, then the handler is called.
N.B. This routine is provided for backward compatibility with release 1.
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.
--*/
{
ULONG_PTR ControlPc;
#if DBG
ULONG_PTR ControlPcHistory[PC_HISTORY_DEPTH];
ULONG ControlPcHistoryIndex = 0;
#endif
DISPATCHER_CONTEXT DispatcherContext;
EXCEPTION_DISPOSITION Disposition;
FRAME_POINTERS EstablisherFrame;
ULONG ExceptionFlags;
EXCEPTION_RECORD ExceptionRecord1;
#if DBG
LONG FrameDepth = 0;
#endif
PRUNTIME_FUNCTION FunctionEntry;
ULONG_PTR HighLimit;
BOOLEAN InFunction;
ULONG_PTR LowLimit;
ULONG_PTR NextPc;
#if DBG
if (RtlDebugFlags & RTL_DBG_UNWIND) {
DbgPrint("\nRtlUnwind(TargetFrame = %p, TargetIp = %p,, ReturnValue = %lx)\n",
TargetFrame, TargetIp, ReturnValue);
}
#endif
// Get current stack limits, capture the current context, virtually
// unwind to the caller of this routine, get the initial PC value, and
// set the unwind target address.
RtlpGetStackLimits(&LowLimit, &HighLimit);
RtlCaptureContext(ContextRecord);
ControlPc = (ULONG_PTR)ContextRecord->IntRa - 4;
FunctionEntry = RtlLookupFunctionEntry(ControlPc);
NextPc = RtlVirtualUnwind(ControlPc,
FunctionEntry,
ContextRecord,
&InFunction,
&EstablisherFrame,
NULL);
ControlPc = NextPc;
ContextRecord->Fir = (ULONGLONG)(LONG_PTR)TargetIp;
// 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.ExceptionRecord = NULL;
ExceptionRecord1.ExceptionAddress = (PVOID)ControlPc;
ExceptionRecord1.NumberParameters = 0;
}
// If the target frame of the unwind is specified, then a normal unwind
// is being performed. Otherwise, an exit unwind is being performed.
ExceptionFlags = EXCEPTION_UNWINDING;
if (ARGUMENT_PRESENT(TargetFrame) == FALSE) {
ExceptionRecord->ExceptionFlags |= EXCEPTION_EXIT_UNWIND;
}
// Scan backward through the call frame hierarchy and call exception
// handlers until the target frame of the unwind is reached.
do {
#if DBG
if (RtlDebugFlags & RTL_DBG_UNWIND_DETAIL) {
DbgPrint("RtlUnwind: Loop: FrameDepth = %d, sp = %p, ControlPc = %p\n",
FrameDepth, (ULONG_PTR)ContextRecord->IntSp, ControlPc);
FrameDepth -= 1;
}
#endif
// Lookup the function table entry using the point at which control
// left the procedure.
FunctionEntry = RtlLookupFunctionEntry(ControlPc);
// If there is a function table entry for the routine, then virtually
// unwind to the caller of the routine to obtain the virtual frame
// pointer of the establisher, but don't update the context record.
if (FunctionEntry != NULL) {
NextPc = RtlpVirtualUnwind(ControlPc,
FunctionEntry,
ContextRecord,
&InFunction,
&EstablisherFrame);
// If the virtual frame pointer is not within the specified stack
// limits, the virtual frame pointer is unaligned, or the target
// frame is below the virtual frame and an exit unwind is not being
// performed, then raise the exception STATUS_BAD_STACK. Otherwise,
// check to determine if the current routine has an exception
// handler.
if ((EstablisherFrame.Virtual < LowLimit) ||
(EstablisherFrame.Virtual > HighLimit) ||
((ARGUMENT_PRESENT(TargetFrame) != FALSE) &&
((ULONG_PTR)TargetFrame < EstablisherFrame.Virtual)) ||
((EstablisherFrame.Virtual & 0xF) != 0)) {
#if DBG
DbgPrint("\n****** Warning - bad stack or target frame (unwind).\n");
DbgPrint(" EstablisherFrame Virtual = %p, Real = %p\n",
EstablisherFrame.Virtual, EstablisherFrame.Real);
DbgPrint(" TargetFrame = %p\n", TargetFrame);
if ((ARGUMENT_PRESENT(TargetFrame) != FALSE) &&
((ULONG_PTR)TargetFrame < EstablisherFrame.Virtual)) {
DbgPrint(" TargetFrame is below EstablisherFrame!\n");
}
DbgPrint(" Previous EstablisherFrame (sp) = %p\n",
(ULONG_PTR)ContextRecord->IntSp);
DbgPrint(" LowLimit = %p, HighLimit = %p\n",
LowLimit, HighLimit);
DbgPrint(" NextPc = %p, ControlPc = %p\n",
NextPc, ControlPc);
DbgPrint(" Now raising STATUS_BAD_STACK exception.\n");
#endif
RAISE_EXCEPTION(STATUS_BAD_STACK, ExceptionRecord);
} else if (IS_HANDLER_DEFINED(FunctionEntry) && InFunction) {
#if DBG
if (RtlDebugFlags & RTL_DBG_DISPATCH_EXCEPTION_DETAIL) {
DbgPrint("RtlUnwind: ExceptionHandler = %p, HandlerData = %p\n",
FunctionEntry->ExceptionHandler, FunctionEntry->HandlerData);
}
#endif
// The frame has an exception handler.
// The control PC, establisher frame pointer, the address
// of the function table entry, and the address of the
// context record are all stored in the dispatcher context.
// This information is used by the unwind linkage routine
// and can be used by the exception handler itself.
// A linkage routine written in assembler is used to actually
// call the actual exception handler. This is required by the
// exception handler that is associated with the linkage
// routine so it can have access to two sets of dispatcher
// context when it is called.
DispatcherContext.ControlPc = ControlPc;
DispatcherContext.FunctionEntry = FunctionEntry;
DispatcherContext.EstablisherFrame = EstablisherFrame.Virtual;
DispatcherContext.ContextRecord = ContextRecord;
// Call the exception handler.
do {
// If the establisher frame is the target of the unwind
// operation, then set the target unwind flag.
if ((ULONG_PTR)TargetFrame == EstablisherFrame.Virtual) {
ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
}
ExceptionRecord->ExceptionFlags = ExceptionFlags;
// Set the specified return value in case the exception
// handler directly continues execution.
ContextRecord->IntV0 = (ULONGLONG)(LONG_PTR)ReturnValue;
#if DBG
if (RtlDebugFlags & RTL_DBG_UNWIND_DETAIL) {
DbgPrint("RtlUnwind: calling RtlpExecuteHandlerForUnwind, ControlPc = %p\n", ControlPc);
}
#endif
Disposition =
RtlpExecuteHandlerForUnwind(ExceptionRecord,
EstablisherFrame.Virtual,
ContextRecord,
&DispatcherContext,
RF_EXCEPTION_HANDLER(FunctionEntry));
#if DBG
if (RtlDebugFlags & RTL_DBG_UNWIND_DETAIL) {
DbgPrint("RtlUnwind: RtlpExecuteHandlerForUnwind returned Disposition = %lx\n", Disposition);
}
#endif
// Clear target unwind and collided unwind flags.
ExceptionFlags &= ~(EXCEPTION_COLLIDED_UNWIND |
EXCEPTION_TARGET_UNWIND);
// Case on the handler disposition.
switch (Disposition) {
// The disposition is to continue the search.
// If the target frame has not been reached, then
// virtually unwind to the caller of the current
// routine, update the context record, and continue
// the search for a handler.
case ExceptionContinueSearch :
if (EstablisherFrame.Virtual != (ULONG_PTR)TargetFrame) {
NextPc = RtlVirtualUnwind(ControlPc,
FunctionEntry,
ContextRecord,
&InFunction,
&EstablisherFrame,
NULL);
}
break;
// The disposition is collided unwind.
// Set the target of the current unwind to the context
// record of the previous unwind, and reexecute the
// exception handler from the collided frame with the
// collided unwind flag set in the exception record.
case ExceptionCollidedUnwind :
ControlPc = DispatcherContext.ControlPc;
FunctionEntry = DispatcherContext.FunctionEntry;
ContextRecord = DispatcherContext.ContextRecord;
ContextRecord->Fir = (ULONGLONG)(LONG_PTR)TargetIp;
ExceptionFlags |= EXCEPTION_COLLIDED_UNWIND;
EstablisherFrame.Virtual = DispatcherContext.EstablisherFrame;
break;
// All other disposition values are invalid.
// Raise invalid disposition exception.
default :
RAISE_EXCEPTION(STATUS_INVALID_DISPOSITION, ExceptionRecord);
}
} while ((ExceptionFlags & EXCEPTION_COLLIDED_UNWIND) != 0);
} else {
// Virtually unwind to the caller of the current routine and
// update the context record.
if (EstablisherFrame.Virtual != (ULONG_PTR)TargetFrame) {
NextPc = RtlVirtualUnwind(ControlPc,
FunctionEntry,
ContextRecord,
&InFunction,
&EstablisherFrame,
NULL);
}
}
} else {
// Set point at which control left the previous routine.
NextPc = (ULONG_PTR)ContextRecord->IntRa - 4;
// If the next control PC is the same as the old control PC, then
// the function table is not correctly formed.
if (NextPc == ControlPc) {
#if DBG
ULONG Count;
DbgPrint("\n****** Warning - malformed function table (unwind).\n");
DbgPrint("ControlPc = %p, %p", NextPc, ControlPc);
for (Count = 0; Count < PC_HISTORY_DEPTH; Count += 1) {
if (ControlPcHistoryIndex > 0) {
ControlPcHistoryIndex -= 1;
ControlPc = ControlPcHistory[ControlPcHistoryIndex % PC_HISTORY_DEPTH];
DbgPrint(", %p", ControlPc);
}
}
DbgPrint(ControlPcHistoryIndex == 0 ? ".\n" : ", ...\n");
DbgPrint(" Now raising STATUS_BAD_FUNCTION_TABLE exception.\n");
#endif
RtlRaiseStatus(STATUS_BAD_FUNCTION_TABLE);
}
}
// Set point at which control left the previous routine.
#if DBG
ControlPcHistory[ControlPcHistoryIndex % PC_HISTORY_DEPTH] = ControlPc;
ControlPcHistoryIndex += 1;
#endif
ControlPc = NextPc;
} while ((EstablisherFrame.Virtual < HighLimit) &&
(EstablisherFrame.Virtual != (ULONG_PTR)TargetFrame));
// If the establisher stack pointer is equal to the target frame
// pointer, then continue execution. Otherwise, an exit unwind was
// performed or the target of the unwind did not exist and the
// debugger and subsystem are given a second chance to handle the
// unwind.
if (EstablisherFrame.Virtual == (ULONG_PTR)TargetFrame) {
ContextRecord->IntV0 = (ULONGLONG)(LONG_PTR)ReturnValue;
#if DBG
if (RtlDebugFlags & RTL_DBG_UNWIND) {
DbgPrint("RtlUnwind: finished unwinding, and calling RtlpRestoreContext(%lx)\n",ContextRecord);
}
#endif
RtlpRestoreContext(ContextRecord);
} else {
#if DBG
if (RtlDebugFlags & RTL_DBG_UNWIND) {
DbgPrint("RtlUnwind: finished unwinding, but calling ZwRaiseException\n");
}
#endif
ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
}
}
#if DBG
// Define an array of symbolic names for the integer registers.
PCHAR RtlpIntegerRegisterNames[32] = {
"v0", "t0", "t1", "t2", "t3", "t4", "t5", "t6", // 0 - 7
"t7", "s0", "s1", "s2", "s3", "s4", "s5", "fp", // 8 - 15
"a0", "a1", "a2", "a3", "a4", "a5", "t8", "t9", // 16 - 23
"t10", "t11", "ra", "t12", "at", "gp", "sp", "zero", // 24 - 31
};
// This function disassembles the instruction at the given address. It is
// only used for debugging and recognizes only those few instructions that
// are relevant during reverse execution of the prologue by virtual unwind.
VOID
_RtlpDebugDisassemble (
IN ULONG_PTR ControlPc,
IN PCONTEXT ContextRecord
)
{
UCHAR Comments[50];
PULONGLONG FloatingRegister;
ULONG Function;
ULONG Hint;
ULONG Literal8;
ALPHA_INSTRUCTION Instruction;
PULONGLONG IntegerRegister;
LONG Offset16;
UCHAR Operands[20];
ULONG Opcode;
PCHAR OpName;
ULONG Ra;
ULONG Rb;
ULONG Rc;
PCHAR RaName;
PCHAR RbName;
PCHAR RcName;
if (RtlDebugFlags & RTL_DBG_VIRTUAL_UNWIND_DETAIL) {
Instruction.Long = *((PULONG)ControlPc);
Hint = Instruction.Jump.Hint;
Literal8 = Instruction.OpLit.Literal;
Offset16 = Instruction.Memory.MemDisp;
Opcode = Instruction.Memory.Opcode;
Ra = Instruction.OpReg.Ra;
RaName = RtlpIntegerRegisterNames[Ra];
Rb = Instruction.OpReg.Rb;
RbName = RtlpIntegerRegisterNames[Rb];
Rc = Instruction.OpReg.Rc;
RcName = RtlpIntegerRegisterNames[Rc];
IntegerRegister = &ContextRecord->IntV0;
FloatingRegister = &ContextRecord->FltF0;
OpName = NULL;
switch (Opcode) {
case JMP_OP :
if (Instruction.Jump.Function == RET_FUNC) {
OpName = "ret";
sprintf(Operands, "%s, (%s), %04lx", RaName, RbName, Hint);
sprintf(Comments, "%s = %Lx", RbName, IntegerRegister[Rb]);
}
break;
case LDAH_OP :
case LDA_OP :
case STQ_OP :
if (Opcode == LDA_OP) {
OpName = "lda";
} else if (Opcode == LDAH_OP) {
OpName = "ldah";
} else if (Opcode == STQ_OP) {
OpName = "stq";
}
sprintf(Operands, "%s, $%d(%s)", RaName, Offset16, RbName);
sprintf(Comments, "%s = %Lx", RaName, IntegerRegister[Ra]);
break;
case ARITH_OP :
case BIT_OP :
Function = Instruction.OpReg.Function;
if ((Opcode == ARITH_OP) && (Function == ADDQ_FUNC)) {
OpName = "addq";
} else if ((Opcode == ARITH_OP) && (Function == SUBQ_FUNC)) {
OpName = "subq";
} else if ((Opcode == BIT_OP) && (Function == BIS_FUNC)) {
OpName = "bis";
} else {
break;
}
if (Instruction.OpReg.RbvType == RBV_REGISTER_FORMAT) {
sprintf(Operands, "%s, %s, %s", RaName, RbName, RcName);
} else {
sprintf(Operands, "%s, $%d, %s", RaName, Literal8, RcName);
}
sprintf(Comments, "%s = %Lx", RcName, IntegerRegister[Rc]);
break;
case FPOP_OP :
if (Instruction.FpOp.Function == CPYS_FUNC) {
OpName = "cpys";
sprintf(Operands, "f%d, f%d, f%d", Ra, Rb, Rc);
sprintf(Comments, "f%d = %Lx", Rc, FloatingRegister[Rc]);
}
break;
case STT_OP :
OpName = "stt";
sprintf(Operands, "f%d, $%d(%s)", Ra, Offset16, RbName);
sprintf(Comments, "f%d = %Lx", Ra, FloatingRegister[Ra]);
break;
}
if (OpName == NULL) {
OpName = "???";
sprintf(Operands, "...");
sprintf(Comments, "Unknown to virtual unwind.");
}
DbgPrint(" %p: %08lx %-5s %-16s // %s\n",
ControlPc, Instruction.Long, OpName, Operands, Comments);
}
return;
}
#define _RtlpFoundTrapFrame(NextPc) \
if (RtlDebugFlags & RTL_DBG_VIRTUAL_UNWIND) { \
DbgPrint(" *** Looks like a trap frame (fake prologue), Fir = %lx\n", \
NextPc); \
}
#define _RtlpVirtualUnwindExit(NextPc, ContextRecord, EstablisherFrame) \
if (RtlDebugFlags & RTL_DBG_VIRTUAL_UNWIND) { \
DbgPrint("RtlVirtualUnwind: EstablisherFrame Virtual = %08lx, Real = %08lx\n", \
(EstablisherFrame)->Virtual, (EstablisherFrame)->Real); \
DbgPrint("RtlVirtualUnwind: returning NextPc = %p, sp = %p\n\n", \
NextPc, (ULONG_PTR)ContextRecord->IntSp); \
}
#else
#define _RtlpDebugDisassemble(ControlPc, ContextRecord)
#define _RtlpFoundTrapFrame(NextPc)
#define _RtlpVirtualUnwindExit(NextPc, ContextRecord, EstablisherFrame)
#endif
ULONG_PTR
RtlVirtualUnwind (
IN ULONG_PTR ControlPc,
IN PRUNTIME_FUNCTION FunctionEntry,
IN OUT PCONTEXT ContextRecord,
OUT PBOOLEAN InFunction,
OUT PFRAME_POINTERS EstablisherFrame,
IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL
)
/*++
Routine Description:
This function virtually unwinds the specified function by executing its
prologue code backwards. Given the current context and the instructions
that preserve registers in the prologue, it is possible to recreate the
nonvolatile context at the point the function was called.
If the function is a leaf function, then the address where control left
the previous frame is obtained from the context record. If the function
is a nested function, but not an exception or interrupt frame, then the
prologue code is executed backwards and the address where control left
the previous frame is obtained from the updated context record.
Otherwise, an exception or interrupt entry to the system is being unwound
and a specially coded prologue restores the return address twice. Once
from the fault instruction address and once from the saved return address
register. The first restore is returned as the function value and the
second restore is placed in the updated context record.
During the unwind, the virtual and real frame pointers for the function
are calculated and returned in the given frame pointers structure.
If a context pointers record is specified, then the address where each
register is restored from is recorded in the appropriate element of the
context pointers record.
Arguments:
ControlPc - Supplies the address where control left the specified
function.
FunctionEntry - Supplies the address of the function table entry for the
specified function.
ContextRecord - Supplies the address of a context record.
InFunction - Supplies a pointer to a variable that receives whether the
control PC is within the current function.
EstablisherFrame - Supplies a pointer to a frame pointers structure
that will receive the values for the virtual frame pointer and the
real frame pointer. The value of the real frame pointer is reliable
only when InFunction is TRUE.
ContextPointers - Supplies an optional pointer to a context pointers
record.
Return Value:
The address where control left the previous frame is returned as the
function value.
Implementation Notes:
N.B. "where control left" is not the "return address" of the call in the
previous frame. For normal frames, NextPc points to the last instruction
that completed in the previous frame (the JSR/BSR). The difference between
NextPc and NextPc + 4 (return address) is important for correct behavior
in boundary cases of exception addresses and scope tables.
For exception and interrupt frames, NextPc is obtained from the trap frame
contination address (Fir). For faults and synchronous traps, NextPc is both
the last instruction to execute in the previous frame and the next
instruction to execute if the function were to return. For asynchronous
traps, NextPc is the continuation address. It is the responsibility of the
compiler to insert TRAPB instructions to insure asynchronous traps do not
occur outside the scope from the instruction(s) that caused them.
N.B. in this and other files where RtlVirtualUnwind is used, the variable
named NextPc is perhaps more accurately, LastPc - the last PC value in
the previous frame, or CallPc - the address of the call instruction, or
ControlPc - the address where control left the previous frame. Instead
think of NextPc as the next PC to use in another call to virtual unwind.
The Alpha version of virtual unwind is similar in design, but slightly
more complex than the Mips version. This is because Alpha compilers
are given more flexibility to optimize generated code and instruction
sequences, including within procedure prologues. In addition, because of
the current inability of the GEM compiler to materialize virtual frame
pointers, this function must manage both virtual and real frame pointers.
--*/
{
ULONG_PTR Address;
ULONG DecrementOffset;
ULONG DecrementRegister;
ALPHA_INSTRUCTION FollowingInstruction;
PULONGLONG FloatingRegister;
ULONG_PTR FrameSize;
ULONG Function;
ALPHA_INSTRUCTION Instruction;
PULONGLONG IntegerRegister;
ULONG Literal8;
ULONG_PTR NextPc;
LONG Offset16;
ULONG Opcode;
ULONG Ra;
ULONG Rb;
ULONG Rc;
BOOLEAN RestoredRa;
BOOLEAN RestoredSp;
RUNTIME_FUNCTION UnwindFunctionEntry;
ULONG StackAdjust;
ULONG_PTR FixedReturn;
#if DBG
if (RtlDebugFlags & RTL_DBG_VIRTUAL_UNWIND) {
DbgPrint("\nRtlVirtualUnwind(ControlPc = %p, FunctionEntry = %p,) sp = %p\n",
ControlPc, FunctionEntry, (ULONG_PTR)ContextRecord->IntSp);
}
#endif
// Construct a function entry suitable for unwinding from ControlPc
RtlGetUnwindFunctionEntry( ControlPc, FunctionEntry, &UnwindFunctionEntry, &StackAdjust, &FixedReturn );
#if DBG
ShowRuntimeFunction(&UnwindFunctionEntry, "RtlVirtualUnwind: unwind function entry" );
#endif
// Set the base address of the integer and floating register arrays within
// the context record. Each set of 32 registers is known to be contiguous.
IntegerRegister = &ContextRecord->IntV0;
FloatingRegister = &ContextRecord->FltF0;
// Handle the epilogue case where the next instruction is a return.
// Exception handlers cannot be called if the ControlPc is within the
// epilogue because exception handlers expect to operate with a current
// stack frame. The value of SP is not current within the epilogue.
Instruction.Long = *((PULONG)ControlPc);
if (IS_RETURN_0001_INSTRUCTION(Instruction.Long)) {
Rb = Instruction.Jump.Rb;
NextPc = (ULONG_PTR)IntegerRegister[Rb] - 4;
// The instruction at the point where control left the specified
// function is a return, so any saved registers have already been
// restored, and the stack pointer has already been adjusted. The
// stack does not need to be unwound in this case and the saved
// return address register is returned as the function value.
// In fact, reverse execution of the prologue is not possible in
// this case: the stack pointer has already been incremented and
// so, for this frame, neither a valid stack pointer nor frame
// pointer exists from which to begin reverse execution of the
// prologue. In addition, the integrity of any data on the stack
// below the stack pointer is never guaranteed (due to interrupts
// and exceptions).
// The epilogue instruction sequence is:
// ==> ret zero, (Ra), 1 // return
// or
// mov ra, Rx // save return address
// ...
// ==> ret zero, (Rx), 1 // return
EstablisherFrame->Real = 0;
EstablisherFrame->Virtual = (ULONG_PTR)ContextRecord->IntSp;
*InFunction = FALSE;
_RtlpDebugDisassemble(ControlPc, ContextRecord);
_RtlpVirtualUnwindExit(NextPc, ContextRecord, EstablisherFrame);
return NextPc;
}
// Handle the epilogue case where the next two instructions are a stack
// frame deallocation and a return.
FollowingInstruction.Long = *((PULONG)(ControlPc + 4));
if (IS_RETURN_0001_INSTRUCTION(FollowingInstruction.Long)) {
Rb = FollowingInstruction.Jump.Rb;
NextPc = (ULONG_PTR)IntegerRegister[Rb] - 4;
// The second instruction following the point where control
// left the specified function is a return. If the instruction
// before the return is a stack increment instruction, then all
// saved registers have already been restored except for SP.
// The value of the stack pointer register cannot be recovered
// through reverse execution of the prologue because in order
// to begin reverse execution either the stack pointer or the
// frame pointer (if any) must still be valid.
// Instead, the effect that the stack increment instruction
// would have had on the context is manually applied to the
// current context. This is forward execution of the epilogue
// rather than reverse execution of the prologue.
// In an epilogue, as in a prologue, the stack pointer is always
// adjusted with a single instruction: either an immediate-value
// (lda) or a register-value (addq) add instruction.
Function = Instruction.OpReg.Function;
Offset16 = Instruction.Memory.MemDisp;
Opcode = Instruction.OpReg.Opcode;
Ra = Instruction.OpReg.Ra;
Rb = Instruction.OpReg.Rb;
Rc = Instruction.OpReg.Rc;
if ((Opcode == LDA_OP) && (Ra == SP_REG)) {
// Load Address instruction.
// Since the destination (Ra) register is SP, an immediate-
// value stack deallocation operation is being performed. The
// displacement value should be added to SP. The displacement
// value is assumed to be positive. The amount of stack
// deallocation possible using this instruction ranges from
// 16 to 32752 (32768 - 16) bytes. The base register (Rb) is
// usually SP, but may be another register.
// The epilogue instruction sequence is:
// ==> lda sp, +N(sp) // deallocate stack frame
// ret zero, (ra) // return
// or
// ==> lda sp, +N(Rx) // restore SP and deallocate frame
// ret zero, (ra) // return
ContextRecord->IntSp = Offset16 + IntegerRegister[Rb];
EstablisherFrame->Real = 0;
EstablisherFrame->Virtual = (ULONG_PTR)ContextRecord->IntSp;
*InFunction = FALSE;
_RtlpDebugDisassemble(ControlPc, ContextRecord);
_RtlpDebugDisassemble(ControlPc + 4, ContextRecord);
_RtlpVirtualUnwindExit(NextPc, ContextRecord, EstablisherFrame);
return NextPc;
} else if ((Opcode == ARITH_OP) && (Function == ADDQ_FUNC) &&
(Rc == SP_REG) &&
(Instruction.OpReg.RbvType == RBV_REGISTER_FORMAT)) {
// Add Quadword instruction.
// Since both source operands are registers, and the
// destination register is SP, a register-value stack
// deallocation is being performed. The value of the two
// source registers should be added and this is the new
// value of SP. One of the source registers is usually SP,
// but may be another register.
// The epilogue instruction sequence is:
// ldiq Rx, N // set [large] frame size
// ...
// ==> addq sp, Rx, sp // deallocate stack frame
// ret zero, (ra) // return
// or
// ==> addq Rx, Ry, sp // restore SP and deallocate frame
// ret zero, (ra) // return
ContextRecord->IntSp = IntegerRegister[Ra] + IntegerRegister[Rb];
EstablisherFrame->Real = 0;
EstablisherFrame->Virtual = (ULONG_PTR)ContextRecord->IntSp;
*InFunction = FALSE;
_RtlpDebugDisassemble(ControlPc, ContextRecord);
_RtlpDebugDisassemble(ControlPc + 4, ContextRecord);
_RtlpVirtualUnwindExit(NextPc, ContextRecord, EstablisherFrame);
return NextPc;
}
}
// By default set the frame pointers to the current value of SP.
// When a procedure is called, the value of SP before the stack
// allocation instruction is the virtual frame pointer. When reverse
// executing instructions in the prologue, the value of SP before the
// stack allocation instruction is encountered is the real frame
// pointer. This is the current value of SP unless the procedure uses
// a frame pointer (e.g., FP_REG).
EstablisherFrame->Real = (ULONG_PTR)ContextRecord->IntSp;
EstablisherFrame->Virtual = (ULONG_PTR)ContextRecord->IntSp;
// If the address where control left the specified function is beyond
// the end of the prologue, then the control PC is considered to be
// within the function and the control address is set to the end of
// the prologue. Otherwise, the control PC is not considered to be
// within the function (i.e., the prologue).
// N.B. PrologEndAddress is equal to BeginAddress for a leaf function.
// The low-order two bits of PrologEndAddress are reserved for the IEEE
// exception mode and so must be masked out.
if ((ControlPc < UnwindFunctionEntry.BeginAddress) ||
(ControlPc >= UnwindFunctionEntry.PrologEndAddress)) {
*InFunction = TRUE;
ControlPc = (UnwindFunctionEntry.PrologEndAddress & (~(UINT_PTR)0x3));
} else {
*InFunction = FALSE;
}
// Scan backward through the prologue to reload callee saved registers
// that were stored or copied and to increment the stack pointer if it
// was decremented.
DecrementRegister = ZERO_REG;
NextPc = (ULONG_PTR)ContextRecord->IntRa - 4;
RestoredRa = FALSE;
RestoredSp = FALSE;
while (ControlPc > UnwindFunctionEntry.BeginAddress) {
// Get instruction value, decode fields, case on opcode value, and
// reverse register store and stack decrement operations.
// N.B. The location of Opcode, Ra, Rb, and Rc is the same across
// all opcode formats. The same is not true for Function.
ControlPc -= 4;
Instruction.Long = *((PULONG)ControlPc);
Function = Instruction.OpReg.Function;
Literal8 = Instruction.OpLit.Literal;
Offset16 = Instruction.Memory.MemDisp;
Opcode = Instruction.OpReg.Opcode;
Ra = Instruction.OpReg.Ra;
Rb = Instruction.OpReg.Rb;
Rc = Instruction.OpReg.Rc;
// Compare against each instruction type that will affect the context
// and that is allowed in a prologue. Any other instructions found
// in the prologue will be ignored since they are assumed to have no
// effect on the context.
switch (Opcode) {
case STQ_OP :
// Store Quad instruction.
// If the base register is SP, then reload the source register
// value from the value stored on the stack.
// The prologue instruction sequence is:
// ==> stq Rx, N(sp) // save integer register Rx
if ((Rb == SP_REG) && (Ra != ZERO_REG)) {
// Reload the register by retrieving the value previously
// stored on the stack.
Address = Offset16 + (LONG_PTR)ContextRecord->IntSp;
IntegerRegister[Ra] = *((PULONGLONG)Address);
// If the destination register is RA and this is the first
// time that RA is being restored, then set the address of
// where control left the previous frame. Otherwise, if this
// is the second time RA is being restored, then the first
// one was an interrupt or exception address and the return
// PC should not have been biased by 4.
if (Ra == RA_REG) {
if (RestoredRa == FALSE) {
NextPc = (ULONG_PTR)ContextRecord->IntRa - 4;
RestoredRa = TRUE;
} else {
NextPc += 4;
_RtlpFoundTrapFrame(NextPc);
}
// Otherwise, if the destination register is SP and this is
// the first time that SP is being restored, then set the
// establisher frame pointers.
} else if ((Ra == SP_REG) && (RestoredSp == FALSE)) {
EstablisherFrame->Virtual = (ULONG_PTR)ContextRecord->IntSp;
EstablisherFrame->Real = (ULONG_PTR)ContextRecord->IntSp;
RestoredSp = TRUE;
}
// If a context pointer record is specified, then record
// the address where the destination register contents
// are stored.
if (ARGUMENT_PRESENT(ContextPointers)) {
ContextPointers->IntegerContext[Ra] = (PULONGLONG)Address;
}
_RtlpDebugDisassemble(ControlPc, ContextRecord);
}
break;
case LDAH_OP :
Offset16 <<= 16;
case LDA_OP :
// Load Address High, Load Address instruction.
// There are several cases where the lda and/or ldah instructions
// are used: one to decrement the stack pointer directly, and the
// others to load immediate values into another register and that
// register is then used to decrement the stack pointer.
// In the examples below, as a single instructions or as a pair,
// a lda may be substituted for a ldah and visa-versa.
if (Ra == SP_REG) {
if (Rb == SP_REG) {
// If both the destination (Ra) and base (Rb) registers
// are SP, then a standard stack allocation was performed
// and the negated displacement value is the stack frame
// size. The amount of stack allocation possible using
// the lda instruction ranges from 16 to 32768 bytes and
// the amount of stack allocation possible using the ldah
// instruction ranges from 65536 to 2GB in multiples of
// 65536 bytes. It is rare for the ldah instruction to be
// used in this manner.
// The prologue instruction sequence is:
// ==> lda sp, -N(sp) // allocate stack frame
FrameSize = -Offset16;
goto StackAllocation;
} else {
// The destination register is SP and the base register
// is not SP, so this instruction must be the second
// half of an instruction pair to allocate a large size
// (>32768 bytes) stack frame. Save the displacement value
// as the partial decrement value and postpone adjusting
// the value of SP until the first instruction of the pair
// is encountered.
// The prologue instruction sequence is:
// ldah Rx, -N(sp) // prepare new SP (upper)
// ==> lda sp, sN(Rx) // allocate stack frame
DecrementRegister = Rb;
DecrementOffset = Offset16;
_RtlpDebugDisassemble(ControlPc, ContextRecord);
}
} else if (Ra == DecrementRegister) {
if (Rb == DecrementRegister) {
// Both the destination and base registers are the
// decrement register, so this instruction exists as the
// second half of a two instruction pair to load a
// 31-bit immediate value into the decrement register.
// Save the displacement value as the partial decrement
// value.
// The prologue instruction sequence is:
// ldah Rx, +N(zero) // set frame size (upper)
// ==> lda Rx, sN(Rx) // set frame size (+lower)
// ...
// subq sp, Rx, sp // allocate stack frame
DecrementOffset += Offset16;
_RtlpDebugDisassemble(ControlPc, ContextRecord);
} else if (Rb == ZERO_REG) {
// The destination register is the decrement register and
// the base register is zero, so this instruction exists
// to load an immediate value into the decrement register.
// The stack frame size is the new displacement value added
// to the previous displacement value, if any.
// The prologue instruction sequence is:
// ==> lda Rx, +N(zero) // set frame size
// ...
// subq sp, Rx, sp // allocate stack frame
// or
// ==> ldah Rx, +N(zero) // set frame size (upper)
// lda Rx, sN(Rx) // set frame size (+lower)
// ...
// subq sp, Rx, sp // allocate stack frame
FrameSize = (Offset16 + DecrementOffset);
goto StackAllocation;
} else if (Rb == SP_REG) {
// The destination (Ra) register is SP and the base (Rb)
// register is the decrement register, so a two
// instruction, large size (>32768 bytes) stack frame
// allocation was performed. Add the new displacement
// value to the previous displacement value. The negated
// displacement value is the stack frame size.
// The prologue instruction sequence is:
// ==> ldah Rx, -N(sp) // prepare new SP (upper)
// lda sp, sN(Rx) // allocate stack frame
FrameSize = -(Offset16 + (LONG)DecrementOffset);
goto StackAllocation;
}
}
break;
case ARITH_OP :
if ((Function == ADDQ_FUNC) &&
(Instruction.OpReg.RbvType != RBV_REGISTER_FORMAT)) {
// Add Quadword (immediate) instruction.
// If the first source register is zero, and the second
// operand is a literal, and the destination register is
// the decrement register, then the instruction exists
// to load an unsigned immediate value less than 256 into
// the decrement register. The immediate value is the stack
// frame size.
// The prologue instruction sequence is:
// ==> addq zero, N, Rx // set frame size
// ...
// subq sp, Rx, sp // allocate stack frame
if ((Ra == ZERO_REG) && (Rc == DecrementRegister)) {
FrameSize = Literal8;
goto StackAllocation;
}
} else if ((Function == SUBQ_FUNC) &&
(Instruction.OpReg.RbvType == RBV_REGISTER_FORMAT)) {
// Subtract Quadword (register) instruction.
// If both source operands are registers and the first
// source (minuend) register and the destination
// (difference) register are both SP, then a register value
// stack allocation was performed and the second source
// (subtrahend) register value will be added to SP when its
// value is known. Until that time save the register number of
// this decrement register.
// The prologue instruction sequence is:
// ldiq Rx, N // set frame size
// ...
// ==> subq sp, Rx, sp // allocate stack frame
if ((Ra == SP_REG) && (Rc == SP_REG)) {
DecrementRegister = Rb;
DecrementOffset = 0;
_RtlpDebugDisassemble(ControlPc, ContextRecord);
}
}
break;
case BIT_OP :
// If the second operand is a register the bit set instruction
// may be a register move instruction, otherwise if the second
// operand is a literal, the bit set instruction may be a load
// immediate value instruction.
if ((Function == BIS_FUNC) && (Rc != ZERO_REG)) {
if (Instruction.OpReg.RbvType == RBV_REGISTER_FORMAT) {
// Bit Set (register move) instruction.
// If both source registers are the same register, or
// one of the source registers is zero, then this is a
// register move operation. Restore the value of the
// source register by copying the current destination
// register value back to the source register.
// The prologue instruction sequence is:
// ==> bis Rx, Rx, Ry // copy register Rx
// or
// ==> bis Rx, zero, Ry // copy register Rx
// or
// ==> bis zero, Rx, Ry // copy register Rx
if (Ra == ZERO_REG) {
// Map the third case above to the first case.
Ra = Rb;
} else if (Rb == ZERO_REG) {
// Map the second case above to the first case.
Rb = Ra;
}
if ((Ra == Rb) && (Ra != ZERO_REG)) {
IntegerRegister[Ra] = IntegerRegister[Rc];
// If the destination register is RA and this is the
// first time that RA is being restored, then set the
// address of where control left the previous frame.
// Otherwise, if this is the second time RA is being
// restored, then the first one was an interrupt or
// exception address and the return PC should not
// have been biased by 4.
if (Ra == RA_REG) {
if (RestoredRa == FALSE) {
NextPc = (ULONG_PTR)ContextRecord->IntRa - 4;
RestoredRa = TRUE;
} else {
NextPc += 4;
_RtlpFoundTrapFrame(NextPc);
}
// If the source register is SP and this is the first
// time SP is set, then this is a frame pointer set
// instruction. Reset the frame pointers to this new
// value of SP.
} else if ((Ra == SP_REG) && (RestoredSp == FALSE)) {
EstablisherFrame->Virtual = (ULONG_PTR)ContextRecord->IntSp;
EstablisherFrame->Real = (ULONG_PTR)ContextRecord->IntSp;
RestoredSp = TRUE;
}
_RtlpDebugDisassemble(ControlPc, ContextRecord);
}
} else {
// Bit Set (load immediate) instruction.
// If the first source register is zero, and the second
// operand is a literal, and the destination register is
// the decrement register, then this instruction exists
// to load an unsigned immediate value less than 256 into
// the decrement register. The decrement register value is
// the stack frame size.
// The prologue instruction sequence is:
// ==> bis zero, N, Rx // set frame size
// ...
// subq sp, Rx, sp // allocate stack frame
if ((Ra == ZERO_REG) && (Rc == DecrementRegister)) {
FrameSize = Literal8;
StackAllocation:
// Add the frame size to SP to reverse the stack frame
// allocation, leave the real frame pointer as is, set
// the virtual frame pointer with the updated SP value,
// and clear the decrement register.
ContextRecord->IntSp += FrameSize;
EstablisherFrame->Virtual = (ULONG_PTR)ContextRecord->IntSp;
DecrementRegister = ZERO_REG;
_RtlpDebugDisassemble(ControlPc, ContextRecord);
}
}
}
break;
case STT_OP :
// Store T-Floating (quadword integer) instruction.
// If the base register is SP, then reload the source register
// value from the value stored on the stack.
// The prologue instruction sequence is:
// ==> stt Fx, N(sp) // save floating register Fx
if ((Rb == SP_REG) && (Ra != FZERO_REG)) {
// Reload the register by retrieving the value previously
// stored on the stack.
Address = Offset16 + (LONG_PTR)ContextRecord->IntSp;
FloatingRegister[Ra] = *((PULONGLONG)Address);
// If a context pointer record is specified, then record
// the address where the destination register contents are
// stored.
if (ARGUMENT_PRESENT(ContextPointers)) {
ContextPointers->FloatingContext[Ra] = (PULONGLONG)Address;
}
_RtlpDebugDisassemble(ControlPc, ContextRecord);
}
break;
case FPOP_OP :
// N.B. The floating operate function field is not the same as
// the integer operate nor the jump function fields.
if (Instruction.FpOp.Function == CPYS_FUNC) {
// Copy Sign (floating-point move) instruction.
// If both source registers are the same register, then this is
// a floating-point register move operation. Restore the value
// of the source register by copying the current destination
// register value to the source register.
// The prologue instruction sequence is:
// ==> cpys Fx, Fx, Fy // copy floating register Fx
if ((Ra == Rb) && (Ra != FZERO_REG)) {
FloatingRegister[Ra] = FloatingRegister[Rc];
_RtlpDebugDisassemble(ControlPc, ContextRecord);
}
}
default :
break;
}
}
// Check for exlicit stack adjust amount
if (StackAdjust) {
ContextRecord->IntSp += StackAdjust;
}
if (FixedReturn != 0) {
NextPc = FixedReturn;
}
_RtlpVirtualUnwindExit(NextPc, ContextRecord, EstablisherFrame);
return NextPc;
}
ULONG_PTR
RtlpVirtualUnwind (
IN ULONG_PTR ControlPc,
IN PRUNTIME_FUNCTION FunctionEntry,
IN PCONTEXT ContextRecord,
OUT PBOOLEAN InFunction,
OUT PFRAME_POINTERS EstablisherFrame
)
/*++
Routine Description:
This function virtually unwinds the specfified function by executing its
prologue code backwards.
If the function is a leaf function, then the address where control left
the previous frame is obtained from the context record. If the function
is a nested function, but not an exception or interrupt frame, then the
prologue code is executed backwards and the address where control left
the previous frame is obtained from the updated context record.
Otherwise, an exception or interrupt entry to the system is being unwound
and a specially coded prologue restores the return address twice. Once
from the fault instruction address and once from the saved return address
register. The first restore is returned as the function value and the
second restore is place in the updated context record.
If a context pointers record is specified, then the address where each
nonvolatile registers is restored from is recorded in the appropriate
element of the context pointers record.
N.B. This function copies the specified context record and only computes
the establisher frame and whether control is actually in a function.
Arguments:
ControlPc - Supplies the address where control left the specified
function.
FunctionEntry - Supplies the address of the function table entry for the
specified function.
ContextRecord - Supplies the address of a context record.
InFunction - Supplies a pointer to a variable that receives whether the
control PC is within the current function.
EstablisherFrame - Supplies a pointer to a variable that receives the
the establisher frame pointer value.
ContextPointers - Supplies an optional pointer to a context pointers
record.
Return Value:
The address where control left the previous frame is returned as the
function value.
--*/
{
CONTEXT LocalContext;
// Copy the context record so updates will not be reflected in the
// original copy and then virtually unwind to the caller of the
// specified control point.
RtlMoveMemory((PVOID)&LocalContext, ContextRecord, sizeof(CONTEXT));
return RtlVirtualUnwind(ControlPc,
FunctionEntry,
&LocalContext,
InFunction,
EstablisherFrame,
NULL);
}
PRUNTIME_FUNCTION
RtlLookupDirectFunctionEntry (
IN ULONG_PTR ControlPc
)
/*++
Routine Description:
This function searches the currently active function tables (static and dynamic)
for an entry that corresponds to the specified PC value.
Arguments:
ControlPc - Supplies the address of an instruction within the specified
function.
Return Value:
If there is no entry in the function table for the specified PC, then
NULL is returned. Otherwise, the address of the function table entry
that corresponds to the specified PC is returned.
--*/
{
PRUNTIME_FUNCTION FunctionEntry;
BOOLEAN InImage;
// look for function entry in static function tables
FunctionEntry = RtlLookupStaticFunctionEntry( ControlPc, &InImage );
#if !defined(NTOS_KERNEL_RUNTIME)
// If not in static image range and no static function entry
// found then look for a dynamic function entry
if (FunctionEntry == NULL && !InImage) {
FunctionEntry = RtlLookupDynamicFunctionEntry( ControlPc );
}
#endif
#if DBG
if (RtlDebugFlags & RTL_DBG_FUNCTION_ENTRY) {
DbgPrint("RtlLookupDirectFunctionEntry: returning FunctionEntry = %p\n", FunctionEntry);
}
#endif
return FunctionEntry;
}
PRUNTIME_FUNCTION
RtlLookupStaticFunctionEntry(
IN ULONG_PTR ControlPc,
OUT PBOOLEAN InImage
)
/*++
Routine Description:
This function searches the currently active static function tables for an
entry that corresponds to the specified PC value.
Arguments:
ControlPc - Supplies the address of an instruction within the specified
function.
InImage - Address to recieve a flag indicating whether the ControlPc
was in the range of a static function table
Return Value:
If there is no entry in the static function tables for the specified PC,
then NULL is returned. Otherwise, the address of the function table entry
that corresponds to the specified PC is returned.
--*/
{
PRUNTIME_FUNCTION FunctionEntry;
PRUNTIME_FUNCTION FunctionTable;
ULONG SizeOfExceptionTable;
LONG High;
PVOID ImageBase;
LONG Low;
LONG Middle;
// Search for the image that includes the specified PC value.
ImageBase = RtlPcToFileHeader((PVOID)ControlPc, &ImageBase);
#if DBG
if (RtlDebugFlags & RTL_DBG_FUNCTION_ENTRY) {
DbgPrint("RtlLookupStaticFunctionEntry(ControlPc = %p) ImageBase = %p\n",
ControlPc, ImageBase);
}
#endif
// If an image is found that includes the specified PC, then locate the
// function table for the image.
*InImage = (ImageBase != NULL);
FunctionEntry = NULL;
if (ImageBase != NULL) {
FunctionTable = (PRUNTIME_FUNCTION)RtlImageDirectoryEntryToData(
ImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXCEPTION,
&SizeOfExceptionTable);
#if DBG
if (RtlDebugFlags & RTL_DBG_FUNCTION_ENTRY_DETAIL) {
DbgPrint("RtlLookupStaticFunctionEntry: FunctionTable = %p, SizeOfExceptionTable = %lx\n",
FunctionTable, SizeOfExceptionTable);
}
#endif
// If a function table is located, then search the function table
// for a function table entry for the specified PC.
if (FunctionTable != NULL) {
// Initialize search indicies.
Low = 0;
High = (SizeOfExceptionTable / sizeof(RUNTIME_FUNCTION)) - 1;
// Perform binary search on the function table for a function table
// entry that subsumes the specified PC.
while (High >= Low) {
// Compute next probe index and test entry. If the specified PC
// is greater than of equal to the beginning address and less
// than the ending address of the function table entry, then
// return the address of the function table entry. Otherwise,
// continue the search.
Middle = (Low + High) >> 1;
FunctionEntry = &FunctionTable[Middle];
if (ControlPc < RF_BEGIN_ADDRESS(FunctionEntry)) {
High = Middle - 1;
} else if (ControlPc >= RF_END_ADDRESS(FunctionEntry)) {
Low = Middle + 1;
} else {
return FunctionEntry;
}
} // while (High >= Low)
} // FunctionTable != NULL
} // ImageBase != NULL
return NULL;
}
VOID
RtlGetUnwindFunctionEntry(
IN ULONG_PTR ControlPc,
IN PRUNTIME_FUNCTION FunctionEntry,
OUT PRUNTIME_FUNCTION UnwindFunctionEntry,
OUT PULONG StackAdjust,
OUT PULONG_PTR FixedReturn
)
/*++
Routine Description:
This function returns a function entry (RUNTIME_FUNCTION) suitable
for unwinding from ControlPc. It encapsulates the handling of primary
and secondary function entries so that this processing is not duplicated
in RtlVirtualUnwind and other similar functions.
Arguments:
ControlPc - Supplies the address where control left the specified
function.
FunctionEntry - Supplies the address of the function table entry for the
specified function.
UnwindFunctionEntry - Supplies the address of a function table entry which
will be setup with appropriate fields for unwinding from ControlPc
StackAdjust - Receives the optional stack adjustment amount specified
in RF_NULL_CONTEXT type secondary function entries. Will be zero
if no null-context stack adjustment is required.
FixedReturn - Receives the return address specified by fixed-return functin
entries. Will be zero if a fixed return address was not specified.
Return Value:
None.
--*/
{
ULONG EntryType = 0;
PRUNTIME_FUNCTION SecondaryFunctionEntry = NULL;
ULONG_PTR AlternateProlog;
*FixedReturn = 0;
*StackAdjust = 0;
// FunctionEntry should never be null, but if it is create one that
// looks like a leaf entry for ControlPc
if (FunctionEntry == NULL) {
#if DBG
DbgPrint("\n****** Warning - Null function table entry for unwinding.\n");
#endif
UnwindFunctionEntry->BeginAddress = ControlPc;
UnwindFunctionEntry->EndAddress = ControlPc+4;
UnwindFunctionEntry->ExceptionHandler = NULL;
UnwindFunctionEntry->HandlerData = 0;
UnwindFunctionEntry->PrologEndAddress = ControlPc;
return;
}
// Because of the secondary-to-primary function entry indirection applied by
// RtlLookupFunctionEntry() ControlPc may not be within the range described
// by the supplied function entry. Call RtlLookupDirectFunctionEntry()
// to recover the actual (secondary) function entry. If we don't get a
// valid associated function entry then process the unwind with the one
// supplied, trusting that the caller has supplied the given entry intentionally.
// A secondary function entry is a RUNTIME_FUNCTION entry where
// PrologEndAddress is not in the range of BeginAddress to EndAddress.
// There are three types of secondary function entries. They are
// distinquished by the Entry Type field (2 bits):
// RF_NOT_CONTIGUOUS - discontiguous code
// RF_ALT_ENT_PROLOG - alternate entry point prologue
// RF_NULL_CONTEXT - null-context code
if ((ControlPc < RF_BEGIN_ADDRESS(FunctionEntry)) ||
(ControlPc >= RF_END_ADDRESS(FunctionEntry))) {
// ControlPC is not in the range of the supplied function entry.
// Get the actual function entry which is expected to be the
// associated secondary function entry.
#if DBG
if (RtlDebugFlags & RTL_DBG_FUNCTION_ENTRY) {
DbgPrint("\nGetUnwindFunctionEntry:RtlLookupDirectFunctionEntry(ControlPc=%lx)\n", ControlPc);
}
#endif
SecondaryFunctionEntry = RtlLookupDirectFunctionEntry( ControlPc );
if (SecondaryFunctionEntry) {
#if DBG
ShowRuntimeFunction(SecondaryFunctionEntry, "GetUnwindFunctionEntry: LookupDirectFunctionEntry");
#endif
// If this is a null-context tail region then unwind with a null-context-like descriptor
if ((ControlPc >= RF_END_ADDRESS(SecondaryFunctionEntry)-(RF_NULL_CONTEXT_COUNT(SecondaryFunctionEntry)*4)) &&
(ControlPc < RF_END_ADDRESS(SecondaryFunctionEntry))) {
// Use the secondary function entry with PrologEndAddress = BeginAddress.
// This ensures that the prologue is not reverse executed.
UnwindFunctionEntry->BeginAddress = RF_BEGIN_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->ExceptionHandler = 0;
UnwindFunctionEntry->HandlerData = 0;
UnwindFunctionEntry->PrologEndAddress = RF_BEGIN_ADDRESS(SecondaryFunctionEntry);
return;
}
if ((SecondaryFunctionEntry->PrologEndAddress < RF_BEGIN_ADDRESS(SecondaryFunctionEntry)) ||
(SecondaryFunctionEntry->PrologEndAddress > RF_END_ADDRESS(SecondaryFunctionEntry))) {
// Got a secondary function entry as expected. But if indirection doesn't point
// to FunctionEntry then ignore it and use the caller supplied FunctionEntry.
if (RF_PROLOG_END_ADDRESS(SecondaryFunctionEntry) != (ULONG_PTR)FunctionEntry) {
#if DBG
DbgPrint("RtlGetUnwindFunctionEntry: unexpected secondary function entry from RtlLookupDirectFunctionEntry\n");
#endif
SecondaryFunctionEntry = NULL;
}
} else {
// Got a primary function entry. The only valid type is a
// Fixed Return Function Entry, which if present gets processed
// at the end. Even if it is not a fixed return function entry,
// use it, since its prolog matches up with the control PC.
FunctionEntry = SecondaryFunctionEntry;
SecondaryFunctionEntry = NULL;
#if DBG
if (!RF_FIXED_RETURN(FunctionEntry)) {
DbgPrint("RtlGetUnwindFunctionEntry: unexpected primary function entry from RtlLookupDirectFunctionEntry\n");
}
#endif
}
#if DBG
} else {
DbgPrint("GetUnwindFunctionEntry: LookupDirectFunctionEntry returned NULL\n");
#endif
}
} else {
// ControlPC is in the range of the supplied function entry.
// Check if it is a secondary function entry. If so, get the
// associated primary function entry.
// If this is a null-context tail region then unwind with a null-context-like descriptor
if ((ControlPc >= RF_END_ADDRESS(FunctionEntry)-(RF_NULL_CONTEXT_COUNT(FunctionEntry)*4)) &&
(ControlPc < RF_END_ADDRESS(FunctionEntry))) {
// Create the unwind function entry with PrologEndAddress = BeginAddress.
// This ensures that the prologue is not reverse executed.
UnwindFunctionEntry->BeginAddress = RF_BEGIN_ADDRESS(FunctionEntry);
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(FunctionEntry);
UnwindFunctionEntry->ExceptionHandler = 0;
UnwindFunctionEntry->HandlerData = 0;
UnwindFunctionEntry->PrologEndAddress = RF_BEGIN_ADDRESS(FunctionEntry);
return;
}
if ((FunctionEntry->PrologEndAddress < RF_BEGIN_ADDRESS(FunctionEntry)) ||
(FunctionEntry->PrologEndAddress > RF_END_ADDRESS(FunctionEntry))) {
SecondaryFunctionEntry = FunctionEntry;
FunctionEntry = (PRUNTIME_FUNCTION)RF_PROLOG_END_ADDRESS(SecondaryFunctionEntry);
#if DBG
DbgPrint("RtlGetUnwindFunctionEntry: received secondary function entry\n");
#endif
}
}
#if DBG
if (ControlPc & 0x3) {
DbgPrint("RtlGetUnwindFunctionEntry: Warning - Invalid ControlPc %lx for unwinding.\n", ControlPc);
} else if (RF_BEGIN_ADDRESS(FunctionEntry) >= RF_END_ADDRESS(FunctionEntry)) {
ShowRuntimeFunction(FunctionEntry, "RtlGetUnwindFunctionEntry: Warning - BeginAddress < EndAddress.");
} else if (FunctionEntry->PrologEndAddress < RF_BEGIN_ADDRESS(FunctionEntry)) {
ShowRuntimeFunction(FunctionEntry, "RtlGetUnwindFunctionEntry: Warning - PrologEndAddress < BeginAddress.");
} else if (FunctionEntry->PrologEndAddress > RF_END_ADDRESS(FunctionEntry)) {
ShowRuntimeFunction(FunctionEntry, "RtlGetUnwindFunctionEntry: Warning - PrologEndAddress > EndAddress.");
}
#endif
// FunctionEntry is now the primary function entry and if SecondaryFunctionEntry is
// not NULL then it is the secondary function entry that contains the ControlPC. Setup a
// copy of the FunctionEntry suitable for unwinding. By default use the supplied FunctionEntry.
if (SecondaryFunctionEntry) {
// Extract the secondary function entry type.
EntryType = RF_ENTRY_TYPE(SecondaryFunctionEntry);
if (EntryType == RF_NOT_CONTIGUOUS) {
// The exception happened in the body of the procedure but in a non-contiguous
// section of code. Regardless of what entry point was used, it is normally valid
// to unwind using the primary entry point prologue. The only exception is when an
// alternate prologue is specified However, there may be an
// alternate prologue end addresss specified in which case unwind using this
// block as though it were the primary.
AlternateProlog = RF_ALT_PROLOG(SecondaryFunctionEntry);
if ((AlternateProlog >= RF_BEGIN_ADDRESS(SecondaryFunctionEntry)) &&
(AlternateProlog < RF_END_ADDRESS(SecondaryFunctionEntry))) {
// If the control PC is in the alternate prologue, use the secondary.
// The control Pc is not in procedure context.
if ((ControlPc >= RF_BEGIN_ADDRESS(SecondaryFunctionEntry)) &&
(ControlPc < AlternateProlog)) {
UnwindFunctionEntry->BeginAddress = RF_BEGIN_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->ExceptionHandler = 0;
UnwindFunctionEntry->HandlerData = 0;
UnwindFunctionEntry->PrologEndAddress = AlternateProlog;
return;
}
}
// Fall out of the if statement to pick up the primary function entry below.
// This code is in-procedure-context and subject to the primary's prologue
// and exception handlers.
} else if (EntryType == RF_ALT_ENT_PROLOG) {
// Exception occured in an alternate entry point prologue.
// Use the secondary function entry with a fixed-up PrologEndAddress.
UnwindFunctionEntry->BeginAddress = RF_BEGIN_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->ExceptionHandler = 0;
UnwindFunctionEntry->HandlerData = 0;
UnwindFunctionEntry->PrologEndAddress = RF_END_ADDRESS(UnwindFunctionEntry);
// Check for an alternate prologue.
AlternateProlog = RF_ALT_PROLOG(SecondaryFunctionEntry);
if (AlternateProlog >= UnwindFunctionEntry->BeginAddress &&
AlternateProlog < UnwindFunctionEntry->EndAddress ) {
// The prologue is only part of the procedure
UnwindFunctionEntry->PrologEndAddress = AlternateProlog;
}
return;
} else if (EntryType == RF_NULL_CONTEXT) {
// Exception occured in null-context code associated with a primary function.
// Use the secondary function entry with a PrologEndAddress = BeginAddress.
// There is no prologue for null-context code.
*StackAdjust = RF_STACK_ADJUST(SecondaryFunctionEntry);
UnwindFunctionEntry->BeginAddress = RF_BEGIN_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(SecondaryFunctionEntry);
UnwindFunctionEntry->ExceptionHandler = 0;
UnwindFunctionEntry->HandlerData = 0;
UnwindFunctionEntry->PrologEndAddress = RF_BEGIN_ADDRESS(SecondaryFunctionEntry);
return;
}
}
// Use the primary function entry
*UnwindFunctionEntry = *FunctionEntry;
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(UnwindFunctionEntry); // Remove null-context count
// If the primary has a fixed return address, pull that out now.
if (RF_IS_FIXED_RETURN(FunctionEntry)) {
*FixedReturn = RF_FIXED_RETURN(FunctionEntry);
UnwindFunctionEntry->ExceptionHandler = 0;
UnwindFunctionEntry->HandlerData = 0;
}
// If the ControlPc is in the primary Null context, return null context.
// Otherwise, remove Null context count
if ((ControlPc >= RF_END_ADDRESS(FunctionEntry)-(RF_NULL_CONTEXT_COUNT(FunctionEntry)*4)) &&
(ControlPc < RF_END_ADDRESS(FunctionEntry))) {
// Exception occured in null-context code associated with a primary function.
// Create the unwind function entry with a PrologEndAddress = BeginAddress.
// This ensures that the prologue is not reverse executed.
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(FunctionEntry);
UnwindFunctionEntry->ExceptionHandler = 0;
UnwindFunctionEntry->HandlerData = 0;
UnwindFunctionEntry->PrologEndAddress = RF_BEGIN_ADDRESS(FunctionEntry);
}
else {
UnwindFunctionEntry->EndAddress = RF_END_ADDRESS(FunctionEntry);
}
}
#if !defined(NTOS_KERNEL_RUNTIME)
PLIST_ENTRY
RtlGetFunctionTableListHead (
VOID
)
/*++
Routine Description:
Return the address of the dynamic function table list head.
Return value:
Address of dynamic function table list head.
--*/
{
return &DynamicFunctionTable;
}
BOOLEAN
RtlAddFunctionTable(
IN PRUNTIME_FUNCTION FunctionTable,
IN ULONG EntryCount
)
/*++
Routine Description:
Add a dynamic function table to the dynamic function table list. Dynamic
function tables describe code generated at run-time. The dynamic function
tables are searched via a call to RtlLookupDynamicFunctionEntry().
Normally this is only invoked via calls to RtlLookupFunctionEntry().
The FunctionTable entries need not be sorted in any particular order. The
list is scanned for a Min and Max address range and whether or not it is
sorted. If the latter RtlLookupDynamicFunctionEntry() uses a binary
search, otherwise it uses a linear search.
The dynamic function entries will be searched only after a search
through the static function entries associated with all current
process images has failed.
Arguments:
FunctionTable Address of an array of function entries where
each element is of type RUNTIME_FUNCTION.
EntryCount The number of function entries in the array
Return value:
TRUE if RtlAddFunctionTable completed successfully
FALSE if RtlAddFunctionTable completed unsuccessfully
--*/
{
PDYNAMIC_FUNCTION_TABLE pNew;
PRUNTIME_FUNCTION FunctionEntry;
ULONG i;
if (EntryCount == 0)
return FALSE;
// Make sure the link list is initialized;
if (DynamicFunctionTable.Flink == NULL) {
InitializeListHead(&DynamicFunctionTable);
}
// Allocate memory for this link list entry
pNew = RtlAllocateHeap( RtlProcessHeap(), 0, sizeof(DYNAMIC_FUNCTION_TABLE) );
if (pNew != NULL) {
pNew->FunctionTable = FunctionTable;
pNew->EntryCount = EntryCount;
NtQuerySystemTime( &pNew->TimeStamp );
// Scan the function table for Minimum/Maximum and to determine
// if it is sorted. If the latter, we can perform a binary search.
FunctionEntry = FunctionTable;
pNew->MinimumAddress = RF_BEGIN_ADDRESS(FunctionEntry);
pNew->MaximumAddress = RF_END_ADDRESS(FunctionEntry);
pNew->Sorted = TRUE;
FunctionEntry++;
for (i = 1; i < EntryCount; FunctionEntry++, i++) {
if (pNew->Sorted && FunctionEntry->BeginAddress < FunctionTable[i-1].BeginAddress) {
pNew->Sorted = FALSE;
}
if (FunctionEntry->BeginAddress < pNew->MinimumAddress) {
pNew->MinimumAddress = RF_BEGIN_ADDRESS(FunctionEntry);
}
if (FunctionEntry->EndAddress > pNew->MaximumAddress) {
pNew->MaximumAddress = RF_END_ADDRESS(FunctionEntry);
}
}
// Insert the new entry in the dynamic function table list.
// Protect the insertion with the loader lock.
RtlEnterCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
InsertTailList((PLIST_ENTRY)&DynamicFunctionTable, (PLIST_ENTRY)pNew);
RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
return TRUE;
} else {
return FALSE;
}
}
BOOLEAN
RtlDeleteFunctionTable (
IN PRUNTIME_FUNCTION FunctionTable
)
{
/*++
Routine Description:
Remove a dynamic function table from the dynamic function table list.
Arguments:
FunctionTable Address of an array of function entries that
was passed in a previous call to RtlAddFunctionTable
Return Value
TRUE - If function completed successfully
FALSE - If function completed unsuccessfully
--*/
PDYNAMIC_FUNCTION_TABLE CurrentEntry;
PLIST_ENTRY Head;
PLIST_ENTRY Next;
BOOLEAN Status = FALSE;
RtlEnterCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
// Search the dynamic function table list for a match on the the function
// table address.
Head = &DynamicFunctionTable;
for (Next = Head->Blink; Next != Head; Next = Next->Blink) {
CurrentEntry = CONTAINING_RECORD(Next,DYNAMIC_FUNCTION_TABLE,Links);
if (CurrentEntry->FunctionTable == FunctionTable) {
RemoveEntryList((PLIST_ENTRY)CurrentEntry);
RtlFreeHeap( RtlProcessHeap(), 0, CurrentEntry );
Status = TRUE;
break;
}
}
RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
return Status;
}
PRUNTIME_FUNCTION RtlLookupDynamicFunctionEntry(IN ULONG_PTR ControlPc)
/*++
Routine Description:
This function searches through the dynamic function entry tables and returns the function entry address that corresponds to the specified ControlPc.
This routine does NOT perform the secondary function entry indirection.
That is performed by RtlLookupFunctionEntry().
Argument:
ControlPc Supplies a ControlPc.
Return Value
NULL - No function entry found that contains the ControlPc.
NON-NULL - Address of the function entry that describes the code containing ControlPC.
--*/
{
PDYNAMIC_FUNCTION_TABLE CurrentEntry;
PLIST_ENTRY Next,Head;
PRUNTIME_FUNCTION FunctionTable;
PRUNTIME_FUNCTION FunctionEntry;
LONG High;
LONG Low;
LONG Middle;
if (DynamicFunctionTable.Flink == NULL)
return NULL;
if (RtlTryEnterCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock) ) {
// Search the tree starting from the head, continue until the entry is found or we reach the end of the list.
Head = &DynamicFunctionTable;
for (Next = Head->Blink; Next != Head; Next = Next->Blink) {
CurrentEntry = CONTAINING_RECORD(Next,DYNAMIC_FUNCTION_TABLE,Links);
FunctionTable = CurrentEntry->FunctionTable;
// Check if the ControlPC is within the range of this function table
if ((ControlPc >= CurrentEntry->MinimumAddress) && (ControlPc < CurrentEntry->MaximumAddress) ) {
// If this function table is sorted do a binary search.
if (CurrentEntry->Sorted) {
// Perform binary search on the function table for a function table entry that subsumes the specified PC.
Low = 0;
High = CurrentEntry->EntryCount -1 ;
while (High >= Low) {
// Compute next probe index and test entry. If the specified PC is greater than of equal to the beginning address and less
// than the ending address of the function table entry, then return the address of the function table entry. Otherwise, continue the search.
Middle = (Low + High) >> 1;
FunctionEntry = &FunctionTable[Middle];
if (ControlPc < RF_BEGIN_ADDRESS(FunctionEntry)) {
High = Middle - 1;
} else if (ControlPc >= RF_END_ADDRESS(FunctionEntry)) {
Low = Middle + 1;
} else {
#if DBG
ShowRuntimeFunction(FunctionEntry, "RtlLookupDynamicFunctionEntry: binary search" );
#endif
RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
return FunctionEntry;
}
}
} else { // Not sorted. Do linear search.
PRUNTIME_FUNCTION LastFunctionEntry = &FunctionTable[CurrentEntry->EntryCount];
for (FunctionEntry = FunctionTable; FunctionEntry < LastFunctionEntry; FunctionEntry++) {
if ((ControlPc >= RF_BEGIN_ADDRESS(FunctionEntry)) && (ControlPc < RF_END_ADDRESS(FunctionEntry))) {
#if DBG
ShowRuntimeFunction(FunctionEntry, "RtlLookupDynamicFunctionEntry: linear search" );
#endif
RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
return FunctionEntry;
}
}
} // binary/linear search
} // if in range
} // for (... Next != Head ...)
RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
} // LoaderLock
return NULL;
}
#endif
#if DBG
void ShowRuntimeFunction( PRUNTIME_FUNCTION FunctionEntry, PSTR Label )
{
BOOLEAN Secondary;
BOOLEAN FixedReturn;
ULONG EntryType;
ULONG NullCount;
if (!(RtlDebugFlags & RTL_DBG_FUNCTION_ENTRY_DETAIL)) {
return;
}
if (FunctionEntry) {
Secondary = FALSE;
FixedReturn = FALSE;
EntryType = 0;
NullCount = 0;
DbgPrint(" %lx: %s\n", FunctionEntry, Label );
if ((RF_PROLOG_END_ADDRESS(FunctionEntry) < RF_BEGIN_ADDRESS(FunctionEntry)) || (RF_PROLOG_END_ADDRESS(FunctionEntry) > RF_END_ADDRESS(FunctionEntry))) {
Secondary = TRUE;
EntryType = RF_ENTRY_TYPE(FunctionEntry);
} else if (RF_IS_FIXED_RETURN(FunctionEntry)) {
FixedReturn = TRUE;
}
NullCount = RF_NULL_CONTEXT_COUNT(FunctionEntry);
DbgPrint(" BeginAddress = %lx\n", FunctionEntry->BeginAddress);
DbgPrint(" EndAddress = %lx", FunctionEntry->EndAddress);
if (NullCount) {
DbgPrint(" %d null-context instructions", NullCount);
}
DbgPrint("\n");
DbgPrint(" ExceptionHandler = %lx", FunctionEntry->ExceptionHandler);
if (FunctionEntry->ExceptionHandler != NULL) {
if (Secondary) {
ULONG_PTR AlternateProlog = RF_ALT_PROLOG(FunctionEntry);
switch( EntryType ) {
case RF_NOT_CONTIGUOUS:
case RF_ALT_ENT_PROLOG:
if ((AlternateProlog >= RF_BEGIN_ADDRESS(FunctionEntry)) && (AlternateProlog <= RF_END_ADDRESS(FunctionEntry))) {
DbgPrint(" alternate PrologEndAddress");
}
break;
case RF_NULL_CONTEXT:
DbgPrint(" stack adjustment");
default:
DbgPrint(" invalid entry type");
}
} else if (FixedReturn) {
DbgPrint(" fixed return address");
}
}
DbgPrint("\n");
DbgPrint(" HandlerData = %lx", FunctionEntry->HandlerData);
if (Secondary) {
DbgPrint(" type %d: ", EntryType);
if (EntryType == RF_NOT_CONTIGUOUS) DbgPrint("RF_NOT_CONTIGUOUS");
else if (EntryType == RF_ALT_ENT_PROLOG) DbgPrint("RF_ALT_ENT_PROLOG");
else if (EntryType == RF_NULL_CONTEXT) DbgPrint("RF_NULL_CONTEXT");
else DbgPrint("***INVALID***");
}
DbgPrint("\n");
DbgPrint(" PrologEndAddress = %lx\n", FunctionEntry->PrologEndAddress );
}
}
#endif