diff --git a/include/common_structs.h b/include/common_structs.h index dd256a2389..6b22c3843d 100644 --- a/include/common_structs.h +++ b/include/common_structs.h @@ -473,6 +473,7 @@ typedef struct Evt { /* 0x164 */ Bytecode* ptrCurLine; /* 0x168 */ b8 debugPaused; /* 0x169 */ s8 debugStep; + /* 0x16A */ u16 curLine; } Evt; // size = 0x16C typedef Evt* ScriptList[MAX_SCRIPTS]; diff --git a/include/evt.h b/include/evt.h index 25d5aa799f..b18eb071fa 100644 --- a/include/evt.h +++ b/include/evt.h @@ -6,6 +6,11 @@ // Should be at least the width of a pointer i.e. intptr_t typedef s32 Bytecode; +// The argc word in EVT bytecode encodes both the argument count (low 16 bits) +// and the source line number (high 16 bits) for crash diagnostics. +#define EVT_CMD_ARGC(raw) ((raw) & 0xFFFF) +#define EVT_CMD_LINE(raw) ((u32)(raw) >> 16) + enum { EVT_OP_INTERNAL_FETCH, EVT_OP_END, @@ -105,6 +110,9 @@ enum { EVT_OP_DEBUG_BREAKPOINT, }; +/// The script currently being executed by evt_execute_next_command, or nullptr. +extern struct Evt* EvtCurrentScript; + #define MAKE_ENTITY_END 0x80000000 /* Return type of evt_execute_next_command */ diff --git a/include/script_api/macros.h b/include/script_api/macros.h index 0cc34de425..dd9c3b4a0c 100644 --- a/include/script_api/macros.h +++ b/include/script_api/macros.h @@ -204,10 +204,10 @@ extern "C" { /// Bytecode argv[argc]; /// } /// This macro expands to the given opcode and argv, with argc calculated automatically. - +/// The line number is also encoded into the upper nibble of argc for debugging purposes. #define EVT_CMD(opcode, argv...) \ opcode, \ - (sizeof((Bytecode[]){argv})/sizeof(Bytecode)), \ + (sizeof((Bytecode[]){argv})/sizeof(Bytecode)) | (__LINE__ << 16), \ ##argv /// Signals the end of EVT script data. A script missing this will likely crash on load. diff --git a/src/crash_screen.c b/src/crash_screen.c index 157d235871..29a7e7901b 100644 --- a/src/crash_screen.c +++ b/src/crash_screen.c @@ -32,6 +32,15 @@ INCLUDE_IMG("crash_screen/font.png", gCrashScreenFont); // The font image is on 6x7 grid #define GLYPH(x, y) (x + (y * 5)) +// RGBA5551 colors +#define COLOR_WHITE 0xFFFF +#define COLOR_RED 0xF801 +#define COLOR_YELLOW 0xFFE1 +#define COLOR_CYAN 0x07FF +#define COLOR_GREY 0x8C63 + +u16 gCrashScreenColor = COLOR_WHITE; + const char* gFaultCauses[18] = { "Interrupt", "TLB modification", @@ -110,6 +119,7 @@ s32 crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { const u32* data = &((u32*)gCrashScreenFont)[glyph / 5 * 7]; s32 i; s32 j; + u16 color = gCrashScreenColor; switch (glyph) { case GLYPH(3, 10): // ; @@ -133,7 +143,7 @@ s32 crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { for (j = 0; j < 6; j++) { if (bit & rowMask) { - *ptr++ = 0xFFFF; // white + *ptr++ = color; } else { ptr++; // dont draw } @@ -150,7 +160,7 @@ s32 crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { u32 rowMask = *data++; for (j = 0; j < 6; j++) { - u16 temp = (bit & rowMask) ? 0xFFFF : 1; + u16 temp = (bit & rowMask) ? color : 1; ptr[0] = temp; ptr[1] = temp; @@ -308,24 +318,237 @@ void crash_screen_print_fpcsr(u32 value) { } } -void crash_screen_draw(OSThread* faultedThread) { - s16 causeIndex; +static const char* sRegNames[32] = { + "zero", "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", +}; - s32 bt[8]; - s32 max = backtrace_thread((void**)bt, ARRAY_COUNT(bt), faultedThread); - s32 i = 0; - static char buf[0x200]; +#define RD(op) (((op) >> 11) & 0x1F) +#define RT(op) (((op) >> 16) & 0x1F) +#define RS(op) (((op) >> 21) & 0x1F) +#define IMM(op) ((s16)((op) & 0xFFFF)) +#define SA(op) (((op) >> 6) & 0x1F) +#define TARGET(op, pc) ((((pc) & 0xF0000000) | (((op) & 0x03FFFFFF) << 2))) + +/// Disassemble a single MIPS instruction into buf. Returns buf. +static char* crash_screen_disasm(u32 pc, u32 op, char* buf) { + u32 opcode = op >> 26; + u32 func = op & 0x3F; + + // Load/store instructions + switch (opcode) { + case 0x20: sprintf(buf, "lb %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x21: sprintf(buf, "lh %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x23: sprintf(buf, "lw %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x24: sprintf(buf, "lbu %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x25: sprintf(buf, "lhu %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x28: sprintf(buf, "sb %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x29: sprintf(buf, "sh %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x2B: sprintf(buf, "sw %s, %d(%s)", sRegNames[RT(op)], IMM(op), sRegNames[RS(op)]); return buf; + case 0x31: sprintf(buf, "lwc1 f%d, %d(%s)", RT(op), IMM(op), sRegNames[RS(op)]); return buf; + case 0x39: sprintf(buf, "swc1 f%d, %d(%s)", RT(op), IMM(op), sRegNames[RS(op)]); return buf; + } - causeIndex = ((faultedThread->context.cause >> 2) & 0x1F); + // ALU immediate + switch (opcode) { + case 0x09: sprintf(buf, "addiu %s, %s, %d", sRegNames[RT(op)], sRegNames[RS(op)], IMM(op)); return buf; + case 0x0C: sprintf(buf, "andi %s, %s, 0x%X", sRegNames[RT(op)], sRegNames[RS(op)], (u16)IMM(op)); return buf; + case 0x0D: sprintf(buf, "ori %s, %s, 0x%X", sRegNames[RT(op)], sRegNames[RS(op)], (u16)IMM(op)); return buf; + case 0x0A: sprintf(buf, "slti %s, %s, %d", sRegNames[RT(op)], sRegNames[RS(op)], IMM(op)); return buf; + case 0x0B: sprintf(buf, "sltiu %s, %s, %d", sRegNames[RT(op)], sRegNames[RS(op)], IMM(op)); return buf; + case 0x0F: sprintf(buf, "lui %s, 0x%04X", sRegNames[RT(op)], (u16)IMM(op)); return buf; + } + + // Branch + switch (opcode) { + case 0x04: sprintf(buf, "beq %s, %s, 0x%08X", sRegNames[RS(op)], sRegNames[RT(op)], pc + 4 + (IMM(op) << 2)); return buf; + case 0x05: sprintf(buf, "bne %s, %s, 0x%08X", sRegNames[RS(op)], sRegNames[RT(op)], pc + 4 + (IMM(op) << 2)); return buf; + case 0x06: sprintf(buf, "blez %s, 0x%08X", sRegNames[RS(op)], pc + 4 + (IMM(op) << 2)); return buf; + case 0x07: sprintf(buf, "bgtz %s, 0x%08X", sRegNames[RS(op)], pc + 4 + (IMM(op) << 2)); return buf; + } - if (causeIndex == 23) { - causeIndex = 16; + // Jump + if (opcode == 0x02) { sprintf(buf, "j 0x%08X", TARGET(op, pc)); return buf; } + if (opcode == 0x03) { sprintf(buf, "jal 0x%08X", TARGET(op, pc)); return buf; } + + // SPECIAL (opcode 0) + if (opcode == 0x00) { + if (op == 0) { sprintf(buf, "nop"); return buf; } + switch (func) { + case 0x08: sprintf(buf, "jr %s", sRegNames[RS(op)]); return buf; + case 0x09: sprintf(buf, "jalr %s", sRegNames[RS(op)]); return buf; + case 0x21: + if (RT(op) == 0) { sprintf(buf, "move %s, %s", sRegNames[RD(op)], sRegNames[RS(op)]); return buf; } + sprintf(buf, "addu %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x23: sprintf(buf, "subu %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x24: sprintf(buf, "and %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x25: + if (RT(op) == 0) { sprintf(buf, "move %s, %s", sRegNames[RD(op)], sRegNames[RS(op)]); return buf; } + sprintf(buf, "or %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x26: sprintf(buf, "xor %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x27: sprintf(buf, "nor %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x2A: sprintf(buf, "slt %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x2B: sprintf(buf, "sltu %s, %s, %s", sRegNames[RD(op)], sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x00: sprintf(buf, "sll %s, %s, %d", sRegNames[RD(op)], sRegNames[RT(op)], SA(op)); return buf; + case 0x02: sprintf(buf, "srl %s, %s, %d", sRegNames[RD(op)], sRegNames[RT(op)], SA(op)); return buf; + case 0x03: sprintf(buf, "sra %s, %s, %d", sRegNames[RD(op)], sRegNames[RT(op)], SA(op)); return buf; + case 0x18: sprintf(buf, "mult %s, %s", sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x19: sprintf(buf, "multu %s, %s", sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x1A: sprintf(buf, "div %s, %s", sRegNames[RS(op)], sRegNames[RT(op)]); return buf; + case 0x10: sprintf(buf, "mfhi %s", sRegNames[RD(op)]); return buf; + case 0x12: sprintf(buf, "mflo %s", sRegNames[RD(op)]); return buf; + } } - if (causeIndex == 31) { - causeIndex = 17; + sprintf(buf, ".word 0x%08X", op); + return buf; +} + +/// Print disassembly context around the crash PC (1 line before, crash line, 1 line after). +static s32 crash_screen_print_disasm(s32 x, s32 y, u32 pc) { + char buf[64]; + + for (s32 i = -1; i <= 1; i++) { + u32 addr = pc + (i * 4); + u32 phys = 0x80000000 | osVirtualToPhysical((void*)addr); + if (phys < 0x80000400 || phys >= 0x80800000 || (addr & 3) != 0) { + continue; + } + u32 instr = *(u32*)phys; + crash_screen_disasm(addr, instr, buf); + + if (i == 0) { + gCrashScreenColor = COLOR_WHITE; + y = crash_screen_printf(x, y, " > %08X: %s", addr, buf); + } else { + gCrashScreenColor = COLOR_GREY; + y = crash_screen_printf(x, y, " %08X: %s", addr, buf); + } } + return y; +} + +/// Formats a human-readable error message for the crash. +static s32 crash_screen_print_error(s32 x, s32 y, OSThread* faultedThread) { + __OSThreadContext* ctx = &faultedThread->context; + s16 causeIndex = ((ctx->cause >> 2) & 0x1F); + + gCrashScreenColor = COLOR_RED; + + if (crashScreenAssertMessage[0] != '\0') { + y = crash_screen_printf_proportional(x, y, crashScreenAssertMessage); + gCrashScreenColor = COLOR_WHITE; + return y; + } + + u32 badvaddr = (u32)ctx->badvaddr; + + switch (causeIndex) { + case 1: // TLB modification + y = crash_screen_printf_proportional(x, y, "Segfault: write to 0x%08X", badvaddr); + break; + case 2: // TLB exception on load + if (badvaddr == 0) { + y = crash_screen_printf_proportional(x, y, "Attempt to read from nullptr"); + } else { + y = crash_screen_printf_proportional(x, y, "Segfault: read from 0x%08X", badvaddr); + } + break; + case 3: // TLB exception on store + if (badvaddr == 0) { + y = crash_screen_printf_proportional(x, y, "Attempt to write to nullptr"); + } else { + y = crash_screen_printf_proportional(x, y, "Segfault: write to 0x%08X", badvaddr); + } + break; + case 4: // Address error on load + y = crash_screen_printf_proportional(x, y, "Unaligned read from 0x%08X", badvaddr); + break; + case 5: // Address error on store + y = crash_screen_printf_proportional(x, y, "Unaligned write to 0x%08X", badvaddr); + break; + case 6: // Bus error on inst + case 7: // Bus error on data + y = crash_screen_printf_proportional(x, y, "Bus error at 0x%08X", badvaddr); + break; + case 10: // Reserved instruction + y = crash_screen_printf_proportional(x, y, "Invalid instruction at 0x%08X", (u32)ctx->pc); + break; + case 12: // Arithmetic overflow + y = crash_screen_printf_proportional(x, y, "Integer overflow"); + break; + case 13: // Trap + y = crash_screen_printf_proportional(x, y, "Trap"); + break; + case 15: { // Floating point exception + u32 fpcsr = ctx->fpcsr; + u32 flag = 0x20000; + const char* fpCause = "Float exception"; + for (s32 i = 0; i < 6; i++, flag >>= 1) { + if (fpcsr & flag) { + fpCause = gFPCSRFaultCauses[i]; + break; + } + } + y = crash_screen_printf_proportional(x, y, "Float: %s", fpCause); + break; + } + default: { + // Fall back to raw cause name + s16 idx = causeIndex; + if (idx == 23) idx = 16; + if (idx == 31) idx = 17; + y = crash_screen_printf_proportional(x, y, "%s", gFaultCauses[idx]); + break; + } + } + + gCrashScreenColor = COLOR_WHITE; + return y; +} + +/// Print the location line (file:line and overlay) for a resolved symbol. +/// Returns y advance. +static s32 crash_screen_print_location(s32 x, s32 y, ResolvedSym* sym) { + b32 hasFile = sym->file[0] != '\0'; + b32 hasOverlay = sym->overlay[0] != '\0'; + b32 hasLine = sym->line >= 0; + + if (!hasFile && !hasOverlay && !hasLine) { + return y; + } + + gCrashScreenColor = COLOR_GREY; + if (hasFile && hasLine && hasOverlay) { + y = crash_screen_printf_proportional(x, y, " %s:%ld (%s)", sym->file, sym->line, sym->overlay); + } else if (hasFile && hasLine) { + y = crash_screen_printf_proportional(x, y, " %s:%ld", sym->file, sym->line); + } else if (hasFile && hasOverlay) { + y = crash_screen_printf_proportional(x, y, " %s (%s)", sym->file, sym->overlay); + } else if (hasOverlay && hasLine) { + y = crash_screen_printf_proportional(x, y, " %s:%ld", sym->overlay, sym->line); + } else if (hasOverlay) { + y = crash_screen_printf_proportional(x, y, " (%s)", sym->overlay); + } else if (hasFile) { + y = crash_screen_printf_proportional(x, y, " %s", sym->file); + } else { + y = crash_screen_printf_proportional(x, y, " :%ld", sym->line); + } + + return y; +} + +void crash_screen_draw(OSThread* faultedThread) { + __OSThreadContext* ctx = &faultedThread->context; + s32 bt[16]; + s32 max = backtrace_thread((void**)bt, ARRAY_COUNT(bt), faultedThread); + s32 i = 0; + ResolvedSym sym; + b32 isFirstFrame = true; + osWritebackDCacheAll(); s32 x = 10; @@ -334,84 +557,63 @@ void crash_screen_draw(OSThread* faultedThread) { crash_screen_draw_rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // Print error message - b32 isException = false; - if (crashScreenAssertMessage[0] == '\0') { - y += crash_screen_printf_proportional(x, y, "Exception in thread %d: %s", faultedThread->id, gFaultCauses[causeIndex]); - isException = true; - } else { - y += crash_screen_printf_proportional(x, y, crashScreenAssertMessage); + y = crash_screen_print_error(x, y, faultedThread); + if (crashScreenAssertMessage[0] != '\0') { i = 1; // Don't include is_debug_panic line in backtrace. } + y += 5; - // Print register values - // TODO: print registers relevant to the exception - if (isException) { - __OSThreadContext* ctx = &faultedThread->context; - crash_screen_printf_proportional(x, y, "Registers:"); - y += 10; - crash_screen_printf(x, y, " a0 = 0x%08X a1 = 0x%08X", (u32)ctx->a0, (u32)ctx->a1); - y += 10; - crash_screen_printf(x, y, " a2 = 0x%08X a3 = 0x%08X", (u32)ctx->a2, (u32)ctx->a3); - y += 10; + b32 hasEvtBacktrace = EvtCurrentScript != nullptr; - y += 10; - } + if (hasEvtBacktrace) { + // Print C backtrace, stopping at EVT engine internals + extern s32 evt_execute_next_command(Evt*); + for (; i < max; i++) { + u32 addr = bt[i]; + if (addr >= (u32)evt_execute_next_command && addr < (u32)evt_execute_next_command + 0x2000) { + break; + } + backtrace_resolve_addr(addr, &sym, -1); + gCrashScreenColor = COLOR_CYAN; + y = crash_screen_printf_proportional(x, y, "%s", sym.name); + y = crash_screen_print_location(x, y, &sym); + if (isFirstFrame && crashScreenAssertMessage[0] == '\0') { + y += 5; + y = crash_screen_print_disasm(x, y, (u32)ctx->pc); + y += 5; + isFirstFrame = false; + } + } - // Print backtrace - crash_screen_printf_proportional(x, y, "Call stack:"); - y += 10; - for (; i < max; i++) { - backtrace_address_to_string(bt[i], buf); - crash_screen_printf_proportional(x, y, " in %s", buf); - y += 10; + // Print EVT script chain (innermost to outermost) + Evt* s = EvtCurrentScript; + while (s != nullptr) { + backtrace_resolve_addr((u32)s->ptrFirstLine, &sym, s->curLine); + gCrashScreenColor = COLOR_YELLOW; + y = crash_screen_printf_proportional(x, y, "%s", sym.name); + y = crash_screen_print_location(x, y, &sym); + s = s->blockingParent; + } + } else { + for (; i < max; i++) { + backtrace_resolve_addr(bt[i], &sym, -1); + gCrashScreenColor = COLOR_CYAN; + y = crash_screen_printf_proportional(x, y, "%s", sym.name); + y = crash_screen_print_location(x, y, &sym); + if (isFirstFrame && crashScreenAssertMessage[0] == '\0') { + y += 5; + y = crash_screen_print_disasm(x, y, (u32)ctx->pc); + y += 5; + isFirstFrame = false; + } + } } - y += 10; + gCrashScreenColor = COLOR_WHITE; osViBlack(0); osViRepeatLine(0); osViSwapBuffer(gCrashScreen.frameBuf); - - /* - crash_screen_draw_rect(0, 30, SCREEN_WIDTH, SCREEN_HEIGHT - 30); - - crash_screen_printf(30, 35, "PC:%08XH SR:%08XH VA:%08XH", ctx->pc, ctx->sr, ctx->badvaddr); - crash_screen_printf(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32)ctx->at, (u32)ctx->v0, (u32)ctx->v1); - crash_screen_printf(30, 45, "A0: %08X A1: %08X A2: %08X", (u32)ctx->a0, (u32)ctx->a1, (u32)ctx->a2); - crash_screen_printf(30, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32)ctx->a3, (u32)ctx->t0, (u32)ctx->t1); - crash_screen_printf(30, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32)ctx->t2, (u32)ctx->t3, (u32)ctx->t4); - crash_screen_printf(30, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32)ctx->t5, (u32)ctx->t6, (u32)ctx->t7); - crash_screen_printf(30, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32)ctx->s0, (u32)ctx->s1, (u32)ctx->s2); - crash_screen_printf(30, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32)ctx->s3, (u32)ctx->s4, (u32)ctx->s5); - crash_screen_printf(30, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32)ctx->s6, (u32)ctx->s7, (u32)ctx->t8); - crash_screen_printf(30, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32)ctx->t9, (u32)ctx->gp, (u32)ctx->sp); - crash_screen_printf(30, 140, "S8:%08XH RA:%08XH", (u32)ctx->s8, (u32)ctx->ra); - - crash_screen_print_fpcsr(ctx->fpcsr); - - crash_screen_print_fpr(30, 170, 0, &ctx->fp0.f.f_even); - crash_screen_print_fpr(120, 170, 2, &ctx->fp2.f.f_even); - crash_screen_print_fpr(210, 170, 4, &ctx->fp4.f.f_even); - crash_screen_print_fpr(30, 180, 6, &ctx->fp6.f.f_even); - crash_screen_print_fpr(120, 180, 8, &ctx->fp8.f.f_even); - crash_screen_print_fpr(210, 180, 10, &ctx->fp10.f.f_even); - crash_screen_print_fpr(30, 190, 12, &ctx->fp12.f.f_even); - crash_screen_print_fpr(120, 190, 14, &ctx->fp14.f.f_even); - crash_screen_print_fpr(210, 190, 16, &ctx->fp16.f.f_even); - crash_screen_print_fpr(30, 200, 18, &ctx->fp18.f.f_even); - crash_screen_print_fpr(120, 200, 20, &ctx->fp20.f.f_even); - crash_screen_print_fpr(210, 200, 22, &ctx->fp22.f.f_even); - crash_screen_print_fpr(30, 210, 24, &ctx->fp24.f.f_even); - crash_screen_print_fpr(120, 210, 26, &ctx->fp26.f.f_even); - crash_screen_print_fpr(210, 210, 28, &ctx->fp28.f.f_even); - crash_screen_print_fpr(30, 220, 30, &ctx->fp30.f.f_even); - - crash_screen_sleep(500); - - // all of these null terminators needed to pad the rodata section for this file - // can potentially fix this problem in another way? - crash_screen_printf(210, 140, "MM:%08XH\0\0\0\0\0\0\0\0", *(u32*)ctx->pc); - */ } OSThread* crash_screen_get_faulted_thread(void) { diff --git a/src/dx/backtrace.c b/src/dx/backtrace.c index ce7ba9c26b..85874cc861 100644 --- a/src/dx/backtrace.c +++ b/src/dx/backtrace.c @@ -426,56 +426,115 @@ s32 ovl_address2symbol(u32 offset, u32 debugRomStart, u32 debugRomEnd, Symbol* o #undef chunkSize } -void backtrace_address_to_string(u32 address, char* dest) { - const char* ovl_name = NULL; +/// Extract file basename and line number from a symbol table file string. +/// File strings are stored as "file.c:123" or just "file.c" (with line -1). +/// If lineOverride >= 0, it replaces the parsed line number. +static void parse_file_string(char* filep, char* outFile, s32 outFileSize, s32* outLine, s32 lineOverride) { + outFile[0] = '\0'; + *outLine = -1; + + if (filep != nullptr) { + char* colon = strchr(filep, ':'); + if (colon != nullptr) { + *colon = '\0'; + // Parse line number (no atoi available) + s32 num = 0; + for (char* p = colon + 1; *p >= '0' && *p <= '9'; p++) { + num = num * 10 + (*p - '0'); + } + *outLine = num; + } + strncpy(outFile, filep, outFileSize - 1); + outFile[outFileSize - 1] = '\0'; + } + + if (lineOverride >= 0) { + *outLine = lineOverride; + } +} + +void backtrace_resolve_addr(u32 address, ResolvedSym* out, s32 lineOverride) { + out->name[0] = '\0'; + out->file[0] = '\0'; + out->overlay[0] = '\0'; + out->line = -1; + + const char* ovl_name = nullptr; u32 debugRomStart = 0, debugRomEnd = 0, ovlBase = 0; const char* ovl_sym_name = ovl_resolve_addr(address, &ovl_name, &debugRomStart, &debugRomEnd, &ovlBase); - if (ovl_sym_name != NULL) { + if (ovl_sym_name != nullptr) { + if (ovl_name != nullptr) { + strncpy(out->overlay, ovl_name, sizeof(out->overlay) - 1); + } + if (debugRomStart != 0) { u32 offset = address - ovlBase; Symbol sym; s32 sym_offset = ovl_address2symbol(offset, debugRomStart, debugRomEnd, &sym); if (sym_offset >= 0 && sym_offset < 0x1000) { - char name[0x40]; - char file[0x40]; - char* namep = load_symbol_string(name, sym.nameOffset, ARRAY_COUNT(name)); - char* filep = load_symbol_string(file, sym.fileOffset, ARRAY_COUNT(file)); - - if (filep == NULL) - sprintf(dest, "%s (%s)", namep, ovl_name); - else - sprintf(dest, "%s (%s %s)", namep, ovl_name, filep); + char name_buf[0x40]; + char file_buf[0x40]; + char* namep = load_symbol_string(name_buf, sym.nameOffset, ARRAY_COUNT(name_buf)); + char* filep = load_symbol_string(file_buf, sym.fileOffset, ARRAY_COUNT(file_buf)); + + if (namep != nullptr) { + strncpy(out->name, namep, sizeof(out->name) - 1); + } + parse_file_string(filep, out->file, sizeof(out->file), &out->line, lineOverride); return; } } - sprintf(dest, "%s (%s)", ovl_sym_name, ovl_name); + if (ovl_sym_name[0] != '\0') { + strncpy(out->name, ovl_sym_name, sizeof(out->name) - 1); + } else { + sprintf(out->name, "0x%08lX", address); + } + if (lineOverride >= 0) out->line = lineOverride; return; } Symbol sym; s32 offset = address2symbol(address, &sym); - if (offset >= 0 && offset < 0x1000) { // 0x1000 = arbitrary func size limit - char name[0x40]; - char file[0x40]; - char* namep = load_symbol_string(name, sym.nameOffset, ARRAY_COUNT(name)); - char* filep = load_symbol_string(file, sym.fileOffset, ARRAY_COUNT(file)); - - offset = 0; // Don't show offsets - - if (filep == nullptr) - if (offset == 0) - sprintf(dest, "%s", namep); - else - sprintf(dest, "%s+0x%lX", namep, offset); - else - if (offset == 0) - sprintf(dest, "%s (%s)", namep, filep); - else - sprintf(dest, "%s (%s+0x%lX)", namep, filep, offset); + if (offset >= 0 && offset < 0x1000) { + char name_buf[0x40]; + char file_buf[0x40]; + char* namep = load_symbol_string(name_buf, sym.nameOffset, ARRAY_COUNT(name_buf)); + char* filep = load_symbol_string(file_buf, sym.fileOffset, ARRAY_COUNT(file_buf)); + + if (namep != nullptr) { + strncpy(out->name, namep, sizeof(out->name) - 1); + } + parse_file_string(filep, out->file, sizeof(out->file), &out->line, lineOverride); } else { - sprintf(dest, "0x%lX", address); + sprintf(out->name, "0x%08lX", address); + if (lineOverride >= 0) out->line = lineOverride; + } +} + +void backtrace_address_to_string(u32 address, char* dest, s32 line) { + ResolvedSym sym; + backtrace_resolve_addr(address, &sym, line); + + // Build a single-line string for debug_backtrace() and other callers + s32 pos = sprintf(dest, "%s", sym.name); + b32 hasMeta = sym.file[0] != '\0' || sym.overlay[0] != '\0' || sym.line >= 0; + if (hasMeta) { + pos += sprintf(dest + pos, " ("); + if (sym.overlay[0] != '\0') { + pos += sprintf(dest + pos, "%s", sym.overlay); + if (sym.file[0] != '\0') { + pos += sprintf(dest + pos, " "); + } + } + if (sym.file[0] != '\0') { + pos += sprintf(dest + pos, "%s", sym.file); + } + if (sym.line >= 0) { + pos += sprintf(dest + pos, ":%ld", sym.line); + } + sprintf(dest + pos, ")"); } } @@ -487,7 +546,7 @@ void debug_backtrace(void) { debugf("Backtrace:\n"); for (i = 0; i < max; i++) { - backtrace_address_to_string(bt[i], buf); + backtrace_address_to_string(bt[i], buf, -1); debugf(" %s\n", buf); } } diff --git a/src/dx/backtrace.h b/src/dx/backtrace.h index dee2d4bbc5..e18bd03eb5 100644 --- a/src/dx/backtrace.h +++ b/src/dx/backtrace.h @@ -53,7 +53,20 @@ int backtrace_thread(void **buffer, int size, OSThread *thread); /** Print a backtrace. */ void debug_backtrace(void); -/** Converts a function address to a string representation using its name, offset, and file. */ -void backtrace_address_to_string(u32 address, char* dest); +/// Resolved symbol information for a backtrace frame. +typedef struct { + char name[0x40]; ///< Function/symbol name, or hex address fallback. + char file[0x40]; ///< Source file basename (e.g. "main.c"), or empty. + char overlay[0x20]; ///< Overlay name (e.g. "kmr_20"), or empty. + s32 line; ///< Source line number, or -1 if unknown. +} ResolvedSym; + +/// Resolve an address into structured symbol information. +/// If `lineOverride` >= 0, it replaces the symbol table's line number (used for EVT scripts). +void backtrace_resolve_addr(u32 address, ResolvedSym* out, s32 lineOverride); + +/// Converts a function address to a string representation using its name, offset, and file. +/// If line >= 0, it overrides the file's line number (used for EVT script lines). +void backtrace_address_to_string(u32 address, char* dest, s32 line); #endif diff --git a/src/dx/debug_menu.c b/src/dx/debug_menu.c index 8d262a90e7..929a8138f5 100644 --- a/src/dx/debug_menu.c +++ b/src/dx/debug_menu.c @@ -2511,7 +2511,7 @@ void dx_debug_evt_draw_disasm() { } op = *pos++; - nargs = *pos++; + nargs = EVT_CMD_ARGC(*pos++); pos += nargs; DebugEvtLineCount++; @@ -2528,7 +2528,7 @@ void dx_debug_evt_draw_disasm() { for (i = DebugEvtDrawLine; i < last; i++) { pos = DebugEvtAttached->ptrFirstLine + DebugEvtLineOffsets[i]; op = *pos++; - nargs = *pos++; + nargs = EVT_CMD_ARGC(*pos++); s32* args = pos; pos += nargs; diff --git a/src/dx/overlay.c b/src/dx/overlay.c index 911db7a572..c221f4d3a0 100644 --- a/src/dx/overlay.c +++ b/src/dx/overlay.c @@ -50,6 +50,7 @@ struct Overlay { OverlayType type; u8* base; ///< Where the text/data/bss/meta is. u32 text_size; + u32 load_size; ///< text + data OverlayExport* exports; u32 export_count; const char* strtab; @@ -207,6 +208,7 @@ Overlay* ovl_load(const char* name, OverlayType type) { // Set up pointers into loaded region ovl->text_size = hdr.text_size; + ovl->load_size = hdr.load_size; ovl->export_count = hdr.export_count; ovl->exports = (OverlayExport*)meta_base; ovl->strtab = (const char*)(meta_base + hdr.export_count * sizeof(OverlayExport)); @@ -294,7 +296,7 @@ static const char* name_for_addr(const Overlay* ovl, u32 addr) { static b32 contains(const Overlay* ovl, u32 addr) { if (ovl->name[0] == '\0') return false; u32 b = (u32)ovl->base; - return addr >= b && addr < b + ovl->text_size; + return addr >= b && addr < b + ovl->load_size; } const char* ovl_resolve_addr(u32 addr, const char** outOverlayName, @@ -313,7 +315,7 @@ const char* ovl_resolve_addr(u32 addr, const char** outOverlayName, *outDebugRomEnd = ovl->debugRomEnd; if (outOverlayBase) *outOverlayBase = (u32)ovl->base; - return sym; + return sym != nullptr ? sym : ""; } } return nullptr; diff --git a/src/dx/overlay.h b/src/dx/overlay.h index a72e29a621..4d4b39a0c4 100644 --- a/src/dx/overlay.h +++ b/src/dx/overlay.h @@ -34,7 +34,10 @@ void ovl_unload_type(OverlayType type); /// Look up an exported symbol by name. Returns nullptr if not found. void* ovl_import(const Overlay* ovl, const char* name); -/// Searches all loaded overlays for the symbol name nearest to `addr`. +/// Searches all loaded overlays for the symbol nearest to `addr`. +/// Returns an empty string (not NULL) if the address is in an overlay but has +/// no matching export, so the caller can still use the debug symbol table. +/// Returns nullptr if the address is not in any loaded overlay. const char* ovl_resolve_addr(u32 addr, const char** outOverlayName, u32* outDebugRomStart, u32* outDebugRomEnd, u32* outOverlayBase); diff --git a/src/evt/evt.c b/src/evt/evt.c index d319c753bb..ddc4a36120 100644 --- a/src/evt/evt.c +++ b/src/evt/evt.c @@ -8,7 +8,7 @@ extern u32* gMapFlags; extern s32* gMapVars; extern char evtDebugPrintBuffer[0x100]; -Bytecode* EvtCallingLine; +Evt* EvtCurrentScript; Bytecode* evt_find_label(Evt* script, s32 arg1); Bytecode* evt_skip_if(Evt* script); @@ -891,8 +891,6 @@ ApiStatus evt_handle_call(Evt* script) { s32 isInitialCall; ApiFunc func; ApiStatus ret; - EvtCallingLine = script->ptrCurLine; - if (script->blocked) { isInitialCall = false; func = script->callFunction; @@ -907,7 +905,6 @@ ApiStatus evt_handle_call(Evt* script) { ret = func(script, isInitialCall); } - EvtCallingLine = nullptr; return ret; } @@ -1358,6 +1355,7 @@ ApiStatus evt_handle_debug_breakpoint(Evt* script) { } s32 evt_execute_next_command(Evt* script) { + EvtCurrentScript = script; s32 commandsExecuted = 0; while (true) { @@ -1383,7 +1381,7 @@ s32 evt_execute_next_command(Evt* script) { commandsExecuted++; if (commandsExecuted >= 10000) { char scriptName[0x80]; - backtrace_address_to_string((u32)script->ptrFirstLine, scriptName); + backtrace_address_to_string((u32)script->ptrFirstLine, scriptName, -1); PANIC_MSG("Script %s is blocking for ages (infinite loop?)", scriptName); } @@ -1393,6 +1391,8 @@ s32 evt_execute_next_command(Evt* script) { lines = script->ptrNextLine; script->curOpcode = *lines++; nargs = *lines++; + script->curLine = EVT_CMD_LINE(nargs); + nargs = EVT_CMD_ARGC(nargs); script->ptrReadPos = lines; script->blocked = false; script->curArgc = nargs; @@ -2087,7 +2087,7 @@ Bytecode* evt_skip_if(Evt* script) { do { opcode = *pos++; - nargs = *pos++; + nargs = EVT_CMD_ARGC(*pos++); pos += nargs; switch (opcode) { @@ -2126,7 +2126,7 @@ Bytecode* evt_skip_else(Evt* script) { do { opcode = *pos++; - nargs = *pos++; + nargs = EVT_CMD_ARGC(*pos++); pos += nargs; switch (opcode) { @@ -2175,7 +2175,7 @@ Bytecode* evt_goto_end_case(Evt* script) { do { opcode = pos++; nargs = pos++; - pos += *nargs; + pos += EVT_CMD_ARGC(*nargs); switch (*opcode) { case EVT_OP_END: @@ -2203,7 +2203,7 @@ Bytecode* evt_goto_next_case(Evt* script) { do { opcode = pos++; nargs = pos++; - pos += *nargs; + pos += EVT_CMD_ARGC(*nargs); switch (*opcode) { case EVT_OP_END: @@ -2245,7 +2245,7 @@ Bytecode* evt_goto_end_loop(Evt* script) { do { opcode = *pos++; - nargs = *pos++; + nargs = EVT_CMD_ARGC(*pos++); pos += nargs; switch (opcode) { diff --git a/src/evt/script_list.c b/src/evt/script_list.c index 3ff1cf64a9..45e91207df 100644 --- a/src/evt/script_list.c +++ b/src/evt/script_list.c @@ -131,7 +131,7 @@ void find_script_labels(Evt* script) { curLine = script->ptrNextLine; while (j < ARRAY_COUNT(script->labelIndices)) { type = *curLine++; - numArgs = *curLine++; + numArgs = EVT_CMD_ARGC(*curLine++); label = *curLine; curLine += numArgs; @@ -604,6 +604,7 @@ void update_scripts(void) { } } IsUpdatingScripts = false; + EvtCurrentScript = nullptr; } // Does nothing, is cursed diff --git a/tools/build/append_symbol_table.py b/tools/build/append_symbol_table.py index 8bf47e73e6..4dc3e027ee 100644 --- a/tools/build/append_symbol_table.py +++ b/tools/build/append_symbol_table.py @@ -20,7 +20,7 @@ def readelf(elf: str) -> List[Tuple[int, str, str, int]]: parts = line.split() # 75082: 8048d5bc 44 FUNC GLOBAL DEFAULT 1845 func_802BC0B8_E2E9E8 - if len(parts) == 8 and parts[3] == "FUNC": + if len(parts) == 8 and parts[3] in ("FUNC", "OBJECT"): addr = int(parts[1], 16) name = parts[-1] if name.startswith("dead_"): @@ -50,6 +50,20 @@ def readelf(elf: str) -> List[Tuple[int, str, str, int]]: (addr, addr2name[sorted_addr2name_addrs[i]], file_basename, line_number) ) + # Add symbols that have no debug line info (e.g. data/EvtScript symbols). + # Try to find the file from the nearest debug line entry. + sorted_line_addrs = sorted(addr2line.keys()) + addrs_with_line = set(addr2line.keys()) + for addr, name in addr2name.items(): + if addr not in addrs_with_line: + # Find closest debug line entry to guess the source file + i = bisect.bisect_right(sorted_line_addrs, addr) - 1 + if i >= 0: + file_basename = addr2line[sorted_line_addrs[i]][0] + symbols.append((addr, name, file_basename, -1)) + else: + symbols.append((addr, name, "", -1)) + # non-debug builds if len(symbols) == 0: print("no debug symbols found, using func names only") diff --git a/tools/build/overlay.py b/tools/build/overlay.py index 192941e78d..8e356bc9b9 100644 --- a/tools/build/overlay.py +++ b/tools/build/overlay.py @@ -607,15 +607,22 @@ def extract_debug_info_from_objects(obj_paths, section_map, sec_vma_map, elfs, l Returns a debug blob (SYMS format) with rom_base=0. """ + STT_OBJECT = 1 STT_FUNC = 2 addr2name = {} + addr2file = {} # addr -> file basename (from object file path) all_line_entries = [] for elf_idx, elf in enumerate(elfs): - # Collect FUNC symbols + obj_basename = obj_paths[elf_idx].replace("\\", "/").rsplit("/", 1)[-1] + # Strip .o extension to get source file name (e.g. "main.c.o" -> "main.c") + if obj_basename.endswith(".o"): + obj_basename = obj_basename[:-2] + + # Collect FUNC and OBJECT symbols for sym in elf.symbols: - if sym.type != STT_FUNC or sym.value == 0: + if sym.type not in (STT_FUNC, STT_OBJECT) or sym.value == 0: continue if sym.shndx in (SHN_UNDEF, SHN_ABS, SHN_COMMON): continue @@ -626,6 +633,7 @@ def extract_debug_info_from_objects(obj_paths, section_map, sec_vma_map, elfs, l name = itanium_demangle(sym.name) if not name.startswith("dead_"): addr2name[addr] = name + addr2file[addr] = obj_basename # Parse .debug_line (may fail for compressed/unusual debug sections) try: @@ -666,6 +674,13 @@ def extract_debug_info_from_objects(obj_paths, section_map, sec_vma_map, elfs, l (addr - link_addr, addr2name[closest_addr], file_basename, line_number) ) + # Add symbols that have no debug line info (e.g. EvtScript data) + addrs_with_line = set(addr for addr, _, _ in all_line_entries) + for addr, name in addr2name.items(): + if addr not in addrs_with_line: + file_basename = addr2file.get(addr, "") + symbols.append((addr - link_addr, name, file_basename, -1)) + if len(symbols) == 0: for addr, name in addr2name.items(): symbols.append((addr - link_addr, name, "", -1))