diff --git a/android/android.make b/android/android.make index a62c47e188..ae8238e1de 100644 --- a/android/android.make +++ b/android/android.make @@ -114,7 +114,7 @@ dex: $(DEX) classes: $(R_OBJ) $(JAVA_OBJ) -$(BUILDDIR)/$(BINARY): $$(OBJ) $(VOICESPEEXLIB) $(FIRMLIB) $(SKINLIB) $(CPUFEAT_BUILD)/cpu-features.o +$(BUILDDIR)/$(BINARY): $$(OBJ) $(VOICESPEEXLIB) $(FIRMLIB) $(SKINLIB) $(UNWARMINDER) $(CPUFEAT_BUILD)/cpu-features.o $(call PRINTS,LD $(BINARY))$(CC) -o $@ $^ $(LDOPTS) $(GLOBAL_LDOPTS) -Wl,-Map,$(BUILDDIR)/rockbox.map $(call PRINTS,OC $(@F))$(OC) -S -x $@ diff --git a/firmware/SOURCES b/firmware/SOURCES index e5df779260..85705cb556 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -433,7 +433,7 @@ target/coldfire/i2c-coldfire.c target/coldfire/ata-as-coldfire.S #endif -#elif defined(CPU_PP) || (defined(CPU_ARM) && CONFIG_PLATFORM & PLATFORM_NATIVE) +#elif defined(CPU_PP) || (defined(CPU_ARM) && (CONFIG_PLATFORM & PLATFORM_NATIVE)) /* CPU_PP => CPU_ARM, CPU_ARM !=> CPU_PP */ # if ARM_ARCH < 6 diff --git a/firmware/export/panic.h b/firmware/export/panic.h index b0325aaeb7..7767c675cf 100644 --- a/firmware/export/panic.h +++ b/firmware/export/panic.h @@ -22,8 +22,12 @@ #ifndef __PANIC_H__ #define __PANIC_H__ +#include "config.h" #include "gcc_extensions.h" +#if defined(CPU_ARM) +void panicf( const char *fmt, ... ) __attribute__ ((naked)) ATTRIBUTE_PRINTF(1, 2); +#else void panicf( const char *fmt, ... ) ATTRIBUTE_PRINTF(1, 2); - +#endif #endif /* __PANIC_H__ */ diff --git a/firmware/panic.c b/firmware/panic.c index bd2c719607..cdefc5a0b8 100644 --- a/firmware/panic.c +++ b/firmware/panic.c @@ -31,14 +31,42 @@ #include "power.h" #include "system.h" +#if defined(CPU_ARM) +#include "gcc_extensions.h" +#include +#endif + static char panic_buf[128]; #define LINECHARS (LCD_WIDTH/SYSFONT_WIDTH) - 2 +#if defined(CPU_ARM) +void panicf_f( const char *fmt, ...); + +/* we wrap panicf() here with naked function to catch SP value */ +void panicf( const char *fmt, ...) +{ + (void)fmt; + asm volatile ("mov r4, sp \n" + "b panicf_f \n" + ); +} + /* * "Dude. This is pretty fucked-up, right here." */ +void panicf_f( const char *fmt, ...) +{ + int sp; + + asm volatile ("mov %[SP],r4 \n" + : [SP] "=r" (sp) + ); + + int pc = (int)__builtin_return_address(0); +#else void panicf( const char *fmt, ...) { +#endif va_list ap; #if (CONFIG_PLATFORM & PLATFORM_NATIVE) @@ -82,6 +110,10 @@ void panicf( const char *fmt, ...) panic_buf[i+LINECHARS] = c; } } + +#if defined(CPU_ARM) + backtrace(pc, sp, &y); +#endif #else /* no LCD */ #endif diff --git a/firmware/target/arm/system-arm.c b/firmware/target/arm/system-arm.c index 23ccfd1f14..c4692bbf29 100644 --- a/firmware/target/arm/system-arm.c +++ b/firmware/target/arm/system-arm.c @@ -25,6 +25,9 @@ #include "font.h" #include "gcc_extensions.h" +#include +#include + static const char* const uiename[] = { "Undefined instruction", "Prefetch abort", @@ -38,6 +41,12 @@ static const char* const uiename[] = { */ void NORETURN_ATTR UIE(unsigned int pc, unsigned int num) { + /* safe guard variable - we call backtrace() only on first + * UIE call. This prevent endless loop if backtrace() touches + * memory regions which cause abort + */ + static bool triggered = false; + #if LCD_DEPTH > 1 lcd_set_backdrop(NULL); lcd_set_drawmode(DRMODE_SOLID); @@ -49,9 +58,7 @@ void NORETURN_ATTR UIE(unsigned int pc, unsigned int num) lcd_setfont(FONT_SYSFIXED); lcd_set_viewport(NULL); lcd_clear_display(); - lcd_puts(0, line++, uiename[num]); - lcd_putsf(0, line++, "at %08x" IF_COP(" (%d)"), pc - IF_COP(, CURRENT_CORE)); + lcd_putsf(0, line++, "%s at %08x" IF_COP(" (%d)"), uiename[num], pc IF_COP(, CURRENT_CORE)); #if !defined(CPU_ARM7TDMI) && (CONFIG_CPU != RK27XX) /* arm7tdmi has no MPU/MMU */ if(num == 1 || num == 2) /* prefetch / data abort */ @@ -88,6 +95,12 @@ void NORETURN_ATTR UIE(unsigned int pc, unsigned int num) } /* num == 1 || num == 2 // prefetch/data abort */ #endif /* !defined(CPU_ARM7TDMI */ + if (!triggered) + { + triggered = true; + backtrace(pc, __get_sp(), &line); + } + lcd_update(); disable_interrupt(IRQ_FIQ_STATUS); diff --git a/firmware/target/hosted/ypr0/ypr0.make b/firmware/target/hosted/ypr0/ypr0.make index c2114878db..1b67054183 100644 --- a/firmware/target/hosted/ypr0/ypr0.make +++ b/firmware/target/hosted/ypr0/ypr0.make @@ -14,7 +14,7 @@ SIMFLAGS += $(INCLUDES) $(DEFINES) -DHAVE_CONFIG_H $(GCCOPTS) .SECONDEXPANSION: # $$(OBJ) is not populated until after this -$(BUILDDIR)/rockbox.elf : $$(OBJ) $$(FIRMLIB) $$(VOICESPEEXLIB) $$(SKINLIB) +$(BUILDDIR)/rockbox.elf : $$(OBJ) $$(FIRMLIB) $$(VOICESPEEXLIB) $$(SKINLIB) $$(UNWARMINDER) $(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -Os -o $@ $(OBJ) \ -L$(BUILDDIR)/firmware -lfirmware \ -L$(BUILDDIR)/apps/codecs $(VOICESPEEXLIB:lib%.a=-l%) \ diff --git a/lib/unwarminder/SOURCES b/lib/unwarminder/SOURCES new file mode 100644 index 0000000000..055e6d0ab3 --- /dev/null +++ b/lib/unwarminder/SOURCES @@ -0,0 +1,7 @@ +backtrace.c +get_sp.S +unwarm_arm.c +unwarm.c +unwarminder.c +unwarmmem.c +unwarm_thumb.c diff --git a/lib/unwarminder/backtrace.c b/lib/unwarminder/backtrace.c new file mode 100644 index 0000000000..4e1609137c --- /dev/null +++ b/lib/unwarminder/backtrace.c @@ -0,0 +1,124 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Unwinder client that reads local memory. + * This client reads from local memory and is designed to run on target + * along with the unwinder. Memory read requests are implemented by + * casting a point to read the memory directly, although checks for + * alignment should probably also be made if this is to be used in + * production code, as otherwise the ARM may return the memory in a + * rotated/rolled format, or the MMU may generate an alignment exception + * if present and so configured. + **************************************************************************/ + +/*************************************************************************** + * Includes + ***************************************************************************/ + +#include "backtrace.h" + +/*************************************************************************** + * Prototypes + ***************************************************************************/ + +static Boolean CliReport(void *data, Int32 address); +static Boolean CliReadW(Int32 a, Int32 *v); +static Boolean CliReadH(Int32 a, Int16 *v); +static Boolean CliReadB(Int32 a, Int8 *v); + +/*************************************************************************** + * Variables + ***************************************************************************/ + +/* Table of function pointers for passing to the unwinder */ +const UnwindCallbacks cliCallbacks = + { + CliReport, + CliReadW, + CliReadH, + CliReadB +#if defined(UNW_DEBUG) + ,printf +#endif + }; + + +/*************************************************************************** + * Callbacks + ***************************************************************************/ + +/*************************************************************************** + * + * Function: CliReport + * + * Parameters: data - Pointer to data passed to UnwindStart() + * address - The return address of a stack frame. + * + * Returns: TRUE if unwinding should continue, otherwise FALSE to + * indicate that unwinding should stop. + * + * Description: This function is called from the unwinder each time a stack + * frame has been unwound. The LSB of address indicates if + * the processor is in ARM mode (LSB clear) or Thumb (LSB + * set). + * + ***************************************************************************/ +static Boolean CliReport(void *data, Int32 address) +{ + /* CliStack *s = (CliStack *)data; */ + unsigned *line = (unsigned *)data; + + + lcd_putsf(0, (*line)++, " %c: 0x%08x", + (address & 0x1) ? 'T' : 'A', + address & (~0x1)); + lcd_update(); + + return TRUE; +} + +static Boolean CliReadW(const Int32 a, Int32 *v) +{ + *v = *(Int32 *)a; + return TRUE; +} + +static Boolean CliReadH(const Int32 a, Int16 *v) +{ + *v = *(Int16 *)a; + return TRUE; +} + +static Boolean CliReadB(const Int32 a, Int8 *v) +{ + *v = *(Int8 *)a; + return TRUE; +} + +Boolean CliInvalidateW(const Int32 a) +{ + *(Int32 *)a = 0xdeadbeef; + return TRUE; +} + +void backtrace(int pcAddr, int spAddr, unsigned *line) +{ + UnwResult r; + + lcd_putsf(0, (*line)++, "bt pc: 0x%08x, sp: 0x%08x", pcAddr, spAddr); + lcd_update(); + + r = UnwindStart(pcAddr, spAddr, &cliCallbacks, (void *)line); + + lcd_puts(0, (*line)++, "bt end"); + lcd_update(); +} + +/* END OF FILE */ diff --git a/lib/unwarminder/backtrace.h b/lib/unwarminder/backtrace.h new file mode 100644 index 0000000000..3bf3eb5aac --- /dev/null +++ b/lib/unwarminder/backtrace.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Unwinder client that reads local memory. + **************************************************************************/ + +#ifndef CLIENT_H +#define CLIENT_H + +/*************************************************************************** + * Nested Includes + ***************************************************************************/ +#include "config.h" +#include "system.h" +#include "lcd.h" + +#include +#include "unwarminder.h" +#include "get_sp.h" +#include "gcc_extensions.h" + +#if defined(SIM_CLIENT) +#error This file is not for the simulated unwinder client +#endif + +/*************************************************************************** + * Typedefs + ***************************************************************************/ + +/** Example structure for holding unwind results. + */ +typedef struct +{ + Int16 frameCount; + Int32 address[32]; +} +CliStack; + +/*************************************************************************** + * Variables + ***************************************************************************/ + +extern const UnwindCallbacks cliCallbacks; + +void backtrace(int pcAddr, int spAddr, unsigned *line); + +#endif + + +/* END OF FILE */ diff --git a/lib/unwarminder/get_sp.S b/lib/unwarminder/get_sp.S new file mode 100644 index 0000000000..29356b3ec3 --- /dev/null +++ b/lib/unwarminder/get_sp.S @@ -0,0 +1,34 @@ +#include "config.h" +/* On native platform we protect ourself by disabling interrupts + * then we check current processor mode. If we are called + * from exception we need to save state and switch to SYS and + * after obtaining SP we restore everything from saved state. + * + * On RaaA we are called in USER mode most probably and + * cpsr mangling is restricted. We simply copy SP value + * in this situation + */ +.section .text +.type __get_sp,%function +.global __get_sp + +__get_sp: +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) + mrs r1, cpsr /* save current state */ + orr r0, r1, #0xc0 + msr cpsr, r0 /* disable IRQ and FIQ */ + and r0, r1, #0x1f /* get current mode */ + cmp r0, #0x1f /* are we in sys mode? */ + beq get_sp +call_from_exception: + mrs r0, spsr /* get saved state */ + and r0, r0, #0x1f /* get mode bits */ + orr r0, r0, #0xc0 /* no FIQ no IRQ */ + msr cpsr, r0 /* change mode */ +get_sp: +#endif + mov r0, sp /* get SP */ +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) + msr cpsr, r1 /* restore mode */ +#endif +.size __get_sp, . - __get_sp diff --git a/lib/unwarminder/get_sp.h b/lib/unwarminder/get_sp.h new file mode 100644 index 0000000000..a8c965f063 --- /dev/null +++ b/lib/unwarminder/get_sp.h @@ -0,0 +1 @@ +int __get_sp(void); diff --git a/lib/unwarminder/types.h b/lib/unwarminder/types.h new file mode 100644 index 0000000000..2e902f34d2 --- /dev/null +++ b/lib/unwarminder/types.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + **************************************************************************/ +/** \file + * Types common across the whole system. + **************************************************************************/ + +#ifndef TYPES_H +#define TYPES_H + +#define UPGRADE_ARM_STACK_UNWIND +#undef UNW_DEBUG + +typedef unsigned char Int8; +typedef unsigned short Int16; +typedef unsigned int Int32; + + +typedef signed char SignedInt8; +typedef signed short SignedInt16; +typedef signed int SignedInt32; + + +typedef enum +{ + FALSE, + TRUE +} Boolean; + +#endif + +/* END OF FILE */ diff --git a/lib/unwarminder/unwarm.c b/lib/unwarminder/unwarm.c new file mode 100644 index 0000000000..99f6a12ccb --- /dev/null +++ b/lib/unwarminder/unwarm.c @@ -0,0 +1,183 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Utility functions and glue for ARM unwinding sub-modules. + **************************************************************************/ + +#define MODULE_NAME "UNWARM" + +/*************************************************************************** + * Include Files + **************************************************************************/ + +#include "types.h" +#include +#include +#include +#include "unwarm.h" +#include "unwarmmem.h" + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + + +/*************************************************************************** + * Variables + **************************************************************************/ + + +/*************************************************************************** + * Macros + **************************************************************************/ + + +/*************************************************************************** + * Local Functions + **************************************************************************/ + + +/*************************************************************************** + * Global Functions + **************************************************************************/ + +#if defined(UNW_DEBUG) +/** Printf wrapper. + * This is used such that alternative outputs for any output can be selected + * by modification of this wrapper function. + */ +void UnwPrintf(const char *format, ...) +{ + va_list args; + + va_start( args, format ); + vprintf(format, args ); +} +#endif + +/** Invalidate all general purpose registers. + */ +void UnwInvalidateRegisterFile(RegData *regFile) +{ + Int8 t = 0; + + do + { + regFile[t].o = REG_VAL_INVALID; + t++; + } + while(t < 13); + +} + + +/** Initialise the data used for unwinding. + */ +void UnwInitState(UnwState * const state, /**< Pointer to structure to fill. */ + const UnwindCallbacks *cb, /**< Callbacks. */ + void *rptData, /**< Data to pass to report function. */ + Int32 pcValue, /**< PC at which to start unwinding. */ + Int32 spValue) /**< SP at which to start unwinding. */ +{ + UnwInvalidateRegisterFile(state->regData); + + /* Store the pointer to the callbacks */ + state->cb = cb; + state->reportData = rptData; + + /* Setup the SP and PC */ + state->regData[13].v = spValue; + state->regData[13].o = REG_VAL_FROM_CONST; + state->regData[15].v = pcValue; + state->regData[15].o = REG_VAL_FROM_CONST; + + UnwPrintd3("\nInitial: PC=0x%08x SP=0x%08x\n", pcValue, spValue); + + /* Invalidate all memory addresses */ + memset(state->memData.used, 0, sizeof(state->memData.used)); +} + + +/** Call the report function to indicate some return address. + * This returns the value of the report function, which if TRUE + * indicates that unwinding may continue. + */ +Boolean UnwReportRetAddr(UnwState * const state, Int32 addr) +{ + /* Cast away const from reportData. + * The const is only to prevent the unw module modifying the data. + */ + return state->cb->report((void *)state->reportData, addr); +} + + +/** Write some register to memory. + * This will store some register and meta data onto the virtual stack. + * The address for the write + * \param state [in/out] The unwinding state. + * \param wAddr [in] The address at which to write the data. + * \param reg [in] The register to store. + * \return TRUE if the write was successful, FALSE otherwise. + */ +Boolean UnwMemWriteRegister(UnwState * const state, + const Int32 addr, + const RegData * const reg) +{ + return UnwMemHashWrite(&state->memData, + addr, + reg->v, + M_IsOriginValid(reg->o)); +} + +/** Read a register from memory. + * This will read a register from memory, and setup the meta data. + * If the register has been previously written to memory using + * UnwMemWriteRegister, the local hash will be used to return the + * value while respecting whether the data was valid or not. If the + * register was previously written and was invalid at that point, + * REG_VAL_INVALID will be returned in *reg. + * \param state [in] The unwinding state. + * \param addr [in] The address to read. + * \param reg [out] The result, containing the data value and the origin + * which will be REG_VAL_FROM_MEMORY, or REG_VAL_INVALID. + * \return TRUE if the address could be read and *reg has been filled in. + * FALSE is the data could not be read. + */ +Boolean UnwMemReadRegister(UnwState * const state, + const Int32 addr, + RegData * const reg) +{ + Boolean tracked; + + /* Check if the value can be found in the hash */ + if(UnwMemHashRead(&state->memData, addr, ®->v, &tracked)) + { + reg->o = tracked ? REG_VAL_FROM_MEMORY : REG_VAL_INVALID; + return TRUE; + } + /* Not in the hash, so read from real memory */ + else if(state->cb->readW(addr, ®->v)) + { + reg->o = REG_VAL_FROM_MEMORY; + return TRUE; + } + /* Not in the hash, and failed to read from memory */ + else + { + return FALSE; + } +} + +/* END OF FILE */ diff --git a/lib/unwarminder/unwarm.h b/lib/unwarminder/unwarm.h new file mode 100644 index 0000000000..45508bd39f --- /dev/null +++ b/lib/unwarminder/unwarm.h @@ -0,0 +1,178 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liablity for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Internal interface between the ARM unwinding sub-modules. + **************************************************************************/ + +#ifndef UNWARM_H +#define UNWARM_H + +/*************************************************************************** + * Nested Include Files + **************************************************************************/ + +#include "types.h" +#include "unwarminder.h" + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + +/** The maximum number of instructions to interpet in a function. + * Unwinding will be unconditionally stopped and UNWIND_EXHAUSTED returned + * if more than this number of instructions are interpreted in a single + * function without unwinding a stack frame. This prevents infinite loops + * or corrupted program memory from preventing unwinding from progressing. + */ +#define UNW_MAX_INSTR_COUNT 1000 /* originaly it was 100 */ + +/** The size of the hash used to track reads and writes to memory. + * This should be a prime value for efficiency. + */ +#define MEM_HASH_SIZE 61 /* originaly it was 31 */ + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + +typedef enum +{ + /** Invalid value. */ + REG_VAL_INVALID = 0x00, + REG_VAL_FROM_STACK = 0x01, + REG_VAL_FROM_MEMORY = 0x02, + REG_VAL_FROM_CONST = 0x04, + REG_VAL_ARITHMETIC = 0x80 +} +RegValOrigin; + + +/** Type for tracking information about a register. + * This stores the register value, as well as other data that helps unwinding. + */ +typedef struct +{ + /** The value held in the register. */ + Int32 v; + + /** The origin of the register value. + * This is used to track how the value in the register was loaded. + */ + RegValOrigin o; +} +RegData; + + +/** Structure used to track reads and writes to memory. + * This structure is used as a hash to store a small number of writes + * to memory. + */ +typedef struct +{ + /** Memory contents. */ + Int32 v[MEM_HASH_SIZE]; + + /** Address at which v[n] represents. */ + Int32 a[MEM_HASH_SIZE]; + + /** Indicates whether the data in v[n] and a[n] is occupied. + * Each bit represents one hash value. + */ + Int8 used[(MEM_HASH_SIZE + 7) / 8]; + + /** Indicates whether the data in v[n] is valid. + * This allows a[n] to be set, but for v[n] to be marked as invalid. + * Specifically this is needed for when an untracked register value + * is written to memory. + */ + Int8 tracked[(MEM_HASH_SIZE + 7) / 8]; +} +MemData; + + +/** Structure that is used to keep track of unwinding meta-data. + * This data is passed between all the unwinding functions. + */ +typedef struct +{ + /** The register values and meta-data. */ + RegData regData[16]; + + /** Memory tracking data. */ + MemData memData; + + /** Pointer to the callback functions */ + const UnwindCallbacks *cb; + + /** Pointer to pass to the report function. */ + const void *reportData; +} +UnwState; + +/*************************************************************************** + * Macros + **************************************************************************/ + +#define M_IsOriginValid(v) (((v) & 0x7f) ? TRUE : FALSE) +#define M_Origin2Str(v) ((v) ? "VALID" : "INVALID") + +#if defined(UNW_DEBUG) +#define UnwPrintd1(a) state->cb->printf(a) +#define UnwPrintd2(a,b) state->cb->printf(a,b) +#define UnwPrintd3(a,b,c) state->cb->printf(a,b,c) +#define UnwPrintd4(a,b,c,d) state->cb->printf(a,b,c,d) +#define UnwPrintd5(a,b,c,d,e) state->cb->printf(a,b,c,d,e) +#define UnwPrintd6(a,b,c,d,e,f) state->cb->printf(a,b,c,d,e,f) +#define UnwPrintd7(a,b,c,d,e,f,g) state->cb->printf(a,b,c,d,e,f,g) +#define UnwPrintd8(a,b,c,d,e,f,g,h) state->cb->printf(a,b,c,d,e,f,g,h) +#else +#define UnwPrintd1(a) +#define UnwPrintd2(a,b) +#define UnwPrintd3(a,b,c) +#define UnwPrintd4(a,b,c,d) +#define UnwPrintd5(a,b,c,d,e) +#define UnwPrintd6(a,b,c,d,e,f) +#define UnwPrintd7(a,b,c,d,e,f,g) +#define UnwPrintd8(a,b,c,d,e,f,g,h) +#endif + +/*************************************************************************** + * Function Prototypes + **************************************************************************/ + +UnwResult UnwStartArm (UnwState * const state); + +UnwResult UnwStartThumb (UnwState * const state); + +void UnwInvalidateRegisterFile(RegData *regFile); + +void UnwInitState (UnwState * const state, + const UnwindCallbacks *cb, + void *rptData, + Int32 pcValue, + Int32 spValue); + +Boolean UnwReportRetAddr (UnwState * const state, Int32 addr); + +Boolean UnwMemWriteRegister (UnwState * const state, + const Int32 addr, + const RegData * const reg); + +Boolean UnwMemReadRegister (UnwState * const state, + const Int32 addr, + RegData * const reg); + +void UnwMemHashGC (UnwState * const state); + +#endif /* UNWARM_H */ + +/* END OF FILE */ + + diff --git a/lib/unwarminder/unwarm_arm.c b/lib/unwarminder/unwarm_arm.c new file mode 100644 index 0000000000..0c41c05919 --- /dev/null +++ b/lib/unwarminder/unwarm_arm.c @@ -0,0 +1,701 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Abstract interpreter for ARM mode. + **************************************************************************/ + +#define MODULE_NAME "UNWARM_ARM" + +/*************************************************************************** + * Include Files + **************************************************************************/ + +#include "types.h" +#if defined(UPGRADE_ARM_STACK_UNWIND) +#include +#include "unwarm.h" + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + + +/*************************************************************************** + * Variables + **************************************************************************/ + + +/*************************************************************************** + * Macros + **************************************************************************/ + + +/*************************************************************************** + * Local Functions + **************************************************************************/ + +/** Check if some instruction is a data-processing instruction. + * Decodes the passed instruction, checks if it is a data-processing and + * verifies that the parameters and operation really indicate a data- + * processing instruction. This is needed because some parts of the + * instruction space under this instruction can be extended or represent + * other operations such as MRS, MSR. + * + * \param[in] inst The instruction word. + * \retval TRUE Further decoding of the instruction indicates that this is + * a valid data-processing instruction. + * \retval FALSE This is not a data-processing instruction, + */ +static Boolean isDataProc(Int32 instr) +{ + Int8 opcode = (instr & 0x01e00000) >> 21; + Boolean S = (instr & 0x00100000) ? TRUE : FALSE; + + if((instr & 0xfc000000) != 0xe0000000) + { + return FALSE; + } + else if(!S && opcode >= 8 && opcode <= 11) + { + /* TST, TEQ, CMP and CMN all require S to be set */ + return FALSE; + } + else + { + return TRUE; + } +} + +/*************************************************************************** + * Global Functions + **************************************************************************/ + + +UnwResult UnwStartArm(UnwState * const state) +{ + Boolean found = FALSE; + Int16 t = UNW_MAX_INSTR_COUNT; + + do + { + Int32 instr; + + /* Attempt to read the instruction */ + if(!state->cb->readW(state->regData[15].v, &instr)) + { + return UNWIND_IREAD_W_FAIL; + } + + UnwPrintd4("A %x %x %08x:", + state->regData[13].v, state->regData[15].v, instr); + + /* Check that the PC is still on Arm alignment */ + if(state->regData[15].v & 0x3) + { + UnwPrintd1("\nError: PC misalignment\n"); + return UNWIND_INCONSISTENT; + } + + /* Check that the SP and PC have not been invalidated */ + if(!M_IsOriginValid(state->regData[13].o) || !M_IsOriginValid(state->regData[15].o)) + { + UnwPrintd1("\nError: PC or SP invalidated\n"); + return UNWIND_INCONSISTENT; + } + + /* Branch and Exchange (BX) + * This is tested prior to data processing to prevent + * mis-interpretation as an invalid TEQ instruction. + */ + if((instr & 0xfffffff0) == 0xe12fff10) + { + Int8 rn = instr & 0xf; + + UnwPrintd4("BX r%d\t ; r%d %s\n", rn, rn, M_Origin2Str(state->regData[rn].o)); + + if(!M_IsOriginValid(state->regData[rn].o)) + { + UnwPrintd1("\nUnwind failure: BX to untracked register\n"); + return UNWIND_FAILURE; + } + + /* Set the new PC value */ + state->regData[15].v = state->regData[rn].v; + + /* Check if the return value is from the stack */ + if(state->regData[rn].o == REG_VAL_FROM_STACK) + { + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v & (~0x1)); + + /* Report the return address */ + if(!UnwReportRetAddr(state, state->regData[rn].v)) + { + return UNWIND_TRUNCATED; + } + } + + /* Determine the return mode */ + if(state->regData[rn].v & 0x1) + { + /* Branching to THUMB */ + return UnwStartThumb(state); + } + else + { + /* Branch to ARM */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 4; + } + } + /* Branch */ + else if((instr & 0xff000000) == 0xea000000) + { + SignedInt32 offset = (instr & 0x00ffffff); + + /* Shift value */ + offset = offset << 2; + + /* Sign extend if needed */ + if(offset & 0x02000000) + { + offset |= 0xfc000000; + } + + UnwPrintd2("B %d\n", offset); + + /* Adjust PC */ + state->regData[15].v += offset; + + /* Account for pre-fetch, where normally the PC is 8 bytes + * ahead of the instruction just executed. + */ + state->regData[15].v += 4; + + } + /* MRS */ + else if((instr & 0xffbf0fff) == 0xe10f0000) + { +#if defined(UNW_DEBUG) + Boolean R = (instr & 0x00400000) ? TRUE : FALSE; +#endif + Int8 rd = (instr & 0x0000f000) >> 12; + + UnwPrintd4("MRS r%d,%s\t; r%d invalidated", rd, R ? "SPSR" : "CPSR", rd); + + /* Status registers untracked */ + state->regData[rd].o = REG_VAL_INVALID; + } + /* MSR */ + else if((instr & 0xffb0f000) == 0xe120f000) + { +#if defined(UNW_DEBUG) + Boolean R = (instr & 0x00400000) ? TRUE : FALSE; + + UnwPrintd2("MSR %s_?, ???", R ? "SPSR" : "CPSR"); +#endif + /* Status registers untracked. + * Potentially this could change processor mode and switch + * banked registers r8-r14. Most likely is that r13 (sp) will + * be banked. However, invalidating r13 will stop unwinding + * when potentially this write is being used to disable/enable + * interrupts (a common case). Therefore no invalidation is + * performed. + */ + } + /* Data processing */ + else if(isDataProc(instr)) + { + Boolean I = (instr & 0x02000000) ? TRUE : FALSE; + Int8 opcode = (instr & 0x01e00000) >> 21; +#if defined(UNW_DEBUG) + Boolean S = (instr & 0x00100000) ? TRUE : FALSE; +#endif + Int8 rn = (instr & 0x000f0000) >> 16; + Int8 rd = (instr & 0x0000f000) >> 12; + Int16 operand2 = (instr & 0x00000fff); + Int32 op2val; + RegValOrigin op2origin; + + switch(opcode) + { + case 0: UnwPrintd4("AND%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 1: UnwPrintd4("EOR%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 2: UnwPrintd4("SUB%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 3: UnwPrintd4("RSB%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 4: UnwPrintd4("ADD%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 5: UnwPrintd4("ADC%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 6: UnwPrintd4("SBC%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 7: UnwPrintd4("RSC%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 8: UnwPrintd3("TST%s r%d,", S ? "S" : "", rn); break; + case 9: UnwPrintd3("TEQ%s r%d,", S ? "S" : "", rn); break; + case 10: UnwPrintd3("CMP%s r%d,", S ? "S" : "", rn); break; + case 11: UnwPrintd3("CMN%s r%d,", S ? "S" : "", rn); break; + case 12: UnwPrintd3("ORR%s r%d,", S ? "S" : "", rn); break; + case 13: UnwPrintd3("MOV%s r%d,", S ? "S" : "", rd); break; + case 14: UnwPrintd4("BIC%s r%d,r%d", S ? "S" : "", rd, rn); break; + case 15: UnwPrintd3("MVN%s r%d,", S ? "S" : "", rd); break; + } + + /* Decode operand 2 */ + if(I) + { + Int8 shiftDist = (operand2 & 0x0f00) >> 8; + Int8 shiftConst = (operand2 & 0x00ff); + + /* rotate const right by 2 * shiftDist */ + shiftDist *= 2; + op2val = (shiftConst >> shiftDist) | + (shiftConst << (32 - shiftDist)); + op2origin = REG_VAL_FROM_CONST; + + UnwPrintd2("#0x%x", op2val); + } + else + { + /* Register and shift */ + Int8 rm = (operand2 & 0x000f); + Int8 regShift = (operand2 & 0x0010) ? TRUE : FALSE; + Int8 shiftType = (operand2 & 0x0060) >> 5; + Int32 shiftDist; +#if defined(UNW_DEBUG) + const char * const shiftMnu[4] = { "LSL", "LSR", "ASR", "ROR" }; +#endif + UnwPrintd2("r%d ", rm); + + /* Get the shift distance */ + if(regShift) + { + Int8 rs = (operand2 & 0x0f00) >> 8; + + if(operand2 & 0x00800) + { + UnwPrintd1("\nError: Bit should be zero\n"); + return UNWIND_ILLEGAL_INSTR; + } + else if(rs == 15) + { + UnwPrintd1("\nError: Cannot use R15 with register shift\n"); + return UNWIND_ILLEGAL_INSTR; + } + + /* Get shift distance */ + shiftDist = state->regData[rs].v; + op2origin = state->regData[rs].o; + + UnwPrintd7("%s r%d\t; r%d %s r%d %s", + shiftMnu[shiftType], rs, + rm, M_Origin2Str(state->regData[rm].o), + rs, M_Origin2Str(state->regData[rs].o)); + } + else + { + shiftDist = (operand2 & 0x0f80) >> 7; + op2origin = REG_VAL_FROM_CONST; + + if(shiftDist) + { + UnwPrintd3("%s #%d", + shiftMnu[shiftType], shiftDist); + } + UnwPrintd3("\t; r%d %s", rm, M_Origin2Str(state->regData[rm].o)); + + } + + /* Apply the shift type to the source register */ + switch(shiftType) + { + case 0: /* logical left */ + op2val = state->regData[rm].v << shiftDist; + break; + case 1: /* logical right */ + + if(!regShift && shiftDist == 0) + { + shiftDist = 32; + } + + op2val = state->regData[rm].v >> shiftDist; + break; + case 2: /* arithmetic right */ + + if(!regShift && shiftDist == 0) + { + shiftDist = 32; + } + + if(state->regData[rm].v & 0x80000000) + { + /* Register shifts maybe greater than 32 */ + if(shiftDist >= 32) + { + op2val = 0xffffffff; + } + else + { + op2val = state->regData[rm].v >> shiftDist; + op2val |= 0xffffffff << (32 - shiftDist); + } + } + else + { + op2val = state->regData[rm].v >> shiftDist; + } + break; + case 3: /* rotate right */ + + if(!regShift && shiftDist == 0) + { + /* Rotate right with extend. + * This uses the carry bit and so always has an + * untracked result. + */ + op2origin = REG_VAL_INVALID; + op2val = 0; + } + else + { + /* Limit shift distance to 0-31 incase of register shift */ + shiftDist &= 0x1f; + + op2val = (state->regData[rm].v >> shiftDist) | + (state->regData[rm].v << (32 - shiftDist)); + } + break; + + default: + UnwPrintd2("\nError: Invalid shift type: %d\n", shiftType); + return UNWIND_FAILURE; + } + + /* Decide the data origin */ + if(M_IsOriginValid(op2origin) && + M_IsOriginValid(state->regData[rm].o)) + { + op2origin = state->regData[rm].o; + op2origin |= REG_VAL_ARITHMETIC; + } + else + { + op2origin = REG_VAL_INVALID; + } + + } + + /* Propagate register validity */ + switch(opcode) + { + case 0: /* AND: Rd := Op1 AND Op2 */ + case 1: /* EOR: Rd := Op1 EOR Op2 */ + case 2: /* SUB: Rd:= Op1 - Op2 */ + case 3: /* RSB: Rd:= Op2 - Op1 */ + case 4: /* ADD: Rd:= Op1 + Op2 */ + case 12: /* ORR: Rd:= Op1 OR Op2 */ + case 14: /* BIC: Rd:= Op1 AND NOT Op2 */ + if(!M_IsOriginValid(state->regData[rn].o) || + !M_IsOriginValid(op2origin)) + { + state->regData[rd].o = REG_VAL_INVALID; + } + else + { + state->regData[rd].o = state->regData[rn].o; + state->regData[rd].o |= op2origin; + } + break; + case 5: /* ADC: Rd:= Op1 + Op2 + C */ + case 6: /* SBC: Rd:= Op1 - Op2 + C */ + case 7: /* RSC: Rd:= Op2 - Op1 + C */ + /* CPSR is not tracked */ + state->regData[rd].o = REG_VAL_INVALID; + break; + + case 8: /* TST: set condition codes on Op1 AND Op2 */ + case 9: /* TEQ: set condition codes on Op1 EOR Op2 */ + case 10: /* CMP: set condition codes on Op1 - Op2 */ + case 11: /* CMN: set condition codes on Op1 + Op2 */ + break; + + + case 13: /* MOV: Rd:= Op2 */ + case 15: /* MVN: Rd:= NOT Op2 */ + state->regData[rd].o = op2origin; + break; + } + + /* Account for pre-fetch by temporarily adjusting PC */ + if(rn == 15) + { + /* If the shift amount is specified in the instruction, + * the PC will be 8 bytes ahead. If a register is used + * to specify the shift amount the PC will be 12 bytes + * ahead. + */ + if(!I && (operand2 & 0x0010)) + state->regData[rn].v += 12; + else + state->regData[rn].v += 8; + } + + /* Compute values */ + switch(opcode) + { + case 0: /* AND: Rd := Op1 AND Op2 */ + state->regData[rd].v = state->regData[rn].v & op2val; + break; + + case 1: /* EOR: Rd := Op1 EOR Op2 */ + state->regData[rd].v = state->regData[rn].v ^ op2val; + break; + + case 2: /* SUB: Rd:= Op1 - Op2 */ + state->regData[rd].v = state->regData[rn].v - op2val; + break; + case 3: /* RSB: Rd:= Op2 - Op1 */ + state->regData[rd].v = op2val - state->regData[rn].v; + break; + + case 4: /* ADD: Rd:= Op1 + Op2 */ + state->regData[rd].v = state->regData[rn].v + op2val; + break; + + case 5: /* ADC: Rd:= Op1 + Op2 + C */ + case 6: /* SBC: Rd:= Op1 - Op2 + C */ + case 7: /* RSC: Rd:= Op2 - Op1 + C */ + case 8: /* TST: set condition codes on Op1 AND Op2 */ + case 9: /* TEQ: set condition codes on Op1 EOR Op2 */ + case 10: /* CMP: set condition codes on Op1 - Op2 */ + case 11: /* CMN: set condition codes on Op1 + Op2 */ + UnwPrintd1("\t; ????"); + break; + + case 12: /* ORR: Rd:= Op1 OR Op2 */ + state->regData[rd].v = state->regData[rn].v | op2val; + break; + + case 13: /* MOV: Rd:= Op2 */ + state->regData[rd].v = op2val; + break; + + case 14: /* BIC: Rd:= Op1 AND NOT Op2 */ + state->regData[rd].v = state->regData[rn].v & (~op2val); + break; + + case 15: /* MVN: Rd:= NOT Op2 */ + state->regData[rd].v = ~op2val; + break; + } + + /* Remove the prefetch offset from the PC */ + if(rd != 15 && rn == 15) + { + if(!I && (operand2 & 0x0010)) + state->regData[rn].v -= 12; + else + state->regData[rn].v -= 8; + } + + } + /* Block Data Transfer + * LDM, STM + */ + else if((instr & 0xfe000000) == 0xe8000000) + { + Boolean P = (instr & 0x01000000) ? TRUE : FALSE; + Boolean U = (instr & 0x00800000) ? TRUE : FALSE; + Boolean S = (instr & 0x00400000) ? TRUE : FALSE; + Boolean W = (instr & 0x00200000) ? TRUE : FALSE; + Boolean L = (instr & 0x00100000) ? TRUE : FALSE; + Int16 baseReg = (instr & 0x000f0000) >> 16; + Int16 regList = (instr & 0x0000ffff); + Int32 addr = state->regData[baseReg].v; + Boolean addrValid = M_IsOriginValid(state->regData[baseReg].o); + SignedInt8 r; + +#if defined(UNW_DEBUG) + /* Display the instruction */ + if(L) + { + UnwPrintd6("LDM%c%c r%d%s, {reglist}%s\n", + P ? 'E' : 'F', + U ? 'D' : 'A', + baseReg, + W ? "!" : "", + S ? "^" : ""); + } + else + { + UnwPrintd6("STM%c%c r%d%s, {reglist}%s\n", + !P ? 'E' : 'F', + !U ? 'D' : 'A', + baseReg, + W ? "!" : "", + S ? "^" : ""); + } +#endif + /* S indicates that banked registers (untracked) are used, unless + * this is a load including the PC when the S-bit indicates that + * that CPSR is loaded from SPSR (also untracked, but ignored). + */ + if(S && (!L || (regList & (0x01 << 15)) == 0)) + { + UnwPrintd1("\nError:S-bit set requiring banked registers\n"); + return UNWIND_FAILURE; + } + else if(baseReg == 15) + { + UnwPrintd1("\nError: r15 used as base register\n"); + return UNWIND_FAILURE; + } + else if(regList == 0) + { + UnwPrintd1("\nError: Register list empty\n"); + return UNWIND_FAILURE; + } + + /* Check if ascending or descending. + * Registers are loaded/stored in order of address. + * i.e. r0 is at the lowest address, r15 at the highest. + */ + r = U ? 0 : 15; + + do + { + /* Check if the register is to be transferred */ + if(regList & (0x01 << r)) + { + if(P) addr += U ? 4 : -4; + + if(L) + { + if(addrValid) + { + if(!UnwMemReadRegister(state, addr, &state->regData[r])) + { + return UNWIND_DREAD_W_FAIL; + } + + /* Update the origin if read via the stack pointer */ + if(M_IsOriginValid(state->regData[r].o) && baseReg == 13) + { + state->regData[r].o = REG_VAL_FROM_STACK; + } + + UnwPrintd5(" R%d = 0x%08x\t; r%d %s\n", + r, + state->regData[r].v, + r, + M_Origin2Str(state->regData[r].o)); + } + else + { + /* Invalidate the register as the base reg was invalid */ + state->regData[r].o = REG_VAL_INVALID; + + UnwPrintd2(" R%d = ???\n", r); + } + } + else + { + if(addrValid) + { + if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) + { + return UNWIND_DWRITE_W_FAIL; + } + } + + UnwPrintd2(" R%d = 0x%08x\n", r); + } + + if(!P) addr += U ? 4 : -4; + } + + /* Check the next register */ + r += U ? 1 : -1; + } + while(r >= 0 && r <= 15); + + /* Check the writeback bit */ + if(W) state->regData[baseReg].v = addr; + + /* Check if the PC was loaded */ + if(L && (regList & (0x01 << 15))) + { + if(!M_IsOriginValid(state->regData[15].o)) + { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + else + { + /* Store the return address */ + if(!UnwReportRetAddr(state, state->regData[15].v)) + { + return UNWIND_TRUNCATED; + } + + UnwPrintd2(" Return PC=0x%x", state->regData[15].v); + + /* Determine the return mode */ + if(state->regData[15].v & 0x1) + { + /* Branching to THUMB */ + return UnwStartThumb(state); + } + else + { + /* Branch to ARM */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 4; + } + } + } + } + else + { + UnwPrintd1("????"); + + /* Unknown/undecoded. May alter some register, so invalidate file */ + UnwInvalidateRegisterFile(state->regData); + } + + UnwPrintd1("\n"); + + /* Should never hit the reset vector */ + if(state->regData[15].v == 0) return UNWIND_RESET; + + /* Check next address */ + state->regData[15].v += 4; + + /* Garbage collect the memory hash (used only for the stack) */ + UnwMemHashGC(state); + + t--; + if(t == 0) return UNWIND_EXHAUSTED; + + } + while(!found); + + return UNWIND_UNSUPPORTED; +} + +#endif /* UPGRADE_ARM_STACK_UNWIND */ + +/* END OF FILE */ + diff --git a/lib/unwarminder/unwarm_thumb.c b/lib/unwarminder/unwarm_thumb.c new file mode 100644 index 0000000000..09b3c9e54b --- /dev/null +++ b/lib/unwarminder/unwarm_thumb.c @@ -0,0 +1,740 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Abstract interpretation for Thumb mode. + **************************************************************************/ + +#define MODULE_NAME "UNWARM_THUMB" + +/*************************************************************************** + * Include Files + **************************************************************************/ + +#include "types.h" +#if defined(UPGRADE_ARM_STACK_UNWIND) +#include +#include "unwarm.h" + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + + +/*************************************************************************** + * Variables + **************************************************************************/ + + +/*************************************************************************** + * Macros + **************************************************************************/ + + +/*************************************************************************** + * Local Functions + **************************************************************************/ + +/** Sign extend an 11 bit value. + * This function simply inspects bit 11 of the input \a value, and if + * set, the top 5 bits are set to give a 2's compliment signed value. + * \param value The value to sign extend. + * \return The signed-11 bit value stored in a 16bit data type. + */ +static SignedInt16 signExtend11(Int16 value) +{ + if(value & 0x400) + { + value |= 0xf800; + } + + return value; +} + + +/*************************************************************************** + * Global Functions + **************************************************************************/ + + +UnwResult UnwStartThumb(UnwState * const state) +{ + Boolean found = FALSE; + Int16 t = UNW_MAX_INSTR_COUNT; + + do + { + Int16 instr; + + /* Attempt to read the instruction */ + if(!state->cb->readH(state->regData[15].v & (~0x1), &instr)) + { + return UNWIND_IREAD_H_FAIL; + } + + UnwPrintd4("T %x %x %04x:", + state->regData[13].v, state->regData[15].v, instr); + + /* Check that the PC is still on Thumb alignment */ + if(!(state->regData[15].v & 0x1)) + { + UnwPrintd1("\nError: PC misalignment\n"); + return UNWIND_INCONSISTENT; + } + + /* Check that the SP and PC have not been invalidated */ + if(!M_IsOriginValid(state->regData[13].o) || !M_IsOriginValid(state->regData[15].o)) + { + UnwPrintd1("\nError: PC or SP invalidated\n"); + return UNWIND_INCONSISTENT; + } + + /* Format 1: Move shifted register + * LSL Rd, Rs, #Offset5 + * LSR Rd, Rs, #Offset5 + * ASR Rd, Rs, #Offset5 + */ + if((instr & 0xe000) == 0x0000 && (instr & 0x1800) != 0x1800) + { + Boolean signExtend; + Int8 op = (instr & 0x1800) >> 11; + Int8 offset5 = (instr & 0x07c0) >> 6; + Int8 rs = (instr & 0x0038) >> 3; + Int8 rd = (instr & 0x0007); + + switch(op) + { + case 0: /* LSL */ + UnwPrintd6("LSL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + state->regData[rd].v = state->regData[rs].v << offset5; + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 1: /* LSR */ + UnwPrintd6("LSR r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + state->regData[rd].v = state->regData[rs].v >> offset5; + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 2: /* ASR */ + UnwPrintd6("ASL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + + signExtend = (state->regData[rs].v & 0x8000) ? TRUE : FALSE; + state->regData[rd].v = state->regData[rs].v >> offset5; + if(signExtend) + { + state->regData[rd].v |= 0xffffffff << (32 - offset5); + } + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 2: add/subtract + * ADD Rd, Rs, Rn + * ADD Rd, Rs, #Offset3 + * SUB Rd, Rs, Rn + * SUB Rd, Rs, #Offset3 + */ + else if((instr & 0xf800) == 0x1800) + { + Boolean I = (instr & 0x0400) ? TRUE : FALSE; + Boolean op = (instr & 0x0200) ? TRUE : FALSE; + Int8 rn = (instr & 0x01c0) >> 6; + Int8 rs = (instr & 0x0038) >> 3; + Int8 rd = (instr & 0x0007); + + /* Print decoding */ + UnwPrintd6("%s r%d, r%d, %c%d\t;", + op ? "SUB" : "ADD", + rd, rs, + I ? '#' : 'r', + rn); + UnwPrintd5("r%d %s, r%d %s", + rd, M_Origin2Str(state->regData[rd].o), + rs, M_Origin2Str(state->regData[rs].o)); + if(!I) + { + UnwPrintd3(", r%d %s", rn, M_Origin2Str(state->regData[rn].o)); + + /* Perform calculation */ + if(op) + { + state->regData[rd].v = state->regData[rs].v - state->regData[rn].v; + } + else + { + state->regData[rd].v = state->regData[rs].v + state->regData[rn].v; + } + + /* Propagate the origin */ + if(M_IsOriginValid(state->regData[rs].v) && + M_IsOriginValid(state->regData[rn].v)) + { + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + else + { + state->regData[rd].o = REG_VAL_INVALID; + } + } + else + { + /* Perform calculation */ + if(op) + { + state->regData[rd].v = state->regData[rs].v - rn; + } + else + { + state->regData[rd].v = state->regData[rs].v + rn; + } + + /* Propagate the origin */ + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + } + /* Format 3: move/compare/add/subtract immediate + * MOV Rd, #Offset8 + * CMP Rd, #Offset8 + * ADD Rd, #Offset8 + * SUB Rd, #Offset8 + */ + else if((instr & 0xe000) == 0x2000) + { + Int8 op = (instr & 0x1800) >> 11; + Int8 rd = (instr & 0x0700) >> 8; + Int8 offset8 = (instr & 0x00ff); + + switch(op) + { + case 0: /* MOV */ + UnwPrintd3("MOV r%d, #0x%x", rd, offset8); + state->regData[rd].v = offset8; + state->regData[rd].o = REG_VAL_FROM_CONST; + break; + + case 1: /* CMP */ + /* Irrelevant to unwinding */ + UnwPrintd1("CMP ???"); + break; + + case 2: /* ADD */ + UnwPrintd5("ADD r%d, #0x%x\t; r%d %s", + rd, offset8, rd, M_Origin2Str(state->regData[rd].o)); + state->regData[rd].v += offset8; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 3: /* SUB */ + UnwPrintd5("SUB r%d, #0x%d\t; r%d %s", + rd, offset8, rd, M_Origin2Str(state->regData[rd].o)); + state->regData[rd].v += offset8; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 4: ALU operations + * AND Rd, Rs + * EOR Rd, Rs + * LSL Rd, Rs + * LSR Rd, Rs + * ASR Rd, Rs + * ADC Rd, Rs + * SBC Rd, Rs + * ROR Rd, Rs + * TST Rd, Rs + * NEG Rd, Rs + * CMP Rd, Rs + * CMN Rd, Rs + * ORR Rd, Rs + * MUL Rd, Rs + * BIC Rd, Rs + * MVN Rd, Rs + */ + else if((instr & 0xfc00) == 0x4000) + { + Int8 op = (instr & 0x03c0) >> 6; + Int8 rs = (instr & 0x0038) >> 3; + Int8 rd = (instr & 0x0007); +#if defined(UNW_DEBUG) + static const char * const mnu[16] = + { "AND", "EOR", "LSL", "LSR", + "ASR", "ADC", "SBC", "ROR", + "TST", "NEG", "CMP", "CMN", + "ORR", "MUL", "BIC", "MVN" }; +#endif + /* Print the mnemonic and registers */ + switch(op) + { + case 0: /* AND */ + case 1: /* EOR */ + case 2: /* LSL */ + case 3: /* LSR */ + case 4: /* ASR */ + case 7: /* ROR */ + case 9: /* NEG */ + case 12: /* ORR */ + case 13: /* MUL */ + case 15: /* MVN */ + UnwPrintd8("%s r%d ,r%d\t; r%d %s, r%d %s", + mnu[op], + rd, rs, + rd, M_Origin2Str(state->regData[rd].o), + rs, M_Origin2Str(state->regData[rs].o)); + break; + + case 5: /* ADC */ + case 6: /* SBC */ + UnwPrintd4("%s r%d, r%d", mnu[op], rd, rs); + break; + + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + /* Irrelevant to unwinding */ + UnwPrintd2("%s ???", mnu[op]); + break; + + case 14: /* BIC */ + UnwPrintd5("r%d ,r%d\t; r%d %s", + rd, rs, + rs, M_Origin2Str(state->regData[rs].o)); + state->regData[rd].v &= !state->regData[rs].v; + break; + } + + + /* Perform operation */ + switch(op) + { + case 0: /* AND */ + state->regData[rd].v &= state->regData[rs].v; + break; + + case 1: /* EOR */ + state->regData[rd].v ^= state->regData[rs].v; + break; + + case 2: /* LSL */ + state->regData[rd].v <<= state->regData[rs].v; + break; + + case 3: /* LSR */ + state->regData[rd].v >>= state->regData[rs].v; + break; + + case 4: /* ASR */ + if(state->regData[rd].v & 0x80000000) + { + state->regData[rd].v >>= state->regData[rs].v; + state->regData[rd].v |= 0xffffffff << (32 - state->regData[rs].v); + } + else + { + state->regData[rd].v >>= state->regData[rs].v; + } + + break; + + case 5: /* ADC */ + case 6: /* SBC */ + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + break; + case 7: /* ROR */ + state->regData[rd].v = (state->regData[rd].v >> state->regData[rs].v) | + (state->regData[rd].v << (32 - state->regData[rs].v)); + break; + + case 9: /* NEG */ + state->regData[rd].v = -state->regData[rs].v; + break; + + case 12: /* ORR */ + state->regData[rd].v |= state->regData[rs].v; + break; + + case 13: /* MUL */ + state->regData[rd].v *= state->regData[rs].v; + break; + + case 14: /* BIC */ + state->regData[rd].v &= !state->regData[rs].v; + break; + + case 15: /* MVN */ + state->regData[rd].v = !state->regData[rs].v; + break; + } + + /* Propagate data origins */ + switch(op) + { + case 0: /* AND */ + case 1: /* EOR */ + case 2: /* LSL */ + case 3: /* LSR */ + case 4: /* ASR */ + case 7: /* ROR */ + case 12: /* ORR */ + case 13: /* MUL */ + case 14: /* BIC */ + if(M_IsOriginValid(state->regData[rs].o) && M_IsOriginValid(state->regData[rs].o)) + { + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + else + { + state->regData[rd].o = REG_VAL_INVALID; + } + break; + + case 5: /* ADC */ + case 6: /* SBC */ + /* C-bit not tracked */ + state->regData[rd].o = REG_VAL_INVALID; + break; + + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + /* Nothing propagated */ + break; + + case 9: /* NEG */ + case 15: /* MVN */ + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + } + + } + /* Format 5: Hi register operations/branch exchange + * ADD Rd, Hs + * ADD Hd, Rs + * ADD Hd, Hs + */ + else if((instr & 0xfc00) == 0x4400) + { + Int8 op = (instr & 0x0300) >> 8; + Boolean h1 = (instr & 0x0080) ? TRUE: FALSE; + Boolean h2 = (instr & 0x0040) ? TRUE: FALSE; + Int8 rhs = (instr & 0x0038) >> 3; + Int8 rhd = (instr & 0x0007); + + /* Adjust the register numbers */ + if(h2) rhs += 8; + if(h1) rhd += 8; + + if(op != 3 && !h1 && !h2) + { + UnwPrintd1("\nError: h1 or h2 must be set for ADD, CMP or MOV\n"); + return UNWIND_ILLEGAL_INSTR; + } + + switch(op) + { + case 0: /* ADD */ + UnwPrintd5("ADD r%d, r%d\t; r%d %s", + rhd, rhs, rhs, M_Origin2Str(state->regData[rhs].o)); + state->regData[rhd].v += state->regData[rhs].v; + state->regData[rhd].o = state->regData[rhs].o; + state->regData[rhd].o |= REG_VAL_ARITHMETIC; + break; + + case 1: /* CMP */ + /* Irrelevant to unwinding */ + UnwPrintd1("CMP ???"); + break; + + case 2: /* MOV */ + UnwPrintd5("MOV r%d, r%d\t; r%d %s", + rhd, rhs, rhd, M_Origin2Str(state->regData[rhs].o)); + state->regData[rhd].v += state->regData[rhs].v; + state->regData[rhd].o = state->regData[rhd].o; + break; + + case 3: /* BX */ + UnwPrintd4("BX r%d\t; r%d %s\n", + rhs, rhs, M_Origin2Str(state->regData[rhs].o)); + + /* Only follow BX if the data was from the stack */ + if(state->regData[rhs].o == REG_VAL_FROM_STACK) + { + UnwPrintd2(" Return PC=0x%x\n", state->regData[rhs].v & (~0x1)); + + /* Report the return address, including mode bit */ + if(!UnwReportRetAddr(state, state->regData[rhs].v)) + { + return UNWIND_TRUNCATED; + } + + /* Update the PC */ + state->regData[15].v = state->regData[rhs].v; + + /* Determine the new mode */ + if(state->regData[rhs].v & 0x1) + { + /* Branching to THUMB */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 2; + } + else + { + /* Branch to ARM */ + return UnwStartArm(state); + } + } + else + { + UnwPrintd4("\nError: BX to invalid register: r%d = 0x%x (%s)\n", + rhs, state->regData[rhs].o, M_Origin2Str(state->regData[rhs].o)); + return UNWIND_FAILURE; + } + } + } + /* Format 9: PC-relative load + * LDR Rd,[PC, #imm] + */ + else if((instr & 0xf800) == 0x4800) + { + Int8 rd = (instr & 0x0700) >> 8; + Int8 word8 = (instr & 0x00ff); + Int32 address; + + /* Compute load address, adding a word to account for prefetch */ + address = (state->regData[15].v & (~0x3)) + 4 + (word8 << 2); + + UnwPrintd3("LDR r%d, 0x%08x", rd, address); + + if(!UnwMemReadRegister(state, address, &state->regData[rd])) + { + return UNWIND_DREAD_W_FAIL; + } + } + /* Format 13: add offset to Stack Pointer + * ADD sp,#+imm + * ADD sp,#-imm + */ + else if((instr & 0xff00) == 0xB000) + { + Int8 value = (instr & 0x7f) * 4; + + /* Check the negative bit */ + if((instr & 0x80) != 0) + { + UnwPrintd2("SUB sp,#0x%x", value); + state->regData[13].v -= value; + } + else + { + UnwPrintd2("ADD sp,#0x%x", value); + state->regData[13].v += value; + } + } + /* Format 14: push/pop registers + * PUSH {Rlist} + * PUSH {Rlist, LR} + * POP {Rlist} + * POP {Rlist, PC} + */ + else if((instr & 0xf600) == 0xb400) + { + Boolean L = (instr & 0x0800) ? TRUE : FALSE; + Boolean R = (instr & 0x0100) ? TRUE : FALSE; + Int8 rList = (instr & 0x00ff); + + if(L) + { + Int8 r; + + /* Load from memory: POP */ + UnwPrintd2("POP {Rlist%s}\n", R ? ", PC" : ""); + + for(r = 0; r < 8; r++) + { + if(rList & (0x1 << r)) + { + /* Read the word */ + if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) + { + return UNWIND_DREAD_W_FAIL; + } + + /* Alter the origin to be from the stack if it was valid */ + if(M_IsOriginValid(state->regData[r].o)) + { + state->regData[r].o = REG_VAL_FROM_STACK; + } + + state->regData[13].v += 4; + + UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); + } + } + + /* Check if the PC is to be popped */ + if(R) + { + /* Get the return address */ + if(!UnwMemReadRegister(state, state->regData[13].v, &state->regData[15])) + { + return UNWIND_DREAD_W_FAIL; + } + + /* Alter the origin to be from the stack if it was valid */ + if(!M_IsOriginValid(state->regData[15].o)) + { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + else + { + /* The bottom bit should have been set to indicate that + * the caller was from Thumb. This would allow return + * by BX for interworking APCS. + */ + if((state->regData[15].v & 0x1) == 0) + { + UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", + state->regData[15].v); + + /* Pop into the PC will not switch mode */ + return UNWIND_INCONSISTENT; + } + + /* Store the return address */ + if(!UnwReportRetAddr(state, state->regData[15].v)) + { + return UNWIND_TRUNCATED; + } + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v); + + /* Update the pc */ + state->regData[13].v += 4; + + /* Compensate for the auto-increment, which isn't needed here */ + state->regData[15].v -= 2; + } + } + + } + else + { + SignedInt8 r; + + /* Store to memory: PUSH */ + UnwPrintd2("PUSH {Rlist%s}", R ? ", LR" : ""); + + /* Check if the LR is to be pushed */ + if(R) + { + UnwPrintd3("\n lr = 0x%08x\t; %s", + state->regData[14].v, M_Origin2Str(state->regData[14].o)); + + state->regData[13].v -= 4; + + /* Write the register value to memory */ + if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[14])) + { + return UNWIND_DWRITE_W_FAIL; + } + } + + for(r = 7; r >= 0; r--) + { + if(rList & (0x1 << r)) + { + UnwPrintd4("\n r%d = 0x%08x\t; %s", + r, state->regData[r].v, M_Origin2Str(state->regData[r].o)); + + state->regData[13].v -= 4; + + if(!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) + { + return UNWIND_DWRITE_W_FAIL; + } + } + } + } + } + /* Format 18: unconditional branch + * B label + */ + else if((instr & 0xf800) == 0xe000) + { + SignedInt16 branchValue = signExtend11(instr & 0x07ff); + + /* Branch distance is twice that specified in the instruction. */ + branchValue *= 2; + + UnwPrintd2("B %d \n", branchValue); + + /* Update PC */ + state->regData[15].v += branchValue; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", state->regData[15].v + 2); + + } + else + { + UnwPrintd1("????"); + + /* Unknown/undecoded. May alter some register, so invalidate file */ + UnwInvalidateRegisterFile(state->regData); + } + + UnwPrintd1("\n"); + + /* Should never hit the reset vector */ + if(state->regData[15].v == 0) return UNWIND_RESET; + + /* Check next address */ + state->regData[15].v += 2; + + /* Garbage collect the memory hash (used only for the stack) */ + UnwMemHashGC(state); + + t--; + if(t == 0) return UNWIND_EXHAUSTED; + + } + while(!found); + + return UNWIND_SUCCESS; +} + +#endif /* UPGRADE_ARM_STACK_UNWIND */ + +/* END OF FILE */ + diff --git a/lib/unwarminder/unwarminder.c b/lib/unwarminder/unwarminder.c new file mode 100644 index 0000000000..68bd9f3986 --- /dev/null +++ b/lib/unwarminder/unwarminder.c @@ -0,0 +1,78 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Implementation of the interface into the ARM unwinder. + **************************************************************************/ + +#define MODULE_NAME "UNWARMINDER" + +/*************************************************************************** + * Include Files + **************************************************************************/ + +#include "types.h" +#include +#include +#include "unwarminder.h" +#include "unwarm.h" + + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + + +/*************************************************************************** + * Variables + **************************************************************************/ + + +/*************************************************************************** + * Macros + **************************************************************************/ + + +/*************************************************************************** + * Local Functions + **************************************************************************/ + + +/*************************************************************************** + * Global Functions + **************************************************************************/ + +UnwResult UnwindStart(Int32 pcValue, + Int32 spValue, + const UnwindCallbacks *cb, + void *data) +{ + UnwState state; + + /* Initialise the unwinding state */ + UnwInitState(&state, cb, data, pcValue, spValue); + + /* Check the Thumb bit */ + if(pcValue & 0x1) + { + return UnwStartThumb(&state); + } + else + { + return UnwStartArm(&state); + } +} + +/* END OF FILE */ + diff --git a/lib/unwarminder/unwarminder.h b/lib/unwarminder/unwarminder.h new file mode 100644 index 0000000000..1c5adbf693 --- /dev/null +++ b/lib/unwarminder/unwarminder.h @@ -0,0 +1,160 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liablity for it's use or misuse - this software is without warranty. + **************************************************************************/ +/** \file + * Interface to the ARM stack unwinding module. + **************************************************************************/ + +#ifndef UNWARMINDER_H +#define UNWARMINDER_H + +/*************************************************************************** + * Nested Include Files + **************************************************************************/ + +#include "types.h" + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + +/** \def UNW_DEBUG + * If this define is set, additional information will be produced while + * unwinding the stack to allow debug of the unwind module itself. + */ +/* #define UNW_DEBUG 1 */ + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + +/** Possible results for UnwindStart to return. + */ +typedef enum UnwResultTag +{ + /** Unwinding was successful and complete. */ + UNWIND_SUCCESS = 0, + + /** More than UNW_MAX_INSTR_COUNT instructions were interpreted. */ + UNWIND_EXHAUSTED, + + /** Unwinding stopped because the reporting func returned FALSE. */ + UNWIND_TRUNCATED, + + /** Read data was found to be inconsistent. */ + UNWIND_INCONSISTENT, + + /** Unsupported instruction or data found. */ + UNWIND_UNSUPPORTED, + + /** General failure. */ + UNWIND_FAILURE, + + /** Illegal instruction. */ + UNWIND_ILLEGAL_INSTR, + + /** Unwinding hit the reset vector. */ + UNWIND_RESET, + + /** Failed read for an instruction word. */ + UNWIND_IREAD_W_FAIL, + + /** Failed read for an instruction half-word. */ + UNWIND_IREAD_H_FAIL, + + /** Failed read for an instruction byte. */ + UNWIND_IREAD_B_FAIL, + + /** Failed read for a data word. */ + UNWIND_DREAD_W_FAIL, + + /** Failed read for a data half-word. */ + UNWIND_DREAD_H_FAIL, + + /** Failed read for a data byte. */ + UNWIND_DREAD_B_FAIL, + + /** Failed write for a data word. */ + UNWIND_DWRITE_W_FAIL +} +UnwResult; + +/** Type for function pointer for result callback. + * The function is passed two parameters, the first is a void * pointer, + * and the second is the return address of the function. The bottom bit + * of the passed address indicates the execution mode; if it is set, + * the execution mode at the return address is Thumb, otherwise it is + * ARM. + * + * The return value of this function determines whether unwinding should + * continue or not. If TRUE is returned, unwinding will continue and the + * report function maybe called again in future. If FALSE is returned, + * unwinding will stop with UnwindStart() returning UNWIND_TRUNCATED. + */ +typedef Boolean (*UnwindReportFunc)(void *data, + Int32 address); + +/** Structure that holds memory callback function pointers. + */ +typedef struct UnwindCallbacksTag +{ + /** Report an unwind result. */ + UnwindReportFunc report; + + /** Read a 32 bit word from memory. + * The memory address to be read is passed as \a address, and + * \a *val is expected to be populated with the read value. + * If the address cannot or should not be read, FALSE can be + * returned to indicate that unwinding should stop. If TRUE + * is returned, \a *val is assumed to be valid and unwinding + * will continue. + */ + Boolean (*readW)(const Int32 address, Int32 *val); + + /** Read a 16 bit half-word from memory. + * This function has the same usage as for readW, but is expected + * to read only a 16 bit value. + */ + Boolean (*readH)(const Int32 address, Int16 *val); + + /** Read a byte from memory. + * This function has the same usage as for readW, but is expected + * to read only an 8 bit value. + */ + Boolean (*readB)(const Int32 address, Int8 *val); + +#if defined(UNW_DEBUG) + /** Print a formatted line for debug. */ + int (*printf)(const char *format, ...); +#endif + +} +UnwindCallbacks; + +/*************************************************************************** + * Macros + **************************************************************************/ + +/*************************************************************************** + * Function Prototypes + **************************************************************************/ + +/** Start unwinding the current stack. + * This will unwind the stack starting at the PC value supplied and + * the stack pointer value supplied. + */ +UnwResult UnwindStart(Int32 pcValue, + Int32 spValue, + const UnwindCallbacks *cb, + void *data); + +#endif /* UNWARMINDER_H */ + +/* END OF FILE */ diff --git a/lib/unwarminder/unwarminder.make b/lib/unwarminder/unwarminder.make new file mode 100644 index 0000000000..f570d49640 --- /dev/null +++ b/lib/unwarminder/unwarminder.make @@ -0,0 +1,21 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# + +UNWARM_DIR = $(ROOTDIR)/lib/unwarminder +UNWARM_SRC = $(call preprocess, $(UNWARM_DIR)/SOURCES) +UNWARM_OBJ := $(call c2obj, $(UNWARM_SRC)) + +OTHER_SRC += $(UNWARM_SRC) + +UNWARMINDER = $(BUILDDIR)/lib/libunwarminder.a + +INCLUDES += -I$(UNWARM_DIR) + +$(UNWARMINDER): $(UNWARM_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null diff --git a/lib/unwarminder/unwarmmem.c b/lib/unwarminder/unwarmmem.c new file mode 100644 index 0000000000..5991720412 --- /dev/null +++ b/lib/unwarminder/unwarmmem.c @@ -0,0 +1,175 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liablity for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Implementation of the memory tracking sub-system. + **************************************************************************/ + +#define MODULE_NAME "UNWARMMEM" + +/*************************************************************************** + * Include Files + **************************************************************************/ + +#include "types.h" +#include +#include "unwarmmem.h" +#include "unwarm.h" + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + + +/*************************************************************************** + * Variables + **************************************************************************/ + + +/*************************************************************************** + * Macros + **************************************************************************/ + + +#define M_IsIdxUsed(a, v) (((a)[v >> 3] & (1 << (v & 0x7))) ? TRUE : FALSE) + +#define M_SetIdxUsed(a, v) ((a)[v >> 3] |= (1 << (v & 0x7))) + +#define M_ClrIdxUsed(a, v) ((a)[v >> 3] &= ~(1 << (v & 0x7))) + +/*************************************************************************** + * Local Functions + **************************************************************************/ + +/** Search the memory hash to see if an entry is stored in the hash already. + * This will search the hash and either return the index where the item is + * stored, or -1 if the item was not found. + */ +static SignedInt16 memHashIndex(MemData * const memData, + const Int32 addr) +{ + const Int16 v = addr % MEM_HASH_SIZE; + Int16 s = v; + + do + { + /* Check if the element is occupied */ + if(M_IsIdxUsed(memData->used, s)) + { + /* Check if it is occupied with the sought data */ + if(memData->a[s] == addr) + { + return s; + } + } + else + { + /* Item is free, this is where the item should be stored */ + return s; + } + + /* Search the next entry */ + s++; + if(s > MEM_HASH_SIZE) + { + s = 0; + } + } + while(s != v); + + /* Search failed, hash is full and the address not stored */ + return -1; +} + + + +/*************************************************************************** + * Global Functions + **************************************************************************/ + +Boolean UnwMemHashRead(MemData * const memData, + Int32 addr, + Int32 * const data, + Boolean * const tracked) +{ + SignedInt16 i = memHashIndex(memData, addr); + + if(i >= 0 && M_IsIdxUsed(memData->used, i) && memData->a[i] == addr) + { + *data = memData->v[i]; + *tracked = M_IsIdxUsed(memData->tracked, i); + return TRUE; + } + else + { + /* Address not found in the hash */ + return FALSE; + } +} + +Boolean UnwMemHashWrite(MemData * const memData, + Int32 addr, + Int32 val, + Boolean valValid) +{ + SignedInt16 i = memHashIndex(memData, addr); + + if(i < 0) + { + /* Hash full */ + return FALSE; + } + else + { + /* Store the item */ + memData->a[i] = addr; + M_SetIdxUsed(memData->used, i); + + if(valValid) + { + memData->v[i] = val; + M_SetIdxUsed(memData->tracked, i); + } + else + { +#if defined(UNW_DEBUG) + memData->v[i] = 0xdeadbeef; +#endif + M_ClrIdxUsed(memData->tracked, i); + } + + return TRUE; + } +} + + +void UnwMemHashGC(UnwState * const state) +{ + const Int32 minValidAddr = state->regData[13].v; + MemData * const memData = &state->memData; + Int16 t; + + for(t = 0; t < MEM_HASH_SIZE; t++) + { + if(M_IsIdxUsed(memData->used, t) && (memData->a[t] < minValidAddr)) + { + UnwPrintd3("MemHashGC: Free elem %d, addr 0x%08x\n", + t, memData->a[t]); + + M_ClrIdxUsed(memData->used, t); + } + } +} + +/* END OF FILE */ diff --git a/lib/unwarminder/unwarmmem.h b/lib/unwarminder/unwarmmem.h new file mode 100644 index 0000000000..4c02d284d7 --- /dev/null +++ b/lib/unwarminder/unwarmmem.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liablity for it's use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Interface to the memory tracking sub-system. + **************************************************************************/ + +#ifndef UNWARMMEM_H +#define UNWARMMEM_H + +/*************************************************************************** + * Nested Include Files + **************************************************************************/ + +#include "types.h" +#include "unwarm.h" + +/*************************************************************************** + * Manifest Constants + **************************************************************************/ + + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + + +/*************************************************************************** + * Macros + **************************************************************************/ + + +/*************************************************************************** + * Function Prototypes + **************************************************************************/ + +Boolean UnwMemHashRead (MemData * const memData, + Int32 addr, + Int32 * const data, + Boolean * const tracked); + +Boolean UnwMemHashWrite (MemData * const memData, + Int32 addr, + Int32 val, + Boolean valValid); + +void UnwMemHashGC (UnwState * const state); + +#endif + +/* END OF FILE */ diff --git a/tools/root.make b/tools/root.make index 3cdee2caf7..74279064bf 100644 --- a/tools/root.make +++ b/tools/root.make @@ -78,6 +78,10 @@ ifndef APP_TYPE endif endif +ifeq (arm,$(ARCH)) + include $(ROOTDIR)/lib/unwarminder/unwarminder.make +endif + ifneq (,$(findstring bootloader,$(APPSDIR))) include $(APPSDIR)/bootloader.make else ifneq (,$(findstring bootbox,$(APPSDIR))) @@ -170,6 +174,12 @@ ifeq (,$(findstring bootloader,$(APPSDIR))) OBJ += $(LANG_O) +ifeq (arm,$(ARCH)) + UNWARMINDER_LINK := -lunwarminder +else + UNWARMINDER_LINK := +endif + ifndef APP_TYPE ## target build @@ -185,8 +195,6 @@ else LIBARMSUPPORT_LINK := endif - - $(LINKRAM): $(RAMLDS) $(CONFIGFILE) $(call PRINTS,PP $(@F)) $(call preprocess2file,$<,$@,-DLOADADDRESS=$(LOADADDRESS)) @@ -195,21 +203,21 @@ $(LINKROM): $(ROMLDS) $(call PRINTS,PP $(@F)) $(call preprocess2file,$<,$@,-DLOADADDRESS=$(LOADADDRESS)) -$(BUILDDIR)/rockbox.elf : $$(OBJ) $$(FIRMLIB) $$(VOICESPEEXLIB) $$(SKINLIB) $$(LIBARMSUPPORT) $$(LINKRAM) +$(BUILDDIR)/rockbox.elf : $$(OBJ) $$(FIRMLIB) $$(VOICESPEEXLIB) $$(SKINLIB) $$(LIBARMSUPPORT) $$(UNWARMINDER) $$(LINKRAM) $(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -Os -nostdlib -o $@ $(OBJ) \ -L$(BUILDDIR)/firmware -lfirmware \ -L$(BUILDDIR)/lib -lskin_parser $(LIBARMSUPPORT_LINK) \ - -L$(BUILDDIR)/apps/codecs $(VOICESPEEXLIB:lib%.a=-l%) \ - -lgcc $(BOOTBOXLDOPTS) $(GLOBAL_LDOPTS) \ - -T$(LINKRAM) -Wl,-Map,$(BUILDDIR)/rockbox.map + $(UNWARMINDER_LINK) -L$(BUILDDIR)/apps/codecs \ + $(VOICESPEEXLIB:lib%.a=-l%) -lgcc $(BOOTBOXLDOPTS) \ + $(GLOBAL_LDOPTS) -T$(LINKRAM) -Wl,-Map,$(BUILDDIR)/rockbox.map -$(BUILDDIR)/rombox.elf : $$(OBJ) $$(FIRMLIB) $$(VOICESPEEXLIB) $$(SKINLIB) $$(LIBARMSUPPORT) $$(LINKROM) +$(BUILDDIR)/rombox.elf : $$(OBJ) $$(FIRMLIB) $$(VOICESPEEXLIB) $$(SKINLIB) $$(LIBARMSUPPORT) $$(UNWARMINDER) $$(LINKROM) $(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -Os -nostdlib -o $@ $(OBJ) \ -L$(BUILDDIR)/firmware -lfirmware \ -L$(BUILDDIR)/lib -lskin_parser $(LIBARMSUPPORT_LINK) \ - -L$(BUILDDIR)/apps/codecs $(VOICESPEEXLIB:lib%.a=-l%) \ - -lgcc $(GLOBAL_LDOPTS) \ - -T$(LINKROM) -Wl,-Map,$(BUILDDIR)/rombox.map + $(UNWARMINDER_LINK) -L$(BUILDDIR)/apps/codecs \ + $(VOICESPEEXLIB:lib%.a=-l%) -lgcc $(GLOBAL_LDOPTS) \ + -T$(LINKROM) -Wl,-Map,$(BUILDDIR)/rombox.map $(BUILDDIR)/rockbox.bin : $(BUILDDIR)/rockbox.elf $(call PRINTS,OC $(@F))$(OC) $(if $(filter yes, $(USE_ELF)), -S -x, -O binary) $< $@ diff --git a/uisimulator/uisimulator.make b/uisimulator/uisimulator.make index 03bf9da2bd..fc58f3a6c1 100644 --- a/uisimulator/uisimulator.make +++ b/uisimulator/uisimulator.make @@ -30,7 +30,7 @@ $(SIMLIB): $$(SIMOBJ) $(UIBMP) $(SILENT)$(shell rm -f $@) $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null -$(BUILDDIR)/$(BINARY): $$(OBJ) $(SIMLIB) $(VOICESPEEXLIB) $(FIRMLIB) $(SKINLIB) +$(BUILDDIR)/$(BINARY): $$(OBJ) $(SIMLIB) $(VOICESPEEXLIB) $(FIRMLIB) $(SKINLIB) $(UNWARMINDER) $(call PRINTS,LD $(BINARY))$(CC) -o $@ $^ $(SIMLIB) $(LDOPTS) $(GLOBAL_LDOPTS) \ -Wl,-Map,$(BUILDDIR)/rockbox.map