diff --git a/include/PR/os.h b/include/PR/os.h index 2ce02f99..8fa825d3 100644 --- a/include/PR/os.h +++ b/include/PR/os.h @@ -63,6 +63,8 @@ extern "C" { #endif +#define PACKED __attribute__((packed)) + #include /************************************************************************** diff --git a/src/fault/assert.h b/src/fault/assert.h new file mode 100644 index 00000000..a8e1860a --- /dev/null +++ b/src/fault/assert.h @@ -0,0 +1,79 @@ +#pragma once + +#define ASSERT_MESGBUF_SIZE 256 + +#ifndef __ASSEMBLER__ + +extern char* __n64Assert_Filename; +extern u32 __n64Assert_LineNum; +extern char* __n64Assert_Condition; +extern char __n64Assert_MessageBuf[ASSERT_MESGBUF_SIZE + 1]; +extern void __n64Assert(char* fileName, u32 lineNum, char* cond); + +/** + * Will always cause a crash with your message of choice + */ +#define errorf(message, ...) \ + { \ + sprintf(__n64Assert_MessageBuf, message##__VA_ARGS__); \ + __n64Assert(__FILE__, __LINE__, " errorf() "); \ + } +#define error(message) \ + { \ + sprintf(__n64Assert_MessageBuf, message); \ + __n64Assert(__FILE__, __LINE__, " error() "); \ + } + +/** + * Wrapper for assert/aggress + */ +#define __assert_wrapper(cond) __n64Assert(__FILE__, __LINE__, (cond)) + +/** + * `aggress` and `aggressf` will always cause a crash if `cond` is not true (handle with care) + */ +#define aggressf(cond, ...) \ + do { \ + if ((cond) == FALSE) { \ + sprintf(__n64Assert_MessageBuf, __VA_ARGS__); \ + __assert_wrapper(#cond); \ + } \ + } while (0); +#define aggress(cond) \ + do { \ + if ((cond) == FALSE) { \ + __n64Assert_MessageBuf[0] = 0; \ + __assert_wrapper(#cond); \ + } \ + } while (0); + +/** + * Will cause a crash if cond is not true, and DEBUG is defined. + * If disabled, `!cond` is marked as unreachable, which should + * improve codegen on release builds + */ +#ifdef DEBUG_ASSERTIONS +#define assertf(cond, ...) \ + do { \ + if ((cond) == FALSE) { \ + sprintf(__n64Assert_MessageBuf, __VA_ARGS__); \ + __assert_wrapper(#cond); \ + } \ + } while (0); +#else +#define assertf(cond, ...) +#endif + +#ifdef DEBUG_ASSERTIONS +#define assert(cond) \ + do { \ + if ((cond) == FALSE) { \ + __n64Assert_MessageBuf[0] = 0; \ + __assert_wrapper(#cond); \ + } \ + } while (0); +#else +#define assert(cond) +#endif + +#endif // ASSEMBLER diff --git a/src/fault/crash_screen.c b/src/fault/crash_screen.c new file mode 100644 index 00000000..eecfd1ee --- /dev/null +++ b/src/fault/crash_screen.c @@ -0,0 +1,612 @@ +#include +#include +#include +#include +#include + +#include "assert.h" +#include "crash_screen.h" +#include "disasm.h" +#include "map_parser.h" +#include "stacktrace.h" + +extern u16 sRenderedFramebuffer; +extern void audio_signal_game_loop_tick(void); +extern void stop_sounds_in_continuous_banks(void); +extern void read_controller_inputs(s32 threadID); + +static OSFaultContext __osCurrentFaultContext; + +// Configurable Defines +#define X_KERNING 6 +#define GLYPH_WIDTH 8 +#define GLYPH_HEIGHT 12 +#define FONT_ROWS 16 +#define LEFT_MARGIN 10 // for crash screen prints + +enum crashPages { PAGE_SIMPLE, PAGE_CONTEXT, PAGE_STACKTRACE, PAGE_DISASM, PAGE_ASSERTS, PAGE_COUNT }; + +static char* crashPageNames[PAGE_COUNT + NUM_USER_PAGES] = { + [PAGE_SIMPLE] = "(Overview)", [PAGE_CONTEXT] = "(Context)", [PAGE_STACKTRACE] = "(Stack Trace)", + [PAGE_DISASM] = "(Disassembly)", [PAGE_ASSERTS] = "(Assert)", +}; + +static u8 sCrashScreenCharToGlyph[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, +}; + +static u32 sCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { + 0x00000000, 0x00000000, 0x00000000, 0x40000000, 0x007070f0, 0x20000000, 0x00888888, 0x00000000, 0x0098b888, + 0x00f00000, 0x00a8a8f0, 0x00880000, 0x00c8b880, 0x00880000, 0x00888080, 0x00880000, 0x00707880, 0x00f00000, + 0x00000000, 0x00800000, 0x00000000, 0x00800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x20202070, 0x00000000, 0x20e05088, 0x00000000, 0x20205088, 0x78780000, 0x20208888, 0x88880000, + 0x2020f888, 0x88880000, 0x002088a8, 0x98880000, 0x20f88870, 0x68780000, 0x00000008, 0x00080000, 0x00000000, + 0x00080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x50000000, 0x00000000, 0x5070f0f0, 0x80000000, + 0x50888888, 0x80000000, 0x00088888, 0xf0b00000, 0x0010f0f0, 0x88c80000, 0x00208888, 0x88800000, 0x00408888, + 0x88800000, 0x00f8f088, 0xf0800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x50707070, 0x00000000, 0xf8888888, 0x00000000, 0x50088080, + 0x70780000, 0xf8308070, 0x88800000, 0x50088008, 0x80700000, 0x00888888, 0x80080000, 0x00707070, 0x78f00000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x20000000, + 0x00000000, 0x7010f0f8, 0x08200000, 0xa8308820, 0x08200000, 0xa0508820, 0x78700000, 0x70908820, 0x88200000, + 0x28f88820, 0x88200000, 0xa8108820, 0x88200000, 0x7038f020, 0x78180000, 0x20000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x48f8f888, 0x00000000, + 0xa8808088, 0x00000000, 0xb0f08088, 0x70880000, 0x7008f088, 0x88880000, 0x68088088, 0xf8880000, 0xa8888088, + 0x80980000, 0x9070f870, 0x78680000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x6070f888, 0x18000000, 0x90808088, 0x20000000, 0xa0f08088, + 0x70880000, 0x4088f050, 0x20880000, 0xa8888050, 0x20500000, 0x90888020, 0x20500000, 0x68708020, 0x20200000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x20000000, + 0x00000000, 0x20f87088, 0x00000000, 0x20088888, 0x00000000, 0x00088088, 0x78a80000, 0x001098a8, 0x88a80000, + 0x002088a8, 0x88a80000, 0x002088d8, 0x88a80000, 0x00207088, 0x78500000, 0x00000000, 0x08000000, 0x00000000, + 0x70000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10000000, 0x00000000, 0x20708888, 0x80000000, + 0x40888888, 0x80000000, 0x40888850, 0xf0880000, 0x4070f820, 0x88500000, 0x40888850, 0x88200000, 0x40888888, + 0x88500000, 0x20708888, 0x88880000, 0x10000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x40000000, 0x00000000, 0x2070f888, 0x20000000, 0x10882088, 0x00000000, 0x10882088, + 0x60880000, 0x10882050, 0x20880000, 0x10782020, 0x20880000, 0x10082020, 0x20880000, 0x2070f820, 0x70780000, + 0x40000000, 0x00080000, 0x00000000, 0x00700000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x200008f8, 0x20000000, 0xa8600808, 0x00000000, 0x70600810, 0x60f80000, 0x70000820, 0x20100000, + 0xa8608840, 0x20200000, 0x20608880, 0x20400000, 0x000070f8, 0x20f80000, 0x00000000, 0x20000000, 0x00000000, + 0xc0000000, 0x00000000, 0x00000000, 0x00000000, 0x00100000, 0x00000030, 0x00200000, 0x00008820, 0x80200000, + 0x20609020, 0x80200000, 0x2060a020, 0x90200000, 0xf800c020, 0xa0400000, 0x2060a020, 0xe0200000, 0x20609020, + 0x90200000, 0x00208820, 0x88200000, 0x00400030, 0x00200000, 0x00000000, 0x00100000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000080, 0x00200000, 0x00088040, 0x60200000, 0x00108040, 0x20200000, 0x00208020, + 0x20200000, 0x00408020, 0x20200000, 0x60208010, 0x20200000, 0x60108010, 0x20200000, 0x2008f808, 0x70200000, + 0x40000008, 0x00200000, 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00400000, 0x00000060, + 0x00200000, 0x00008820, 0x00200000, 0x0000d820, 0x00200000, 0x00f8a820, 0xf0200000, 0x7000a820, 0xa8100000, + 0x00f88820, 0xa8200000, 0x00008820, 0xa8200000, 0x00008820, 0xa8200000, 0x00000060, 0x00200000, 0x00000000, + 0x00400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00408820, 0x00000000, + 0x0020c850, 0x00000000, 0x0010a888, 0xb0680000, 0x00089800, 0xc8b00000, 0x00108800, 0x88000000, 0x60208800, + 0x88000000, 0x60408800, 0x88000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x04000000, 0x00000000, 0x08707000, 0x00000000, 0x08888800, 0x00000000, 0x10088800, + 0x70000000, 0x10108800, 0x88000000, 0x20208800, 0x88000000, 0x20008800, 0x88000000, 0x40207000, 0x70000000, + 0x40000000, 0x00000000, 0x800000fc, 0x00000000, 0x00000000, 0x00000000 +}; + +u8 crashPage = 0; +u8 updateBuffer = TRUE; + +static char crashScreenBuf[0x200]; + +char* gCauseDesc[18] = { + "Interrupt", + "TLB modification", + "TLB exception on load", + "TLB exception on store", + "Address error on load", + "Address error on store", + "Bus error on inst.", + "Bus error on data", + "Failed Assert: See Assert Page", + "Breakpoint exception", + "Reserved instruction", + "Coprocessor unusable", + "Arithmetic overflow", + "Trap exception", + "Virtual coherency on inst.", + "Floating point exception", + "Watchpoint exception", + "Virtual coherency on data", +}; + +char* gFpcsrDesc[6] = { + "Unimplemented operation", "Invalid operation", "Division by zero", "Overflow", "Underflow", "Inexact operation", +}; + +static u32 sProgramPosition = 0; +static u16 gCrashScreenTextColor = 0xFFFF; + +static void set_text_color(u32 r, u32 g, u32 b) { + gCrashScreenTextColor = GPACK_RGBA5551(r, g, b, 255); +} + +static void reset_text_color(void) { + gCrashScreenTextColor = 0xFFFF; +} + +void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { + u16* ptr; + s32 i, j; + + ptr = __osCurrentFaultContext.cfb + __osCurrentFaultContext.width * y + x; + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + *ptr = 0x0001; + ptr++; + } + ptr += __osCurrentFaultContext.width - w; + } +} + +void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { + const u32* data; + u16* ptr; + u32 bit; + u32 rowMask; + s32 i, j; + + if (glyph > 0x7F) + return; + + data = &sCrashScreenFont[((glyph & 0xF) * GLYPH_HEIGHT * 2) + (glyph >= 64)]; + + ptr = __osCurrentFaultContext.cfb + __osCurrentFaultContext.width * y + x; + + u16 color = gCrashScreenTextColor; + + for (i = 0; i < GLYPH_HEIGHT; i++) { + bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); + rowMask = *data++; + data++; + + for (j = 0; j < (GLYPH_WIDTH); j++) { + if (bit & rowMask) { + *ptr = color; + } + ptr++; + bit >>= 1; + } + ptr += __osCurrentFaultContext.width - (GLYPH_WIDTH); + } +} + +static char* write_to_buf(char* buffer, const char* data, size_t size) { + return (char*)memcpy(buffer, data, size) + size; +} + +void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const char* fmt, ...) { + char* ptr; + u32 glyph; + s32 size; + u32 xOffset = x; + + va_list args; + va_start(args, fmt); + + size = _Printf(write_to_buf, crashScreenBuf, fmt, args); + + if (size > 0) { + ptr = crashScreenBuf; + + while (*ptr && size-- > 0) { + if (xOffset >= __osCurrentFaultContext.width - (xNewline + X_KERNING)) { + y += 10; + xOffset = xNewline; + } + + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; + + if (*ptr == '\n') { + y += 10; + xOffset = x; + ptr++; + continue; + } else if (glyph != 0xff) { + crash_screen_draw_glyph(xOffset, y, glyph); + } + + ptr++; + xOffset += X_KERNING; + } + } + + va_end(args); +} + +void crash_screen_print(s32 x, s32 y, const char* fmt, ...) { + char* ptr; + u32 glyph; + s32 size; + + va_list args; + va_start(args, fmt); + + size = _Printf(write_to_buf, crashScreenBuf, fmt, args); + + if (size > 0) { + ptr = crashScreenBuf; + + while (*ptr && size-- > 0) { + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; + + if (glyph != 0xff) { + crash_screen_draw_glyph(x, y, glyph); + } + + ptr++; + x += X_KERNING; + } + } + + va_end(args); +} + +void crash_screen_sleep(s32 ms) { + u64 cycles = ms * 1000LL * osClockRate / 1000000ULL; + osSetTime(0); + while (osGetTime() < cycles) { + } +} + +void crash_screen_print_float_reg(s32 x, s32 y, s32 regNum, void* addr) { + u32 bits = *(u32*)addr; + s32 exponent = ((bits & 0x7f800000U) >> 0x17) - 0x7F; + + if ((exponent >= -0x7E && exponent <= 0x7F) || bits == 0x0) { + crash_screen_print(x, y, "F%02d:%.3e", regNum, *(f32*)addr); + } else { + crash_screen_print(x, y, "F%02d:%08XD", regNum, *(u32*)addr); + } +} + +void crash_screen_print_fpcsr(u32 fpcsr) { + s32 i; + u32 bit = FPCSR_CE; + + crash_screen_print(100, 220, "FPCSR:%08XH", fpcsr); + for (i = 0; i < 6; i++) { + if (fpcsr & bit) { + crash_screen_print(222, 220, "(%s)", gFpcsrDesc[i]); + return; + } + bit >>= 1; + } +} + +void draw_crash_overview(OSThread* thread, s32 cause) { + __OSThreadContext* tc = &thread->context; + + crash_screen_draw_rect(0, 20, 320, 240); + + crash_screen_print(LEFT_MARGIN, 20, "Thread %d (%s)", thread->id, gCauseDesc[cause]); + +#ifdef DEBUG_EXPORT_SYMBOLS + symtable_info_t info = get_symbol_info(tc->pc); + + crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", info.func == NULL ? "Unknown" : info.func); + if (info.line != -1) { + crash_screen_print(LEFT_MARGIN, 60, "File: %s", info.file); +#ifdef DEBUG_EXPORT_ALL_LINES + // This line only shows the correct value if every line is in the sym file + crash_screen_print(LEFT_MARGIN, 72, "Line: %d", info.line); +#endif // DEBUG_EXPORT_ALL_LINES + } +#endif // DEBUG_EXPORT_SYMBOLS + + crash_screen_print(LEFT_MARGIN, 84, "Address: 0x%08X", tc->pc); +} + +void draw_crash_context(OSThread* thread, s32 cause) { + __OSThreadContext* tc = &thread->context; + crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_print(LEFT_MARGIN, 20, "Thread:%d (%s)", thread->id, gCauseDesc[cause]); + crash_screen_print(LEFT_MARGIN, 30, "PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); + osWritebackDCacheAll(); +#ifdef DEBUG_EXPORT_SYMBOLS + char* fname = parse_map(tc->pc, TRUE); + crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); +#endif // DEBUG_EXPORT_SYMBOLS + crash_screen_print(LEFT_MARGIN, 52, "AT:%08XH V0:%08XH V1:%08XH", (u32)tc->at, (u32)tc->v0, (u32)tc->v1); + crash_screen_print(LEFT_MARGIN, 62, "A0:%08XH A1:%08XH A2:%08XH", (u32)tc->a0, (u32)tc->a1, (u32)tc->a2); + crash_screen_print(LEFT_MARGIN, 72, "A3:%08XH T0:%08XH T1:%08XH", (u32)tc->a3, (u32)tc->t0, (u32)tc->t1); + crash_screen_print(LEFT_MARGIN, 82, "T2:%08XH T3:%08XH T4:%08XH", (u32)tc->t2, (u32)tc->t3, (u32)tc->t4); + crash_screen_print(LEFT_MARGIN, 92, "T5:%08XH T6:%08XH T7:%08XH", (u32)tc->t5, (u32)tc->t6, (u32)tc->t7); + crash_screen_print(LEFT_MARGIN, 102, "S0:%08XH S1:%08XH S2:%08XH", (u32)tc->s0, (u32)tc->s1, (u32)tc->s2); + crash_screen_print(LEFT_MARGIN, 112, "S3:%08XH S4:%08XH S5:%08XH", (u32)tc->s3, (u32)tc->s4, (u32)tc->s5); + crash_screen_print(LEFT_MARGIN, 122, "S6:%08XH S7:%08XH T8:%08XH", (u32)tc->s6, (u32)tc->s7, (u32)tc->t8); + crash_screen_print(LEFT_MARGIN, 132, "T9:%08XH GP:%08XH SP:%08XH", (u32)tc->t9, (u32)tc->gp, (u32)tc->sp); + crash_screen_print(LEFT_MARGIN, 142, "S8:%08XH RA:%08XH", (u32)tc->s8, (u32)tc->ra); +#ifdef DEBUG_EXPORT_SYMBOLS + fname = parse_map(tc->ra, TRUE); + crash_screen_print(LEFT_MARGIN, 152, "RA at: %s", fname == NULL ? "Unknown" : fname); +#endif // DEBUG_EXPORT_SYMBOLS + + crash_screen_print_fpcsr(tc->fpcsr); + + osWritebackDCacheAll(); + crash_screen_print_float_reg(10, 170, 0, &tc->fp0.f.f_even); + crash_screen_print_float_reg(100, 170, 2, &tc->fp2.f.f_even); + crash_screen_print_float_reg(190, 170, 4, &tc->fp4.f.f_even); + crash_screen_print_float_reg(10, 180, 6, &tc->fp6.f.f_even); + crash_screen_print_float_reg(100, 180, 8, &tc->fp8.f.f_even); + crash_screen_print_float_reg(190, 180, 10, &tc->fp10.f.f_even); + crash_screen_print_float_reg(10, 190, 12, &tc->fp12.f.f_even); + crash_screen_print_float_reg(100, 190, 14, &tc->fp14.f.f_even); + crash_screen_print_float_reg(190, 190, 16, &tc->fp16.f.f_even); + crash_screen_print_float_reg(10, 200, 18, &tc->fp18.f.f_even); + crash_screen_print_float_reg(100, 200, 20, &tc->fp20.f.f_even); + crash_screen_print_float_reg(190, 200, 22, &tc->fp22.f.f_even); + crash_screen_print_float_reg(10, 210, 24, &tc->fp24.f.f_even); + crash_screen_print_float_reg(100, 210, 26, &tc->fp26.f.f_even); + crash_screen_print_float_reg(190, 210, 28, &tc->fp28.f.f_even); + crash_screen_print_float_reg(10, 220, 30, &tc->fp30.f.f_even); +} + +#ifdef PUPPYPRINT_DEBUG +void draw_crash_log(void) { + s32 i; + crash_screen_draw_rect(0, 20, 320, 210); + osWritebackDCacheAll(); +#define LINE_HEIGHT (25 + ((LOG_BUFFER_SIZE - 1) * 10)) + for (i = 0; i < LOG_BUFFER_SIZE; i++) { + crash_screen_print(LEFT_MARGIN, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); + } +#undef LINE_HEIGHT +} +#endif + +void draw_stacktrace(OSThread* thread, s32 cause) { + __OSThreadContext* tc = &thread->context; + + crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", (u32)tc->sp); + +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + // Current Func (EPC) + crash_screen_print(LEFT_MARGIN, 35, "%08X (%s)", tc->pc, parse_map(tc->pc, TRUE)); + + // Previous Func (RA) + u32 ra = tc->ra; + symtable_info_t info = get_symbol_info(ra); + + crash_screen_print(LEFT_MARGIN, 45, "%08X (%s:%d)", ra, info.func, info.line); + + osWritebackDCacheAll(); + + static u32 generated = 0; + + if (stackTraceGenerated == FALSE) { + generated = generate_stack(thread); + stackTraceGenerated = TRUE; + } + for (u32 i = 0; i < generated; i++) { + crash_screen_print(LEFT_MARGIN, 55 + (i * 10), get_stack_entry(i)); + } +#else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + // simple stack trace + u32 sp = tc->sp; + + for (int i = 0; i < STACK_LINE_COUNT; i++) { + crash_screen_print(LEFT_MARGIN, 55 + (i * 10), "%3d: %08X", i, *((u32*)(sp + (i * 4)))); + crash_screen_print(120, 55 + (i * 10), "%3d: %08X", i + STACK_LINE_COUNT, + *((u32*)(sp + ((i + STACK_LINE_COUNT) * 4)))); + } +#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) +} + +void draw_disasm(OSThread* thread) { + __OSThreadContext* tc = &thread->context; + + crash_screen_draw_rect(0, 20, 320, 240); + if (sProgramPosition == 0) { + sProgramPosition = (tc->pc - 36); + } + crash_screen_print(LEFT_MARGIN, 25, "Program Counter: %08X", sProgramPosition); + osWritebackDCacheAll(); + + int skiplines = 0; +#ifdef DEBUG_EXPORT_SYMBOLS + int currline = 0; +#endif // DEBUG_EXPORT_SYMBOLS + + for (int i = 0; i < 19; i++) { + u32 addr = (sProgramPosition + (i * 4)); + + char* disasm = insn_disasm((InsnData*)addr); + + if (disasm[0] == 0) { + crash_screen_print(LEFT_MARGIN + 22, 35 + (skiplines * 10) + (i * 10), "%08X", addr); + } else { +#ifdef DEBUG_EXPORT_SYMBOLS + symtable_info_t info = get_symbol_info(addr); + + if (info.func_offset == 0 && info.distance == 0 && currline != info.line) { + currline = info.line; + set_text_color(239, 196, 15); + crash_screen_print(LEFT_MARGIN, 35 + (skiplines * 10) + (i * 10), "<%s:>", info.func); + reset_text_color(); + skiplines++; + } +#ifndef DEBUG_EXPORT_ALL_LINES + // catch `jal` and `jalr` callsites + if (disasm[0] == 'j' && disasm[1] == 'a') { +#endif // DEBUG_EXPORT_ALL_LINES + if (info.line != -1) { + set_text_color(200, 200, 200); + crash_screen_print(LEFT_MARGIN, 35 + (skiplines * 10) + (i * 10), "%d:", info.line); + reset_text_color(); + } +#ifndef DEBUG_EXPORT_ALL_LINES + } +#endif // DEBUG_EXPORT_ALL_LINES + +#endif // DEBUG_EXPORT_SYMBOLS + if (addr == tc->pc) { + set_text_color(255, 0, 0); + } else { + reset_text_color(); + } + crash_screen_print(LEFT_MARGIN + 22, 35 + (skiplines * 10) + (i * 10), "%s", disasm); + } + } + + reset_text_color(); + osWritebackDCacheAll(); +} + +void draw_assert(OSThread* thread) { + crash_screen_draw_rect(0, 20, 320, 240); + + crash_screen_print(LEFT_MARGIN, 25, "Assert"); + + if (__n64Assert_Filename != NULL) { + crash_screen_print(LEFT_MARGIN, 35, "File: %s", __n64Assert_Filename); + crash_screen_print(LEFT_MARGIN, 45, "Line %d", __n64Assert_LineNum); + crash_screen_print(LEFT_MARGIN, 55, "Condition:"); + crash_screen_print(LEFT_MARGIN, 65, "(%s)", __n64Assert_Condition); + if (__n64Assert_MessageBuf[0] != 0) { + crash_screen_print(LEFT_MARGIN, 75, "Message:"); + crash_screen_print(LEFT_MARGIN, 85, " %s", __n64Assert_MessageBuf); + } + } else { + crash_screen_print(LEFT_MARGIN, 35, "No failed assert to report."); + } + + osWritebackDCacheAll(); +} + +void draw_crash_screen(OSThread* thread) { + __OSThreadContext* tc = &thread->context; + + s32 cause = ((tc->cause >> 2) & 0x1F); + if (cause == 23) { // EXC_WATCH + cause = 16; + } + if (cause == 31) { // EXC_VCED + cause = 17; + } + + // if (gPlayer1Controller->buttonPressed & R_TRIG) { + // crashPage++; + // if (crashPage == PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage++; + // updateBuffer = TRUE; + // } + // if (gPlayer1Controller->buttonPressed & (L_TRIG | Z_TRIG)) { + // crashPage--; + // if (crashPage == PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; + // updateBuffer = TRUE; + // } + + // if (crashPage == PAGE_DISASM) { + // if (gPlayer1Controller->buttonDown & D_CBUTTONS) { + // sProgramPosition += 4; + // updateBuffer = TRUE; + // } + // if (gPlayer1Controller->buttonDown & U_CBUTTONS) { + // sProgramPosition -= 4; + // updateBuffer = TRUE; + // } + // } + + if ((crashPage >= PAGE_COUNT) && (crashPage != 255)) { + crashPage = 0; + } + if (crashPage == 255) { + crashPage = (PAGE_COUNT - 1); + if (crashPage == PAGE_ASSERTS && tc->cause != EXC_SYSCALL) + crashPage--; + } + if (updateBuffer) { + crash_screen_draw_rect(0, 0, 320, 20); + crash_screen_print(LEFT_MARGIN, 5, "Page:%02d %-22s L/Z: Left R: Right", crashPage, + crashPageNames[crashPage]); + switch (crashPage) { + case PAGE_SIMPLE: + draw_crash_overview(thread, cause); + break; + case PAGE_CONTEXT: + draw_crash_context(thread, cause); + break; +#ifdef PUPPYPRINT_DEBUG + case PAGE_LOG: + draw_crash_log(); + break; +#endif + case PAGE_STACKTRACE: + draw_stacktrace(thread, cause); + break; + case PAGE_DISASM: + draw_disasm(thread); + break; + case PAGE_ASSERTS: + draw_assert(thread); + break; + } + + osWritebackDCacheAll(); + osViBlack(FALSE); + osViSwapBuffer(__osCurrentFaultContext.cfb); + updateBuffer = FALSE; + } +} + +OSThread* get_crashed_thread(void) { + OSThread* thread = __osGetCurrFaultedThread(); + + while (thread->priority != -1) { + if (thread->priority > OS_PRIORITY_IDLE && thread->priority < OS_PRIORITY_APPMAX + && ((thread->flags & (BIT(0) | BIT(1))) != 0)) { + return thread; + } + thread = thread->tlnext; + } + return NULL; +} + +void osFaultMain(void* arg) { + OSMesg mesg; + OSThread* thread = NULL; + OSFaultProgramArguments *args = (OSFaultProgramArguments *)arg; + + __osCurrentFaultContext.width = args->width; + __osCurrentFaultContext.height = args->height; + __osCurrentFaultContext.cfb = args->cfb; + + osSetEventMesg(OS_EVENT_CPU_BREAK, &__osCurrentFaultContext.mesgQueue, (OSMesg)1); + osSetEventMesg(OS_EVENT_FAULT, &__osCurrentFaultContext.mesgQueue, (OSMesg)2); + while (TRUE) { + if (thread == NULL) { + osRecvMesg(&__osCurrentFaultContext.mesgQueue, &mesg, 1); + thread = get_crashed_thread(); + if (thread) { + __osCurrentFaultContext.thread.priority = 15; + crash_screen_sleep(200); + audio_signal_game_loop_tick(); + crash_screen_sleep(200); + // If an assert happened, go straight to that page + if (thread->context.cause == EXC_SYSCALL) { + crashPage = PAGE_ASSERTS; + } + continue; + } + } else { + // if (gControllerBits) { + // #if ENABLE_RUMBLE + // block_until_rumble_pak_free(); + // #endif + // osContStartReadDataEx(&gSIEventMesgQueue); + // } + read_controller_inputs(FAULT_THREAD); + draw_crash_screen(thread); + } + } +} + +void osCreateFaultHandler(void) { + osCreateMesgQueue(&__osCurrentFaultContext.mesgQueue, &__osCurrentFaultContext.mesg, 1); + osCreateThread(&__osCurrentFaultContext.thread, FAULT_THREAD, osFaultMain, NULL, + (u8*)__osCurrentFaultContext.stack + sizeof(__osCurrentFaultContext.stack), OS_PRIORITY_APPMAX); + osStartThread(&__osCurrentFaultContext.thread); +} diff --git a/src/fault/crash_screen.h b/src/fault/crash_screen.h new file mode 100644 index 00000000..f0d3fc14 --- /dev/null +++ b/src/fault/crash_screen.h @@ -0,0 +1,25 @@ +#pragma once + +#define FAULT_THREAD 2 + +#define NUM_USER_PAGES 16 + +typedef struct { + OSMesgQueue mesgQueue; + OSMesg mesg; + OSThread thread; + OSContPad pad; + u32 width; + u32 height; + u16* cfb; + u64 stack[100]; +} OSFaultContext; + +typedef void (*OSFaultUserPage)(OSThread *); + +typedef struct { + u32 width; + u32 height; + u16 *cfb; + OSFaultUserPage pages[NUM_USER_PAGES]; +} OSFaultProgramArguments; diff --git a/src/fault/disasm.c b/src/fault/disasm.c new file mode 100644 index 00000000..90b4ea55 --- /dev/null +++ b/src/fault/disasm.c @@ -0,0 +1,399 @@ +#include +#include +#include + +#include "disasm.h" +#include "map_parser.h" + +static char insn_as_string[100]; + +InsnTemplate insn_db[] = { + // We want instructions with opcodes first (prioritized) + + // load/store + { I_TYPE, PARAM_LUI, 0b001111, 0, "lui" }, + { I_TYPE, PARAM_NONE, 0b100000, 0, "lb" }, + { I_TYPE, PARAM_NONE, 0b100100, 0, "lbu" }, + { I_TYPE, PARAM_NONE, 0b101000, 0, "sb" }, + { I_TYPE, PARAM_NONE, 0b100001, 0, "lh" }, + { I_TYPE, PARAM_NONE, 0b100101, 0, "lhu" }, + { I_TYPE, PARAM_NONE, 0b101001, 0, "sh" }, + { I_TYPE, PARAM_NONE, 0b100011, 0, "lw" }, + { I_TYPE, PARAM_NONE, 0b101011, 0, "sw" }, + { I_TYPE, PARAM_NONE, 0b110111, 0, "ld" }, + { I_TYPE, PARAM_NONE, 0b111111, 0, "sd" }, + { I_TYPE, PARAM_FLOAT_RT, 0b110001, 0, "lwc1" }, + { I_TYPE, PARAM_FLOAT_RT, 0b111001, 0, "swc1" }, + { I_TYPE, PARAM_FLOAT_RT, 0b110101, 0, "ldc1" }, + { I_TYPE, PARAM_FLOAT_RT, 0b111101, 0, "sdc1" }, + + // unaligned + { I_TYPE, PARAM_NONE, 0b100010, 0, "lwl" }, + { I_TYPE, PARAM_NONE, 0b100110, 0, "lwr" }, + { I_TYPE, PARAM_NONE, 0b101010, 0, "swl" }, + { I_TYPE, PARAM_NONE, 0b101110, 0, "swr" }, + // atomics + { I_TYPE, PARAM_NONE, 0b110000, 0, "ll" }, + { I_TYPE, PARAM_NONE, 0b111000, 0, "sc" }, + { I_TYPE, PARAM_NONE, 0b111100, 0, "scd" }, + // branches + { I_TYPE, PARAM_SWAP_RS_IMM, 0b000100, 0, "beq" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b010100, 0, "beql" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b000101, 0, "bne" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b010101, 0, "bnel" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b000111, 0, "bgtz" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b010111, 0, "bgtzl" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b000110, 0, "blez" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b010110, 0, "blezl" }, + { I_TYPE, PARAM_NONE, 0b001010, 0, "slti" }, + { I_TYPE, PARAM_NONE, 0b001011, 0, "sltiu" }, + + // jal (special) + { J_TYPE, PARAM_JAL, 0b000011, 0, "jal" }, + { J_TYPE, PARAM_JUMP, 0b000010, 0, "j" }, + + // bitwise ops (which are opcodes) + { I_TYPE, PARAM_NONE, 0b001100, 0, "andi" }, + { I_TYPE, PARAM_NONE, 0b001101, 0, "ori" }, + { I_TYPE, PARAM_NONE, 0b001110, 0, "xori" }, + + // arithmetic + { I_TYPE, PARAM_SWAP_RS_IMM, 0b011000, 0, "daddi" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b011001, 0, "daddiu" }, + // and now the ones with 0 for the opcode + { R_TYPE, PARAM_NONE, 0, 0b100000, "add" }, + { R_TYPE, PARAM_NONE, 0, 0b100001, "addu" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b001000, 0, "addi" }, + { I_TYPE, PARAM_SWAP_RS_IMM, 0b001001, 0, "addiu" }, + { R_TYPE, PARAM_NONE, 0, 0b100010, "sub" }, + { R_TYPE, PARAM_NONE, 0, 0b100011, "subu" }, + { R_TYPE, PARAM_NONE, 0, 0b011000, "mult" }, + { R_TYPE, PARAM_NONE, 0, 0b011001, "multu" }, + { R_TYPE, PARAM_NONE, 0, 0b011010, "div" }, + { R_TYPE, PARAM_NONE, 0, 0b011011, "divu" }, + { R_TYPE, PARAM_MULT_MOVE, 0, 0b010000, "mfhi" }, + { R_TYPE, PARAM_MULT_MOVE, 0, 0b010001, "mthi" }, + { R_TYPE, PARAM_MULT_MOVE, 0, 0b010010, "mflo" }, + { R_TYPE, PARAM_MULT_MOVE, 0, 0b010011, "mtlo" }, + { R_TYPE, PARAM_NONE, 0, 0b101010, "slt" }, + { R_TYPE, PARAM_NONE, 0, 0b101011, "sltu" }, + + // bitwise ops (which are functions) + { R_TYPE, PARAM_NONE, 0, 0b100100, "and" }, + { R_TYPE, PARAM_NONE, 0, 0b100101, "or" }, + { R_TYPE, PARAM_NONE, 0, 0b100110, "xor" }, + { R_TYPE, PARAM_BITSHIFT, 0, 0b000000, "sll" }, + { R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000100, "sllv" }, + { R_TYPE, PARAM_BITSHIFT, 0, 0b000010, "srl" }, + { R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000110, "srlv" }, + { R_TYPE, PARAM_BITSHIFT, 0, 0b000011, "sra" }, + { R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000111, "srav" }, + { R_TYPE, PARAM_SWAP_RS_RT, 0, 0b100111, "nor" }, + + { R_TYPE, PARAM_NONE, 0, 0b001001, "jalr" }, + { R_TYPE, PARAM_NONE, 0, 0b001000, "jr" }, + { R_TYPE, PARAM_TRAP, 0, 0b110100, "teq" }, + { R_TYPE, PARAM_EMUX, 0, 0b110110, "tne" }, + + { 0, PARAM_SYSCALL, 0, 0b001100, "syscall" }, + + // instructions involving doubles (deprioritized on the list) + { R_TYPE, PARAM_NONE, 0, 0b101101, "daddu" }, + { R_TYPE, PARAM_NONE, 0, 0b101110, "dsub" }, + { R_TYPE, PARAM_NONE, 0, 0b101111, "dsubu" }, + { R_TYPE, PARAM_NONE, 0, 0b011101, "dmultu" }, + { R_TYPE, PARAM_NONE, 0, 0b011110, "ddiv" }, + { R_TYPE, PARAM_NONE, 0, 0b011111, "ddivu" }, + { R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010100, "dsllv" }, + { R_TYPE, PARAM_BITSHIFT, 0, 0b111100, "dsll32" }, + { R_TYPE, PARAM_BITSHIFT, 0, 0b111110, "dsrl32" }, + { R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010110, "dsrlv" }, + { R_TYPE, PARAM_BITSHIFT, 0, 0b111111, "dsra32" }, + { R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010111, "dsrav" }, +}; + +char __mips_gpr[][4] = { "$r0", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", "$t0", "$t1", "$t2", "$t3", + "$t4", "$t5", "$t6", "$t7", "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", + "$t8", "$t9", "$k0", "$k1", "$gp", "$sp", "$fp", "$ra", "$lo", "$hi" }; + +char __mips_fpreg[][5] = { + "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", "$f8", "$f9", "$f10", + "$f11", "$f12", "$f13", "$f14", "$f15", "$f16", "$f17", "$f18", "$f19", "$f20", "$f21", + "$f22", "$f23", "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", +}; + +u8 insn_is_jal(Insn* i) { + return (i->opcode == 0b000011); +} + +u8 insn_is_jalr(Insn* i) { + return (i->opcode == 0) && (i->rdata.function == 0b001001); +} + +// Last Resort C0/C1 disassembler, from libdragon +static void c1_disasm(u32* ptr, char* out) { + static const char* fpu_ops[64] = { + "radd", "rsub", "rmul", "rdiv", "ssqrt", "sabs", "smov", "sneg", + "sround.l", "strunc.l", "sceil.l", "sfloor.l", "sround.w", "strunc.w", "sceil.w", "sfloor.w", + "*", "*", "*", "*", "*", "*", "*", "*", + "*", "*", "*", "*", "*", "*", "*", "*", + "scvt.s", "scvt.d", "*", "*", "scvt.w", "scvt.l", "*", "*", + "*", "*", "*", "*", "*", "*", "*", "*", + "hc.f", "hc.un", "hc.eq", "hc.ueq", "hc.olt", "hc.ult", "hc.ole", "hc.ule", + "hc.sf", "hc.ngle", "hc.seq", "hc.ngl", "hc.lt", "hc.nge", "hc.le", "hc.ngt", + }; + + char symbuf[64]; + + // Disassemble MIPS instruction + u32 pc = (u32)ptr; + u32 op = *ptr; + s16 imm16 = op & 0xFFFF; + u32 tgt16 = (pc + 4) + (imm16 << 2); + u32 imm26 = op & 0x3FFFFFF; + u32 tgt26 = ((pc + 4) & 0xf0000000) | (imm26 << 2); + const char* rs = __mips_gpr[(op >> 21) & 0x1F]; + const char* rt = __mips_gpr[(op >> 16) & 0x1F]; + const char* rd = __mips_gpr[(op >> 11) & 0x1F]; + const char* opn = "unimpl"; + if (((op >> 26) & 0x3F) == 17) { + u32 sub = (op >> 21) & 0x1F; + switch (sub) { + case 0: + opn = "gmfc1"; + break; + case 1: + opn = "gdmfc1"; + break; + case 4: + opn = "gmtc1"; + break; + case 5: + opn = "gdmtc1"; + break; + case 8: + switch ((op >> 16) & 0x1F) { + case 0: + opn = "ybc1f"; + break; + case 2: + opn = "ybc1fl"; + break; + case 1: + opn = "ybc1t"; + break; + case 3: + opn = "ybc1tl"; + break; + } + break; + case 16: + case 17: + case 20: + case 21: + opn = fpu_ops[(op >> 0) & 0x3F]; + sprintf(symbuf, "%s.%s", opn, (sub == 16) ? "s" : (sub == 17) ? "d" : (sub == 20) ? "w" : "l"); + opn = symbuf; + rt = __mips_fpreg[(op >> 16) & 0x1F]; + rs = __mips_fpreg[(op >> 11) & 0x1F]; + rd = __mips_fpreg[(op >> 6) & 0x1F]; + break; + } + } + switch (*opn) { +#ifdef DEBUG_EXPORT_SYMBOLS + /* op tgt26 */ case 'j': + sprintf(out, "%-9s %08lx <%s>", opn + 1, tgt26, parse_map(tgt26, FALSE)); + break; + /* op rs, rt, tgt16 */ case 'b': + sprintf(out, "%-9s %s, %s, %08lx <%s>", opn + 1, rs, rt, tgt16, parse_map(tgt16, TRUE)); + break; + /* op tgt16 */ case 'y': + sprintf(out, "%-9s %08lx <%s>", opn + 1, tgt16, parse_map(tgt16, TRUE)); + break; +#else + /* op tgt26 */ case 'j': + sprintf(out, "%-9s %08lx", opn + 1, tgt26); + break; + /* op rs, rt, tgt16 */ case 'b': + sprintf(out, "%-9s %s, %s, %08lx", opn + 1, rs, rt, tgt16); + break; + /* op tgt16 */ case 'y': + sprintf(out, "%-9s %08lx", opn + 1, tgt16); + break; +#endif // DEBUG_EXPORT_SYMBOLS + /* op rt, rs, imm */ case 'i': + sprintf(out, "%-9s %s, %s, %d", opn + 1, rt, rs, (s16)op); + break; + /* op rt, imm */ case 'k': + sprintf(out, "%-9s %s, %d", opn + 1, rt, (s16)op); + break; + /* op rt, imm(rs) */ case 'm': + sprintf(out, "%-9s %s, %d(%s)", opn + 1, rt, (s16)op, rs); + break; + /* op fd, imm(rs) */ case 'n': + sprintf(out, "%-9s %s, %d(%s)", opn + 1, __mips_fpreg[(op >> 16) & 0x1F], (s16)op, rs); + break; + /* op rd, rs, rt */ case 'r': + sprintf(out, "%-9s %s, %s, %s", opn + 1, rd, rs, rt); + break; + /* op rd, rs */ case 's': + sprintf(out, "%-9s %s, %s", opn + 1, rd, rs); + break; + /* op rd, rt, sa */ case 'e': + sprintf(out, "%-9s %s, %s, %ld", opn + 1, rd, rt, (op >> 6) & 0x1F); + break; + /* op rs */ case 'w': + sprintf(out, "%-9s %s", opn + 1, rs); + break; + /* op rd */ case 'c': + sprintf(out, "%-9s %s", opn + 1, rd); + break; + /* op */ case 'z': + sprintf(out, "%-9s", opn + 1); + break; + /* op fd, fs, ft */ case 'f': + sprintf(out, "%-9s %s, %s, %s", opn + 1, rd, rs, rt); + break; + /* op rt, fs */ case 'g': + sprintf(out, "%-9s %s, %s", opn + 1, rt, __mips_fpreg[(op >> 11) & 0x1F]); + break; + /* op rs, rt */ case 'h': + sprintf(out, "%-9s %s, %s", opn + 1, rs, rt); + break; + /* op code20 */ case 'a': + sprintf(out, "%-9s 0x%lx", opn + 1, (op >> 6) & 0xFFFFF); + break; + /* op rs, rt, code */ case 't': + sprintf(out, "%-9s %s, %s, 0x%lx", opn + 1, rs, rt, (op >> 6) & 0x3FF); + break; + default: + sprintf(out, "%-9s", opn + 1); + break; + } +} + +char* cop1_insn_disasm(InsnData* pc) { + c1_disasm((u32*)pc, insn_as_string); + + return insn_as_string; +} + +char* branch_insn_disasm(InsnData insn) { + static char* insn_names[] = { + [0b00001] = "bgez", [0b00011] = "bgezl", [0b10001] = "bgezal", [0b10011] = "bgezall", + [0b00000] = "bltz", [0b00010] = "bltzl", [0b10000] = "bltzal", [0b10010] = "bltzall", + }; + char* strp = &insn_as_string[0]; + char* rs = __mips_gpr[insn.b.rs]; + u16 offset = insn.b.offset; + + for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) + insn_as_string[i] = 0; + + sprintf(strp, "%-9s %s %04X", insn_names[insn.b.sub], rs, offset); + + return insn_as_string; +} + +char* insn_disasm(InsnData* addr) { + InsnData insn = *addr; + char* strp = &insn_as_string[0]; + int successful_print = 0; + u32 target; + + if (insn.d == 0) { // trivial case + return "nop"; + } + + if (insn.i.opcode == OP_BRANCH) { + return branch_insn_disasm(insn); + } + if (insn.i.opcode == OP_COP0) { + return "cop0 (UNIMPL)"; + } + if (insn.i.opcode == OP_COP1) { + return cop1_insn_disasm(addr); + } + + for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) + insn_as_string[i] = 0; + + for (int i = 0; i < ARRAY_COUNT(insn_db); i++) { + if (insn.i.opcode != 0 && insn.i.opcode == insn_db[i].opcode) { + switch (insn_db[i].arbitraryParam) { + case PARAM_SWAP_RS_IMM: + strp += sprintf(strp, "%-9s %s %s %04X", insn_db[i].name, __mips_gpr[insn.i.rt], + __mips_gpr[insn.i.rs], insn.i.immediate); + break; + case PARAM_LUI: + strp += sprintf(strp, "%-9s %s %04X", insn_db[i].name, __mips_gpr[insn.i.rt], insn.i.immediate); + break; + break; + case PARAM_JAL: + target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); +#ifdef DEBUG_EXPORT_SYMBOLS + strp += sprintf(strp, "%-9s %s(%08lX)", insn_db[i].name, parse_map(target, FALSE), target); +#else + strp += sprintf(strp, "%-9s %08lX", insn_db[i].name, target); +#endif // DEBUG_EXPORT_SYMBOLS + break; + case PARAM_JUMP: + target = 0x80000000 | (insn.d & 0x03FFFFFF); + strp += sprintf(strp, "%-9s %08lX", insn_db[i].name, target); + break; + case PARAM_FLOAT_RT: + strp += sprintf(strp, "%-9s %s, %04X (%s)", insn_db[i].name, __mips_fpreg[insn.i.rt], + insn.i.immediate, __mips_gpr[insn.i.rs]); + break; + case PARAM_NONE: + strp += sprintf(strp, "%-9s %s %04X (%s)", insn_db[i].name, __mips_gpr[insn.i.rt], insn.i.immediate, + __mips_gpr[insn.i.rs]); + break; + } + successful_print = 1; + break; + } else if ((insn.i.rdata.function == 0 && insn.i.opcode == 0) // specifically catch `sll` + || (insn.i.rdata.function != 0 && insn.i.rdata.function == insn_db[i].function)) { + switch (insn_db[i].arbitraryParam) { + case PARAM_BITSHIFT: + strp += sprintf(strp, "%-9s %s %s %04X", insn_db[i].name, __mips_gpr[insn.i.rdata.rd], + __mips_gpr[insn.i.rt], insn.i.rdata.shift_amt); + break; + case PARAM_SWAP_RS_RT: + strp += sprintf(strp, "%-9s %s %s %s", insn_db[i].name, __mips_gpr[insn.i.rdata.rd], + __mips_gpr[insn.i.rt], __mips_gpr[insn.i.rs]); + break; + case PARAM_MULT_MOVE: + strp += sprintf(strp, "%-9s %s", insn_db[i].name, __mips_gpr[insn.i.rdata.rd]); + break; + case PARAM_EMUX: + target = (insn.d >> 6) & 0x3FF; + if (insn.i.rs == insn.i.rt) { + strp += sprintf(strp, "%-9s %s 0x%02lX", "emux", __mips_gpr[insn.i.rs], target); + } else { + strp += + sprintf(strp, "%-9s %s %s", insn_db[i].name, __mips_gpr[insn.i.rs], __mips_gpr[insn.i.rt]); + } + break; + case PARAM_TRAP: + strp += sprintf(strp, "%-9s %s %s", insn_db[i].name, __mips_gpr[insn.i.rs], __mips_gpr[insn.i.rt]); + break; + case PARAM_SYSCALL: + strp += sprintf(strp, "%-9s %ld", insn_db[i].name, (insn.d & 0x03FFFFC0) >> 6); + break; + case PARAM_NONE: + strp += sprintf(strp, "%-9s %s %s %s", insn_db[i].name, __mips_gpr[insn.i.rdata.rd], + __mips_gpr[insn.i.rs], __mips_gpr[insn.i.rt]); + break; + } + successful_print = 1; + break; + } + } + if (successful_print == 0) { + strp += sprintf(strp, "unimpl %08lX", insn.d); + } + + return insn_as_string; +} diff --git a/src/fault/disasm.h b/src/fault/disasm.h new file mode 100644 index 00000000..e47eda87 --- /dev/null +++ b/src/fault/disasm.h @@ -0,0 +1,87 @@ +#pragma once + +enum InsnTypes { + R_TYPE, + I_TYPE, + J_TYPE, + COP0, + COP1, +}; + +enum ParamTypes { + PARAM_NONE, + PARAM_SWAP_RS_IMM, + PARAM_BITSHIFT, + PARAM_FLOAT_RT, + PARAM_SWAP_RS_RT, + PARAM_JAL, + PARAM_JUMP, + PARAM_JR, + PARAM_LUI, + PARAM_MULT_MOVE, + PARAM_TRAP, + PARAM_EMUX, + PARAM_SYSCALL, +}; + +typedef struct PACKED { + u16 rd : 5; + u16 shift_amt : 5; + u16 function : 6; +} RTypeData; + +typedef struct PACKED { + u16 opcode : 6; + u16 rs : 5; + u16 rt : 5; + union { + RTypeData rdata; + u16 immediate; + }; +} Insn; + +typedef struct PACKED { + u16 opcode : 6; + u16 fmt : 5; + u16 ft : 5; + u16 fs : 5; + u16 fd : 5; + u16 func : 6; +} CzInsn; + +typedef struct PACKED { + u16 regimm : 6; + u16 rs : 5; + u16 sub : 5; + u16 offset; +} BranchInsn; + +typedef union { + Insn i; + CzInsn f; + BranchInsn b; + u32 d; +} InsnData; + +typedef struct PACKED { + u32 type; + u32 arbitraryParam; + u16 opcode : 6; + u16 function : 6; + u8 name[10]; +} InsnTemplate; + +typedef struct PACKED { + u32 type; + u32 arbitraryParam; + u16 function : 6; + u8 name[10]; +} COPzInsnTemplate; + +#define OP_COP0 0b010000 +#define OP_COP1 0b010001 +#define OP_BRANCH 0b000001 // technically "REGIMM" + +extern char* insn_disasm(InsnData* insn); +extern u8 insn_is_jal(Insn* i); +extern u8 insn_is_jalr(Insn* i); diff --git a/src/fault/map_parser.c b/src/fault/map_parser.c new file mode 100644 index 00000000..e80af811 --- /dev/null +++ b/src/fault/map_parser.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +#include "map_parser.h" +#include "symtable.h" + +#ifdef DEBUG_EXPORT_SYMBOLS + +#define UNKNOWN_SYMBOL "???" + +char* __symbolize(void* vaddr, char* buf, int size, u32 andOffset) { + symtable_header_t symt = symt_open(); + if (symt.head[0]) { + u32 addr = (u32)vaddr; + int idx = 0; + addrtable_entry_t a = symt_addrtab_search(&symt, addr, &idx); + while (!ADDRENTRY_IS_FUNC(a)) { + a = symt_addrtab_entry(&symt, --idx); + } + + symtable_entry_t ALIGNED16 entry; + // Read the symbol name + symt_entry_fetch(&symt, &entry, idx); + char* func = symt_entry_func(&symt, &entry, addr, buf, size - 12); + char lbuf[12]; + if (andOffset) { + sprintf(lbuf, "+0x%lx", addr - ADDRENTRY_ADDR(a)); + } else { + lbuf[0] = 0; + } + entry.func_off = addr - ADDRENTRY_ADDR(a); + + return strcat(func, lbuf); + } + sprintf(buf, "%s", UNKNOWN_SYMBOL); + return buf; +} + +char* parse_map(u32 addr, u32 andOffset) { + static char map_name[64] ALIGNED16; + char* ret = map_name; + + __symbolize((u32*)addr, map_name, sizeof(map_name), andOffset); + + if (ret[0] == ' ') { + ret++; + } + return ret; +} + +symtable_info_t get_symbol_info(u32 addr) { + static char filebuf[100]; + void* vaddr = (void*)addr; + symtable_header_t symt = symt_open(); + + if (symt.head[0]) { + symtable_info_t info; + u32 addr = (u32)vaddr; + int idx = 0; + symt_addrtab_search(&symt, addr, &idx); + + symtable_entry_t ALIGNED16 entry; + + // Read the symbol name + filebuf[0] = 0; + symt_entry_fetch(&symt, &entry, idx); + info.line = entry.line; + info.func_offset = entry.func_off; + addrtable_entry_t a = symt_addrtab_entry(&symt, idx); + info.distance = addr - ADDRENTRY_ADDR(a); + info.file = symt_entry_file(&symt, &entry, filebuf, sizeof(filebuf)); + info.func = parse_map(addr, FALSE); + + return info; + } + return (symtable_info_t){ .line = -1 }; +} + +#endif // DEBUG_EXPORT_SYMBOLS diff --git a/src/fault/map_parser.h b/src/fault/map_parser.h new file mode 100644 index 00000000..3e2cd262 --- /dev/null +++ b/src/fault/map_parser.h @@ -0,0 +1,14 @@ +#pragma once + +typedef struct { + char* file; + char* func; + int line; + u16 distance; + u16 func_offset; +} symtable_info_t; + +symtable_info_t get_symbol_info(u32 vaddr); +extern char* parse_map(u32 pc, u32 andOffset); +extern symtable_info_t walk_stack(u32* addr); +extern char* __symbolize(void* vaddr, char* buf, int size, u32 andOffset); diff --git a/src/fault/stacktrace.c b/src/fault/stacktrace.c new file mode 100644 index 00000000..ee161254 --- /dev/null +++ b/src/fault/stacktrace.c @@ -0,0 +1,113 @@ +#include +#include +#include + +#include "map_parser.h" +#include "symtable.h" +#include "stacktrace.h" +#include "disasm.h" + +static StackFrame stack[STACK_LINE_COUNT]; +static u32 stackIdx; +u32 stackTraceGenerated = FALSE; + +#define STACK_END_STR "[End of stack]" + +static u8 is_top_of_stack(u32 ra) { + return (ra == ((u32)__osCleanupThread)); +} + +// TODO: +static u8 is_text_addr(u32 addr) { + // if ((addr >= (u32)_mainSegmentStart) && (addr <= (u32)_mainSegmentTextEnd)) { + // return TRUE; + // } + // else if ((addr >= (u32)_engineSegmentStart) && (addr <= (u32)_engineSegmentTextEnd)) { + // return TRUE; + // } + // else if ((addr >= (u32)_goddardSegmentStart) && (addr <= (u32)_goddardSegmentTextEnd)) { + // return TRUE; + // } + + return FALSE; +} + +static void add_entry_to_stack(u32 addr, u32 ra, symtable_info_t* info) { + StackFrame* frame = &stack[stackIdx++]; + + frame->func = addr; + frame->offset = info->func_offset; + frame->ra = ra; + frame->line = info->line; + sprintf(frame->funcname, "%s", info->func); +} + +char* get_stack_entry(u32 idx) { + static char stackbuf[256]; + + sprintf(stackbuf, "%08lX: %s:%d", stack[idx].func, stack[idx].funcname, stack[idx].line); + + return stackbuf; +} + +u32 generate_stack(OSThread* thread) { + static u32 breadcrumb = 0; + symtable_header_t symt = symt_open(); + + __OSThreadContext* tc = &thread->context; + + u32 sp = tc->sp; + breadcrumb = tc->ra; + + while (1) { // dont know the end goal yet + sp += 4; + + u32 val = *(u32*)sp; + + // make sure we're working on an actual address + if (is_text_addr(val + CALLSITE_OFFSET)) { + int idx = 0; + symtable_info_t info = get_symbol_info(val + CALLSITE_OFFSET); + addrtable_entry_t funcstart = symt_addrtab_search(&symt, breadcrumb, &idx); + + // If we can't logically go further, we're done! + if (is_top_of_stack(val)) { + symtable_info_t info = get_symbol_info(val + CALLSITE_OFFSET); + info.func = STACK_END_STR; + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + return stackIdx; + } + + // get the start of the current frame's func + while (!ADDRENTRY_IS_FUNC(funcstart) && !ADDRENTRY_IS_INLINE(funcstart)) { + funcstart = symt_addrtab_entry(&symt, --idx); + } + + // Make sure the address is an actual callsite + if (info.distance == 0) { + u32 jal = *(u32*)(val + CALLSITE_OFFSET); + + if (insn_is_jal((Insn*)&jal)) { + u32 jalTarget = 0x80000000 | ((jal & 0x03FFFFFF) * 4); + + // make sure JAL is to the current func + if (jalTarget == ADDRENTRY_ADDR(funcstart)) { + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + } + } else if (insn_is_jalr((Insn*)&jal)) { + // Always add a JALR to the stack, in absence of a better heuristic + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + } + } + + if (stackIdx >= STACK_LINE_COUNT) { + break; + } + } + } + + return stackIdx; +} diff --git a/src/fault/stacktrace.h b/src/fault/stacktrace.h new file mode 100644 index 00000000..2d794c2c --- /dev/null +++ b/src/fault/stacktrace.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +typedef struct { + u32 func; + u32 offset; + u32 ra; + int line; + char funcname[100]; +} StackFrame; + +#define STACK_TRAVERSAL_LIMIT 100 +#define STACK_LINE_COUNT 17 +// RA points to 2 instructions past any given callsite +#define CALLSITE_OFFSET -8 + +extern u32 stackTraceGenerated; + +extern u32 generate_stack(OSThread*); +extern char* get_stack_entry(u32 idx); diff --git a/src/fault/symtable.c b/src/fault/symtable.c new file mode 100644 index 00000000..307a2d22 --- /dev/null +++ b/src/fault/symtable.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include + +#include "symtable.h" + +#ifdef DEBUG_EXPORT_SYMBOLS + +u32 SYMT_ROM = 0xFFFFFFFF; +extern u8 _mapDataSegmentRomStart[]; +// The start and end of the exception vector +extern u8 __osExceptionPreamble[]; +extern u8 send_mesg[]; + +#define is_in_exception(addr) ((addr) >= (u32)__osExceptionPreamble && (addr) < (u32)send_mesg) + +// code provided by Wiseguy +static void headless_dma(u32 devAddr, void* dramAddr, u32 size) { + register u32 stat = IO_READ(PI_STATUS_REG); + while (stat & (PI_STATUS_IO_BUSY | PI_STATUS_DMA_BUSY)) { + stat = IO_READ(PI_STATUS_REG); + } + IO_WRITE(PI_DRAM_ADDR_REG, K0_TO_PHYS(dramAddr)); + IO_WRITE(PI_CART_ADDR_REG, K1_TO_PHYS((u32)osRomBase | devAddr)); + IO_WRITE(PI_WR_LEN_REG, size - 1); +} +static u32 headless_pi_status(void) { + return IO_READ(PI_STATUS_REG); +} +// end of code provided by Wiseguy + +void map_parser_dma(void* dst, void* src, size_t size) { + headless_dma((u32)src, dst, size); + while (headless_pi_status() & PI_STATUS_IO_BUSY) + ; +} + +/** + * @brief Open the SYMT symbol table in the rompak. + * + * If not found, return a null header. + */ +symtable_header_t symt_open(void) { + SYMT_ROM = (u32)_mapDataSegmentRomStart; + + symtable_header_t ALIGNED8 symt_header; + + if (SYMT_ROM == 0) { + return (symtable_header_t){ 0 }; + } + + osWritebackDCache(&symt_header, sizeof(symt_header)); + map_parser_dma(&symt_header, (uintptr_t*)SYMT_ROM, sizeof(symtable_header_t)); + + if (symt_header.head[0] != 'S' || symt_header.head[1] != 'Y' || symt_header.head[2] != 'M' + || symt_header.head[3] != 'T') { + osSyncPrintf("symt_open: invalid symbol table found at 0x%08lx\n", SYMT_ROM); + SYMT_ROM = 0; + return (symtable_header_t){ 0 }; + } + if (symt_header.version != 2) { + osSyncPrintf("symt_open: unsupported symbol table version %ld -- please update your n64sym tool\n", + symt_header.version); + SYMT_ROM = 0; + return (symtable_header_t){ 0 }; + } + + return symt_header; +} + +/** + * @brief Return an entry in the address table by index + * + * @param symt SYMT file header + * @param idx Index of the entry to return + * @return addrtable_entry_t Entry of the address table + */ +addrtable_entry_t symt_addrtab_entry(symtable_header_t* symt, int idx) { + return IO_READ(0xB0000000 | (SYMT_ROM + symt->addrtab_off + idx * 4)); +} + +/** + * @brief Search the SYMT address table for the given address. + * + * Run a binary search to find the entry in the table. If there is a single exact match, + * the entry is returned. If there are multiple entries with the same address, the first + * entry is returned (this is the case for inlined functions: so some entries following + * the current one will have the same address). If there is no exact match, the entry + * with the biggest address just before the given address is returned. + * + * @param symt SYMT file header + * @param addr Address to search for + * @param idx If not null, will be set to the index of the entry found (or the index just before) + * @return The found entry (or the entry just before) + */ +addrtable_entry_t symt_addrtab_search(symtable_header_t* symt, u32 addr, int* idx) { + int min = 0; + int max = symt->addrtab_size - 1; + while (min < max) { + int mid = (min + max) / 2; + addrtable_entry_t entry = symt_addrtab_entry(symt, mid); + if (addr <= ADDRENTRY_ADDR(entry)) { + max = mid; + } else { + min = mid + 1; + } + } + addrtable_entry_t entry = symt_addrtab_entry(symt, min); + if (min > 0 && ADDRENTRY_ADDR(entry) > addr) { + entry = symt_addrtab_entry(symt, --min); + } + if (idx) { + *idx = min; + } + return entry; +} + +/** + * @brief Fetch a string from the string table + * + * @param symt SYMT file + * @param sidx Index of the first character of the string in the string table + * @param slen Length of the string + * @param buf Destination buffer + * @param size Size of the destination buffer + * @return char* Fetched string within the destination buffer (might not be at offset 0 for alignment reasons) + */ +char* symt_string(symtable_header_t* symt, int sidx, int slen, char* buf, int size) { + // Align 2-byte phase of the RAM buffer with the ROM address. This is required + // for map_parser_dma. + int tweak = (sidx ^ (u32)buf) & 1; + char* func = buf + tweak; + size -= tweak; + int nbytes = MIN(slen, size); + + osWritebackDCache(buf, size); + map_parser_dma(func, (uintptr_t*)(SYMT_ROM + symt->strtab_off + sidx), nbytes); + func[nbytes] = 0; + + if (tweak) { + buf[0] = ' '; + } + return func; +} + +/** + * @brief Fetch a symbol table entry from the SYMT file. + * + * @param symt SYMT file + * @param entry Output entry pointer + * @param idx Index of the entry to fetch + */ +void symt_entry_fetch(symtable_header_t* symt, symtable_entry_t* entry, int idx) { + osWritebackDCache(entry, sizeof(symtable_entry_t)); + + // char dbg[100]; + // sprintf(dbg,"symt_entry_fetch %d\n", idx); + // osSyncPrintf(dbg); + map_parser_dma(entry, (uintptr_t*)(SYMT_ROM + symt->symtab_off + idx * sizeof(symtable_entry_t)), + sizeof(symtable_entry_t)); +} + +// Fetch the function name of an entry +char* symt_entry_func(symtable_header_t* symt, symtable_entry_t* entry, u32 addr, char* buf, int size) { + if (is_in_exception(addr)) { + // Special case exception handlers. This is just to show something slightly + // more readable instead of "notcart+0x0" or similar assembly symbols + sprintf(buf, ""); + return buf; + } else { + return symt_string(symt, entry->func_sidx, entry->func_len, buf, size); + } +} + +// Fetch the file name of an entry +char* symt_entry_file(symtable_header_t* symt, symtable_entry_t* entry, char* buf, int size) { + return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); +} + +#endif // DEBUG_EXPORT_SYMBOLS diff --git a/src/fault/symtable.h b/src/fault/symtable.h new file mode 100644 index 00000000..0efea2a4 --- /dev/null +++ b/src/fault/symtable.h @@ -0,0 +1,76 @@ +#pragma once + +/** + * @brief Symbol table file header + * + * The SYMT file is made of three main tables: + * + * * Address table: this is a sequence of 32-bit integers, each representing an address in the ROM. + * The table is sorted in ascending order to allow for binary search. Moreover, the lowest 2 bits + * of each address can store additional information: If bit 0 is set to 1, the address is the start + * of a function. If bit 1 is set to 1, the address is an inline duplicate. In fact, there might be + * multiple symbols at the same address for inlined functions, so we need one entry in this table + * for each entry; all of them will have the same address, and all but the last one will have bit + * 1 set to 1. + * * Symbol table: this is a sequence of symbol table entries, each representing a symbol. The size + * of this table (in number of entries) is exactly the same as the address table. In fact, each + * address of the address table can be thought of as an external member of this structure; it's + * split externally to allow for efficiency reasons. Each entry stores the function name, + * the source file name and line number, and the binary offset of the symbol within the containing + * function. + * * String table: this table can be thought as a large buffer holding all the strings needed by all + * symbol entries (function names and file names). Each symbol entry stores a string as an offset + * within the symbol table and a length. This allows to reuse the same string (or prefix thereof) + * multiple times. Notice that strings are not null terminated in the string table. + * + * The SYMT file is generated by the n64sym tool during the build process. + */ +typedef struct { + char head[4]; ///< Magic ID "SYMT" + u32 version; ///< Version of the symbol table + u32 addrtab_off; ///< Offset of the address table in the file + u32 addrtab_size; ///< Size of the address table in the file (number of entries) + u32 symtab_off; ///< Offset of the symbol table in the file + u32 symtab_size; ///< Size of the symbol table in the file (number of entries); always equal to addrtab_size. + u32 strtab_off; ///< Offset of the string table in the file + u32 strtab_size; ///< Size of the string table in the file (number of entries) +} symtable_header_t; + +/** @brief Symbol table entry **/ +typedef struct { + u32 func_sidx; ///< Offset of the function name in the string table + u32 file_sidx; ///< Offset of the file name in the string table + u16 func_len; ///< Length of the function name + u16 file_len; ///< Length of the file name + u16 line; ///< Line number (or 0 if this symbol generically refers to a whole function) + u16 func_off; ///< Offset of the symbol within its function +} symtable_entry_t; + +/** + * @brief Entry in the address table. + * + * This is an address in RAM, with the lowest 2 bits used to store additional information. + * See the ADDRENTRY_* macros to access the various components. + */ +typedef u32 addrtable_entry_t; + +#define ADDRENTRY_ADDR(e) ((e) & ~3) ///< Address (without the flags) +#define ADDRENTRY_IS_FUNC(e) ((e) & 1) ///< TRUE if the address is the start of a function +#define ADDRENTRY_IS_INLINE(e) ((e) & 2) ///< TRUE if the address is an inline duplicate + +#define MIPS_OP_ADDIU_SP(op) (((op) & 0xFFFF0000) == 0x27BD0000) ///< Matches: addiu $sp, $sp, imm +#define MIPS_OP_DADDIU_SP(op) (((op) & 0xFFFF0000) == 0x67BD0000) ///< Matches: daddiu $sp, $sp, imm +#define MIPS_OP_JR_RA(op) (((op) & 0xFFFFFFFF) == 0x03E00008) ///< Matches: jr $ra +#define MIPS_OP_SD_RA_SP(op) (((op) & 0xFFFF0000) == 0xAFBF0000) ///< Matches: sw $ra, imm($sp) +#define MIPS_OP_SD_FP_SP(op) (((op) & 0xFFFF0000) == 0xAFBE0000) ///< Matches: sw $fp, imm($sp) +#define MIPS_OP_LUI_GP(op) (((op) & 0xFFFF0000) == 0x3C1C0000) ///< Matches: lui $gp, imm +#define MIPS_OP_NOP(op) ((op) == 0x00000000) ///< Matches: nop +#define MIPS_OP_MOVE_FP_SP(op) ((op) == 0x03A0F025) ///< Matches: move $fp, $sp + +symtable_header_t symt_open(void); +addrtable_entry_t symt_addrtab_entry(symtable_header_t* symt, int idx); +addrtable_entry_t symt_addrtab_search(symtable_header_t* symt, u32 addr, int* idx); +char* symt_string(symtable_header_t* symt, int sidx, int slen, char* buf, int size); +void symt_entry_fetch(symtable_header_t* symt, symtable_entry_t* entry, int idx); +char* symt_entry_func(symtable_header_t* symt, symtable_entry_t* entry, u32 addr, char* buf, int size); +char* symt_entry_file(symtable_header_t* symt, symtable_entry_t* entry, char* buf, int size);