mirror of
https://github.com/n64decomp/sm64.git
synced 2024-11-14 05:15:09 -05:00
530 lines
13 KiB
Diff
530 lines
13 KiB
Diff
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 <sm64.h>
|
|
+
|
|
+#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 <types.h>
|
|
+
|
|
+#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_ */
|