From 981e9728390b401404c36241e2ce6bd4cfcb723d Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Mon, 16 May 2022 14:33:26 +0100 Subject: mips: add native backtrace implementation Should make debugging crashes on native MIPS targets far easier. This is by no means a 100% complete or robust implementation but it seems to handle the vast majority of functions. Change-Id: Id5f430270e02b5092b79026b6876675c784aa649 --- lib/mipsunwinder/SOURCES | 2 + lib/mipsunwinder/backtrace-mips32.c | 236 ++++++++++++++++++++++++++++++ lib/mipsunwinder/backtrace-mipsunwinder.h | 65 ++++++++ lib/mipsunwinder/init_context_32.S | 12 ++ lib/mipsunwinder/mipsunwinder.make | 23 +++ 5 files changed, 338 insertions(+) create mode 100644 lib/mipsunwinder/SOURCES create mode 100644 lib/mipsunwinder/backtrace-mips32.c create mode 100644 lib/mipsunwinder/backtrace-mipsunwinder.h create mode 100644 lib/mipsunwinder/init_context_32.S create mode 100644 lib/mipsunwinder/mipsunwinder.make (limited to 'lib/mipsunwinder') diff --git a/lib/mipsunwinder/SOURCES b/lib/mipsunwinder/SOURCES new file mode 100644 index 0000000000..32fa57d157 --- /dev/null +++ b/lib/mipsunwinder/SOURCES @@ -0,0 +1,2 @@ +backtrace-mips32.c +init_context_32.S diff --git a/lib/mipsunwinder/backtrace-mips32.c b/lib/mipsunwinder/backtrace-mips32.c new file mode 100644 index 0000000000..c5ab41e628 --- /dev/null +++ b/lib/mipsunwinder/backtrace-mips32.c @@ -0,0 +1,236 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * MIPS32 backtrace implementation + * Copyright (C) 2022 Aidan MacDonald + * + * References: + * https://yosefk.com/blog/getting-the-call-stack-without-a-frame-pointer.html + * https://elinux.org/images/6/68/ELC2008_-_Back-tracing_in_MIPS-based_Linux_Systems.pdf + * System V ABI MIPS Supplement, 3rd edition + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "backtrace.h" +#include "lcd.h" +#include +#include +#include +#include +#include + +#define MIN_ADDR 0x80000000ul +#define MAX_ADDR (MIN_ADDR + (MEMORYSIZE * 1024 * 1024) - 1) + +static bool read_check(const void* addr, size_t alignment) +{ + if(addr < (const void*)MIN_ADDR || + addr > (const void*)MAX_ADDR) + return false; + + if((uintptr_t)addr & (alignment - 1)) + return false; + + return true; +} + +static bool read32(const void* addr, uint32_t* val) +{ + if(!read_check(addr, alignof(uint32_t))) + return false; + + *val = *(const uint32_t*)addr; + return true; +} + +#if 0 +static bool read16(const void* addr, uint16_t* val) +{ + if(!read_check(addr, alignof(uint16_t))) + return false; + + *val = *(const uint16_t*)addr; + return true; +} + +static bool read8(const void* addr, uint8_t* val) +{ + if(!read_check(addr, alignof(uint8_t))) + return false; + + *val = *(const uint8_t*)addr; + return true; +} +#endif + +static long extract_s16(uint32_t val) +{ +#if 1 + /* not ISO C, but gets GCC to emit 'seh' which is more compact */ + return (int32_t)(val << 16) >> 16; +#else + val &= 0xffff; + + if(val > 0x7fff) + return (long)val - 0x10000l; + else + return val; +#endif +} + +/* TODO - cases not handled by the backtrace algorithm + * + * 1. functions that save the frame pointer will not be handled correctly + * (need to implement the algorithm specified by the System V ABI). + * + * 2. GCC can generate functions with a "false" stack pointer decrement, + * for some examples see read_bmp_fd and walk_path. those functions + * seem to be more difficult to deal with and the SysV algorithm will + * also get confused by them, but they are not common. + */ +int mips_bt_step(struct mips_bt_context* ctx) +{ + /* go backward and look for the stack pointer decrement */ + uint32_t* pc = ctx->pc; + uint32_t insn; + long sp_off; + + while(true) { + if(!read32(pc, &insn)) + return 0; + + /* addiu sp, sp, sp_off */ + if((insn >> 16) == 0x27bd) { + sp_off = extract_s16(insn); + if(sp_off < 0) + break; + } + + /* jr ra */ + if(insn == 0x03e00008) { + /* end if this is not a leaf or we lack register info */ + if(ctx->depth > 0 || !(ctx->valid & (1 << MIPSBT_RA))) { + mips_bt_debug(ctx, "unexpected leaf function"); + return 0; + } + + /* this is a leaf function - ra contains the return address + * and sp is unchanged */ + ctx->pc = (void*)ctx->reg[MIPSBT_RA] - 8; + ctx->depth++; + return 1; + } + + --pc; + } + + mips_bt_debug(ctx, "found sp_off=%ld at %p", sp_off, pc); + + /* now go forward and find the saved return address */ + while((void*)pc < ctx->pc) { + if(!read32(pc, &insn)) + return 0; + + /* sw ra, ra_off(sp) */ + if((insn >> 16) == 0xafbf) { + long ra_off = extract_s16(insn); + uint32_t save_ra; + + /* load the saved return address */ + mips_bt_debug(ctx, "load ra from %p+%ld", ctx->sp, ra_off); + if(!read32(ctx->sp + ra_off, &save_ra)) + return 0; + + if(save_ra == 0) { + mips_bt_debug("hit root"); + return 0; + } + + /* update bt context */ + ctx->pc = (void*)save_ra - 8; + ctx->sp -= sp_off; + + /* update saved register info */ + ctx->reg[MIPSBT_RA] = save_ra; + ctx->valid |= (1 << MIPSBT_RA); + + ctx->depth++; + return 1; + } + + ++pc; + } + + /* sometimes an exception occurs before ra is saved - in this case + * the ra register should contain the caller PC, but we still need + * to adjust the stack frame. */ + if(ctx->depth == 0 && (ctx->valid & (1 << MIPSBT_RA))) { + ctx->pc = (void*)ctx->reg[MIPSBT_RA] - 8; + ctx->sp -= sp_off; + + ctx->depth++; + return 1; + } + + mips_bt_debug(ctx, "ra not found"); + return 0; +} + +#ifdef MIPSUNWINDER_DEBUG +static void rb_backtrace_debugf(void* arg, const char* fmt, ...) +{ + static char buf[64]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + unsigned int* line = arg; + lcd_putsf(4, (*line)++, "%s", buf); + lcd_update(); +} +#endif + +void rb_backtrace_ctx(void* arg, unsigned* line) +{ + struct mips_bt_context* ctx = arg; +#ifdef MIPSUNWINDER_DEBUG + ctx->debugf = rb_backtrace_debugf; + ctx->debug_arg = line; +#endif + + do { + lcd_putsf(0, (*line)++, "%02d pc:%08lx sp:%08lx", + ctx->depth, (unsigned long)ctx->pc, (unsigned long)ctx->sp); + lcd_update(); + } while(mips_bt_step(ctx)); + + lcd_puts(0, (*line)++, "bt end"); + lcd_update(); +} + +void rb_backtrace(int pcAddr, int spAddr, unsigned* line) +{ + (void)pcAddr; + (void)spAddr; + + struct mips_bt_context ctx; + mips_bt_start(&ctx); + mips_bt_step(&ctx); /* step over this function */ + + rb_backtrace_ctx(&ctx, line); +} diff --git a/lib/mipsunwinder/backtrace-mipsunwinder.h b/lib/mipsunwinder/backtrace-mipsunwinder.h new file mode 100644 index 0000000000..4d6288b5fe --- /dev/null +++ b/lib/mipsunwinder/backtrace-mipsunwinder.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2022 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef BACKTRACE_MIPSUNWINDER_H +#define BACKTRACE_MIPSUNWINDER_H + +/*#define MIPSUNWINDER_DEBUG*/ + +#include + +enum { + MIPSBT_RA, + MIPSBT_NREG, +}; + +struct mips_bt_context { + void* pc; + void* sp; + int depth; + uint32_t valid; + uint32_t reg[MIPSBT_NREG]; +#ifdef MIPSUNWINDER_DEBUG + void(*debugf)(void*, const char*, ...); + void* debug_arg; +#endif +}; + +int mips_bt_step(struct mips_bt_context* ctx); +void mips_bt_start(struct mips_bt_context* ctx); + +#ifdef MIPSUNWINDER_DEBUG +# define mips_bt_debug(ctx, ...) \ + do { struct mips_bt_context* __ctx = ctx; \ + if(__ctx->debugf) \ + __ctx->debugf(__ctx->debug_arg, __VA_ARGS__);\ + } while(0) +#else +# define mips_bt_debug(...) do { } while(0) +#endif + +/* NOTE: ignores pcAddr and spAddr, backtrace starts from caller */ +void rb_backtrace(int pcAddr, int spAddr, unsigned* line); + +/* given struct mips_bt_context argument, print stack traceback */ +void rb_backtrace_ctx(void* arg, unsigned* line); + +#endif /* BACKTRACE_MIPSUNWINDER_H */ diff --git a/lib/mipsunwinder/init_context_32.S b/lib/mipsunwinder/init_context_32.S new file mode 100644 index 0000000000..a943d13dc3 --- /dev/null +++ b/lib/mipsunwinder/init_context_32.S @@ -0,0 +1,12 @@ +#include "mips.h" + + .text + .global mips_bt_start + +mips_bt_start: + addiu v0, ra, -8 + sw v0, 0(a0) /* ctx->pc = ra - 8 */ + sw sp, 4(a0) /* ctx->sp = sp */ + sw zero, 8(a0) /* ctx->depth = 0 */ + sw zero, 12(a0) /* ctx->valid = 0 */ + jr ra diff --git a/lib/mipsunwinder/mipsunwinder.make b/lib/mipsunwinder/mipsunwinder.make new file mode 100644 index 0000000000..ddd1ce078f --- /dev/null +++ b/lib/mipsunwinder/mipsunwinder.make @@ -0,0 +1,23 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# + +MIPSUNWINDERLIB_DIR = $(ROOTDIR)/lib/mipsunwinder +MIPSUNWINDERLIB_SRC = $(call preprocess, $(MIPSUNWINDERLIB_DIR)/SOURCES) +MIPSUNWINDERLIB_OBJ := $(call c2obj, $(MIPSUNWINDERLIB_SRC)) + +OTHER_SRC += $(MIPSUNWINDERLIB_SRC) + +MIPSUNWINDERLIB = $(BUILDDIR)/lib/libmipsunwinder.a +CORE_LIBS += $(MIPSUNWINDERLIB) + +INCLUDES += -I$(MIPSUNWINDERLIB_DIR) +DEFINES += -DBACKTRACE_MIPSUNWINDER + +$(MIPSUNWINDERLIB): $(MIPSUNWINDERLIB_OBJ) + $(SILENT)$(shell rm -f $@) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null -- cgit v1.2.3