https://bitbucket.org/daniel_fort/magic-lantern
Tip revision: 107aa7fdc0732f83c9f2996649523749898a549c authored by Daniel Fort on 01 May 2018, 19:47:47 UTC
Closed branch update-to-1200D.102-wip
Closed branch update-to-1200D.102-wip
Tip revision: 107aa7f
backtrace.c
/* Attempt to rebuild the call stack directly from stack contents
* --------------------------------------------------------------
*
* Advantage: no need to monitor program execution continuously
* (no instrumentation needed until this gets called, unlike callstack)
*
* The same file can be compiled to run on the camera (binary less than 1K).
*/
#ifdef CONFIG_MAGICLANTERN
#include "dryos.h"
#include "tasks.h"
#include "backtrace.h"
#define qemu_log_mask(...)
#define qemu_loglevel_mask(x) 0
#define EOSState void
static char * eos_get_current_task_name(EOSState *s)
{
return current_task->name;
}
static int eos_get_current_task_stack(EOSState *s, uint32_t * top, uint32_t * bottom)
{
*bottom = current_task->stackStartAddr;
*top = *bottom + current_task->stackSize;
return 1;
}
#define fprintf(stream, ...) printf(__VA_ARGS__)
#else /* without CONFIG_MAGICLANTERN (in QEMU) */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "cpu.h"
#include "../eos.h"
#include "../model_list.h"
#include "logging.h"
#include "backtrace.h"
static uint32_t MEM(uint32_t addr)
{
uint32_t buf;
cpu_physical_memory_read(addr, &buf, sizeof(buf));
return buf;
}
#endif
#ifdef BKT_TRACK_STATS
/* some stats */
static int stacks_completed = 0; /* number of call stacks fully identified */
static int stacks_incomplete = 0; /* num. where at least one caller was identified (besides LR) */
static int stacks_gaveup = 0; /* num. where no caller could be identified */
static int callers_found = 0; /* number of callers found (total over all stacks) */
static int gaveups_loop = 0; /* num. of callers missed because the simulation got stuck in a loop */
static int gaveups_insn = 0; /* num. of callers missed because the simulation found some unhandled instruction */
static int random_helped = 0; /* num. of callers where taking random branches helped (when the deterministic method got stuck in a loop) */
static int brute_force_helped = 0; /* num. of callers found by brute force scanning of the stack */
#endif
static uint32_t ror(uint32_t word, uint32_t count)
{
return word >> count | word << (32 - count);
}
static uint32_t decode_immediate_shifter_operand(uint32_t insn)
{
uint32_t inmed_8 = insn & 0xFF;
uint32_t rotate_imm = (insn & 0xF00) >> 7;
return ror(inmed_8, rotate_imm);
}
static inline int32_t decode_signed_offset12(uint32_t insn)
{
uint32_t off = (insn & 0x00000FFF);
if (insn & (1 << 23))
return off;
return -off;
}
static uint32_t branch_destination(uint32_t insn, uint32_t pc)
{
int32_t off = (((int32_t) insn & 0x00FFFFFF) << 8) >> 6;
uint32_t dest = pc + off + 8;
return dest;
}
static inline int is_mov(uint32_t insn)
{
if ((insn & 0xFDFF0000) == 0xE1A00000)
{
if ((insn & 0x02000090) == 0x00000090)
{
/* Multiply instruction extension space */
return 0;
}
return 1;
}
return 0;
}
static inline int is_compare(uint32_t insn)
{
if ((insn & 0xFDF0F000) == 0xE1500000 ||
(insn & 0xFDF0F000) == 0xE1700000 ||
(insn & 0xFDF0F000) == 0xE1100000 ||
(insn & 0xFDF0F000) == 0xE1300000)
{
if ((insn & 0x02000090) == 0x00000090)
{
/* Multiply instruction extension space */
return 0;
}
/* CMP, CMN, TST, TEQ */
return 1;
}
return 0;
}
static inline int possibly_func_start(uint32_t dest)
{
uint32_t insn = MEM(dest);
if (is_mov(insn) || is_compare(insn)) {
insn = MEM(dest+4);
}
if (is_mov(insn) || is_compare(insn)) {
insn = MEM(dest+8);
}
if ((insn & 0xFFFF0000) == 0xE92D0000)
{
/* STMFD */
return 1;
}
if (insn == 0xe52de004)
{
/* STR LR, [SP,#-4]! */
return 1;
}
return 0;
}
static const char * called_func(uint32_t pc)
{
static char buf[16];
snprintf(buf, sizeof(buf), "0xUNKNOWN ");
uint32_t insn = MEM(pc);
if ((insn & 0x0E000000) == 0x0A000000)
{
#ifdef CONFIG_MAGICLANTERN
snprintf(buf, sizeof(buf), "0x%08X", branch_destination(insn, pc));
#else
snprintf(buf, sizeof(buf), "0x%-8X", branch_destination(insn, pc));
#endif
}
return buf;
}
static void handle_pop_pc(uint32_t pc, uint32_t dest, uint32_t *next_pc)
{
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: POP PC => %x\n", pc, dest);
*next_pc = dest;
}
/* stack change should be handled by the caller */
static void handle_tail_call_pop_lr(uint32_t pc, uint32_t dest, uint32_t *plr, uint32_t *next_pc)
{
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: POP LR => %x\n", pc, dest);
*plr = dest;
#ifdef BKT_ASSUME_TAIL_CALL_AFTER_POP_LR
/* all cases that were not found to be tail calls by the #else branch,
* appear to be valid tail calls, just harder to identify by pattern matching
* to simplify things, let's just assume a POP LR is followed by a tail call. */
*next_pc = *plr;
#else
/* if testing this, change the function return type to int and check it */
uint32_t ins2 = MEM(pc + 4);
if (is_mov(ins2)) {
ins2 = MEM(pc + 8);
}
if (is_mov(ins2)) {
ins2 = MEM(pc + 12);
}
/* fixme: not very robust on conditional branches */
if ((ins2 & 0x0F000000) == 0x0A000000 || /* B{cond} func */
(ins2 & 0x0FFFFFF0) == 0x012FFF10) /* BX{cond} Rn */
{
/* we are not interested in following tail calls
* just go back to the caller */
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: tail call\n", pc+4);
*next_pc = *plr;
return 1;
}
else
{
fprintf(stderr, "*** %x no tail\n", pc);
}
return 0;
#endif
}
/* emulate one instruction, updating pc, lr and sp */
/* fixme: ARM only */
/* returns 1 if a return address was found; otherwise 0 */
static int sim_instr(EOSState *s,
uint32_t *ppc, uint32_t *plr, uint32_t *psp
#ifdef BKT_RANDOM_BRANCHES
, uint32_t *pcond
#endif
)
{
#define assert_retry(x) if (!(x)) { \
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: assert at line %d\n", pc, __LINE__); \
return -1; \
}
const uint32_t pc = *ppc;
uint32_t lr = *plr;
uint32_t sp = *psp;
uint32_t next_pc = pc + 4;
if (pc & 1)
{
/* fixme: not implemented for Thumb code */
/* still, some parts of the code are ARM
* just stop on the first Thumb instruction for now */
assert_retry(0);
}
uint32_t insn = MEM(pc);
#ifdef BKT_RANDOM_BRANCHES
uint32_t cond = *pcond;
if (cond != 0xE0000000)
{
/* instruction matching current condition? */
if ((insn & 0xF0000000) == cond)
{
/* patch the instruction to emulate as if it would be unconditional */
insn = (insn & 0x0FFFFFFF) | 0xE0000000;
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: patch condition (%x -> %x)\n", pc, MEM(pc), insn);
}
}
#endif
#ifndef CONFIG_MAGICLANTERN
if (qemu_loglevel_mask(BKT_LOG_DISAS))
{
CPUARMState *env = &(CURRENT_CPU->env);
target_disas(stderr, CPU(arm_env_get_cpu(env)), pc, 4, 0);
}
#endif
#ifdef BKT_RANDOM_BRANCHES
if (is_compare(insn))
{
/* decide to execute only one random condition from next instructions
* this should handle grouped instructions that depend on the same condition
* (execute either none or all of them)
*/
uint32_t cond_pc = pc;
cond = 0xF0000000;
do
{
cond_pc += 4;
uint32_t next = MEM(cond_pc);
if ((rand() & 1) || /* use condition from this instruction? */
((next & 0xF0000000) == 0xE0000000)) /* no more conditional instructions? */
{
cond = next & 0xF0000000;
}
}
while (cond == 0xF0000000);
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: cond = %x (from %x)\n", pc, cond, cond_pc);
*pcond = cond;
goto ret_regular_insn;
}
#endif
if ((insn & 0xFF000000) == 0xEA000000) /* B dest */
{
/* unconditional branch */
uint32_t dest = branch_destination(insn, pc);
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: B %x\n", pc, dest);
#ifndef BKT_ASSUME_TAIL_CALL_AFTER_POP_LR
if (possibly_func_start(dest))
{
/* this appears to be a branch to a function (tail call)
* just go back to the caller */
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: tail call\n", pc+4);
assert_retry(lr);
next_pc = lr;
goto ret_caller_found;
}
#endif
next_pc = dest;
goto ret_regular_insn;
}
if ((insn & 0xFFFFF000) == 0xE28DD000) /* ADD SP, SP, #off */
{
uint32_t off = decode_immediate_shifter_operand(insn);
sp += off;
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: sp += %x => %x\n", pc, off, sp);
goto ret_regular_insn;
}
if ((insn & 0xFFFFF000) == 0xE24DD000) /* SUB SP, SP, #off */
{
uint32_t off = decode_immediate_shifter_operand(insn);
sp -= off;
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: sp -= %x => %x\n", pc, off, sp);
goto ret_regular_insn;
}
if ((insn & 0xFFFF0000) == 0xE92D0000) /* STMFD SP!, {regs} */
{
/* pushing SP/PC/LR is not handled (we would need to keep track of the modified stack) */
assert_retry((insn & 0xE000) == 0);
int num_regs = __builtin_popcount(insn & 0xFFFF);
sp -= num_regs * 4;
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: sp -= %d*4 => %x\n", pc, num_regs, sp);
goto ret_regular_insn;
}
if ((insn & 0xFFFF0000) == 0xE8BD0000) /* LDMFD SP!, {regs} */
{
int num_regs = __builtin_popcount(insn & 0xFFFF);
/* pop both LR and PC? not handled */
assert_retry((insn & 0xC000) != 0xC000);
sp += num_regs * 4;
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: sp += %d*4 => %x\n", pc, num_regs, sp);
if (insn & 0x8000)
{
handle_pop_pc(pc, MEM(sp - 4), &next_pc);
goto ret_caller_found;
}
if (insn & 0x4000)
{
handle_tail_call_pop_lr(pc, MEM(sp - 4), &lr, &next_pc);
goto ret_caller_found;
}
#ifdef BKT_HANDLE_UNLIKELY_CASES
if (insn & 0x2000)
{
/* POP SP (unlikely) */
assert(0);
}
#endif
goto ret_regular_insn;
}
if ((insn & 0xFF7F0000) == 0xe41d0000) /* LDR Rn, [SP], #off */
{
uint32_t dest = MEM(sp);
int reg = (insn & 0xF000) >> 12;
sp += decode_signed_offset12(insn);
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: pop R%d\n", pc, reg);
switch (reg)
{
case 15:
handle_pop_pc(pc, dest, &next_pc);
goto ret_caller_found;
case 14:
handle_tail_call_pop_lr(pc, dest, &lr, &next_pc);
goto ret_caller_found;
#ifdef BKT_HANDLE_UNLIKELY_CASES
case 13:
assert(0);
break;
#endif
}
goto ret_regular_insn;
}
/* the following cases are unlikely to appear
* optionally ignore them to keep the binary small
* even so, we are still going to pass the BKT_CROSSCHECK_CALLSTACK tests
* for every single function called until booting the GUI.
*/
#ifdef BKT_HANDLE_UNLIKELY_CASES
if (insn == 0xe12fff1e || /* BX LR */
insn == 0xe1a0f00e) /* MOV PC, LR */
{
/* never encountered with BKT_ASSUME_TAIL_CALL_AFTER_POP_LR */
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: RET to %x\n", pc, lr);
assert_retry(lr);
next_pc = lr;
goto ret_caller_found;
}
if ((insn & 0xFF7F0000) == 0xe52d0000) /* STR Rn, [SP, #off]! */
{
/* pushing PC/LR/SP unhandled */
int reg = (insn & 0xF000) >> 12;
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: push R%d\n", pc, reg);
sp += decode_signed_offset12(insn);
assert_retry(reg != 13);
assert_retry(reg != 14);
assert_retry(reg != 15);
goto ret_regular_insn;
}
if (insn == 0xe1a0e00f) /* MOV LR, PC */
{
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: MOV LR, PC\n", pc);
lr = pc + 8;
goto ret_regular_insn;
}
if ((insn & 0xFFFFFFF0) == 0xe1a0e000) /* mov lr, rn */
{
/* invalidate LR */
qemu_log_mask(BKT_LOG_VERBOSE, "*** %x: invalid LR\n", pc);
lr = 0;
goto ret_regular_insn;
}
if ((insn & 0xFFFFF000) == 0xe3a0e000) /* mov lr, #imm */
{
lr = decode_immediate_shifter_operand(insn);
goto ret_regular_insn;
}
if ((insn & 0xFF7F0000) == 0xe51f0000) /* LDR Rn, [PC, #off] */
{
int off = decode_signed_offset12(insn);
uint32_t dest = MEM(pc + off + 8);
int reg = (insn & 0xF000) >> 12;
qemu_log_mask(BKT_LOG_VERBOSE, "%08x: ldr R%d, =%x (%d)\n", pc, reg, dest, off);
switch (reg)
{
case 15:
next_pc = dest;
break;
case 14:
qemu_log_mask(BKT_LOG_VERBOSE, "*** %x: LDR LR\n", pc);
lr = dest;
break;
case 13:
sp = dest;
break;
}
goto ret_regular_insn;
}
if (insn == 0xe8fd901f || /* LDMFD SP!, {R0-R4,R12,PC}^ */
insn == 0xe8fddfff || /* LDMFD SP!, {R0-R12,LR,PC}^ */
insn == 0xe82d407f || /* stmda sp!, {r0, r1, r2, r3, r4, r5, r6, lr} */
insn == 0xe9bd407f || /* ldmib sp!, {r0, r1, r2, r3, r4, r5, r6, lr} */
(insn & 0xFFF0F000) == 0xe590d000 || /* LDR SP, [Rn] */
(insn & 0xFFFFFFF0) == 0xe12ff000 || /* MSR CPSR_fsxc, Rn */
(insn & 0xFFFFF000) == 0xe321f000 || /* MSR CPSR_c, #off */
(insn & 0xFFFFFFF0) == 0xe04dd000 || /* SUB SP, SP, Rn */
(insn & 0xFFFFF000) == 0xe24bd000 || /* SUB SP, FP, #off */
0)
{
/* unhandled - just fail this code path and hope for the best */
qemu_log_mask(BKT_LOG_VERBOSE, "%x: unhandled\n", pc);
assert_retry(0);
}
if ((insn & 0xFFF00000) == 0xE8b00000) /* LDM{cond} Rn!, {regs} */
{
/* make sure it doesn't touch SP, LR (invalidate), PC */
/* otherwise, not interesting */
qemu_log_mask(BKT_LOG_VERBOSE, "%x: ldm\n", pc);
assert_retry((insn & 0xA000) == 0);
if (insn & 0x4000) lr = 0;
}
if ((insn & 0xFFFFF000) == 0xe28ee000)
{
/* add lr, lr, #off */
qemu_log_mask(BKT_LOG_VERBOSE, "%x: add lr\n", pc);
lr += decode_immediate_shifter_operand(insn);
goto ret_regular_insn;
}
if ((insn & 0xFFFFF000) == 0xe24ee000)
{
/* sub lr, lr, #off */
qemu_log_mask(BKT_LOG_VERBOSE, "%x: sub lr\n", pc);
lr -= decode_immediate_shifter_operand(insn);
goto ret_regular_insn;
}
if ((insn & 0xFFFFF000) == 0xe3a0d000)
{
/* mov sp, #imm */
qemu_log_mask(BKT_LOG_VERBOSE, "%x: mov sp\n", pc);
sp = decode_immediate_shifter_operand(insn);
goto ret_regular_insn;
}
#endif /* BKT_HANDLE_UNLIKELY_CASES */
ret_regular_insn:
*ppc = next_pc; *plr = lr; *psp = sp;
return 0;
ret_caller_found:
*ppc = next_pc; *plr = lr; *psp = sp;
return 1;
#undef assert_retry
}
static uint32_t find_caller(EOSState *s, uint32_t pc, uint32_t *psp)
{
uint32_t pc0 = pc;
uint32_t lr = 0;
uint32_t sp = *psp;
int iter = 0;
/* we need to find one code path that returns from the function */
/* first we try the deterministic path (no conditional branches taken) */
/* if that doesn't work, we'll try taking them randomly */
#ifdef BKT_RANDOM_BRANCHES
uint32_t cond = 0xE0000000;
for (int retries = 0; retries < 10; retries++)
#endif
{
for (iter = 0; iter < 1000; iter++)
{
#ifdef BKT_RANDOM_BRANCHES
if (retries == 0)
{
/* don't take any conditional branches on first try */
cond = 0xE0000000;
}
int ret = sim_instr(s, &pc, &lr, &sp, &cond);
#else
int ret = sim_instr(s, &pc, &lr, &sp);
#endif
if (ret == -1)
{
/* retry from start */
pc = pc0;
lr = 0;
sp = *psp;
break;
}
if (ret == 1)
{
#ifdef BKT_TRACK_STATS
callers_found++;
#ifdef BKT_RANDOM_BRANCHES
if (retries)
{
random_helped++;
}
#endif
#endif
*psp = sp;
return pc;
}
}
qemu_log_mask(BKT_LOG_VERBOSE, "[BKT] retrying (%d)...\n", iter);
}
#ifdef BKT_BRUTE_FORCE_STACK
/* idea from g3gg0, gdb.c */
pc = pc0;
sp = *psp;
qemu_log_mask(BKT_LOG_VERBOSE, "[BKT] trying brute force (pc=%x, sp=%x)\n", pc, sp);
pc = pc0;
sp = *psp;
for (int pos = 0; pos < 64; pos++)
{
uint32_t lr = MEM(sp);
sp += 4;
if (lr == pc)
{
continue;
}
/* address in ROM? -> might be a LR */
/* same for RAM */
if ((lr & 0xF8000000) == 0xF8000000 ||
(lr > 0x100 && lr < 0x20000000))
{
uint32_t pre_lr = MEM(lr - 4);
if ((pre_lr & 0xFF000000) == 0xEB000000) /* BL dest */
{
*psp = sp;
uint32_t dest = branch_destination(pre_lr, lr - 4);
if (dest < pc && dest > pc - 1024)
{
/* likely BL to this function? */
qemu_log_mask(BKT_LOG_VERBOSE, "found BL %x (pc=%x)\n", dest, pc);
#ifdef BKT_TRACK_STATS
callers_found++;
brute_force_helped++;
#endif
return lr;
}
}
}
}
#endif
#ifdef BKT_TRACK_STATS
if (iter == 1000)
{
gaveups_loop++;
}
else
{
gaveups_insn++;
}
#endif
/* fail */
#ifndef BKT_CROSSCHECK_CALLSTACK
fprintf(stderr, "[BKT] giving up.\n");
#endif
return 0;
}
void eos_backtrace_rebuild(EOSState *s, char * buf, int size)
{
#ifdef CONFIG_MAGICLANTERN
//uint32_t pc = (uint32_t) &eos_backtrace_rebuild;
uint32_t lr = read_lr();
uint32_t sp = (uint32_t) s; /* hack: use "s" to transmit the sp before calling this */
#else
uint32_t pc = CURRENT_CPU->env.regs[15] | CURRENT_CPU->env.thumb;
uint32_t lr = CURRENT_CPU->env.regs[14];
uint32_t sp = CURRENT_CPU->env.regs[13];
#endif
uint32_t stack_top, stack_bot;
if (!eos_get_current_task_stack(s, &stack_top, &stack_bot))
{
return;
}
uint32_t lrs[64];
uint32_t sps[64];
int i = 0;
lrs[i] = lr;
sps[i] = sp;
i++;
#ifdef BKT_CROSSCHECK_CALLSTACK
/* we need something to cross-check against :) */
assert(qemu_loglevel_mask(EOS_LOG_CALLSTACK));
int max_depth = eos_callstack_get_caller_param(s, 0, CALL_DEPTH);
int d = 1;
int error = 0;
#endif
/* rebuild the call stack trace by walking the stack */
while (i < COUNT(lrs))
{
lr = find_caller(s, lr, &sp);
if (sp >= stack_top)
{
/* finished */
#ifdef BKT_TRACK_STATS
stacks_completed++;
#endif
break;
}
if (!lr)
{
/* gave up */
#ifdef BKT_TRACK_STATS
if (i > 1)
{
/* at least 1 caller identified? */
stacks_incomplete++;
}
else
{
stacks_gaveup++;
}
#endif
break;
}
#ifdef BKT_CROSSCHECK_CALLSTACK
if (d >= max_depth)
{
error = 1;
}
else
{
uint32_t expected_lr = eos_callstack_get_caller_param(s, d++, CALL_LOCATION) + 4;
if (lr != expected_lr)
{
error = 1;
}
}
#endif
/* store the result */
lrs[i] = lr;
sps[i] = sp;
i++;
}
#ifdef BKT_CROSSCHECK_CALLSTACK
if (error)
{
/* print some info to diagnose the error */
eos_callstack_print_verbose(s);
uint32_t lr = CURRENT_CPU->env.regs[14];
uint32_t sp = CURRENT_CPU->env.regs[13];
d = 1;
while (sp < stack_top)
{
lr = find_caller(s, lr, &sp);
if (sp >= stack_top) break;
if (!lr) break;
uint32_t expected_lr = eos_callstack_get_caller_param(s, d++, CALL_LOCATION) + 4;
printf("lr %x exp %x %s\n", lr, expected_lr, lr == expected_lr ? "" : "!!!");
}
assert(!error);
}
#endif
#ifndef CONFIG_MAGICLANTERN
#ifdef BKT_CROSSCHECK_CALLSTACK
if (0) /* disable output during self tests */
#endif
{
int len = fprintf(stderr, "Current stack: [%x-%x] sp=%x lr=%x", stack_top, stack_bot, sps[0], lrs[0]);
len += eos_indent(len, 80);
len += eos_print_location(s, pc, lrs[0], " @ ", "\n");
/* now go backwards and print the stack trace */
int depth = 0;
while (--i >= 0)
{
int len = eos_indent(0, depth++);
len += fprintf(stderr, "%s ", called_func(lrs[i] - 4));
len += eos_indent(len, 80);
len += eos_print_location(s, lrs[i] - 4, sps[i], " @ ", " (pc:sp)\n");
}
}
#endif
if (buf)
{
int len = snprintf(buf, size,
"%s stack: %x [%x-%x]\n",
eos_get_current_task_name(s),
sps[0], stack_top, stack_bot
);
while (--i >= 0)
{
len += snprintf(
buf + len, size - len,
"%s @ %x:%x\n",
called_func(lrs[i] - 4), lrs[i] - 4, sps[i]
);
}
}
#ifdef BKT_TRACK_STATS
if (rand() % 1000 == 4)
{
fprintf(stderr, "stacks ok:%d incomplete:%d gaveup:%d; callers ok:%d gaveup: %d (loop) %d (insn) helped rand:%d bfs:%d\n",
stacks_completed, stacks_incomplete, stacks_gaveup,
callers_found, gaveups_loop, gaveups_insn,
random_helped, brute_force_helped
);
}
#endif
}
#ifndef CONFIG_MAGICLANTERN
/* this serves as unit test, to check whether instructions
* are emulated correctly in sim_instr (regarding SP, LR and PC)
* to enable: define BKT_CROSSCHECK_EXEC in backtrace.h
* and run with e.g. -d callstack */
void eos_bkt_log_exec(EOSState *s)
{
static uint32_t prev_pc = 0;
static uint32_t prev_lr = 0;
static uint32_t prev_sp = 0;
uint32_t pc = CURRENT_CPU->env.regs[15];
uint32_t lr = CURRENT_CPU->env.regs[14];
uint32_t sp = CURRENT_CPU->env.regs[13];
if (pc == prev_pc) return;
if (pc == 0x18) goto end;
if (sp != prev_sp || lr != prev_lr)
{
/* does our simulation match QEMU's? */
uint32_t insn = MEM(prev_pc);
if ((insn & 0xF0000000) != 0xE0000000)
{
/* conditional instruction - ignore for now */
goto end;
}
uint32_t spc = prev_pc;
uint32_t slr = prev_lr;
uint32_t ssp = prev_sp;
#ifdef BKT_RANDOM_BRANCHES
uint32_t scond = 0xE0000000;
int ret = sim_instr(s, &spc, &slr, &ssp, &scond);
#else
int ret = sim_instr(s, &spc, &slr, &ssp);
#endif
if (ret != -1)
{
if (ssp != sp)
{
fprintf(stderr, "SP mismatch: expected %x->%x, got %x (=> %x at %x,%x)\n", prev_sp, sp, ssp, ret, prev_pc, pc);
CPUARMState *env = &(CURRENT_CPU->env);
target_disas(stderr, CPU(arm_env_get_cpu(env)), prev_pc, 4, 0);
assert(0);
}
if (slr != lr)
{
if (slr == 0)
{
/* LR invalidated (unhandled) */
goto end;
}
uint32_t insn = MEM(prev_pc);
if ((insn & 0x0F000000) == 0x0B000000 || /* BL{cond} */
(insn & 0x0FFFFFF0) == 0x012fff30) /* BLX{cond} Rn */
{
/* we just skip the call and assume proper ABI behavior */
goto end;
}
fprintf(stderr, "LR mismatch: expected %x->%x, got %x (=> %x at %x,%x)\n", prev_lr, lr, slr, ret, prev_pc, pc);
CPUARMState *env = &(CURRENT_CPU->env);
target_disas(stderr, CPU(arm_env_get_cpu(env)), prev_pc, 4, 0);
//assert(0);
}
}
}
end:
prev_pc = pc;
prev_lr = lr;
prev_sp = sp;
}
#endif
#ifdef CONFIG_MAGICLANTERN
void backtrace_print()
{
char buf[512];
void * sp = (void *) read_sp();
eos_backtrace_rebuild(sp, buf, sizeof(buf));
puts(buf);
}
void __attribute__((naked)) backtrace_getstr(char * buf, int size)
{
void * sp = (void *) read_sp();
eos_backtrace_rebuild(sp, buf, size);
}
#endif