diff --git a/asm/crash.s b/asm/crash.s new file mode 100644 index 00000000..870b7e2c --- /dev/null +++ b/asm/crash.s @@ -0,0 +1,160 @@ +# SM64 Crash Handler +# See Readme below. + +.include "macros.inc" + +.set COP0_CAUSE, 13 +.set COP0_EPC, 14 +.set COP0_BADVADDR, 8 + +/* --------------------------------------------------------------- + * IMPORTANT README: + * --------------------------------------------------------------- + * To use this crash screen, in lib/__osExceptionPreamble.s, change + * the function to use the following assembly: + * + * lui $k0, %hi(__crash_handler_entry) + * addiu $k0, $k0, %lo(__crash_handler_entry) + * jr $k0 + * nop + * + * Doing just a jal __crash_handler_entry will cause mupen recompiler + * errors, so be sure to use the original exception style assembly + * above! + * + * Be sure to add #include "../../enhancements/crash.inc.c" to + * the top of game.c. Add .include "../enhancements/crash.inc.s" to + * the bottom of asm/decompress.s. Add "../enhancements/crash.h" to + * the top of sm64.h, above the CRASH_SCREEN_INCLUDED condition. + * + * See the DEBUG_ASSERT macro on how to call the crash screen for + * detected exceptions. + */ + +glabel crashFont + .incbin "enhancements/crash_font.bin" + .align 4 + +glabel exceptionRegContext + .fill 0x108 + +glabel pAssertFile + .dword 0 +glabel nAssertLine + .dword 0 +glabel pAssertExpression + .dword 0 +glabel nAssertStopProgram + .dword 0 + +glabel _n64_assert + lui $at, %hi(pAssertFile) + sw $a0, %lo(pAssertFile)($at) + lui $at, %hi(nAssertLine) + sw $a1, %lo(nAssertLine)($at) + lui $at, %hi(pAssertExpression) + sw $a2, %lo(pAssertExpression)($at) + lui $at, %hi(nAssertStopProgram) + sw $a3, %lo(nAssertStopProgram)($at) + beqz $a3, .end_2 + nop + syscall # trigger crash screen +.end_2: + jr $ra + nop + +glabel cop0_get_cause + jr $ra + mfc0 $v0, $13 # COP0_CAUSE + +glabel cop0_get_epc + jr $ra + mfc0 $v0, $14 # COP0_EPC + +glabel cop0_get_badvaddr + jr $ra + mfc0 $v0, $8 # COP0_BADVADDR + +# If the error code field of cop0's cause register is non-zero, +# draw crash details to the screen and hang +# +# If there wasn't an error, continue to the original handler + +glabel __crash_handler_entry + la $k0, exceptionRegContext + sd $zero, 0x018 ($k0) + sd $at, 0x020 ($k0) + sd $v0, 0x028 ($k0) + sd $v1, 0x030 ($k0) + sd $a0, 0x038 ($k0) + sd $a1, 0x040 ($k0) + sd $a2, 0x048 ($k0) + sd $a3, 0x050 ($k0) + sd $t0, 0x058 ($k0) + sd $t1, 0x060 ($k0) + sd $t2, 0x068 ($k0) + sd $t3, 0x070 ($k0) + sd $t4, 0x078 ($k0) + sd $t5, 0x080 ($k0) + sd $t6, 0x088 ($k0) + sd $t7, 0x090 ($k0) + sd $s0, 0x098 ($k0) + sd $s1, 0x0A0 ($k0) + sd $s2, 0x0A8 ($k0) + sd $s3, 0x0B0 ($k0) + sd $s4, 0x0B8 ($k0) + sd $s5, 0x0C0 ($k0) + sd $s6, 0x0C8 ($k0) + sd $s7, 0x0D0 ($k0) + sd $t8, 0x0D8 ($k0) + sd $t9, 0x0E0 ($k0) + sd $gp, 0x0E8 ($k0) + sd $sp, 0x0F0 ($k0) + sd $s8, 0x0F8 ($k0) + sd $ra, 0x100 ($k0) + mfc0 $t0, $13 # COP0_CAUSE + srl $t0, $t0, 2 + andi $t0, $t0, 0x1F + beqz $t0, .end + nop + # cop unusable exception fired twice on startup so we'll ignore it for now + li $at, 0x0B + beq $t0, $at, .end + nop + jal show_crash_screen_and_hang + nop + .end: + ld $zero, 0x018 ($k0) + ld $at, 0x020 ($k0) + ld $v0, 0x028 ($k0) + ld $v1, 0x030 ($k0) + ld $a0, 0x038 ($k0) + ld $a1, 0x040 ($k0) + ld $a2, 0x048 ($k0) + ld $a3, 0x050 ($k0) + ld $t0, 0x058 ($k0) + ld $t1, 0x060 ($k0) + ld $t2, 0x068 ($k0) + ld $t3, 0x070 ($k0) + ld $t4, 0x078 ($k0) + ld $t5, 0x080 ($k0) + ld $t6, 0x088 ($k0) + ld $t7, 0x090 ($k0) + ld $s0, 0x098 ($k0) + ld $s1, 0x0A0 ($k0) + ld $s2, 0x0A8 ($k0) + ld $s3, 0x0B0 ($k0) + ld $s4, 0x0B8 ($k0) + ld $s5, 0x0C0 ($k0) + ld $s6, 0x0C8 ($k0) + ld $s7, 0x0D0 ($k0) + ld $t8, 0x0D8 ($k0) + ld $t9, 0x0E0 ($k0) + ld $gp, 0x0E8 ($k0) + ld $sp, 0x0F0 ($k0) + ld $s8, 0x0F8 ($k0) + ld $ra, 0x100 ($k0) + lui $k0, %hi(__osException) + addiu $k0, $k0, %lo(__osException) + jr $k0 # run the original handler + nop diff --git a/lib/asm/__osExceptionPreamble.s b/lib/asm/__osExceptionPreamble.s index fdc36c8b..ccbf4ecc 100644 --- a/lib/asm/__osExceptionPreamble.s +++ b/lib/asm/__osExceptionPreamble.s @@ -8,12 +8,11 @@ .section .text, "ax" glabel __osExceptionPreamble - lui $k0, %hi(__osException) # $k0, 0x8032 - addiu $k0, %lo(__osException) # addiu $k0, $k0, 0x66d0 + lui $k0, %hi(__crash_handler_entry) # $k0, 0x8032 + addiu $k0, %lo(__crash_handler_entry) # addiu $k0, $k0, 0x66d0 jr $k0 nop - glabel __osException lui $k0, %hi(gInterruptedThread) # $k0, 0x8036 addiu $k0, %lo(gInterruptedThread) # addiu $k0, $k0, 0x5f40 diff --git a/sm64.ld b/sm64.ld index dea5c8bd..22fff2d8 100755 --- a/sm64.ld +++ b/sm64.ld @@ -112,6 +112,7 @@ SECTIONS BUILD_DIR/src/game/rendering_graph_node.o(.text); BUILD_DIR/src/game/profiler.o(.text); BUILD_DIR/asm/decompress.o(.text); + BUILD_DIR/asm/crash.o(.text); BUILD_DIR/src/game/camera.o(.text); BUILD_DIR/src/game/debug_course.o(.text); BUILD_DIR/src/game/object_list_processor.o(.text); diff --git a/src/game/crash.c b/src/game/crash.c new file mode 100644 index 00000000..587ac86d --- /dev/null +++ b/src/game/crash.c @@ -0,0 +1,291 @@ +/* SM64 Crash Handler */ + +#include + +#include "crash.h" + +extern u32 exceptionRegContext[]; + +extern char *pAssertFile; +extern int nAssertLine; +extern char *pAssertExpression; +extern int nAssertStopProgram; + +u16 fbFillColor = 0xFFFF; +u16 fbShadeColor = 0x0000; +u16 *fbAddress = NULL; + +extern u8 crashFont[]; + +const char *szErrCodes[] = { + "INTERRUPT", + "TLB MOD", + "UNMAPPED LOAD ADDR", + "UNMAPPED STORE ADDR", + "BAD LOAD ADDR", + "BAD STORE ADDR", + "BUS ERR ON INSTR FETCH", + "BUS ERR ON LOADSTORE", + "SYSCALL", + "BREAKPOINT", + "UNKNOWN INSTR", + "COP UNUSABLE", + "ARITHMETIC OVERFLOW", + "TRAP EXC", + "VIRTUAL COHERENCY INSTR", + "FLOAT EXC", +}; + +const char *szGPRegisters1[] = { "R0", "AT", "V0", "V1", "A0", "A1", "A2", "A3", + "T0", "T1", "T2", "T3", "T4", "T5", "T6", NULL }; + +const char *szGPRegisters2[] = { "T7", "S0", "S1", "S2", "S3", "S4", + "S5", "S6", "S7", "T8", "T9", /*"K0", "K1",*/ + "GP", "SP", "FP", "RA", NULL }; + +/* + Generates new preamble code at the exception vectors (0x000, 0x180) + + eg: generate_exception_preambles(crash_handler_entry); + + 000: lui k0, hi(crash_handler_entry) + 004: addiu k0, k0, lo(crash_handler_entry) + 008: jr k0 + 00C: nop +*/ +void generate_exception_preambles(void *entryPoint) { + u8 *mem = (u8 *) 0xA0000000; + int offs = 0; + int i; + + u16 hi = (u32) entryPoint >> 16; + u16 lo = (u32) entryPoint & 0xFFFF; + + if (lo & 0x8000) { + hi++; + } + + for (i = 0; i < 2; i++) { + *(u32 *) &mem[offs + 0x00] = 0x3C1A0000 | hi; + *(u32 *) &mem[offs + 0x04] = 0x275A0000 | lo; + *(u32 *) &mem[offs + 0x08] = 0x03400008; + *(u32 *) &mem[offs + 0x0C] = 0x00000000; + offs += 0x180; + } +} + +int crash_strlen(char *str) { + int len = 0; + while (*str++) { + len++; + } + return len; +} + +void show_crash_screen_and_hang(void) { + u32 cause; + u32 epc; + u8 errno; + + fb_set_address((void *) (*(u32 *) 0xA4400004 | 0x80000000)); // replace me + + cause = cop0_get_cause(); + epc = cop0_get_epc(); + + errno = (cause >> 2) & 0x1F; + + if (nAssertStopProgram == 0) { + fbFillColor = 0x6253; + fb_fill(10, 10, 300, 220); + + fb_print_str(80, 20, "AN ERROR HAS OCCURRED!"); + fb_print_int_hex(80, 30, errno, 8); + fb_print_str(107, 30, szErrCodes[errno]); + + if (errno >= 2 && errno <= 5) { + /* + 2 UNMAPPED LOAD ADDR + 3 UNMAPPED STORE ADDR + 4 BAD LOAD ADDR + 5 BAD STORE ADDR + */ + u32 badvaddr = cop0_get_badvaddr(); + + fb_print_str(188, 50, "VA"); + fb_print_int_hex(215, 50, badvaddr, 32); + } + } else { + int afterFileX; + int exprBoxWidth; + fbFillColor = 0x5263; + fb_fill(10, 10, 300, 220); + + fb_print_str(80, 20, "ASSERTION FAILED!"); + + afterFileX = fb_print_str(80, 30, pAssertFile); + fb_print_str(afterFileX, 30, ":"); + fb_print_uint(afterFileX + 5, 30, nAssertLine); + + exprBoxWidth = (crash_strlen(pAssertExpression) * 5) + 2; + fbFillColor = 0x0001; + fb_fill(80 - 1, 40 - 1, exprBoxWidth, 10); + fb_print_str(80, 40, pAssertExpression); + } + + fb_print_str(80, 50, "PC"); + fb_print_int_hex(95, 50, epc, 32); + + fb_print_gpr_states(80, 70, szGPRegisters1, &exceptionRegContext[6 + 0]); + fb_print_gpr_states(145, 70, szGPRegisters2, &exceptionRegContext[6 + 15 * 2]); + + fb_swap(); + osWritebackDCacheAll(); + + while (1) // hang forever + { + UNUSED volatile int t = 0; // keep pj64 happy + } +} + +u8 ascii_to_idx(char c) { + return c - 0x20; +} + +void fb_set_address(void *address) { + fbAddress = (u16 *) address; +} + +void fb_swap() { + // update VI frame buffer register + // todo other registers + *(u32 *) (0xA4400004) = (u32) fbAddress & 0x00FFFFFF; +} + +void fb_fill(int baseX, int baseY, int width, int height) { + int y, x; + + for (y = baseY; y < baseY + height; y++) { + for (x = baseX; x < baseX + width; x++) { + fbAddress[y * 320 + x] = fbFillColor; + } + } +} + +void fb_draw_char(int x, int y, u8 idx) { + u16 *out = &fbAddress[y * 320 + x]; + const u8 *in = &crashFont[idx * 3]; + int nbyte; + int nrow; + int ncol; + + for (nbyte = 0; nbyte < 3; nbyte++) { + u8 curbyte = in[nbyte]; + for (nrow = 0; nrow < 2; nrow++) { + for (ncol = 0; ncol < 4; ncol++) { + u8 px = curbyte & (1 << 7 - (nrow * 4 + ncol)); + if (px != 0) { + out[ncol] = fbFillColor; + } + } + out += 320; + } + } +} + +void fb_draw_char_shaded(int x, int y, u8 idx) { + fbFillColor = 0x0001; + fb_draw_char(x - 1, y + 1, idx); + + fbFillColor = 0xFFFF; + fb_draw_char(x, y, idx); +} + +int fb_print_str(int x, int y, const char *str) { + while (1) { + int yoffs = 0; + u8 idx; + char c = *str++; + + if (c == '\0') { + break; + } + + if (c == ' ') { + x += 5; + continue; + } + + switch (c) { + case 'j': + case 'g': + case 'p': + case 'q': + case 'y': + case 'Q': + yoffs = 1; + break; + case ',': + yoffs = 2; + break; + } + + idx = ascii_to_idx(c); + fb_draw_char_shaded(x, y + yoffs, idx); + x += 5; + } + + return x; +} + +void fb_print_int_hex(int x, int y, u32 value, int nbits) { + nbits -= 4; + + while (nbits >= 0) { + int nib = ((value >> nbits) & 0xF); + u8 idx; + + if (nib > 9) { + idx = ('A' - 0x20) + (nib - 0xa); + } else { + idx = ('0' - 0x20) + nib; + } + + fb_draw_char_shaded(x, y, idx); + x += 5; + + nbits -= 4; + } +} + +int fb_print_uint(int x, int y, u32 value) { + int nchars = 0; + + int v = value; + int i; + while (v /= 10) { + nchars++; + } + + x += nchars * 5; + + for (i = nchars; i >= 0; i--) { + fb_draw_char_shaded(x, y, ('0' - 0x20) + (value % 10)); + value /= 10; + x -= 5; + } + + return (x + nchars * 5); +} + +void fb_print_gpr_states(int x, int y, const char *regNames[], u32 *regContext) { + int i; + for (i = 0;; i++) { + if (regNames[i] == NULL) { + break; + } + + fb_print_str(x, y, regNames[i]); + fb_print_int_hex(x + 15, y, regContext[i * 2 + 1], 32); + y += 10; + } +} diff --git a/src/game/crash.h b/src/game/crash.h new file mode 100644 index 00000000..da4e011e --- /dev/null +++ b/src/game/crash.h @@ -0,0 +1,29 @@ +#ifndef _CRASH_H_ +#define _CRASH_H_ + +#include + +#define CRASH_SCREEN_INCLUDED 1 + +extern u32 cop0_get_cause(void); +extern u32 cop0_get_epc(void); +extern u32 cop0_get_badvaddr(void); + +extern void _n64_assert(const char* pFile, int nLine, const char *pExpression, int nStopProgram); + +extern u8 __crash_handler_entry[]; + +void generate_exception_preambles(void *entryPoint); +void show_crash_screen_and_hang(void); +u8 ascii_to_idx(char c); +void fb_set_address(void *address); +void fb_swap(void); +void fb_fill(int baseX, int baseY, int width, int height); +void fb_draw_char(int x, int y, u8 idx); +void fb_draw_char_shaded(int x, int y, u8 idx); +int fb_print_str(int x, int y, const char *str); +int fb_print_uint(int x, int y, u32 value); +void fb_print_int_hex(int x, int y, u32 value, int nbits); +void fb_print_gpr_states(int x, int y, const char* regStrs[], u32 *regContext); + +#endif /* _CRASH_H_ */