Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2496 lines
71 KiB

/*++
Copyright (c) 1993-2001 Microsoft Corporation
Module Name:
walkarm.c
Abstract:
This file implements the ARM stack walking api.
Author:
Wesley Witt (wesw) 1-Oct-1993
Glenn Hirschowitz Jan-1995
Janet Schneider 17-March-1997
Robert Denkewalter Jan-1999
Added Thumb unwinder, modified WalkArm, added caching,
split FunctionTableAccessOrTranslateAddress, added global hProcess, etc.,
Environment:
User Mode
--*/
#define TARGET_ARM
#define _IMAGEHLP_SOURCE_
//#define _CROSS_PLATFORM_
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include "private.h"
#define NOEXTAPI
#include "wdbgexts.h"
#include "ntdbg.h"
#include "symbols.h"
#include "fecache.hpp"
#include "globals.h"
#include <arminst.h>
#define MODE_ARM 0
#define MODE_THUMB 1
#define REGISTER_SIZE 4
//
// XXX drewb - Need to provide a real implementation.
//
DWORD
WceTranslateAddress(HANDLE hpid,
HANDLE htid,
DWORD addrIn,
DWORD dwStackFrameAddr,
DWORD * pdwNewStackFrameAddr)
{
*pdwNewStackFrameAddr = 0;
return addrIn;
}
//
// Conspicuously absent from VC6's nt headers.
//
#define STRI_LR_SPU_MASK 0x073de000L // Load or Store of LR with stack update
#define STRI_LR_SPU_INSTR 0x052de000L // Store LR (with immediate offset, update SP)
#if DBG
// This is our local version of "assert."
#define TestAssumption(c, m) assert(c)
#else
#define TestAssumption(c, m)
#endif
// If the Thumb unwinder handles the unwind,
// it'll return UNWIND_HANDLED, so that the
// ARM unwinder will not bother...
#define UNWIND_NOT_HANDLED 0
#define UNWIND_HANDLED 1
// Face it: Many of the routines in this module require these routines
// and variables. Further, WalkArm is the only way into this module.
// Just let WalkArm initialize these on every pass, and then we don't
// have to keep passing them around. Further, we can build nice wrappers
// around them to make them easier to use.
//
// XXX drewb - However, this isn't thread-safe and technically
// is broken. It's not important enough to fix right now.
// A similar approach with putting the variables in TLS data would
// be a simple fix and would preserve the convenience. It should
// probably be built into StackWalk64 itself so that it's available
// by default in all walkers.
static HANDLE UW_hProcess;
static HANDLE UW_hThread;
static PREAD_PROCESS_MEMORY_ROUTINE64 UW_ReadMemory;
static PFUNCTION_TABLE_ACCESS_ROUTINE64 UW_FunctionTableAccess;
// We do this enough to make a special routine for it.
static BOOL
LoadWordIntoRegister(ULONG StartAddr, LPDWORD pRegister)
{
BOOL rc;
ULONG cb;
rc = UW_ReadMemory( UW_hProcess, (ULONG64)(LONG)StartAddr,
(LPVOID)pRegister, REGISTER_SIZE, &cb );
if (!rc || (cb != REGISTER_SIZE)) {
return FALSE;
} else {
return rc;
}
}
static BOOL
ReadMemory(ULONG StartAddr, ULONG Size, LPVOID Buffer)
{
BOOL rc;
ULONG cb;
rc = UW_ReadMemory( UW_hProcess, (ULONG64)(LONG)StartAddr,
(LPVOID)Buffer, Size, &cb );
if (!rc || (cb != Size)) {
return FALSE;
} else {
return rc;
}
}
#define EXCINFO_NULL_HANDLER 0
// For ARM, prolog helpers have HandlerData == 1, not 2 as in MIPS
#define EXCINFO_PROLOG_HELPER 1
// For ARM, epilog helpers have HandlerData == 2, not 3 as in MIPS
#define EXCINFO_EPILOG_HELPER 2
#define IS_HELPER_FUNCTION(rfe) \
( \
(rfe)? \
( \
((rfe)->ExceptionHandler==EXCINFO_NULL_HANDLER) && \
( ((rfe)->HandlerData==EXCINFO_PROLOG_HELPER) || \
((rfe)->HandlerData==EXCINFO_EPILOG_HELPER) \
) \
): \
FALSE \
)
#define IS_PROLOG_HELPER_FUNCTION(rfe) \
( \
(rfe)? \
( \
((rfe)->ExceptionHandler==EXCINFO_NULL_HANDLER) && \
((rfe)->HandlerData==EXCINFO_PROLOG_HELPER) \
): \
FALSE \
)
#define IS_EPILOG_HELPER_FUNCTION(rfe) \
( \
(rfe)? \
( \
((rfe)->ExceptionHandler==EXCINFO_NULL_HANDLER) && \
((rfe)->HandlerData==EXCINFO_EPILOG_HELPER) \
): \
FALSE \
)
static int
ThumbVirtualUnwind (DWORD,PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY,
PARM_CONTEXT,DWORD*);
static BOOL
WalkArmGetStackFrame(
LPDWORD ReturnAddress,
LPDWORD FramePointer,
PARM_CONTEXT Context,
int * Mode
);
static VOID
WalkArmDataProcess(
ARMI instr,
PULONG Register
);
static BOOL
WalkArmLoadI(
ARMI instr,
PULONG Register
);
static BOOL
WalkArmLoadMultiple(
PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY FunctionEntry,
ARMI instr,
PULONG Register
);
static BOOL
CheckConditionCodes(
ULONG CPSR,
DWORD instr
);
//
// The saved registers are the permanent general registers (ie. those
// that get restored in the epilog) R4-R11 R13-R15
//
#define SAVED_REGISTER_MASK 0x0000eff0 /* saved integer registers */
#define IS_REGISTER_SAVED(Register) ((SAVED_REGISTER_MASK >> Register) & 1L)
BOOL
WalkArm(
HANDLE hProcess,
HANDLE hThread,
LPSTACKFRAME64 StackFrame,
PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE64 GetModuleBase
)
{
static DWORD PrevFramePC;
static ARM_CONTEXT SavedContext;
BOOL rval;
PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY Rfe;
IMAGE_ARM_RUNTIME_FUNCTION_ENTRY LoopRFE;
DWORD TempPc, TempFp;
PARM_CONTEXT Context = (PARM_CONTEXT)ContextRecord;
// Initialize the module's "global" variables and routines:
UW_hProcess = hProcess;
UW_hThread = hThread;
UW_ReadMemory = ReadMemoryRoutine;
UW_FunctionTableAccess = FunctionTableAccessRoutine;
// This way of unwinding differs from the other, "old" ways of unwinding.
// It removes duplicate code from WalkArmInit and WalkArmNext, and it saves
// the previous stack frame so that each stack frame is unwound only once.
rval = TRUE;
do {
Rfe = (PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY)
UW_FunctionTableAccess( UW_hProcess, Context->Pc );
if (Rfe) {
LoopRFE = *Rfe;
} else {
ZeroMemory(&LoopRFE, sizeof(LoopRFE));
}
// If this is the first pass in the unwind,
// fill in the SavedContext from the
// Context passed in, and initialize the StackFrame fields
if (!StackFrame->Virtual) {
ZeroMemory(StackFrame, sizeof(*StackFrame));
// Set Virtual so that next pass, we know we're not initializing
StackFrame->Virtual = TRUE;
StackFrame->AddrPC.Mode = AddrModeFlat;
StackFrame->AddrFrame.Mode = AddrModeFlat;
StackFrame->AddrReturn.Mode = AddrModeFlat;
PrevFramePC = 0;
SavedContext = *Context;
}
// Use the context we saved from last time (or just initialized)
// to set the previous frame.
*Context = SavedContext;
DWORD dwNewSp = 0;
StackFrame->AddrPC.Offset =
WceTranslateAddress(UW_hProcess, UW_hThread,
Context->Pc, Context->Sp, &dwNewSp);
if (dwNewSp) {
Context->Sp = dwNewSp;
}
StackFrame->AddrFrame.Offset = Context->Sp;
// PC == 0 means unwinding is finished.
if ( StackFrame->AddrPC.Offset != 0x0 ) {
int Mode;
PrevFramePC = TempPc = SavedContext.Pc;
TempFp = SavedContext.Sp;
// We already have the frame we want to return, except for one
// little detail. We want the return address from this frame.
// So, we unwind it. This unwinding actually yields the
// previous frame. We don't want to duplicate this effort
// next time, so we'll save the results.
if (WalkArmGetStackFrame(&TempPc, &TempFp, &SavedContext, &Mode)) {
SavedContext.Pc = TempPc;
TestAssumption(TempFp == SavedContext.Sp, "FP wrong2");
StackFrame->AddrReturn.Offset = SavedContext.Pc;
StackFrame->Params[0] = SavedContext.R0;
StackFrame->Params[1] = SavedContext.R1;
StackFrame->Params[2] = SavedContext.R2;
StackFrame->Params[3] = SavedContext.R3;
} else {
// No place to return to...
StackFrame->AddrReturn.Offset = 0;
// ...and the saved context probably has a bogus PC.
SavedContext.Pc = 0;
rval = FALSE;
}
} else {
rval = FALSE;
}
} while (IS_HELPER_FUNCTION(&LoopRFE) && rval);
if (rval) {
StackFrame->FuncTableEntry =
UW_FunctionTableAccess(UW_hProcess, StackFrame->AddrPC.Offset);
}
return rval;
}
static DWORD
ArmVirtualUnwind (
DWORD ControlPc,
PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY FunctionEntry,
PARM_CONTEXT Context
)
/*++
Routine Description:
This function virtually unwinds the specfified function by executing its
prologue code backwards (or its epilog forward).
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.
Arguments:
ControlPc - Supplies the address where control left the specified
function.
FunctionEntry - Supplies the address of the function table entry for the
specified function.
Context - Supplies the address of a context record.
Return Value:
The address where control left the previous frame is returned as the
function value.
--*/
{
ULONG Address;
LONG cb;
DWORD EpilogPc;
BOOL ExecutingEpilog;
BOOL IsFramePointer = FALSE;
LONG i,j;
ARMI instr, instr2;
BOOL PermanentsRestored = FALSE;
ARMI Prolog[50]; // The prolog will never be more than 10 instructions
PULONG Register;
BOOL bEpiWindAlready = FALSE;
ARM_CONTEXT ContextBeforeEpiWind;
//
// NOTE: When unwinding the call stack, we assume that all instructions
// in the prolog have the condition code "Always execute." This is not
// necessarily true for the epilog.
//
if( !FunctionEntry ) {
return 0;
}
Register = &Context->R0;
if( FunctionEntry->PrologEndAddress - FunctionEntry->BeginAddress == 0 ) {
//
// No prolog, so just copy the link register into the PC and return.
//
goto CopyLrToPcAndExit;
}
//
// First check to see if we are in the epilog. If so, forward execution
// through the end of the epilog is required. Epilogs are composed of the
// following:
//
// An ldmdb which uses the frame pointer, R11, as the base and updates
// the PC.
//
// -or-
//
// A stack unlink (ADD R13, x) if necessary, followed by an ldmia which
// updates the PC or a single mov instruction to copy the link register
// to the PC
//
// -or-
//
// An ldmia which updates the link register, followed by a regular
// branch instruction. (This is an optimization when the last instruction
// before a return is a call.)
//
// A routine may also have an empty epilog. (The last instruction is to
// branch to another routine, and it doesn't modify any permanent
// registers.) But, in this case, we would also have an empty prolog.
//
// If we are in an epilog, and the condition codes dictate that the
// instructions should not be executed, treat this as not an epilog at all.
//
// backup the context before trying to execute the epilogue forward
ContextBeforeEpiWind = *Context;
EpilogPc = ControlPc;
if( EpilogPc >= FunctionEntry->PrologEndAddress ) {
//
// Check the condition code of the first instruction. If it won't be
// executed, don't bother checking what type of instruction it is.
//
if(!ReadMemory(EpilogPc, 4L, (LPVOID)&instr ))
return 0;
ExecutingEpilog = CheckConditionCodes( Register[16],
instr.instruction );
while( ExecutingEpilog ) {
if( !ReadMemory( EpilogPc, 4L, (LPVOID)&instr ))
return 0;
//
// Test for these instructions:
//
// ADD R13, X - stack unlink
//
// MOV PC, LR - return
//
// LDMIA or LDMDB including PC - update registers and return
// ( SP ) ( FP )
//
// LDMIA including LR, followed by a branch
// Update registers and branch. (In our case, return.)
//
// A branch preceded by an LDMIA including LR
// Copy the LR to the PC to get the call stack
//
if(( instr.instruction & ADD_SP_MASK ) == ADD_SP_INSTR ) {
WalkArmDataProcess( instr, Register );
} else if(( instr.instruction & MOV_PC_LR_MASK ) == MOV_PC_LR ) {
WalkArmDataProcess( instr, Register );
goto ExitReturnPc;
} else if(( instr.instruction & LDM_PC_MASK ) == LDM_PC_INSTR ) {
if( !WalkArmLoadMultiple( FunctionEntry,
instr,
Register )) {
return 0;
}
goto ExitReturnPc;
} else if(( instr.instruction & LDM_LR_MASK ) == LDM_LR_INSTR ) {
if( !ReadMemory( (ULONG)EpilogPc + 4, 4L, (LPVOID)&instr2))
return 0;
if (((instr2.instruction & B_BL_MASK ) == B_INSTR) || ((instr2.instruction & BX_MASK ) == BX_INSTR)){
if( !WalkArmLoadMultiple( FunctionEntry,
instr,
Register )) {
return 0;
}
goto CopyLrToPcAndExit;
} else {
ExecutingEpilog = FALSE;
}
} else if (((instr.instruction & B_BL_MASK ) == B_INSTR) || ((instr.instruction & BX_MASK ) == BX_INSTR)) {
if( !ReadMemory( (ULONG)EpilogPc - 4, 4L,(LPVOID)&instr2))
return 0;
if(( instr2.instruction & LDM_LR_MASK ) == LDM_LR_INSTR ) {
goto CopyLrToPcAndExit;
}
} else {
ExecutingEpilog = FALSE;
}
EpilogPc += 4;
bEpiWindAlready = TRUE;
}
}
//
// We were not in the epilog. Load in the prolog, and reverse execute it.
//
if (bEpiWindAlready)
{
// if we have already winded some instructions that we
// thought to be the epilog, we may have mess up with the context
// restore the initial context
*Context = ContextBeforeEpiWind;
}
cb = FunctionEntry->PrologEndAddress - FunctionEntry->BeginAddress;
if( cb > sizeof( Prolog )) {
assert( FALSE ); // The prolog should never be more than 10 instructions
return 0;
}
if( !ReadMemory( FunctionEntry->BeginAddress, cb, (LPVOID)Prolog))
return 0;
//
// Check to see if we're already in the prolog.
//
if( ControlPc < FunctionEntry->PrologEndAddress ) {
cb -= ( FunctionEntry->PrologEndAddress - ControlPc );
}
//
// Reverse execute starting with the last instruction in the prolog
// that has been executed.
//
i = cb/4;
i--;
while( i >= 0 ) {
if(( Prolog[i].instruction & DATA_PROC_MASK ) == DP_R11_INSTR ) {
//
// We have a frame pointer.
//
IsFramePointer = TRUE;
} else if((( Prolog[i].instruction & SUB_SP_MASK ) == SUB_SP_INSTR) ||
(( Prolog[i].instruction & ADD_SP_MASK ) == ADD_SP_INSTR)
) {
//
// This is a stack link. Unlink the stack.
//
if(( Prolog[i].dataproc.bits != 0x1 ) &&
( Prolog[i].dpshi.rm == 0xc )) {
//
// Look for an LDR instruction above this one.
//
j = i - 1;
while(( j >= 0 ) &&
(( Prolog[j].instruction & LDR_MASK ) != LDR_PC_INSTR )) {
j--;
}
if( j < 0 ) {
assert( FALSE ); // This should never happen
return 0;
}
//
// Get the address of the ldr instruction + 8 + the offset
//
Address = (( j*4 + FunctionEntry->BeginAddress ) +
Prolog[j].ldr.offset ) + 8;
//
// R12 is the value at that location.
//
if( !LoadWordIntoRegister(Address, &Register[12] ))
return 0;
}
//
// Change the subtract to an add (or the add to a subtract)
// and execute the instruction
//
if( Prolog[i].dataproc.opcode == OP_SUB ) {
Prolog[i].dataproc.opcode = OP_ADD;
} else {
Prolog[i].dataproc.opcode = OP_SUB;
}
WalkArmDataProcess( Prolog[i], Register );
} else if(( Prolog[i].instruction & STM_MASK ) == STM_INSTR ) {
if( Prolog[i].ldm.reglist & 0x7ff0 ) { // Check for permanent regs
//
// This is the instruction that stored all of the permanent
// registers. Change the reglist to update R13 (SP) instead of
// R12, and change the STMDB to an LDMDB of R11 or an LDMIA of
// R13.
// Note: We are restoring R14 (LR) - so we'll need to copy
// it to the PC at the end of this function.
//
if(( Prolog[i].ldm.reglist & 0x1000 ) &&
!( Prolog[i].ldm.reglist & 0x2000 )) {
Prolog[i].ldm.reglist &= 0xefff; // Mask out R12
Prolog[i].ldm.reglist |= 0x2000; // Add in R13 (SP)
}
if(( Prolog[i].ldm.reglist & 0x2000 ) || IsFramePointer ) {
Prolog[i].ldm.w = 0; // Clear write-back bit.
}
Prolog[i].ldm.l = 1; // Change to a load.
if( IsFramePointer ) {
Prolog[i].ldm.u = 0; // Decrement
Prolog[i].ldm.p = 1; // Before
Prolog[i].ldm.rn = 0xb; // R11
} else {
Prolog[i].ldm.u = 1; // Increment
Prolog[i].ldm.p = 0; // After
Prolog[i].ldm.rn = 0xd; // R13 (Stack pointer)
}
if( !WalkArmLoadMultiple( FunctionEntry,
Prolog[i],
Register )) {
return 0;
}
PermanentsRestored = TRUE;
} else if( !PermanentsRestored ) {
//
// This is the instruction to load the arguments. Reverse
// execute this instruction only if the permanent registers
// have not been restored.
//
Prolog[i].ldm.l = 1; // Change to a load.
Prolog[i].ldm.u = 1; // Increment
Prolog[i].ldm.p = 0; // After
if( !WalkArmLoadMultiple( FunctionEntry,
Prolog[i],
Register )) {
return 0;
}
}
//
// ajtuck 12/21/97 - added changes from Arm compiler team's unwind.c per jls
//
} else if ((Prolog[i].instruction & STRI_LR_SPU_MASK) == STRI_LR_SPU_INSTR) {
// Store of the link register that updates the stack as a base
// register must be reverse executed to restore LR and SP
// to their values on entry. This type of prolog is generated
// for finally funclets.
Prolog[i].ldr.l = 1; // Clange to a load.
// Since we are updating the base register, we need to change
// when the offset is added to reverse execute the store.
if (Prolog[i].ldr.p == 1)
Prolog[i].ldr.p = 0;
else
Prolog[i].ldr.p = 1;
// And negate the offset
if (Prolog[i].ldr.u == 1)
Prolog[i].ldr.u = 0;
else
Prolog[i].ldr.u = 1;
if (!WalkArmLoadI(Prolog[i], Register))
return ControlPc;
// NOTE: Code could be added above to execute the epilog which
// in this case would be a load to the PC that updates SP as
// the base register. Since the epilog will always be only one
// instruction in this case it is not needed since reverse
// executing the prolog will have the same result.
}
i--;
}
//
// Move the link register into the PC and return.
//
CopyLrToPcAndExit:
// To continue unwinding, put the Link Register into
// the Program Counter slot and carry on; stopping
// when the PC says 0x0. However, the catch is that
// for ARM the Link Register at the bottom of the
// stack says 0x4, not 0x0 as we expect.
// So, do a little dance to take an LR of 0x4
// and turn it into a PC of 0x0 to stop the unwind.
// -- stevea 10/7/99.
if( Register[14] != 0x4 )
Register[15] = Register[14];
else
Register[15] = 0x0;
ExitReturnPc:
return Register[15];
}
BOOL
WalkArmGetStackFrame(
LPDWORD ReturnAddress,
LPDWORD FramePointer,
PARM_CONTEXT Context,
int * Mode
)
{
PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY Rfe;
IMAGE_ARM_RUNTIME_FUNCTION_ENTRY PcFe;
DWORD dwRa;
if (Mode) {
*Mode = MODE_ARM;
}
TestAssumption(*ReturnAddress == Context->Pc, "WAGSF:SF-Cxt mismatch!");
Rfe = (PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY)
UW_FunctionTableAccess(UW_hProcess, *ReturnAddress);
if (!Rfe) {
// For leaf functions, just return the Lr as the return Address.
dwRa = Context->Pc = Context->Lr;
} else {
PcFe = *Rfe;
if (ThumbVirtualUnwind (*ReturnAddress, &PcFe, Context,
&dwRa) == UNWIND_HANDLED) {
// The thumb unwinder handled it.
if (Mode) {
*Mode = MODE_THUMB;
}
} else {
// Now, let the ARM unwinder try it.
dwRa = ArmVirtualUnwind( *ReturnAddress, &PcFe, Context );
}
}
DWORD dwNewSp = 0;
*ReturnAddress = WceTranslateAddress(UW_hProcess, UW_hThread,
dwRa, Context->Sp, &dwNewSp);
if (dwNewSp)
{
Context->Sp = dwNewSp;
}
*FramePointer = Context->Sp;
return TRUE;
}
static VOID
WalkArmDataProcess(
ARMI instr,
PULONG Register
)
/*++
Routine Description:
This function executes an ARM data processing instruction using the
current register set. It automatically updates the destination register.
Arguments:
instr The ARM 32-bit instruction
Register Pointer to the ARM integer registers.
Return Value:
None.
--*/
{
ULONG Op1, Op2;
ULONG Cflag = (Register[16] & 0x20000000L) == 0x20000000L; // CPSR
ULONG shift;
//
// We are checking for all addressing modes and op codes, even though
// the prolog and epilog don't use them all right now. In the future,
// more instructions and addressing modes may be added.
//
//
// Figure out the addressing mode (there are 11 of them), and get the
// operands.
//
Op1 = Register[ instr.dataproc.rn ];
if( instr.dataproc.bits == 0x1 ) {
//
// Immediate addressing - Type 1
//
Op2 = _lrotr( instr.dpi.immediate,
instr.dpi.rotate * 2 );
} else {
//
// Register addressing - start by getting the value of Rm.
//
Op2 = Register[ instr.dpshi.rm ];
if( instr.dprre.bits == 0x6 ) {
//
// Rotate right with extended - Type 11
//
Op2 = ( Cflag << 31 ) | ( Op2 >> 1 );
} else if( instr.dataproc.operand2 & 0x10 ) {
//
// Register shifts. Types 4, 6, 8, and 10
//
//
// Get the shift value from the least-significant byte of the
// shift register.
//
shift = Register[ instr.dpshr.rs ];
shift &= 0xff;
switch( instr.dpshr.bits ) {
case 0x1: // 4 Logical shift left by register
if( shift >= 32 ) {
Op2 = 0;
} else {
Op2 = Op2 << shift;
}
break;
case 0x3: // 6 Logical shift right by register
if( shift >= 32 ) {
Op2 = 0;
} else {
Op2 = Op2 >> shift;
}
break;
case 0x5: // 8 Arithmetic shift right by register
if( shift >= 32 ) {
if( Op2 & 0x80000000 ) {
Op2 = 0xffffffff;
} else {
Op2 = 0;
}
} else {
Op2 = (LONG)Op2 >> shift;
}
break;
case 0x7: // 10 Rotate right by register
if( !( shift == 0 ) && !(( shift & 0xf ) == 0 ) ) {
Op2 = _lrotl( Op2, shift );
}
default:
break;
}
} else {
//
// Immediate shifts. Types 2, 3, 5, 7, and 9
//
//
// Get the shift value from the instruction.
//
shift = instr.dpshi.shift;
switch( instr.dpshi.bits ) {
case 0x0: // 2,3 Register, Logical shift left by immediate
if( shift != 0 ) {
Op2 = Op2 << shift;
}
break;
case 0x2: // 5 Logical shift right by immediate
if( shift == 0 ) {
Op2 = 0;
} else {
Op2 = Op2 >> shift;
}
break;
case 0x4: // 7 Arithmetic shift right by immediate
if( shift == 0 ) {
Op2 = 0;
} else {
Op2 = (LONG)Op2 >> shift;
}
break;
case 0x6: // 9 Rotate right by immediate
Op2 = _lrotl( Op2, shift );
break;
default:
break;
}
}
}
//
// Determine the result (the new PC), based on the opcode.
//
switch( instr.dataproc.opcode ) {
case OP_AND:
Register[ instr.dataproc.rd ] = Op1 & Op2;
break;
case OP_EOR:
Register[ instr.dataproc.rd ] = Op1 ^ Op2;
break;
case OP_SUB:
Register[ instr.dataproc.rd ] = Op1 - Op2;
break;
case OP_RSB:
Register[ instr.dataproc.rd ] = Op2 - Op1;
break;
case OP_ADD:
Register[ instr.dataproc.rd ] = Op1 + Op2;
break;
case OP_ADC:
Register[ instr.dataproc.rd ] = (Op1 + Op2) + Cflag;
break;
case OP_SBC:
Register[ instr.dataproc.rd ] = (Op1 - Op2) - ~Cflag;
break;
case OP_RSC:
Register[ instr.dataproc.rd ] = (Op2 - Op1) - ~Cflag;
break;
case OP_ORR:
Register[ instr.dataproc.rd ] = Op1 | Op2;
break;
case OP_MOV:
Register[ instr.dataproc.rd ] = Op2;
break;
case OP_BIC:
Register[ instr.dataproc.rd ] = Op1 & ~Op2;
break;
case OP_MVN:
Register[ instr.dataproc.rd ] = ~Op2;
break;
case OP_TST:
case OP_TEQ:
case OP_CMP:
case OP_CMN:
default:
//
// These instructions do not have a destination register.
// There is nothing to do.
//
break;
}
}
static BOOL
WalkArmLoadMultiple(
PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY FunctionEntry,
ARMI instr,
PULONG Register
)
/*++
Routine Description:
This function executes an ARM load multiple instruction.
Arguments:
FunctionEntry Supplies the address of the function table entry for the
specified function.
instr The ARM 32-bit instruction
Register Pointer to the ARM integer registers.
Return Value:
TRUE if successful, FALSE otherwise.
--*/
{
ULONG cb;
LONG i;
ULONG RegList;
PULONG Rn;
//
// Load multiple with the PC bit set. We are currently checking for all
// four addressing modes, even though decrement before and increment
// after are the only modes currently used in the epilog and prolog.
//
//
// Rn is the address at which to begin, and RegList is the bit map of which
// registers to read.
//
Rn = (PULONG)(ULONG_PTR)Register[ instr.ldm.rn ];
RegList = instr.ldm.reglist;
if( instr.ldm.p ) {
if( instr.ldm.u ) {
//
// Increment before
//
for( i = 0; i <= 15; i++ ) {
if( RegList & 0x1 ) {
Rn++;
if(!LoadWordIntoRegister((ULONG)(ULONG_PTR)Rn,&Register[i])){
return FALSE;
}
}
RegList = RegList >> 1;
}
} else {
//
// Decrement before
//
for( i = 15; i >= 0; i-- ) {
if( RegList & 0x8000 ) {
Rn--;
if( !LoadWordIntoRegister((ULONG)(ULONG_PTR)Rn,&Register[i])) {
return FALSE;
}
}
RegList = RegList << 1;
}
}
} else {
if( instr.ldm.u ) {
//
// Increment after
//
for( i = 0; i <= 15; i++ ) {
if( RegList & 0x1 ) {
if( !LoadWordIntoRegister((ULONG)(ULONG_PTR)Rn,&Register[i])) {
return FALSE;
}
Rn++;
}
RegList = RegList >> 1;
}
} else {
//
// Decrement after
//
for( i = 15; i >= 0; i-- ) {
if( RegList & 0x8000 ) {
if( !LoadWordIntoRegister((ULONG)(ULONG_PTR)Rn,&Register[i])) {
return FALSE;
}
Rn--;
}
RegList = RegList << 1;
}
}
}
if( instr.ldm.w ) {
//
// Update the base register.
//
Register[ instr.ldm.rn ] = (ULONG)(ULONG_PTR)Rn;
}
return TRUE;
}
static BOOL
WalkArmLoadI(
ARMI instr,
PULONG Register
)
/*++
Routine Description:
This function executes an ARM load instruction with an immediat offset.
Arguments:
instr The ARM 32-bit instruction
Register Pointer to the ARM integer registers.
Return Value:
TRUE if successful, FALSE otherwise.
--*/
{
LONG offset;
LONG size;
PULONG Rn;
DWORD cb;
Rn = (PULONG)(ULONG_PTR)Register[instr.ldr.rn];
offset = instr.ldr.offset;
if (instr.ldr.u == 0)
offset = -offset;
if (instr.ldr.b == 0)
size = 4;
else
size = 1;
if (instr.ldm.p) { // add offset before transfer
if( !ReadMemory( (ULONG)(ULONG_PTR)(Rn + offset), size, (LPVOID)&Register[instr.ldr.rd]))
return FALSE;
if (instr.ldr.w)
Register[instr.ldr.rn] += offset;
} else {
if( !ReadMemory( (ULONG)(ULONG_PTR)Rn, size, (LPVOID)&Register[instr.ldr.rd]))
return FALSE;
if (instr.ldr.w)
Register[instr.ldr.rn] += offset;
}
return TRUE;
}
static BOOL
CheckConditionCodes(
ULONG CPSR,
DWORD instr
)
/*++
Routine Description:
Checks the condition codes of the instruction and the values of the
condition flags in the current program status register, and determines
whether or not the instruction will be executed.
Arguments:
CPSR - The value of the Current Program Status Register.
instr - The instruction to analyze.
Return Value:
TRUE if the instruction will be executed, FALSE otherwise.
--*/
{
BOOL Execute = FALSE;
BOOL Nset = (CPSR & 0x80000000L) == 0x80000000L;
BOOL Zset = (CPSR & 0x40000000L) == 0x40000000L;
BOOL Cset = (CPSR & 0x20000000L) == 0x20000000L;
BOOL Vset = (CPSR & 0x10000000L) == 0x10000000L;
instr &= COND_MASK;
switch( instr ) {
case COND_EQ: // Z set
if( Zset ) Execute = TRUE;
break;
case COND_NE: // Z clear
if( !Zset ) Execute = TRUE;
break;
case COND_CS: // C set
if( Cset ) Execute = TRUE;
break;
case COND_CC: // C clear
if( !Cset ) Execute = TRUE;
break;
case COND_MI: // N set
if( Nset ) Execute = TRUE;
break;
case COND_PL: // N clear
if( !Nset ) Execute = TRUE;
break;
case COND_VS: // V set
if( Vset ) Execute = TRUE;
break;
case COND_VC: // V clear
if( !Vset ) Execute = TRUE;
break;
case COND_HI: // C set and Z clear
if( Cset && !Zset ) Execute = TRUE;
break;
case COND_LS: // C clear or Z set
if( !Cset || Zset ) Execute = TRUE;
break;
case COND_GE: // N == V
if(( Nset && Vset ) || ( !Nset && !Vset )) Execute = TRUE;
break;
case COND_LT: // N != V
if(( Nset && !Vset ) || ( !Nset && Vset )) Execute = TRUE;
break;
case COND_GT: // Z clear, and N == V
if( !Zset &&
(( Nset && Vset ) || ( !Nset && !Vset ))) Execute = TRUE;
break;
case COND_LE: // Z set, and N != V
if( Zset &&
(( Nset && !Vset ) || ( !Nset && Vset ))) Execute = TRUE;
break;
case COND_AL: // Always execute
Execute = TRUE;
break;
default:
case COND_NV: // Never - undefined.
assert( FALSE );
break;
}
return Execute;
}
/*
THUMB!!!
*/
typedef struct _DcfInst {
int InstNum;
union {
DWORD Auxil;
DWORD Rd;
};
union {
DWORD Aux2;
DWORD Rs;
};
} DcfInst;
typedef struct _DIList {
DWORD Val,Mask;
int InstNum;
DWORD RdMask;
int RdShift;
DWORD RsMask;
int RsShift;
} DIList;
DIList dilistThumb[] = {
#define DI_PUSH 0x02
#define DI_POP 0x03
{0xB400,0xFE00,DI_PUSH, 0x00FF,0,0x0100,-8}, //PUSH
{0xBC00,0xFE00,DI_POP, 0x00FF,0,0x0100,-8}, //POP
#define DI_DECSP 0x04
#define DI_INCSP 0x05
{0xB080,0xFF80,DI_DECSP, 0x007F,2,0x0000,0}, //DecSP
{0xB000,0xFF80,DI_INCSP, 0x007F,2,0x0000,0}, //IncSP
#define DI_MOVHI 0x08
#define DI_ADDHI 0x09
{0x4600,0xFF00,DI_MOVHI, 0x0007,0,0x0078,-3}, //MovHiRegs
{0x4400,0xFF00,DI_ADDHI, 0x0007,0,0x0078,-3}, //AddHiRegs
#define DI_BLPFX 0x10
#define DI_BL 0x11
{0xF000,0xF800,DI_BLPFX, 0x07FF,12,0x0000,0}, //BL prefix
{0xF800,0xF800,DI_BL, 0x07FF,1,0x0000,0}, //BL
#define DI_BX_TMB 0x20
{0x4700,0xFF87,DI_BX_TMB, 0x0078,-3,0x0000,0}, //BX
#define DI_LDRPC 0x40
{0x4800,0xF800,DI_LDRPC, 0x0700,-8,0x00FF,2}, //LDR pc
#define DI_NEG 0x80
{0x4240,0xFFC0,DI_NEG, 0x0007,0,0x0038,-3}, //Neg Rx,Ry
{0x0000,0x0000,0x00, 0x0000,0,0x0000,0} //End of list
};
DIList dilistARM[] = {
#define DI_STMDB 0x102
#define DI_LDMIA 0x103
{0xE92D0000,0xFFFF0000,DI_STMDB, 0x0000FFFF,0,0x00000000,0}, // STMDB
{0xE8BD0000,0xFFFF0000,DI_LDMIA, 0x0000FFFF,0,0x00000000,0}, // LDMIA
#define DI_BX_ARM 0x120
{0x012FFF10,0x0FFFFFF0,DI_BX_ARM, 0x0000000F,0,0x00000000,0}, // BX_ARM
{0x00000000,0x00000000,0, 0x00000000,0,0x00000000,0} // end of list
};
static int DecipherInstruction(DWORD inst, DcfInst *DI, int Mode)
{
int i;
DIList *dl = dilistThumb;
assert(DI);
if(!DI) return 0;
memset(DI,0,sizeof(DcfInst));
if(Mode==MODE_ARM) dl = dilistARM;
for(i=0;dl[i].Mask!=0 && DI->InstNum==0; i++) {
if((inst&dl[i].Mask)==dl[i].Val) {
DI->InstNum = dl[i].InstNum;
DI->Rd = (inst&dl[i].RdMask);
if(DI->Rd && dl[i].RdShift) {
if(dl[i].RdShift>0) DI->Rd <<= dl[i].RdShift;
else if(dl[i].RdShift<0) DI->Rd >>= (-dl[i].RdShift);
}
DI->Rs = (inst&dl[i].RsMask);
if(DI->Rs && dl[i].RsShift) {
if(dl[i].RsShift>0) DI->Rs <<= dl[i].RsShift;
else if(dl[i].RsShift<0) DI->Rs >>= (-dl[i].RsShift);
}
// Special case to handle MovHiRegs and AddHiRegs.
if((DI->InstNum&~0x01)==8 ) {
DI->Rd |= ((inst&0x0080)>>4);
}
}
}
if(Mode==MODE_ARM) return 4;
return 2; // Instructions are 2 bytes long.
}
#if 0
static DWORD
ComputeCallAddress(DWORD RetAddr, int Mode)
{
DWORD instr;
DcfInst di;
// If the caller is ARM mode, then the call address
// is always 4 less than the return address.
if(RetAddr&0x01) return RetAddr-4;
if(!ReadMemory(&instr,RetAddr,2)) return RetAddr;
DcfInst(instr,&di,Mode);
if(di.InstNum==BL_TMB)
#endif
enum
{
PushOp, // Also used for Pop operations.
AdjSpOp,
MovOp
}; // Used for operation field below
typedef struct _OpEntry {
struct _OpEntry *next;
struct _OpEntry *prev;
int Operation;
int RegNumber; // Used for Push/Pop
int SpAdj; // Used for AdjSpOp, and PushOp.
int Rd; // Used for MovOp
int Rs; // Used for MovOp
ULONG Address; // Instruction address that generated this OpEntry
} OpEntry;
typedef struct _OpList {
OpEntry *head;
OpEntry *tail;
} OpList;
static OpEntry*
MakeNewOpEntry
(
int Operation,
OpEntry* prev,
ULONG Address
)
{
ULONG size = sizeof(OpEntry);
OpEntry *oe = (OpEntry*)MemAlloc(size);
if(!oe) return NULL;
memset(oe,0,sizeof(OpEntry));
oe->prev = prev;
oe->Operation = Operation;
oe->Address = Address;
return oe;
}
static void
FreeOpList(OpList* ol)
{
OpEntry *oe;
OpEntry *next;
if(!ol)return;
for(oe=ol->head;oe;oe=next){
next = oe->next;
memset(oe,0xCA,sizeof(OpEntry));
MemFree(oe);
}
ol->head = ol->tail = NULL;
}
static void
BuildOnePushPopOp
(
OpList* pOL,
int RegNum,
ULONG Address
)
{
if(pOL->head == NULL) {
OpEntry* Entry = MakeNewOpEntry(PushOp,NULL,Address);
if (!Entry) {
return;
}
pOL->head = pOL->tail = Entry;
} else {
OpEntry* Entry = MakeNewOpEntry(PushOp,pOL->tail,Address);
if (!Entry) {
return;
}
pOL->tail->next = Entry;
pOL->tail = pOL->tail->next;
}
pOL->tail->RegNumber = RegNum;
pOL->tail->SpAdj = REGISTER_SIZE;
}
// PushLR is only for use by Thumb PUSH op, and should be 0
// for ARM STMDB op.
static int
BuildPushOp
(
OpList* pOL,
DWORD PushList,
DWORD PushLR,
ULONG Address
)
{
int RegNum;
int cop = 0;
if(PushList==0 && PushLR==0) return 0;
DWORD RegMask = 0x8000;
if(PushLR){ BuildOnePushPopOp(pOL,14,Address); cop++; }
for(RegNum=15;RegNum>=0;RegNum--) {
if(PushList&RegMask) { BuildOnePushPopOp(pOL,RegNum,Address); cop++; }
RegMask = RegMask>>1;
}
return cop;
}
// PopPC is only for use by Thumb Pop op, and should be 0
// for ARM LDMIA op.
static int
BuildPopOp
(
OpList* pOL,
DWORD PopList,
DWORD PopPC,
ULONG Address
)
{
int RegNum;
int cop = 0;
if(PopList==0 && PopPC==0) return 0;
for(RegNum=0;PopList;RegNum++) {
if(PopList&1) { BuildOnePushPopOp(pOL,RegNum,Address); cop++; }
PopList = PopList>>1;
}
if(PopPC) { BuildOnePushPopOp(pOL,15,Address); cop++; }
return cop;
}
static int
BuildAdjSpOp
(
OpList *pOL,
int Val,
ULONG Address
)
{
if(Val==0) return 0;
if(pOL->head == NULL) {
OpEntry* Entry = MakeNewOpEntry(AdjSpOp,NULL,Address);
if (!Entry) {
return 0;
}
pOL->head = pOL->tail = Entry;
} else {
// Don't try to compress this by combining adjacent AdjSpOp's.
// Each actual instruction must yield at least one OpEntry
// for use when we unwind the epilog.
OpEntry* Entry = MakeNewOpEntry(AdjSpOp,pOL->tail,Address);
if (!Entry) {
return 0;
}
pOL->tail->next = Entry;
pOL->tail = pOL->tail->next;
}
pOL->tail->SpAdj = Val;
return 1;
}
static int
BuildMovOp(OpList *pOL, int Rd, int Rs, ULONG Address)
{
if(pOL->head == NULL) {
OpEntry* Entry = MakeNewOpEntry(MovOp,NULL,Address);
if (!Entry) {
return 0;
}
pOL->head = pOL->tail = Entry;
} else {
OpEntry* Entry = MakeNewOpEntry(MovOp,pOL->tail,Address);
if (!Entry) {
return 0;
}
pOL->tail->next = Entry;
pOL->tail = pOL->tail->next;
}
pOL->tail->Rd = Rd;
pOL->tail->Rs = Rs;
return 1;
}
static BOOL
BuildOps
(
ULONG SectionStart,
ULONG SectionLen,
PULONG CxtRegs,
int Mode,
OpList *pOL,
LONG* cOps
)
{
BOOL rc; ULONG cb;
ULONG Pc;
ULONG PcTemp;
ULONG InstAddr;
DWORD ThisInstruction;
BOOL Continue;
ULONG Ra;
BOOL InHelper = FALSE;
DcfInst di;
int len;
long spadj;
PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY HelperFE;
ULONG HelperStart;
ULONG HelperLen;
ULONG HelperEnd;
ULONG Register[16];
ULONG DummyReg[16];
BOOL DummyInit[16];
ULONG SectionEnd = SectionStart + SectionLen;
int i;
OpEntry *pOE;
pOL->head = pOL->tail = NULL;
for(i=0;i<16;i++){
Register[i] = CxtRegs[i];
DummyInit[i]=FALSE;
DummyReg[i]=0xBADDC0DE;
}
*cOps = 0;
Pc = SectionStart;
Continue = TRUE;
while(Continue || (InHelper && (Pc <= HelperEnd))) {
InstAddr = Pc;
ReadMemory(Pc,4,&ThisInstruction);
len = DecipherInstruction(ThisInstruction,&di,Mode);
Pc += len;
if(((Pc >= SectionEnd) && !InHelper) ||
(InHelper && (Pc > HelperEnd)))
{
Continue = FALSE; // This will be our last pass.
}
switch(di.InstNum) {
case DI_STMDB:
case DI_PUSH:
*cOps += BuildPushOp(pOL,di.Auxil,di.Aux2,InstAddr);
break;
case DI_LDMIA:
case DI_POP:
*cOps += BuildPopOp(pOL,di.Auxil,di.Aux2,InstAddr);
break;
case DI_DECSP:
case DI_INCSP:
*cOps += BuildAdjSpOp(pOL,di.Auxil,InstAddr);
break;
case DI_MOVHI:
// The ops we care about are
// MOV Rx,SP / MOV SP,Rx FramePointer saves
// MOV Rx,LR Used in epilog helpers
if ((di.Rd != 15) && ((di.Rs == 13) || (di.Rd == 13) || (di.Rs == 14)))
{
// epilogue helpers move LR to R3 and BX to R3 to return.
if (DummyInit[di.Rs])
{
DummyReg[di.Rd] = DummyReg[di.Rs];
DummyInit[di.Rd] = TRUE;
}
*cOps += BuildMovOp(pOL,di.Rd,di.Rs,InstAddr);
}
break;
case DI_LDRPC:
{
// the offset for the ldr instruction is always
// pc + 4 (InstAddr is pc here).
DWORD Addr = InstAddr+4+di.Aux2;
// Also need to ensure that the data is 4-byte aligned
// so mask off the last bits (we sometimes get 2-byte
// aligned offsets in retail builds of the OS).
Addr &= ~(0x3);
if(!LoadWordIntoRegister(Addr, &DummyReg[di.Rd])) return FALSE;
DummyInit[di.Rd] = TRUE;
break;
}
case DI_NEG:
assert(DummyInit[di.Rs]);
DummyReg[di.Rd] = (~DummyReg[di.Rs])+1;
DummyInit[di.Rd] = DummyInit[di.Rs];
break;
case DI_ADDHI:
assert(di.Rd==13); // Used only to make big changes to SP.
// Better have the source register initialized with an
// immediate.
if (!DummyInit[di.Rs])
{
// we're probably walking the epilogue forward and the
// value we need is already in the real register
DummyReg[di.Rs] = Register[di.Rs];
DummyInit[di.Rs] = TRUE;
}
spadj = (long)(DummyReg[di.Rs]);
if(spadj<0) spadj = -spadj;
*cOps += BuildAdjSpOp(pOL,spadj,InstAddr);
break;
case DI_BLPFX:
// Sign extend the Auxil:
if(di.Auxil & 0x00400000) di.Auxil |= 0xFF800000;
DummyReg[14] = Pc + 2 + (int)(di.Auxil);
DummyInit[14] = TRUE;
break;
case DI_BL:
{
// This can happen if there is an epilog/prolog helper
// function for this particular unwind.
// use some local value to verify that it is indeed an
// epilog/prolog helper before messing up the global
// data
DWORD TempPc = Pc;
DWORD TempRa;
DWORD DummyReg14 = DummyReg[14];
if(DummyInit[14]==FALSE)
{
// Didn't catch the first instruction of the two
// instruction BL. That means that it's likely
// we're attempting to forward execute an epilog.
// The heuristic used to find the beginning
// of the epilog doesn't always do exactly the
// right thing, so the address indicated as the
// beginning of the epilog really isn't, and in
// this case ended up grabbing the last half of
// a BL pair. To get around this, just NOP it.
// -- stevea 3/21/2000
break;
}
// Compute the return address.
TempRa = TempPc | 1; // Pc already points to next instruction.
// Sign extend the Auxil:
if(di.Auxil & 0x00001000) di.Auxil |= 0xFFFFE000;
// Generate the BL target
TempPc = DummyReg14 + (int)(di.Auxil);
DummyReg14 = TempRa;
// Examine the target of this branch:
HelperFE = (PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY)
UW_FunctionTableAccess(UW_hProcess, TempPc);
if (HelperFE)
{
// Make sure that this is a prologue/epilogue helper.
if (IS_HELPER_FUNCTION(HelperFE))
{
// just continue with the next instruction
break;
}
// Actually is a prologue/epilogue helper
Pc = TempPc;
Ra = TempRa;
DummyReg[14] = DummyReg14;
HelperStart = HelperFE->BeginAddress & ~0x01;
HelperEnd = HelperFE->EndAddress & ~0x01;
HelperLen = HelperEnd - HelperStart;
InHelper = TRUE;
}
break;
}
case DI_BX_ARM:
// If we're unwinding, and we're working our way
// through a helper, then when we get to this
// instruction, we just slip neatly back to the main body:
if(InHelper) {
assert((di.Rd==14) || (di.Rd==3)); // BX LR is the only way out of a prologue helper.
// BX r3 is the only way out of an epilogue helper.
assert(DummyInit[di.Rd]);
InHelper = FALSE;
if(DummyInit[di.Rd] && (DummyReg[di.Rd] & 0x1)) { // returning to thumb code...
Pc = DummyReg[di.Rd] & ~0x01;
Mode = MODE_THUMB;
assert(Pc>SectionStart && Pc<=SectionEnd);
} else { // returning to ARM code? This is wrong.
assert(FALSE);
return FALSE;
}
} else {
// We've encountered this instruction, but not in a helper.
// We must have started out inside a helper, and somehow
// got this far. OK, we're done unwinding.
Continue = FALSE;
}
break;
case DI_BX_TMB:
if(di.Auxil==15) { // BX PC
Pc = (Pc+2)&~0x03;
Mode = MODE_ARM; // Now, we're in ARM mode, because PC is always even.
} else {
ULONG NewPc;
Mode = (Register[di.Auxil] &0x01)?MODE_THUMB:MODE_ARM;
if(Mode==MODE_THUMB)
NewPc = Register[di.Auxil] & ~0x01;
else
NewPc = Register[di.Auxil] & ~0x03;
}
break;
default:
break;
} // end of switch statement
} // end of while(Pc<EndAddress) loop.
return TRUE;
}
#define NEED_MORE_EPILOG -1
#define DOESNT_MATCH 0
#define DOES_MATCH 1
static BOOL PrologMatchesCandidateEpilog(OpList*,OpList*,int,ULONG*);
// TODO: This function currently treats a prologue/epilogue helper function as if it has
// TODO: its own stack frame when the PC is inside the helper. This doesn't work very
// TODO: well because the frame below it has special knowledge of the helper as well,
// TODO: so we end up unwinding the helper anywhere (fractions included) between 0 and
// TODO: times. It should really treat the helper as part of its calling frame and
// TODO: unwind everything exactly once.
static int
ThumbVirtualUnwind (
DWORD ControlPc,
PIMAGE_ARM_RUNTIME_FUNCTION_ENTRY FunctionEntry,
PARM_CONTEXT Context,
DWORD* ReturnAddress
)
/*++
Routine Description:
This function virtually unwinds the specfified function by executing its
prologue code backwards (or its epilog forward).
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.
Arguments:
ControlPc - Supplies the address where control left the specified
function.
FunctionEntry - Supplies the address of the function table entry for the
specified function.
Context - Supplies the address of a context record.
Return Value:
The address where control left the previous frame is returned as the
function value.
--*/
{
ULONG Address;
ULONG FunctionStart;
OpEntry* pOE;
BYTE* Prolog;
ULONG PrologStart,PrologLen,PrologEnd;
OpList PrologOL = {NULL,NULL};
LONG PrologOpCount = 0;
BYTE* Epilog;
ULONG EpilogStart,EpilogLen,MaxEpilogLen,EpilogEnd;
BOOL FoundEpilogEnd = FALSE;
int ReturnRegisterIndex = 14;
DWORD EpilogPc;
LONG i,j,Sp;
ARMI instr, instr2;
PULONG Register = &Context->R0;
LONG StackSize = 0;
ULONG FramePointer = 0;
ULONG DummyReg[16];
BOOL DummyRegInit[16];
BOOL rc; LONG cb;
enum {
StartingInProlog,
StartingInFunctionBody,
StartingInEpilog,
StartingInPrologHelper,
StartingInEpilogHelper,
StartingInCallThunk,
StartingInLongBranchThunk
} StartingPlace = StartingInFunctionBody; // default assumption.
// Default: return the value that will terminate unwind.
*ReturnAddress = 0;
if( !FunctionEntry ) return UNWIND_NOT_HANDLED;
// If not a Thumb function, don't handle it here.
if(!(FunctionEntry->BeginAddress&0x01)) return UNWIND_NOT_HANDLED;
// Inside of a thumb function, so the PC will have the
// 16-bit mode set. Clear that out for our purposes here.
ControlPc &= ~0x1;
PrologStart = FunctionStart = FunctionEntry->BeginAddress & ~0x01;
PrologEnd = FunctionEntry->PrologEndAddress & ~0x01;
PrologLen = PrologEnd-PrologStart;
// Look at Exception Info to see if we're in a helper.
if(FunctionEntry->ExceptionHandler == EXCINFO_NULL_HANDLER) {
switch ((int)FunctionEntry->HandlerData) {
case EXCINFO_PROLOG_HELPER:
StartingPlace = StartingInPrologHelper;
break;
case EXCINFO_EPILOG_HELPER:
StartingPlace = StartingInEpilogHelper;
break;
}
}
switch(StartingPlace) {
case StartingInFunctionBody:
FoundEpilogEnd = FALSE;
if(ControlPc==PrologStart) {
// Haven't done anything yet, just copy LR to PC and return:
goto ThumbUnwindExit;
}
if(PrologStart==PrologEnd) {
// No prolog. Just copy LR to PC and return.
goto ThumbUnwindExit;
}
if(ControlPc>PrologStart && ControlPc<=PrologEnd) {
StartingPlace = StartingInProlog;
}
break;
case StartingInPrologHelper:
// If we're in a prolog helper, then the whole function is a prolog!
PrologEnd = FunctionEntry->EndAddress & ~0x1;
PrologLen = PrologEnd-PrologStart;
break;
case StartingInEpilogHelper:
// If we're in an epilog helper, then the whole function is an epilog!
FoundEpilogEnd = TRUE;
EpilogStart = FunctionEntry->BeginAddress & ~0x01;
EpilogEnd = FunctionEntry->EndAddress & ~0x01;
EpilogLen = EpilogEnd-EpilogStart;
break;
}
if((StartingPlace != StartingInProlog) && (StartingPlace != StartingInPrologHelper))
{
DWORD inst;
DcfInst di;
// First, let's see if we're in the epilog...
// We'll know that we are, because the epilog is the only place where
// we find a set of instructions that undoes the action of the prolog,
// and then does a MOV PC,Rx, or BX Rx.
// If we don't know where the end of the epilog is yet, find a candidate.
if(FoundEpilogEnd==FALSE) {
// The epilog can be a few instructions longer than the prolog. That
// limits our search distance:
MaxEpilogLen = PrologLen+4;
// Find a MOV PC,Rx or BX Rx within that distance, or we're not in the
// epilog.
for(EpilogPc=ControlPc;EpilogPc<ControlPc+MaxEpilogLen&&FoundEpilogEnd==FALSE;) {
if(!ReadMemory(EpilogPc,4,(LPVOID)&inst)) return UNWIND_HANDLED;
EpilogPc += DecipherInstruction(inst,&di,MODE_THUMB);
if(di.InstNum==DI_MOVHI && di.Rd==15){
FoundEpilogEnd = TRUE;
EpilogEnd = EpilogPc;
EpilogStart = EpilogPc-MaxEpilogLen;
ReturnRegisterIndex = di.Rs;
} else if(di.InstNum==DI_BX_TMB){
FoundEpilogEnd = TRUE;
EpilogEnd = EpilogPc;
EpilogStart = EpilogPc-MaxEpilogLen;
ReturnRegisterIndex = di.Rd;
}
} // end of loop through instructions
}
// Either we started in an Epilog Helper, or we found a candidate for the
// end of the Epilog.
if(FoundEpilogEnd==TRUE) {
LONG EpilogOpCount;
OpList EpilogOL = {NULL,NULL};
int Mode = MODE_THUMB;
if (StartingPlace == StartingInEpilogHelper)
{
// we skipped the part above where we find the return address register, so
// find it here. The return for an epilogue helper is an ARM instruction.
if(ReadMemory(EpilogEnd-4,4, (LPVOID)&inst) &&
(DecipherInstruction(inst, &di, MODE_ARM) == 4) &&
di.InstNum == DI_BX_ARM)
{
// The epilogue doesn't always return via LR
ReturnRegisterIndex = di.Rd;
}
else
{
// Unexpected helper; terminate the walk
Register[ReturnRegisterIndex] = 0x4;
goto ThumbUnwindExit;
}
// If we're inside the epilog helper we can imply that we're unwinding the top
// frame where it is valid to use the T-bit to determine the mode from which
// we should start to disassemble
if (!(Context->Psr & 0x20))
{
Mode = MODE_ARM;
}
}
// If we are in the epilog, then we've found the end. Let's build the ops for the
// epilog, so that we can compare it to the prolog.
BuildOps(ControlPc,EpilogEnd-ControlPc,Register,Mode,&EpilogOL,&EpilogOpCount);
// Extract total stack size from ops, and fill the stack cache.
for(pOE=EpilogOL.tail;pOE;pOE=pOE->prev) {
StackSize += pOE->SpAdj;
}
// Forward execute the rest of the epilog
for(pOE=EpilogOL.head;pOE;pOE=pOE->next) {
switch(pOE->Operation) {
case MovOp:
Register[pOE->Rd] = Register[pOE->Rs];
break;
case AdjSpOp:
Register[13] += pOE->SpAdj;
break;
case PushOp:
LoadWordIntoRegister(Register[13],&Register[pOE->RegNumber]);
Register[13] += pOE->SpAdj;
break;
}
}
FreeOpList(&EpilogOL);
goto ThumbUnwindExit;
}
}
// If we've started inside the function body, move the PC to the end of the prolog.
if(ControlPc > PrologEnd) ControlPc = PrologEnd;
// We're in the prolog. Because of the use of prolog helpers,
// we cannot merely execute backwards. We need to step forward
// through the prolog, accumulating information about what has been
// done, and then undo that.
BuildOps(PrologStart,ControlPc-PrologStart,Register,MODE_THUMB,&PrologOL,&PrologOpCount);
// Extract total stack size from ops, and fill the stack cache.
FramePointer = Register[13];
for(pOE=PrologOL.head;pOE;pOE=pOE->next) {
StackSize += pOE->SpAdj;
if(pOE->Operation==MovOp && pOE->Rs==13)
FramePointer = Register[pOE->Rd];
}
// At this point, we've got an exact description of the prolog's action.
// Let's undo it.
for(pOE = PrologOL.tail; pOE; pOE=pOE->prev) {
switch(pOE->Operation) {
case MovOp:
Register[pOE->Rs] = Register[pOE->Rd];
break;
case AdjSpOp:
Register[13] += pOE->SpAdj;
break;
case PushOp:
LoadWordIntoRegister(Register[13],&Register[pOE->RegNumber]);
Register[13] += pOE->SpAdj;
break;
}
}
FreeOpList(&PrologOL);
ThumbUnwindExit:
// Now, whatever's left in Register[14] is our return address:
// To continue unwinding, put the Link Register into
// the Program Counter slot and carry on; stopping
// when the PC says 0x0. However, the catch is that
// for THUMB the Link Register at the bottom of the
// stack says 0x4, not 0x0 as we expect.
// So, do a little dance to take an LR of 0x4
// and turn it into a PC of 0x0 to stop the unwind.
// -- stevea 2/23/00.
if( Register[ReturnRegisterIndex] != 0x4 )
Register[15] = Register[ReturnRegisterIndex];
else
Register[15] = 0x0;
*ReturnAddress = Register[15];
return UNWIND_HANDLED;
}
static int
PrologMatchesCandidateEpilog
(
OpList* PrologOL,
OpList* EpilogOL,
int ReturnRegister,
PULONG EpilogStart
)
{
int Matches = DOES_MATCH;
OpEntry* pPOE=(OpEntry*)MemAlloc(sizeof(OpEntry));
OpEntry* pEOE=(OpEntry*)MemAlloc(sizeof(OpEntry));
// We aren't allowed to damage the OpLists we get, so copy
// the entries like this.
if(PrologOL->head) memcpy(pPOE,PrologOL->head,sizeof(OpEntry));
else { MemFree(pPOE); pPOE = NULL; }
if(EpilogOL->tail) memcpy(pEOE,EpilogOL->tail,sizeof(OpEntry));
else { MemFree(pEOE); pEOE = NULL; }
while(pPOE && Matches == TRUE) {
if(pEOE==NULL) return NEED_MORE_EPILOG;
// Keep track of the actual start of the epilog.
*EpilogStart = pEOE->Address;
switch(pPOE->Operation) {
case PushOp:
switch (pPOE->RegNumber) {
case 0:case 1:case 2:case 3:
// the epilog will just adjsp to pop these registers.
if(pEOE->Operation!=AdjSpOp) Matches = DOESNT_MATCH;
if(pEOE->SpAdj<4) Matches = DOESNT_MATCH;
pEOE->SpAdj-=4; pPOE->SpAdj-=4;
break;
case 4:case 5:case 6:case 7:case 8:case 9:case 10:case 11:
// The epilog must pop these saved regs back into their original location.
if(pEOE->Operation!=PushOp) Matches = DOESNT_MATCH;
if(pEOE->RegNumber!=pPOE->RegNumber) Matches = DOESNT_MATCH;
pEOE->SpAdj-=4; pPOE->SpAdj-=4;
break;
case 14:
// The epilog must pop the saved LR into the register it will use to
// return. We found the index of this register when we searched for the
// end of the epilog...
if(pEOE->Operation!=PushOp) Matches = DOESNT_MATCH;
if(pEOE->RegNumber!=ReturnRegister) Matches = DOESNT_MATCH;
pEOE->SpAdj-=4; pPOE->SpAdj-=4;
break;
}
break;
case AdjSpOp:
if(pEOE->Operation!=AdjSpOp) Matches = DOESNT_MATCH;
// The addspspi's and subspspi's could be mixed in with
// pop's and push's, so just do it this way.
if(pEOE->SpAdj >= pPOE->SpAdj) {
pEOE->SpAdj -= pPOE->SpAdj;
pPOE->SpAdj = 0;
} else {
pPOE->SpAdj -= pEOE->SpAdj;
pEOE->SpAdj = 0;
}
break;
case MovOp:
if(pEOE->Operation!=MovOp) Matches = DOESNT_MATCH;
if(pEOE->Rs != pPOE->Rd) Matches = DOESNT_MATCH;
if(pEOE->Rd != pPOE->Rs) Matches = DOESNT_MATCH;
break;
}
// If we're comparing a bunch of pushes to addspspi, then only
// move on to the previous epilog instruction when we've pushed
// enough registers to account for the addspspi.
if(pEOE->SpAdj<=0) {
if(pEOE->prev) memcpy(pEOE,pEOE->prev,sizeof(OpEntry));
else { MemFree(pEOE); pEOE = NULL; }
}
if(pPOE->SpAdj<=0) {
if(pPOE->next) memcpy(pPOE,pPOE->next,sizeof(OpEntry));
else { MemFree(pPOE); pPOE = NULL; }
}
}
if(!Matches) *EpilogStart = 0L;
if(pEOE) MemFree(pEOE);
if(pPOE) MemFree(pPOE);
return Matches;
}