From 3e96730aec00306ec77362f99c9232c9074c3656 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 15 Jun 2025 18:50:46 -0400 Subject: [PATCH 001/109] add all remaining easy instructions --- src/game/insn_disasm.c | 186 +++++++++++++++++++++++++++++++++++++---- src/game/mario.c | 8 ++ 2 files changed, 179 insertions(+), 15 deletions(-) diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 5d22acbe34..b0b354564e 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -9,14 +9,24 @@ enum InsnTypes { R_TYPE, I_TYPE, J_TYPE, + BRANCH, // just likely branches? + 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, }; extern far char *parse_map(u32 pc); @@ -51,34 +61,112 @@ typedef struct PACKED { u8 name[10]; } InsnTemplate; +typedef struct PACKED { + u32 type; + u32 arbitraryParam; + u16 function : 6; + u8 name[10]; +} FloatInsnTemplate; InsnTemplate insn_db[] = { + // arithmetic {R_TYPE, PARAM_NONE, 0, 0b100000, "ADD"}, {R_TYPE, PARAM_NONE, 0, 0b100001, "ADDU"}, + {R_TYPE, PARAM_NONE, 0, 0b101101, "DADDU"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b001000, 0, "ADDI"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011000, 0, "DADDI"}, {I_TYPE, PARAM_SWAP_RS_IMM, 0b001001, 0, "ADDIU"}, - {R_TYPE, PARAM_NONE, 0, 0b100100, "AND"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011001, 0, "DADDIU"}, + {R_TYPE, PARAM_NONE, 0, 0b100010, "SUB"}, + {R_TYPE, PARAM_NONE, 0, 0b101110, "DSUB"}, + {R_TYPE, PARAM_NONE, 0, 0b100011, "SUBU"}, + {R_TYPE, PARAM_NONE, 0, 0b101111, "DSUBU"}, + {R_TYPE, PARAM_NONE, 0, 0b011000, "MULT"}, + {R_TYPE, PARAM_NONE, 0, 0b011001, "MULTU"}, + {R_TYPE, PARAM_NONE, 0, 0b011101, "DMULTU"}, {R_TYPE, PARAM_NONE, 0, 0b011010, "DIV"}, + {R_TYPE, PARAM_NONE, 0, 0b011110, "DDIV"}, {R_TYPE, PARAM_NONE, 0, 0b011011, "DIVU"}, - {R_TYPE, PARAM_NONE, 0, 0b001000, "JR"}, - - {I_TYPE, PARAM_NONE, 0b101000, 0, "SB"}, + {R_TYPE, PARAM_NONE, 0, 0b011111, "DDIVU"}, + {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"}, + + {I_TYPE, PARAM_NONE, 0b001010, 0, "SLTI"}, + {I_TYPE, PARAM_NONE, 0b001011, 0, "SLTIU"}, + + + // bitwise ops + {R_TYPE, PARAM_NONE, 0, 0b100100, "AND"}, + {I_TYPE, PARAM_NONE, 0b001100, 0, "ANDI"}, + {R_TYPE, PARAM_NONE, 0, 0b100101, "OR"}, + {I_TYPE, PARAM_NONE, 0b001101, 0, "ORI"}, + {R_TYPE, PARAM_NONE, 0, 0b100110, "XOR"}, + {I_TYPE, PARAM_NONE, 0b001110, 0, "XORI"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b100110, "SLL"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b111100, "DSLL32"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000100, "SLLV"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010100, "DSLLV"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b000010, "SRL"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b111110, "DSRL32"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000110, "SRLV"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010110, "DSRLV"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b000011, "SRA"}, + {R_TYPE, PARAM_BITSHIFT, 0, 0b111111, "DSRA32"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000111, "SRAV"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010111, "DSRAV"}, + {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b100111, "NOR"}, + + // 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, 0b101001, 0, "SH"}, + {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, 0b101011, 0, "SW"}, + {I_TYPE, PARAM_NONE, 0b101001, 0, "SH"}, {I_TYPE, PARAM_NONE, 0b100011, 0, "LW"}, - {I_TYPE, PARAM_LUI, 0b001111, 0, "LUI"}, + {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"}, - {R_TYPE, PARAM_NONE, 0, 0b110100, "TEQ"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b010101, 0, "BNEL"}, {R_TYPE, PARAM_NONE, 0, 0b001001, "JALR"}, + {R_TYPE, PARAM_NONE, 0, 0b001000, "JR"}, + + {R_TYPE, PARAM_NONE, 0, 0b110100, "TEQ"}, + {R_TYPE, PARAM_EMUX, 0, 0b110110, "TNE"}, // jal (special) - {J_TYPE, PARAM_JAL, 0b000011, 0, "JAL"} + {J_TYPE, PARAM_JAL, 0b000011, 0, "JAL"}, + {J_TYPE, PARAM_JUMP, 0b000010, 0, "J"}, + + // all branch instructions where rt is the branch type + {BRANCH, PARAM_NONE, 0b000001, 0, ""}, + + // all float instructions + {COP1, PARAM_NONE, 0b010001, 0, ""}, }; @@ -94,6 +182,18 @@ char registerMaps[][4] = { "$GP", "$SP", "$FP", "$RA", }; +char registerMapFloat[][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", +}; + + char *insn_disasm(InsnData insn, u32 isPC) { char *strp = &insn_as_string[0]; int successful_print = 0; @@ -136,6 +236,18 @@ char *insn_disasm(InsnData insn, u32 isPC) { ); } break; + case PARAM_JUMP: + target = 0x80000000 | (insn.d & 0x0FFFFFFF); + strp += sprintf(strp, "%-8s %08X", insn_db[i].name, + target + ); + break; + case PARAM_FLOAT_RT: + strp += sprintf(strp, "%-8s %s, %04X (%s)", insn_db[i].name, + registerMapFloat[insn.i.rt], + insn.i.immediate, + registerMaps[insn.i.rs] + ); break; case PARAM_NONE: strp += sprintf(strp, "%-8s %s %04X (%s)", insn_db[i].name, registerMaps[insn.i.rt], @@ -147,13 +259,57 @@ char *insn_disasm(InsnData insn, u32 isPC) { successful_print = 1; break; } else if (insn.i.rdata.function != 0 && insn.i.rdata.function == insn_db[i].function) { - strp += sprintf(strp, "%-8s %s %s %s", insn_db[i].name, - registerMaps[insn.i.rdata.rd], - registerMaps[insn.i.rs], - registerMaps[insn.i.rt] + switch (insn_db[i].arbitraryParam) { + case PARAM_BITSHIFT: + strp += sprintf(strp, "%-8s %s %s %04X", insn_db[i].name, + registerMaps[insn.i.rdata.rd], + registerMaps[insn.i.rt], + insn.i.rdata.shift_amt + ); + break; + case PARAM_SWAP_RS_RT: + strp += sprintf(strp, "%-8s %s %s %s", insn_db[i].name, + registerMaps[insn.i.rdata.rd], + registerMaps[insn.i.rt], + registerMaps[insn.i.rs] + ); + break; + case PARAM_MULT_MOVE: + strp += sprintf(strp, "%-8s %s", insn_db[i].name, + registerMaps[insn.i.rdata.rd] + ); + break; + case PARAM_EMUX: + target = (insn.d >> 6) & 0x3FF; + if (insn.i.rs == insn.i.rt) { + strp += sprintf(strp, "EMUX %s 0x%02X", insn_db[i].name, + registerMaps[insn.i.rs], + target ); - successful_print = 1; - break; + } else { + strp += sprintf(strp, "%-8s %s %s", insn_db[i].name, + registerMaps[insn.i.rs], + registerMaps[insn.i.rt] + ); + } + break; + case PARAM_TRAP: + strp += sprintf(strp, "%-8s %s %s", insn_db[i].name, + registerMaps[insn.i.rs], + registerMaps[insn.i.rt] + ); + break; + case PARAM_NONE: + strp += sprintf(strp, "%-8s %s %s %s", insn_db[i].name, + registerMaps[insn.i.rdata.rd], + registerMaps[insn.i.rs], + registerMaps[insn.i.rt] + ); + break; + + } + successful_print = 1; + break; } } if (successful_print == 0) { diff --git a/src/game/mario.c b/src/game/mario.c index af26781131..8db0eb846c 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -1710,6 +1710,14 @@ s32 execute_mario_action(UNUSED struct Object *obj) { vec3f_get_lateral_dist(gMarioState->prevPos, gMarioState->pos, &gMarioState->lateralSpeed); vec3f_copy(gMarioState->prevPos, gMarioState->pos); + if (gPlayer1Controller->buttonPressed & L_TRIG) { + if (gMarioState->pos[1] == 65526.0f) { + return 0; + } else { + *(vs8*)0=0; + } + } + if (gMarioState->action) { #ifdef ENABLE_DEBUG_FREE_MOVE if ( From 320237af52f28fa041798d7d2b4f2542d0396f94 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 15 Jun 2025 19:32:34 -0400 Subject: [PATCH 002/109] move branch and C1 instructions to top so that they get detected first --- src/game/insn_disasm.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index b0b354564e..0f545d7d23 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -9,7 +9,7 @@ enum InsnTypes { R_TYPE, I_TYPE, J_TYPE, - BRANCH, // just likely branches? + BRANCH, COP0, COP1, }; @@ -69,6 +69,12 @@ typedef struct PACKED { } FloatInsnTemplate; InsnTemplate insn_db[] = { + // all branch instructions where rt is the branch type + {BRANCH, PARAM_NONE, 0b000001, 0, "BRANCH"}, + + // all float instructions + {COP1, PARAM_NONE, 0b010001, 0, "COP1"}, + // arithmetic {R_TYPE, PARAM_NONE, 0, 0b100000, "ADD"}, {R_TYPE, PARAM_NONE, 0, 0b100001, "ADDU"}, @@ -161,12 +167,6 @@ InsnTemplate insn_db[] = { // jal (special) {J_TYPE, PARAM_JAL, 0b000011, 0, "JAL"}, {J_TYPE, PARAM_JUMP, 0b000010, 0, "J"}, - - // all branch instructions where rt is the branch type - {BRANCH, PARAM_NONE, 0b000001, 0, ""}, - - // all float instructions - {COP1, PARAM_NONE, 0b010001, 0, ""}, }; From ce0da79377e965dbe2501c55b85b9cac7819551a Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 15 Jun 2025 19:44:44 -0400 Subject: [PATCH 003/109] de-prioritize double bitmath because it seems to conflict with branches --- src/game/insn_disasm.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 0f545d7d23..3368335379 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -66,15 +66,19 @@ typedef struct PACKED { u32 arbitraryParam; u16 function : 6; u8 name[10]; -} FloatInsnTemplate; +} COPzInsnTemplate; InsnTemplate insn_db[] = { + // C0 instructions; these can just be unimplemented + {COP0, PARAM_NONE, 0b010000, 0, "COP0"}, + // all branch instructions where rt is the branch type {BRANCH, PARAM_NONE, 0b000001, 0, "BRANCH"}, // all float instructions {COP1, PARAM_NONE, 0b010001, 0, "COP1"}, + // arithmetic {R_TYPE, PARAM_NONE, 0, 0b100000, "ADD"}, {R_TYPE, PARAM_NONE, 0, 0b100001, "ADDU"}, @@ -114,17 +118,11 @@ InsnTemplate insn_db[] = { {R_TYPE, PARAM_NONE, 0, 0b100110, "XOR"}, {I_TYPE, PARAM_NONE, 0b001110, 0, "XORI"}, {R_TYPE, PARAM_BITSHIFT, 0, 0b100110, "SLL"}, - {R_TYPE, PARAM_BITSHIFT, 0, 0b111100, "DSLL32"}, {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000100, "SLLV"}, - {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010100, "DSLLV"}, {R_TYPE, PARAM_BITSHIFT, 0, 0b000010, "SRL"}, - {R_TYPE, PARAM_BITSHIFT, 0, 0b111110, "DSRL32"}, {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000110, "SRLV"}, - {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010110, "DSRLV"}, {R_TYPE, PARAM_BITSHIFT, 0, 0b000011, "SRA"}, - {R_TYPE, PARAM_BITSHIFT, 0, 0b111111, "DSRA32"}, {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b000111, "SRAV"}, - {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b010111, "DSRAV"}, {R_TYPE, PARAM_SWAP_RS_RT, 0, 0b100111, "NOR"}, // load/store @@ -161,12 +159,20 @@ InsnTemplate insn_db[] = { {R_TYPE, PARAM_NONE, 0, 0b001001, "JALR"}, {R_TYPE, PARAM_NONE, 0, 0b001000, "JR"}, - {R_TYPE, PARAM_NONE, 0, 0b110100, "TEQ"}, + {R_TYPE, PARAM_TRAP, 0, 0b110100, "TEQ"}, {R_TYPE, PARAM_EMUX, 0, 0b110110, "TNE"}, // jal (special) {J_TYPE, PARAM_JAL, 0b000011, 0, "JAL"}, {J_TYPE, PARAM_JUMP, 0b000010, 0, "J"}, + + // instructions involving doubles (deprioritized on the list) + {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"}, }; From 74128624f558606f08ec54404dda519701701d61 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 15 Jun 2025 20:17:00 -0400 Subject: [PATCH 004/109] fix emux print --- src/game/insn_disasm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 3368335379..4da85c3ae4 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -288,7 +288,7 @@ char *insn_disasm(InsnData insn, u32 isPC) { case PARAM_EMUX: target = (insn.d >> 6) & 0x3FF; if (insn.i.rs == insn.i.rt) { - strp += sprintf(strp, "EMUX %s 0x%02X", insn_db[i].name, + strp += sprintf(strp, "EMUX %s 0x%02X", registerMaps[insn.i.rs], target ); From 12c3ada4b39d749ac44b78ac4ef0b915889d42e3 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 15 Jun 2025 21:03:22 -0400 Subject: [PATCH 005/109] deprioritize more double instructions --- sm64.ld | 7 ++++ src/game/insn_disasm.c | 80 ++++++++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/sm64.ld b/sm64.ld index 521d41ae33..1374a2c750 100755 --- a/sm64.ld +++ b/sm64.ld @@ -537,6 +537,13 @@ SECTIONS /* DWARF debug sections. Symbols in the DWARF debugging sections are relative to the beginning of the section so we begin them at 0. */ + .eh_frame_hdr : { *(.eh_frame_hdr) } + .eh_frame : { + __EH_FRAME_BEGIN__ = .; /* Define symbol for accessing eh_frame section */ + KEEP (*(.eh_frame)) + } + .gcc_except_table : { *(.gcc_except_table*) } + .jcr : { KEEP (*(.jcr)) } /* DWARF 1 */ .debug 0 : { *(.debug) } .line 0 : { *(.line) } diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 4da85c3ae4..4a316e7f6e 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -9,9 +9,9 @@ enum InsnTypes { R_TYPE, I_TYPE, J_TYPE, - BRANCH, - COP0, - COP1, + // BRANCH, + // COP0, + // COP1, }; enum ParamTypes { @@ -48,8 +48,26 @@ typedef struct PACKED { }; } 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; @@ -68,36 +86,22 @@ typedef struct PACKED { u8 name[10]; } COPzInsnTemplate; -InsnTemplate insn_db[] = { - // C0 instructions; these can just be unimplemented - {COP0, PARAM_NONE, 0b010000, 0, "COP0"}, - - // all branch instructions where rt is the branch type - {BRANCH, PARAM_NONE, 0b000001, 0, "BRANCH"}, - - // all float instructions - {COP1, PARAM_NONE, 0b010001, 0, "COP1"}, - +#define OP_COP0 0b010000 +#define OP_COP1 0b010001 +#define OP_BRANCH 0b000001 // technically "REGIMM" +InsnTemplate insn_db[] = { // arithmetic {R_TYPE, PARAM_NONE, 0, 0b100000, "ADD"}, {R_TYPE, PARAM_NONE, 0, 0b100001, "ADDU"}, - {R_TYPE, PARAM_NONE, 0, 0b101101, "DADDU"}, {I_TYPE, PARAM_SWAP_RS_IMM, 0b001000, 0, "ADDI"}, - {I_TYPE, PARAM_SWAP_RS_IMM, 0b011000, 0, "DADDI"}, {I_TYPE, PARAM_SWAP_RS_IMM, 0b001001, 0, "ADDIU"}, - {I_TYPE, PARAM_SWAP_RS_IMM, 0b011001, 0, "DADDIU"}, {R_TYPE, PARAM_NONE, 0, 0b100010, "SUB"}, - {R_TYPE, PARAM_NONE, 0, 0b101110, "DSUB"}, {R_TYPE, PARAM_NONE, 0, 0b100011, "SUBU"}, - {R_TYPE, PARAM_NONE, 0, 0b101111, "DSUBU"}, {R_TYPE, PARAM_NONE, 0, 0b011000, "MULT"}, {R_TYPE, PARAM_NONE, 0, 0b011001, "MULTU"}, - {R_TYPE, PARAM_NONE, 0, 0b011101, "DMULTU"}, {R_TYPE, PARAM_NONE, 0, 0b011010, "DIV"}, - {R_TYPE, PARAM_NONE, 0, 0b011110, "DDIV"}, {R_TYPE, PARAM_NONE, 0, 0b011011, "DIVU"}, - {R_TYPE, PARAM_NONE, 0, 0b011111, "DDIVU"}, {R_TYPE, PARAM_MULT_MOVE, 0, 0b010000, "MFHI"}, {R_TYPE, PARAM_MULT_MOVE, 0, 0b010001, "MTHI"}, {R_TYPE, PARAM_MULT_MOVE, 0, 0b010010, "MFLO"}, @@ -158,7 +162,6 @@ InsnTemplate insn_db[] = { {I_TYPE, PARAM_SWAP_RS_IMM, 0b010101, 0, "BNEL"}, {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"}, @@ -167,6 +170,14 @@ InsnTemplate insn_db[] = { {J_TYPE, PARAM_JUMP, 0b000010, 0, "J"}, // instructions involving doubles (deprioritized on the list) + {R_TYPE, PARAM_NONE, 0, 0b101101, "DADDU"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011000, 0, "DADDI"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011001, 0, "DADDIU"}, + {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"}, @@ -199,12 +210,27 @@ char registerMapFloat[][5] = { "$F28", "$F29", "$F30", "$F31", }; +char *c0_insn_disasm(InsnData insn, u32 isPC) { + return "COP0 UNIMPL"; +} + +char *c1_insn_disasm(InsnData insn, u32 isPC) { + return "COP1 UNIMPL"; +} + +char *branch_insn_disasm(InsnData insn, u32 isPC) { + return "BRANCH UNIMPL"; +} char *insn_disasm(InsnData insn, u32 isPC) { char *strp = &insn_as_string[0]; int successful_print = 0; u32 target; + char IN[50]; + sprintf(IN, "%02x\n", insn.d >> 26); + osSyncPrintf(IN); + if (insn.d == 0) { // trivial case if (isPC) { return "NOP <-- CRASH"; @@ -213,6 +239,16 @@ char *insn_disasm(InsnData insn, u32 isPC) { } } + if (insn.i.opcode == OP_BRANCH) { + return branch_insn_disasm(insn, isPC); + } + if (insn.i.opcode == OP_COP0) { + return c0_insn_disasm(insn, isPC); + } + if (insn.i.opcode == OP_COP1) { + return c1_insn_disasm(insn, isPC); + } + 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++) { From 49586a9807c659c97a2e4f501c0c60eb647ea952 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 15 Jun 2025 21:19:49 -0400 Subject: [PATCH 006/109] Add REGIMM branches --- src/game/insn_disasm.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 4a316e7f6e..69eea13ac8 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -160,6 +160,10 @@ InsnTemplate insn_db[] = { {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"}, {R_TYPE, PARAM_NONE, 0, 0b001001, "JALR"}, {R_TYPE, PARAM_NONE, 0, 0b001000, "JR"}, {R_TYPE, PARAM_TRAP, 0, 0b110100, "TEQ"}, @@ -210,16 +214,36 @@ char registerMapFloat[][5] = { "$F28", "$F29", "$F30", "$F31", }; -char *c0_insn_disasm(InsnData insn, u32 isPC) { +char *c0_insn_disasm(UNUSED InsnData insn, UNUSED u32 isPC) { return "COP0 UNIMPL"; } -char *c1_insn_disasm(InsnData insn, u32 isPC) { +char *c1_insn_disasm(UNUSED InsnData insn, UNUSED u32 isPC) { + // char *strp = &insn_as_string[0]; + // for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; return "COP1 UNIMPL"; } char *branch_insn_disasm(InsnData insn, u32 isPC) { - return "BRANCH UNIMPL"; + 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 = registerMaps[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, "%-8s %s %04X %s", insn_names[insn.b.sub], rs, offset, isPC ? "<-- CRASH" : ""); + + return insn_as_string; } char *insn_disasm(InsnData insn, u32 isPC) { @@ -227,10 +251,6 @@ char *insn_disasm(InsnData insn, u32 isPC) { int successful_print = 0; u32 target; - char IN[50]; - sprintf(IN, "%02x\n", insn.d >> 26); - osSyncPrintf(IN); - if (insn.d == 0) { // trivial case if (isPC) { return "NOP <-- CRASH"; From ab2599b93a3a3fd7622f7a99249afc03009d25e4 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 15 Jun 2025 21:50:40 -0400 Subject: [PATCH 007/109] Add n64sym from libdragon --- tools/.gitignore | 1 + tools/Makefile | 7 +- tools/common/binout.c | 289 ++++++ tools/common/binout.h | 83 ++ tools/common/polyfill.h | 254 +++++ tools/common/stb_ds.h | 1895 +++++++++++++++++++++++++++++++++++++ tools/common/subprocess.h | 1168 +++++++++++++++++++++++ tools/common/utils.h | 179 ++++ tools/n64sym.c | 409 ++++++++ 9 files changed, 4283 insertions(+), 2 deletions(-) create mode 100644 tools/common/binout.c create mode 100644 tools/common/binout.h create mode 100644 tools/common/polyfill.h create mode 100644 tools/common/stb_ds.h create mode 100644 tools/common/subprocess.h create mode 100644 tools/common/utils.h create mode 100644 tools/n64sym.c diff --git a/tools/.gitignore b/tools/.gitignore index 9161546014..7ae592f372 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -6,6 +6,7 @@ /lz4tpack /mio0 /n64cksum +/n64sym /n64graphics /n64graphics_ci /patch_elf_32bit diff --git a/tools/Makefile b/tools/Makefile index 2c485a16f1..86ed184db8 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -7,8 +7,7 @@ CC := gcc CXX := g++ CFLAGS := -I. -O2 -s LDFLAGS := -lm -ALL_PROGRAMS := armips filesizer lz4tpack rncpack n64graphics n64graphics_ci mio0 slienc n64cksum textconv aifc_decode aiff_extract_codebook vadpcm_enc tabledesign extract_data_for_mio skyconv -LIBAUDIOFILE := audiofile/libaudiofile.a +ALL_PROGRAMS := armips filesizer lz4tpack rncpack n64graphics n64graphics_ci n64sym mio0 slienc n64cksum textconv aifc_decode aiff_extract_codebook vadpcm_enc tabledesign extract_data_for_mio skyconv OS := $(shell uname) @@ -56,6 +55,10 @@ n64cksum_CFLAGS := -DN64CKSUM_STANDALONE textconv_SOURCES := textconv.c utf8.c hashtable.c +n64sym_SOURCES := n64sym.c + +patch_elf_32bit_SOURCES := patch_elf_32bit.c + aifc_decode_SOURCES := aifc_decode.c aiff_extract_codebook: $(LIBAUDIOFILE) diff --git a/tools/common/binout.c b/tools/common/binout.c new file mode 100644 index 0000000000..534666300a --- /dev/null +++ b/tools/common/binout.c @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include "binout.h" + +#define STBDS_NO_SHORT_NAMES +#define STB_DS_IMPLEMENTATION //Hack to get tools to compile +#include "stb_ds.h" + +struct placeholder_data { + int64_t offset; + uint64_t *pending_offsets_64; + uint32_t *pending_offsets_32; + uint16_t *pending_offsets_16; + uint8_t *pending_offsets_8; +}; + +struct { + char *key; + struct placeholder_data value; +} *placeholder_hash = NULL; + +void _w8(FILE *f, uint8_t v) +{ + fputc(v, f); +} + +void _w16(FILE *f, uint16_t v) +{ + w8(f, v >> 8); + w8(f, v & 0xff); +} + +void _w32(FILE *f, uint32_t v) +{ + w16(f, v >> 16); + w16(f, v & 0xffff); +} + +void _w64(FILE *f, uint64_t v) +{ + w32(f, v >> 32); + w32(f, v & 0xffffffff); +} + +int w32_placeholder(FILE *f) +{ + int pos = ftell(f); + w32(f, 0); + return pos; +} + +void _w64_at(FILE *f, int pos, uint64_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w64(f, v); + fseek(f, cur, SEEK_SET); +} + +void _w32_at(FILE *f, int pos, uint32_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w32(f, v); + fseek(f, cur, SEEK_SET); +} + +void _w16_at(FILE *f, int pos, uint16_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w16(f, v); + fseek(f, cur, SEEK_SET); +} + +void _w8_at(FILE *f, int pos, uint8_t v) +{ + int cur = ftell(f); + assert(cur >= 0); // fail on pipes + fseek(f, pos, SEEK_SET); + w8(f, v); + fseek(f, cur, SEEK_SET); +} + +void walign(FILE *f, int align) +{ + int pos = ftell(f); + assert(pos >= 0); // fail on pipes + while (pos++ % align) w8(f, 0); +} + +void wpad(FILE *f, int size) +{ + while (size--) { + w8(f, 0); + } +} + +struct placeholder_data *__placeholder_get_data(const char *name) +{ + if(placeholder_hash == NULL) { + stbds_sh_new_arena(placeholder_hash); + } + ptrdiff_t index = stbds_shgeti(placeholder_hash, name); + if(index == -1) { + struct placeholder_data default_value = {-1, NULL}; + index = stbds_shlen(placeholder_hash); + stbds_shput(placeholder_hash, name, default_value); + } + return &placeholder_hash[index].value; +} + +void __placeholder_make(FILE *file, int64_t offset, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + data->offset = offset; + for (int i=0; ipending_offsets_64); i++) { + w32_at(file, data->pending_offsets_64[i], data->offset); + } + for(int i=0; ipending_offsets_32); i++) { + w32_at(file, data->pending_offsets_32[i], data->offset); + } + for(int i=0; ipending_offsets_16); i++) { + w16_at(file, data->pending_offsets_16[i], data->offset); + } + for(int i=0; ipending_offsets_8); i++) { + w8_at(file, data->pending_offsets_8[i], data->offset); + } + stbds_arrfree(data->pending_offsets_64); + stbds_arrfree(data->pending_offsets_32); + stbds_arrfree(data->pending_offsets_16); + stbds_arrfree(data->pending_offsets_8); +} + +void placeholder_setv(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __placeholder_make(file, ftell(file), name); + free(name); +} + +void placeholder_set(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + placeholder_setv(file, format, args); + va_end(args); +} + +void placeholder_setv_offset(FILE *file, int64_t offset, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __placeholder_make(file, offset, name); + free(name); +} + +void placeholder_set_offset(FILE *file, int64_t offset, const char *format, ...) +{ + va_list args; + va_start(args, format); + placeholder_setv_offset(file, offset, format, args); + va_end(args); +} + +void __w64_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_64, ftell(file)); + w64(file, 0); + } else { + w64(file, data->offset); + } +} + +void __w32_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_32, ftell(file)); + w32(file, 0); + } else { + w32(file, data->offset); + } +} + +void __w16_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_16, ftell(file)); + w16(file, 0); + } else { + w16(file, data->offset); + } +} + +void __w8_placeholder_named(FILE *file, const char *name) +{ + struct placeholder_data *data = __placeholder_get_data(name); + if(data->offset == -1) { + stbds_arrpush(data->pending_offsets_8, ftell(file)); + w8(file, 0); + } else { + w8(file, data->offset); + } +} + +void w64_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w64_placeholder_named(file, name); + free(name); +} + +void w64_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w64_placeholdervf(file, format, args); + va_end(args); +} + +void w32_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w32_placeholder_named(file, name); + free(name); +} + +void w32_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w32_placeholdervf(file, format, args); + va_end(args); +} + +void w16_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w16_placeholder_named(file, name); + free(name); +} + +void w16_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w16_placeholdervf(file, format, args); + va_end(args); +} + +void w8_placeholdervf(FILE *file, const char *format, va_list arg) +{ + char *name = NULL; + vasprintf(&name, format, arg); + __w8_placeholder_named(file, name); + free(name); +} + +void w8_placeholderf(FILE *file, const char *format, ...) +{ + va_list args; + va_start(args, format); + w8_placeholdervf(file, format, args); + va_end(args); +} + +void placeholder_clear() +{ + for(int i=0; i +#include +#include +#include +#include + +#define BITCAST_F2I(f) ({ uint32_t __i; float __f = (f); memcpy(&__i, &(__f), 4); __i; }) + +#define _wconv(type, v) ({ \ + typeof(v) _v = (v); \ + if (sizeof(type) < sizeof(_v)) { \ + int64_t ext = (int64_t)_v >> (sizeof(type) * 8 - 1) >> 1; \ + if (ext != 0 && ext != (uint64_t)-1) { \ + fprintf(stderr, "fatal: truncating value %lld to %s (ext=%lld)\n", (long long)_v, #type, (long long)ext); \ + assert(ext == 0 || ext == (uint64_t)-1); \ + } \ + } \ + (type)_v; \ +}) + +#ifdef __cplusplus +extern "C" { +#endif + +void placeholder_setv(FILE *file, const char *format, va_list arg); +void placeholder_set(FILE *file, const char *format, ...); +void placeholder_setv_offset(FILE *file, int64_t offset, const char *format, va_list arg); +void placeholder_set_offset(FILE *file, int64_t offset, const char *format, ...); +void placeholder_clear(); + +void _w8(FILE *f, uint8_t v); +void _w16(FILE *f, uint16_t v); +void _w32(FILE *f, uint32_t v); +void _w64(FILE *f, uint64_t v); +#define w8(f, v) _w8(f, _wconv(uint8_t, v)) +#define w16(f, v) _w16(f, _wconv(uint16_t, v)) +#define w32(f, v) _w32(f, _wconv(uint32_t, v)) +#define w64(f, v) _w64(f, _wconv(uint64_t, v)) +#define wf32(f, v) _w32(f, BITCAST_F2I(v)) +#define wf32approx(f, v, prec) wf32(f, roundf((v)/(prec))*(prec)) +#define wa(f, v, s) fwrite(v, s, 1, f) + +int w64_placeholder(FILE *f); +void w64_placeholdervf(FILE *file, const char *format, va_list arg); +void w64_placeholderf(FILE *file, const char *format, ...); + +int w32_placeholder(FILE *f); +void w32_placeholdervf(FILE *file, const char *format, va_list arg); +void w32_placeholderf(FILE *file, const char *format, ...); + +int w16_placeholder(FILE *f); +void w16_placeholdervf(FILE *file, const char *format, va_list arg); +void w16_placeholderf(FILE *file, const char *format, ...); + +int w8_placeholder(FILE *f); +void w8_placeholdervf(FILE *file, const char *format, va_list arg); +void w8_placeholderf(FILE *file, const char *format, ...); + +void _w64_at(FILE *f, int pos, uint64_t v); +void _w32_at(FILE *f, int pos, uint32_t v); +void _w16_at(FILE *f, int pos, uint16_t v); +void _w8_at(FILE *f, int pos, uint8_t v); +#define w64_at(f, pos, v) _w64_at(f, pos, _wconv(uint64_t, v)) +#define w32_at(f, pos, v) _w32_at(f, pos, _wconv(uint32_t, v)) +#define w16_at(f, pos, v) _w16_at(f, pos, _wconv(uint16_t, v)) +#define w8_at(f, pos, v) _w8_at(f, pos, _wconv(uint8_t, v)) + +void walign(FILE *f, int align); +void wpad(FILE *f, int size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/common/polyfill.h b/tools/common/polyfill.h new file mode 100644 index 0000000000..543ba73cbe --- /dev/null +++ b/tools/common/polyfill.h @@ -0,0 +1,254 @@ +#ifndef LIBDRAGON_TOOLS_POLYFILL_H +#define LIBDRAGON_TOOLS_POLYFILL_H + +#ifdef __MINGW32__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// if typedef doesn't exist (msvc, blah) +typedef intptr_t ssize_t; + +/* Fetched from: https://stackoverflow.com/a/47229318 */ +/* The original code is public domain -- Will Hartung 4/9/09 */ +/* Modifications, public domain as well, by Antti Haapala, 11/10/17 + - Switched to getc on 5/23/19 */ + +ssize_t getline(char **lineptr, size_t *n, FILE *stream) { + size_t pos; + int c; + + if (lineptr == NULL || stream == NULL || n == NULL) { + errno = EINVAL; + return -1; + } + + c = getc(stream); + if (c == EOF) { + return -1; + } + + if (*lineptr == NULL) { + *lineptr = (char*)malloc(128); + if (*lineptr == NULL) { + return -1; + } + *n = 128; + } + + pos = 0; + while(c != EOF) { + if (pos + 1 >= *n) { + size_t new_size = *n + (*n >> 2); + if (new_size < 128) { + new_size = 128; + } + char *new_ptr = (char*)realloc(*lineptr, new_size); + if (new_ptr == NULL) { + return -1; + } + *n = new_size; + *lineptr = new_ptr; + } + + ((unsigned char *)(*lineptr))[pos ++] = c; + if (c == '\n') { + break; + } + c = getc(stream); + } + + (*lineptr)[pos] = '\0'; + return pos; +} + +/* This function is original code in libdragon */ +char *strndup(const char *s, size_t n) +{ + size_t len = strnlen(s, n); + char *ret = (char*)malloc(len + 1); + if (!ret) return NULL; + memcpy(ret, s, len); + ret[len] = '\0'; + return ret; +} + +// tmpfile in mingw is broken (it uses msvcrt that tries to +// create a file in C:\, which is non-writable nowadays) +#define tmpfile() mingw_tmpfile() + +typedef void* HANDLE; +typedef const char* LPCSTR; +typedef int BOOL; +#define INVALID_HANDLE_VALUE ((HANDLE)(long)-1) +struct _SECURITY_ATTRIBUTES; + +// Access rights +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 + +// Share modes +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 +#define FILE_SHARE_DELETE 0x00000004 + +// Creation disposition +#define CREATE_NEW 1 + +// Flags and attributes +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000 + +__declspec(dllimport) HANDLE __stdcall CreateFileA( + LPCSTR lpFileName, + unsigned long dwDesiredAccess, + unsigned long dwShareMode, + struct _SECURITY_ATTRIBUTES* lpSecurityAttributes, + unsigned long dwCreationDisposition, + unsigned long dwFlagsAndAttributes, + HANDLE hTemplateFile +); +__declspec(dllimport) int __stdcall CloseHandle(HANDLE); +__declspec(dllimport) unsigned long __stdcall GetLastError(void); +__declspec(dllimport) unsigned long __stdcall GetTickCount(void); + +FILE *mingw_tmpfile(void) { + static int counter = 0; + char path[260]; + + for (int i = 0; i < 4096; i++) { + // Generate a random filename. Notice we *purposedly* not make this + // very random with PID, timestamp, etc. because this is the third + // iteration of mingw_tmpfile(): the previous ones were misbehaving + // in various ways on various CRTs, Windows versions, etc. + // We want this code to be exercised often so that it is robust. + snprintf(path, sizeof(path), "mksprite-%04x.tmp", counter++); + + HANDLE h = CreateFileA( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + NULL + ); + + if (h != INVALID_HANDLE_VALUE) { + int fd = _open_osfhandle((intptr_t)h, _O_RDWR | _O_BINARY); + if (fd == -1) { + CloseHandle(h); + return NULL; + } + return fdopen(fd, "w+b"); + } + + // 80 = ERROR_FILE_EXISTS + if (GetLastError() != 80) + break; + } + + return NULL; +} +char* strcasestr(const char* haystack, const char* needle) +{ + size_t needle_len = strlen(needle); + size_t haystack_len = strlen(haystack); + size_t i; + + if (needle_len > haystack_len) + return NULL; + + for (i = 0; i <= haystack_len - needle_len; i++) + { + if (strncasecmp(haystack + i, needle, needle_len) == 0) + return (char*)(haystack + i); + } + + return NULL; +} + +// Implementation from FreeBSD +void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) +{ + char *cur, *last; + const char *cl = (const char *)l; + const char *cs = (const char *)s; + + /* we need something to compare */ + if (l_len == 0 || s_len == 0) + return NULL; + + /* "s" must be smaller or equal to "l" */ + if (l_len < s_len) + return NULL; + + /* special case where s_len == 1 */ + if (s_len == 1) + return memchr(l, (int)*cs, l_len); + + /* the last position where its possible to find "s" in "l" */ + last = (char *)cl + l_len - s_len; + + for (cur = (char *)cl; cur <= last; cur++) + if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) + return cur; + + return NULL; +} + +// rename() that ovewrites the destination file +#define rename(a,b) mingw_rename(a,b) + +#define MOVEFILE_REPLACE_EXISTING 0x00000001 +#define MOVEFILE_WRITE_THROUGH 0x00000008 + +__declspec(dllimport) int __stdcall MoveFileExA(const char *lpExistingFileName, + const char *lpNewFileName, + unsigned long dwFlags); +__declspec(dllimport) unsigned long __stdcall GetLastError(void); + +static void map_windows_error_to_errno(unsigned long err) { + switch (err) { + case 2: errno = ENOENT; break; // ERROR_FILE_NOT_FOUND + case 3: errno = ENOENT; break; // ERROR_PATH_NOT_FOUND + case 5: errno = EACCES; break; // ERROR_ACCESS_DENIED + case 32: errno = EBUSY; break; // ERROR_SHARING_VIOLATION + case 80: errno = EEXIST; break; // ERROR_FILE_EXISTS + case 183: errno = EEXIST; break; // ERROR_ALREADY_EXISTS + default: errno = EIO; break; // Generic error + } +} + +int mingw_rename(const char *oldpath, const char *newpath) { + if (MoveFileExA(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + return 0; + } else { + map_windows_error_to_errno(GetLastError()); + return -1; + } +} + +// POISX mkdir has a mode argument, but mingw's mkdir doesn't +#define mkdir(path, mode) mkdir(path) + +#ifdef __cplusplus +} +#endif + + +#endif + +#endif diff --git a/tools/common/stb_ds.h b/tools/common/stb_ds.h new file mode 100644 index 0000000000..e84c82d1d5 --- /dev/null +++ b/tools/common/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/tools/common/subprocess.h b/tools/common/subprocess.h new file mode 100644 index 0000000000..8fd51b643f --- /dev/null +++ b/tools/common/subprocess.h @@ -0,0 +1,1168 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/subprocess.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_SUBPROCESS_H_INCLUDED +#define SHEREDOM_SUBPROCESS_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push, 1) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) +#endif + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) +#define subprocess_pure +#define subprocess_weak __inline +#define subprocess_tls __declspec(thread) +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) +#define subprocess_tls __thread +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +struct subprocess_s; + +enum subprocess_option_e { + // stdout and stderr are the same FILE. + subprocess_option_combined_stdout_stderr = 0x1, + + // The child process should inherit the environment variables of the parent. + subprocess_option_inherit_environment = 0x2, + + // Enable asynchronous reading of stdout/stderr before it has completed. + subprocess_option_enable_async = 0x4, + + // Enable the child process to be spawned with no window visible if supported + // by the platform. + subprocess_option_no_window = 0x8, + + // Search for program names in the PATH variable. Always enabled on Windows. + // Note: this will **not** search for paths in any provided custom environment + // and instead uses the PATH of the spawning process. + subprocess_option_search_user_path = 0x10 +}; + +#if defined(__cplusplus) +extern "C" { +#endif + +/// @brief Create a process. +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// The memory backing this parameter only needs to persist until this function +/// returns. +/// @param options A bit field of subprocess_option_e's to pass. +/// @param out_process The newly created process. +/// @return On success zero is returned. +subprocess_weak int subprocess_create(const char *const command_line[], + int options, + struct subprocess_s *const out_process); + +/// @brief Create a process (extended create). +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// The memory backing this parameter only needs to persist until this function +/// returns. +/// @param options A bit field of subprocess_option_e's to pass. +/// @param environment An optional array of strings for the environment to use +/// for a child process (each element of the form FOO=BAR). The last element +/// must be NULL to signify the end of the array. +/// @param out_process The newly created process. +/// @return On success zero is returned. +/// +/// If `options` contains `subprocess_option_inherit_environment`, then +/// `environment` must be NULL. +subprocess_weak int +subprocess_create_ex(const char *const command_line[], int options, + const char *const environment[], + struct subprocess_s *const out_process); + +/// @brief Get the standard input file for a process. +/// @param process The process to query. +/// @return The file for standard input of the process. +/// +/// The file returned can be written to by the parent process to feed data to +/// the standard input of the process. +subprocess_pure subprocess_weak FILE * +subprocess_stdin(const struct subprocess_s *const process); + +/// @brief Get the standard output file for a process. +/// @param process The process to query. +/// @return The file for standard output of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard output of the child process. +subprocess_pure subprocess_weak FILE * +subprocess_stdout(const struct subprocess_s *const process); + +/// @brief Get the standard error file for a process. +/// @param process The process to query. +/// @return The file for standard error of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard error of the child process. +/// +/// If the process was created with the subprocess_option_combined_stdout_stderr +/// option bit set, this function will return NULL, and the subprocess_stdout +/// function should be used for both the standard output and error combined. +subprocess_pure subprocess_weak FILE * +subprocess_stderr(const struct subprocess_s *const process); + +/// @brief Wait for a process to finish execution. +/// @param process The process to wait for. +/// @param out_return_code The return code of the returned process (can be +/// NULL). +/// @return On success zero is returned. +/// +/// Joining a process will close the stdin pipe to the process. +subprocess_weak int subprocess_join(struct subprocess_s *const process, + int *const out_return_code); + +/// @brief Destroy a previously created process. +/// @param process The process to destroy. +/// @return On success zero is returned. +/// +/// If the process to be destroyed had not finished execution, it may out live +/// the parent process. +subprocess_weak int subprocess_destroy(struct subprocess_s *const process); + +/// @brief Terminate a previously created process. +/// @param process The process to terminate. +/// @return On success zero is returned. +/// +/// If the process to be destroyed had not finished execution, it will be +/// terminated (i.e killed). +subprocess_weak int subprocess_terminate(struct subprocess_s *const process); + +/// @brief Read the standard output from the child process. +/// @param process The process to read from. +/// @param buffer The buffer to read into. +/// @param size The maximum number of bytes to read. +/// @return The number of bytes actually read into buffer. Can only be 0 if the +/// process has complete. +/// +/// The only safe way to read from the standard output of a process during it's +/// execution is to use the `subprocess_option_enable_async` option in +/// conjuction with this method. +subprocess_weak unsigned +subprocess_read_stdout(struct subprocess_s *const process, char *const buffer, + unsigned size); + +/// @brief Read the standard error from the child process. +/// @param process The process to read from. +/// @param buffer The buffer to read into. +/// @param size The maximum number of bytes to read. +/// @return The number of bytes actually read into buffer. Can only be 0 if the +/// process has complete. +/// +/// The only safe way to read from the standard error of a process during it's +/// execution is to use the `subprocess_option_enable_async` option in +/// conjuction with this method. +subprocess_weak unsigned +subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, + unsigned size); + +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + +#if defined(__cplusplus) +#define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL +#else +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 +#endif + +#if !defined(_WIN32) +#include +#include +#include +#include +#include +#include +#endif + +#if defined(_WIN32) + +#if (_MSC_VER < 1920) +#ifdef _WIN64 +typedef __int64 subprocess_intptr_t; +typedef unsigned __int64 subprocess_size_t; +#else +typedef int subprocess_intptr_t; +typedef unsigned int subprocess_size_t; +#endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif + +typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; +typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; +typedef struct _STARTUPINFOA *LPSTARTUPINFOA; +typedef struct _OVERLAPPED *LPOVERLAPPED; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(_MSC_VER) +#pragma warning(push, 1) +#endif +struct subprocess_subprocess_information_s { + void *hProcess; + void *hThread; + unsigned long dwProcessId; + unsigned long dwThreadId; +}; + +struct subprocess_security_attributes_s { + unsigned long nLength; + void *lpSecurityDescriptor; + int bInheritHandle; +}; + +struct subprocess_startup_info_s { + unsigned long cb; + char *lpReserved; + char *lpDesktop; + char *lpTitle; + unsigned long dwX; + unsigned long dwY; + unsigned long dwXSize; + unsigned long dwYSize; + unsigned long dwXCountChars; + unsigned long dwYCountChars; + unsigned long dwFillAttribute; + unsigned long dwFlags; + unsigned short wShowWindow; + unsigned short cbReserved2; + unsigned char *lpReserved2; + void *hStdInput; + void *hStdOutput; + void *hStdError; +}; + +struct subprocess_overlapped_s { + uintptr_t Internal; + uintptr_t InternalHigh; + union { + struct { + unsigned long Offset; + unsigned long OffsetHigh; + } DUMMYSTRUCTNAME; + void *Pointer; + } DUMMYUNIONNAME; + + void *hEvent; +}; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +__declspec(dllimport) unsigned long __stdcall GetLastError(void); +__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long, + unsigned long); +__declspec(dllimport) int __stdcall CreatePipe(void **, void **, + LPSECURITY_ATTRIBUTES, + unsigned long); +__declspec(dllimport) void *__stdcall CreateNamedPipeA( + const char *, unsigned long, unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, LPSECURITY_ATTRIBUTES); +__declspec(dllimport) int __stdcall ReadFile(void *, void *, unsigned long, + unsigned long *, LPOVERLAPPED); +__declspec(dllimport) unsigned long __stdcall GetCurrentProcessId(void); +__declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +__declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long, + unsigned long, + LPSECURITY_ATTRIBUTES, + unsigned long, unsigned long, + void *); +__declspec(dllimport) void *__stdcall CreateEventA(LPSECURITY_ATTRIBUTES, int, + int, const char *); +__declspec(dllimport) int __stdcall CreateProcessA( + const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int, + unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION); +__declspec(dllimport) int __stdcall CloseHandle(void *); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject( + void *, unsigned long); +__declspec(dllimport) int __stdcall GetExitCodeProcess( + void *, unsigned long *lpExitCode); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); +__declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( + unsigned long, void *const *, int, unsigned long); +__declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, + unsigned long *, int); + +#if defined(_DLL) +#define SUBPROCESS_DLLIMPORT __declspec(dllimport) +#else +#define SUBPROCESS_DLLIMPORT +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif + +SUBPROCESS_DLLIMPORT int __cdecl _fileno(FILE *); +SUBPROCESS_DLLIMPORT int __cdecl _open_osfhandle(subprocess_intptr_t, int); +SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); + +#ifndef __MINGW32__ +void *__cdecl _alloca(subprocess_size_t); +#else +#include // alloca +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else +typedef size_t subprocess_size_t; +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +struct subprocess_s { + FILE *stdin_file; + FILE *stdout_file; + FILE *stderr_file; + +#if defined(_WIN32) + void *hProcess; + void *hStdInput; + void *hEventOutput; + void *hEventError; +#else + pid_t child; + int return_status; +#endif + + subprocess_size_t alive; +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(_WIN32) +subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr); +int subprocess_create_named_pipe_helper(void **rd, void **wr) { + const unsigned long pipeAccessInbound = 0x00000001; + const unsigned long fileFlagOverlapped = 0x40000000; + const unsigned long pipeTypeByte = 0x00000000; + const unsigned long pipeWait = 0x00000000; + const unsigned long genericWrite = 0x40000000; + const unsigned long openExisting = 3; + const unsigned long fileAttributeNormal = 0x00000080; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char name[256] = {0}; + static subprocess_tls long index = 0; + const long unique = index++; + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#pragma warning(push, 1) +#pragma warning(disable : 4996) + _snprintf(name, sizeof(name) - 1, + "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld", + GetCurrentProcessId(), GetCurrentThreadId(), unique); +#pragma warning(pop) +#else + snprintf(name, sizeof(name) - 1, + "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld", + GetCurrentProcessId(), GetCurrentThreadId(), unique); +#endif + + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, 0, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); + + if (invalidHandleValue == *rd) { + return -1; + } + + *wr = CreateFileA(name, genericWrite, 0, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); + + if (invalidHandleValue == *wr) { + return -1; + } + + return 0; +} +#endif + +int subprocess_create(const char *const commandLine[], int options, + struct subprocess_s *const out_process) { + return subprocess_create_ex(commandLine, options, SUBPROCESS_NULL, + out_process); +} + +int subprocess_create_ex(const char *const commandLine[], int options, + const char *const environment[], + struct subprocess_s *const out_process) { +#if defined(_WIN32) + int fd; + void *rd, *wr; + char *commandLineCombined; + subprocess_size_t len; + int i, j; + int need_quoting; + unsigned long flags = 0; + const unsigned long startFUseStdHandles = 0x00000100; + const unsigned long handleFlagInherit = 0x00000001; + const unsigned long createNoWindow = 0x08000000; + struct subprocess_subprocess_information_s processInfo; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *used_environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; + + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = startFUseStdHandles; + + if (subprocess_option_no_window == (options & subprocess_option_no_window)) { + flags |= createNoWindow; + } + + if (subprocess_option_inherit_environment != + (options & subprocess_option_inherit_environment)) { + if (SUBPROCESS_NULL == environment) { + used_environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); + } else { + // We always end with two null terminators. + len = 2; + + for (i = 0; environment[i]; i++) { + for (j = 0; '\0' != environment[i][j]; j++) { + len++; + } + + // For the null terminator too. + len++; + } + + used_environment = SUBPROCESS_CAST(char *, _alloca(len)); + + // Re-use len for the insertion position + len = 0; + + for (i = 0; environment[i]; i++) { + for (j = 0; '\0' != environment[i][j]; j++) { + used_environment[len++] = environment[i][j]; + } + + used_environment[len++] = '\0'; + } + + // End with the two null terminators. + used_environment[len++] = '\0'; + used_environment[len++] = '\0'; + } + } else { + if (SUBPROCESS_NULL != environment) { + return -1; + } + } + + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { + return -1; + } + + if (!SetHandleInformation(wr, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); + + if (-1 != fd) { + out_process->stdin_file = _fdopen(fd, "wb"); + + if (SUBPROCESS_NULL == out_process->stdin_file) { + return -1; + } + } + + startInfo.hStdInput = rd; + + if (options & subprocess_option_enable_async) { + if (subprocess_create_named_pipe_helper(&rd, &wr)) { + return -1; + } + } else { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { + return -1; + } + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); + + if (-1 != fd) { + out_process->stdout_file = _fdopen(fd, "rb"); + + if (SUBPROCESS_NULL == out_process->stdout_file) { + return -1; + } + } + + startInfo.hStdOutput = wr; + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + startInfo.hStdError = startInfo.hStdOutput; + } else { + if (options & subprocess_option_enable_async) { + if (subprocess_create_named_pipe_helper(&rd, &wr)) { + return -1; + } + } else { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { + return -1; + } + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); + + if (-1 != fd) { + out_process->stderr_file = _fdopen(fd, "rb"); + + if (SUBPROCESS_NULL == out_process->stderr_file) { + return -1; + } + } + + startInfo.hStdError = wr; + } + + if (options & subprocess_option_enable_async) { + out_process->hEventOutput = + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); + out_process->hEventError = + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); + } else { + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; + } + + // Combine commandLine together into a single string + len = 0; + for (i = 0; commandLine[i]; i++) { + // for the trailing \0 + len++; + + // Quote the argument if it has a space in it + if (strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL) + len += 2; + + for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } + len++; + } + } + + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); + + if (!commandLineCombined) { + return -1; + } + + // Gonna re-use len to store the write index into commandLineCombined + len = 0; + + for (i = 0; commandLine[i]; i++) { + if (0 != i) { + commandLineCombined[len++] = ' '; + } + + need_quoting = strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL; + if (need_quoting) { + commandLineCombined[len++] = '"'; + } + + for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + + commandLineCombined[len++] = commandLine[i][j]; + } + if (need_quoting) { + commandLineCombined[len++] = '"'; + } + } + + commandLineCombined[len] = '\0'; + + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + flags, // creation flags + used_environment, // used environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { + return -1; + } + + out_process->hProcess = processInfo.hProcess; + + out_process->hStdInput = startInfo.hStdInput; + + // We don't need the handle of the primary thread in the called process. + CloseHandle(processInfo.hThread); + + if (SUBPROCESS_NULL != startInfo.hStdOutput) { + CloseHandle(startInfo.hStdOutput); + + if (startInfo.hStdError != startInfo.hStdOutput) { + CloseHandle(startInfo.hStdError); + } + } + + out_process->alive = 1; + + return 0; +#else + int stdinfd[2]; + int stdoutfd[2]; + int stderrfd[2]; + pid_t child; + extern char **environ; + char *const empty_environment[1] = {SUBPROCESS_NULL}; + posix_spawn_file_actions_t actions; + char *const *used_environment; + + if (subprocess_option_inherit_environment == + (options & subprocess_option_inherit_environment)) { + if (SUBPROCESS_NULL != environment) { + return -1; + } + } + + if (0 != pipe(stdinfd)) { + return -1; + } + + if (0 != pipe(stdoutfd)) { + return -1; + } + + if (subprocess_option_combined_stdout_stderr != + (options & subprocess_option_combined_stdout_stderr)) { + if (0 != pipe(stderrfd)) { + return -1; + } + } + + if (environment) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + used_environment = (char *const *)environment; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } else if (subprocess_option_inherit_environment == + (options & subprocess_option_inherit_environment)) { + used_environment = environ; + } else { + used_environment = empty_environment; + } + + if (0 != posix_spawn_file_actions_init(&actions)) { + return -1; + } + + // Close the stdin write end + if (0 != posix_spawn_file_actions_addclose(&actions, stdinfd[1])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Map the read end to stdin + if (0 != + posix_spawn_file_actions_adddup2(&actions, stdinfd[0], STDIN_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Close the stdout read end + if (0 != posix_spawn_file_actions_addclose(&actions, stdoutfd[0])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Map the write end to stdout + if (0 != + posix_spawn_file_actions_adddup2(&actions, stdoutfd[1], STDOUT_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + if (0 != posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO, + STDERR_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } else { + // Close the stderr read end + if (0 != posix_spawn_file_actions_addclose(&actions, stderrfd[0])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + // Map the write end to stdout + if (0 != posix_spawn_file_actions_adddup2(&actions, stderrfd[1], + STDERR_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + if (subprocess_option_search_user_path == + (options & subprocess_option_search_user_path)) { + if (0 != posix_spawnp(&child, commandLine[0], &actions, SUBPROCESS_NULL, + (char *const *)commandLine, used_environment)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } else { + if (0 != posix_spawn(&child, commandLine[0], &actions, SUBPROCESS_NULL, + (char *const *)commandLine, used_environment)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + // Close the stdin read end + close(stdinfd[0]); + // Store the stdin write end + out_process->stdin_file = fdopen(stdinfd[1], "wb"); + + // Close the stdout write end + close(stdoutfd[1]); + // Store the stdout read end + out_process->stdout_file = fdopen(stdoutfd[0], "rb"); + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + } else { + // Close the stderr write end + close(stderrfd[1]); + // Store the stderr read end + out_process->stderr_file = fdopen(stderrfd[0], "rb"); + } + + // Store the child's pid + out_process->child = child; + + out_process->alive = 1; + + posix_spawn_file_actions_destroy(&actions); + return 0; +#endif +} + +FILE *subprocess_stdin(const struct subprocess_s *const process) { + return process->stdin_file; +} + +FILE *subprocess_stdout(const struct subprocess_s *const process) { + return process->stdout_file; +} + +FILE *subprocess_stderr(const struct subprocess_s *const process) { + if (process->stdout_file != process->stderr_file) { + return process->stderr_file; + } else { + return SUBPROCESS_NULL; + } +} + +int subprocess_join(struct subprocess_s *const process, + int *const out_return_code) { +#if defined(_WIN32) + const unsigned long infinite = 0xFFFFFFFF; + + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = SUBPROCESS_NULL; + } + + WaitForSingleObject(process->hProcess, infinite); + + if (out_return_code) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { + return -1; + } + } + + process->alive = 0; + + return 0; +#else + int status; + + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; + } + + if (out_return_code) { + *out_return_code = process->return_status; + } + + return 0; +#endif +} + +int subprocess_destroy(struct subprocess_s *const process) { + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->stdout_file) { + fclose(process->stdout_file); + + if (process->stdout_file != process->stderr_file) { + fclose(process->stderr_file); + } + + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; + } + +#if defined(_WIN32) + if (process->hProcess) { + CloseHandle(process->hProcess); + process->hProcess = SUBPROCESS_NULL; + + if (process->hStdInput) { + CloseHandle(process->hStdInput); + } + + if (process->hEventOutput) { + CloseHandle(process->hEventOutput); + } + + if (process->hEventError) { + CloseHandle(process->hEventError); + } + } +#endif + + return 0; +} + +int subprocess_terminate(struct subprocess_s *const process) { +#if defined(_WIN32) + unsigned int killed_process_exit_code; + int success_terminate; + int windows_call_result; + + killed_process_exit_code = 99; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; + return success_terminate; +#else + int result; + result = kill(process->child, 9); + return result; +#endif +} + +unsigned subprocess_read_stdout(struct subprocess_s *const process, + char *const buffer, unsigned size) { +#if defined(_WIN32) + void *handle; + unsigned long bytes_read = 0; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; + overlapped.hEvent = process->hEventOutput; + + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); + + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { + const unsigned long errorIoPending = 997; + unsigned long error = GetLastError(); + + // Means we've got an async read! + if (error == errorIoPending) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { + const unsigned long errorIoIncomplete = 996; + const unsigned long errorHandleEOF = 38; + error = GetLastError(); + + if ((error != errorIoIncomplete) && (error != errorHandleEOF)) { + return 0; + } + } + } + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#else + const int fd = fileno(process->stdout_file); + const ssize_t bytes_read = read(fd, buffer, size); + + if (bytes_read < 0) { + return 0; + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#endif +} + +unsigned subprocess_read_stderr(struct subprocess_s *const process, + char *const buffer, unsigned size) { +#if defined(_WIN32) + void *handle; + unsigned long bytes_read = 0; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; + overlapped.hEvent = process->hEventError; + + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); + + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { + const unsigned long errorIoPending = 997; + unsigned long error = GetLastError(); + + // Means we've got an async read! + if (error == errorIoPending) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { + const unsigned long errorIoIncomplete = 996; + const unsigned long errorHandleEOF = 38; + error = GetLastError(); + + if ((error != errorIoIncomplete) && (error != errorHandleEOF)) { + return 0; + } + } + } + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#else + const int fd = fileno(process->stderr_file); + const ssize_t bytes_read = read(fd, buffer, size); + + if (bytes_read < 0) { + return 0; + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#endif +} + +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_WIN32) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* SHEREDOM_SUBPROCESS_H_INCLUDED */ diff --git a/tools/common/utils.h b/tools/common/utils.h new file mode 100644 index 0000000000..6d1cd4b4cf --- /dev/null +++ b/tools/common/utils.h @@ -0,0 +1,179 @@ +#ifndef LIBDRAGON_TOOLS_UTILS_H +#define LIBDRAGON_TOOLS_UTILS_H + +#include "polyfill.h" +#include // memcpy +#include +#include +#include +#include +#include + +/** + * Misc utilities functions and macros. Internal header. + */ + +#define SWAP(a, b) ({ typeof(a) t = a; a = b; b = t; }) + +#define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; }) +#define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; }) +#define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max))) + +/** @brief Round n up to the next multiple of d */ +#define ROUND_UP(n, d) ({ \ + typeof(n) _n = n; typeof(d) _d = d; \ + (((_n) + (_d) - 1) / (_d) * (_d)); \ +}) + +/** @brief Round n down to the previous multiple of d */ +#define ROUND_DOWN(n, d) ({ \ + typeof(n) _n = n; typeof(d) _d = d; \ + ((_n >= 0) ? ((_n) / (_d) * (_d)) : -(((-_n + (_d) - 1) / (_d)) * (_d))); \ +}) + +/** @brief Return the ceil of n/d */ +#define DIVIDE_CEIL(n, d) ({ \ + typeof(n) _n = n; typeof(d) _d = d; \ + ((_n) + (_d) - 1) / (_d); \ +}) + +/** @brief Absolute number */ +#define ABS(x) ({ \ + typeof(x) _x = x; \ + (_x < 0 ? -_x : _x); \ +}) + +/** @brief Type-safe bitcast from float to integer */ +#define F2I(f) ({ uint32_t __i; memcpy(&__i, &(f), 4); __i; }) + +/** @brief Type-safe bitcast from integer to float */ +#define I2F(i) ({ float __f; memcpy(&__f, &(i), 4); __f; }) + +/** @brief Hint for the compiler that the condition is likely to happen */ +#define LIKELY(cond) __builtin_expect(!!(cond), 1) +/** @brief Hint for the compiler that the condition is unlikely to happen */ +#define UNLIKELY(cond) __builtin_expect(!!(cond), 0) + +/** @brief UTF-8 decoding */ +uint32_t __utf8_decode(const char **str); + +__attribute__((used)) +static char* path_remove_trailing_slash(char *path) +{ + path = strdup(path); + int n = strlen(path); + if (path[n-1] == '/' || path[n-1] == '\\') + path[n-1] = 0; + return path; +} + +__attribute__((used)) +static char *change_ext(const char *fn, const char *ext) +{ + char *out = strdup(fn); + char *dot = strrchr(out, '.'); + if (dot) *dot = 0; + strcat(out, ext); + return out; +} + +__attribute__((used)) +static bool file_exists(const char *filename) +{ + FILE *f = fopen(filename, "r"); + if (f) fclose(f); + return f != NULL; +} + +// Find the directory where the libdragon toolchain is installed. +// This is where you can find GCC, the linker, etc. +__attribute__((used)) +static const char *n64_toolchain_dir(void) +{ + static char *n64_inst = NULL; + if (n64_inst) + return n64_inst; + + // Find the toolchain installation directory. + // n64.mk supports having a separate installation for the toolchain and + // libdragon. So first check if N64_GCCPREFIX is set; if so the toolchain + // is there. Otherwise, fallback to N64_INST which is where we expect + // the toolchain to reside. + n64_inst = getenv("N64_GCCPREFIX"); + if (!n64_inst) + n64_inst = getenv("N64_INST"); + if (!n64_inst) + return NULL; + + // Remove the trailing backslash if any. On some system, running + // popen with a path containing double backslashes will fail, so + // we normalize it here. + n64_inst = path_remove_trailing_slash(n64_inst); + return n64_inst; +} + +// Find the directory where the libdragon tools are installed. +// This is where you can find mksprite, mkfont, etc. +__attribute__((used)) +static const char *n64_tools_dir(void) +{ + static char *n64_inst = NULL; + if (n64_inst) + return n64_inst; + + // Find the tools installation directory. + n64_inst = getenv("N64_INST"); + if (!n64_inst) + return NULL; + + // Remove the trailing backslash if any. On some system, running + // popen with a path containing double backslashes will fail, so + // we normalize it here. + n64_inst = path_remove_trailing_slash(n64_inst); + return n64_inst; +} + +__attribute__((used)) +static uint8_t* slurp(const char *fn, int *size) +{ + FILE *f = fopen(fn, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + int sz = ftell(f); + fseek(f, 0, SEEK_SET); + uint8_t *buf = (uint8_t*)malloc(sz); + fread(buf, 1, sz, f); + fclose(f); + if (size) *size = sz; + return buf; +} + +#ifdef __cplusplus +#include +__attribute__((used)) +static std::vector slurp(const char *fn) +{ + std::vector ret; + FILE *f = fopen(fn, "rb"); + if (!f) return ret; + fseek(f, 0, SEEK_END); + ret.resize(ftell(f)); + fseek(f, 0, SEEK_SET); + fread(&ret[0], 1, ret.size(), f); + fclose(f); + return ret; +} +#endif + +__attribute__((used)) +static void forward_to_stderr(FILE *log, const char *prefix) +{ + char *line = 0; size_t linesize = 0; + while (getline(&line, &linesize, log) != -1) { + fputs(prefix, stderr); + fputs(line, stderr); + } + free(line); +} + +#endif diff --git a/tools/n64sym.c b/tools/n64sym.c new file mode 100644 index 0000000000..9bb85f2642 --- /dev/null +++ b/tools/n64sym.c @@ -0,0 +1,409 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "common/subprocess.h" +#include "common/polyfill.h" +#include "common/utils.h" +#include "common/binout.h" + +#include "common/binout.c" + +bool flag_verbose = false; +int flag_max_sym_len = 64; +bool flag_inlines = true; +const char *n64_inst = NULL; + +// Printf if verbose +void verbose(const char *fmt, ...) { + if (flag_verbose) { + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + } +} + +void usage(const char *progname) +{ + fprintf(stderr, "%s - Prepare symbol table for N64 ROMs\n", progname); + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: %s [flags] []\n", progname); + fprintf(stderr, "\n"); + fprintf(stderr, "Command-line flags:\n"); + fprintf(stderr, " -v/--verbose Verbose output\n"); + fprintf(stderr, " -m/--max-len Maximum symbol length (default: 64)\n"); + fprintf(stderr, " --no-inlines Do not export inlined symbols\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "This program requires a libdragon toolchain installed in $N64_INST.\n"); +} + +char *stringtable = NULL; +struct { char *key; int value; } *string_hash = NULL; + +int stringtable_add(char *word) +{ + if (!string_hash) { + stbds_sh_new_arena(string_hash); + stbds_shdefault(string_hash, -1); + } + + int word_len = strlen(word); + if (stringtable) { + int pos = stbds_shget(string_hash, word); + if (pos >= 0) + return pos; + } + + // Append the word (without the trailing \0) + int idx = stbds_arraddnindex(stringtable, word_len); + memcpy(stringtable + idx, word, word_len); + + // Add all prefixes to the hash + for (int i = word_len; i >= 2; --i) { + char ch = word[i]; + word[i] = 0; + stbds_shput(string_hash, word, idx); + word[i] = ch; + } + return idx; +} + +struct symtable_s { + uint32_t uuid; + uint32_t addr; + char *func; + char *file; + int line; + + int func_sidx; + int file_sidx; + + int func_offset; + + bool is_func, is_inline; +} *symtable = NULL; + +void symbol_add(const char *elf, uint32_t addr, bool is_func) +{ + // We keep one addr2line process open for the last ELF file we processed. + // This allows to convert multiple symbols very fast, avoiding spawning a + // new process for each symbol. + // NOTE: we cannot use popen() here because on some platforms (eg. glibc) + // it only allows a single direction pipe, and we need both directions. + // So we rely on the subprocess library for this. + static char *addrbin = NULL; + static struct subprocess_s subp; + static FILE *addr2line_w = NULL, *addr2line_r = NULL; + static const char *cur_elf = NULL; + static char *line_buf = NULL; + static size_t line_buf_size = 0; + + // Check if this is a new ELF file (or it's the first time we run this function) + if (!cur_elf || strcmp(cur_elf, elf)) { + if (cur_elf) { + subprocess_terminate(&subp); + cur_elf = NULL; addr2line_r = addr2line_w = NULL; + } + if (!addrbin) + asprintf(&addrbin, "%s/bin/mips64-elf-addr2line", n64_inst); + + const char *cmd_addr[16] = {0}; int i = 0; + cmd_addr[i++] = addrbin; + cmd_addr[i++] = "--addresses"; + cmd_addr[i++] = "--functions"; + cmd_addr[i++] = "--demangle"; + if (flag_inlines) cmd_addr[i++] = "--inlines"; + cmd_addr[i++] = "--exe"; + cmd_addr[i++] = elf; + + if (subprocess_create(cmd_addr, subprocess_option_no_window, &subp) != 0) { + fprintf(stderr, "Error: cannot run: %s\n", addrbin); + exit(1); + } + addr2line_w = subprocess_stdin(&subp); + addr2line_r = subprocess_stdout(&subp); + cur_elf = elf; + } + + // Send the address to addr2line and fetch back the symbol and the function name + // Since we activated the "--inlines" option, addr2line produces an unknown number + // of output lines. This is a problem with pipes, as we don't know when to stop. + // Thus, we always add a dummy second address (0xffffffff) so that we stop when we see the + // reply for it. NOTE: we can't use 0x0 as dummy address as DSOs are partially + // linked so there are really symbols at 0. + fprintf(addr2line_w, "%08x\n0xffffffff\n", addr); + fflush(addr2line_w); + + // First line is the address. It's just an echo, so ignore it. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + assert(n >= 2 && strncmp(line_buf, "0x", 2) == 0); + + // Add one symbol for each inlined function + bool at_least_one = false; + while (1) { + // First line is the function name. If instead it's the dummy 0x0 address, + // it means that we're done. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + if (strncmp(line_buf, "0xffffffff", 10) == 0) break; + n--; + if (line_buf[n-1] == '\r') n--; // Remove trailing \r (Windows) + + // If the function of name is longer than 64 bytes, truncate it. This also + // avoid paradoxically long function names like in C++ that can even be + // several thousands of characters long. + char *func = strndup(line_buf, MIN(n, flag_max_sym_len)); + if (n > flag_max_sym_len) strcpy(&func[flag_max_sym_len-3], "..."); + + // Second line is the file name and line number + int ret = getline(&line_buf, &line_buf_size, addr2line_r); + assert(ret != -1); + char *colon = strrchr(line_buf, ':'); + char *file = strndup(line_buf, colon - line_buf); + int line = atoi(colon + 1); + + // Add the callsite to the list + stbds_arrput(symtable, ((struct symtable_s) { + .uuid = stbds_arrlen(symtable), + .addr = addr, + .func = func, + .file = file, + .line = line, + .is_func = is_func, + .is_inline = true, + })); + at_least_one = true; + } + assert(at_least_one); + symtable[stbds_arrlen(symtable)-1].is_inline = false; + + // Read and skip the two remaining lines (function and file position) + // that refers to the dummy 0x0 address + getline(&line_buf, &line_buf_size, addr2line_r); + getline(&line_buf, &line_buf_size, addr2line_r); +} + +bool elf_find_callsites(const char *elf) +{ + // Start objdump to parse the disassembly of the ELF file + char *cmd = NULL; + asprintf(&cmd, "%s/bin/mips64-elf-objdump -d %s", n64_inst, elf); + verbose("Running: %s\n", cmd); + FILE *disasm = popen(cmd, "r"); + if (!disasm) { + fprintf(stderr, "Error: cannot run: %s\n", cmd); + exit(1); + } + + // Parse the disassembly + char *line = NULL; size_t line_size = 0; + while (getline(&line, &line_size, disasm) != -1) { + // Find the functions + if (strstr(line, ">:")) { + uint32_t addr = strtoul(line, NULL, 16); + symbol_add(elf, addr, true); + } + // Find the callsites + if (strstr(line, "\tjal\t") || strstr(line, "\tjalr\t") || strstr(line, "\tsyscall")) { + uint32_t addr = strtoul(line, NULL, 16); + symbol_add(elf, addr, false); + } + } + free(line); + free(cmd); + int status = pclose(disasm); +#ifdef __MINGW32__ + return status == 0; +#else + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +#endif +} + +void compute_function_offsets(void) +{ + uint32_t func_addr = 0; + for (int i=0; iis_func) { + func_addr = s->addr; + s->func_offset = 0; + } else { + s->func_offset = s->addr - func_addr; + } + } +} + +int symtable_sort_by_addr(const void *a, const void *b) +{ + const struct symtable_s *sa = a; + const struct symtable_s *sb = b; + // In case the address match, it means that there are multiple + // inlines at this address. Sort by insertion order (aka stable sort) + // so that we preserve the inline order. + if (sa->addr != sb->addr) + return sa->addr - sb->addr; + return sa->uuid - sb->uuid; +} + +int symtable_sort_by_func(const void *a, const void *b) +{ + const struct symtable_s *sa = a; + const struct symtable_s *sb = b; + int sa_len = sa->func ? strlen(sa->func) : 0; + int sb_len = sb->func ? strlen(sb->func) : 0; + return sb_len - sa_len; +} + +void process(const char *infn, const char *outfn) +{ + verbose("Processing: %s -> %s\n", infn, outfn); + + // First, find all functions and call sites. We do this by disassembling + // the ELF file and grepping it. + if (!elf_find_callsites(infn)) { + fprintf(stderr, "Error: objdump failed\n"); + exit(1); + } + verbose("Found %d callsites\n", stbds_arrlen(symtable)); + + // Sort the symbole table by symbol length. We want longer symbols + // to go in first, so that shorter symbols can be found as substrings. + // We sort by function name rather than file name, because we expect + // substrings to match more in functions. + verbose("Sorting symbol table...\n"); + qsort(symtable, stbds_arrlen(symtable), sizeof(struct symtable_s), symtable_sort_by_func); + + // Go through the symbol table and build the string table + verbose("Creating string table...\n"); + for (int i=0; i < stbds_arrlen(symtable); i++) { + if (i % 5000 == 0) + verbose(" %d/%d\n", i, stbds_arrlen(symtable)); + struct symtable_s *sym = &symtable[i]; + if (sym->func) + sym->func_sidx = stringtable_add(sym->func); + else + sym->func_sidx = -1; + if (sym->file) + sym->file_sidx = stringtable_add(sym->file); + else + sym->file_sidx = -1; + } + + // Sort the symbol table by address + qsort(symtable, stbds_arrlen(symtable), sizeof(struct symtable_s), symtable_sort_by_addr); + + // Fill in the function offset field in the entries in the symbol table. + verbose("Computing function offsets...\n"); + compute_function_offsets(); + + // Write the symbol table to file + verbose("Writing %s\n", outfn); + FILE *out = fopen(outfn, "wb"); + if (!out) { + fprintf(stderr, "Cannot create file: symtable.bin\n"); + exit(1); + } + + // Write header. See symtable_header_t in backtrace.c for the layout. + fwrite("SYMT", 4, 1, out); + w32(out, 2); // Version + int addrtable_off = w32_placeholder(out); + w32(out, stbds_arrlen(symtable)); + int symtable_off = w32_placeholder(out); + w32(out, stbds_arrlen(symtable)); + int stringtable_off = w32_placeholder(out); + w32(out, stbds_arrlen(stringtable)); + + // Write address table. This is a sequence of 32-bit addresses. + walign(out, 16); + w32_at(out, addrtable_off, ftell(out)); + for (int i=0; i < stbds_arrlen(symtable); i++) { + struct symtable_s *sym = &symtable[i]; + w32(out, sym->addr | (sym->is_func ? 0x1 : 0) | (sym->is_inline ? 0x2 : 0)); + } + + // Write symbol table. See symtable_entry_t in backtrace.c for the layout. + walign(out, 16); + w32_at(out, symtable_off, ftell(out)); + for (int i=0; i < stbds_arrlen(symtable); i++) { + struct symtable_s *sym = &symtable[i]; + w32(out, sym->func_sidx); + w32(out, sym->file_sidx); + w16(out, strlen(sym->func)); + w16(out, strlen(sym->file)); + w16(out, (uint16_t)(sym->line < 65536 ? sym->line : 0)); + w16(out, (uint16_t)(sym->func_offset < 0x10000 ? sym->func_offset : 0)); + } + + walign(out, 16); + w32_at(out, stringtable_off, ftell(out)); + fwrite(stringtable, stbds_arrlen(stringtable), 1, out); + fclose(out); +} + +int main(int argc, char *argv[]) +{ + const char *outfn = NULL; + + int i; + for (i = 1; i < argc && argv[i][0] == '-'; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + usage(argv[0]); + return 0; + } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) { + flag_verbose = true; + } else if (!strcmp(argv[i], "--no-inlines")) { + flag_inlines = false; + } else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + outfn = argv[i]; + } else if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--max-len")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + flag_max_sym_len = atoi(argv[i]); + } else { + fprintf(stderr, "invalid flag: %s\n", argv[i]); + return 1; + } + } + + if (i == argc) { + fprintf(stderr, "missing input filename\n"); + return 1; + } + + // Find n64 installation directory + n64_inst = n64_toolchain_dir(); + if (!n64_inst) { + // Do not mention N64_GCCPREFIX in the error message, since it is + // a seldom used configuration. + fprintf(stderr, "Error: N64_INST environment variable not set\n"); + return 1; + } + + const char *infn = argv[i]; + if (i < argc-1) + outfn = argv[i+1]; + else + outfn = change_ext(infn, ".sym"); + + // Check that infn exists and is readable + FILE *in = fopen(infn, "rb"); + if (!in) { + fprintf(stderr, "Error: cannot open file: %s\n", infn); + return 1; + } + fclose(in); + + process(infn, outfn); + return 0; +} + From e898278bbd3d2d534ff1aad678f479b29b444a56 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 16 Jun 2025 20:54:00 -0400 Subject: [PATCH 008/109] turn magic numbers into defines for draw_glyph --- src/game/crash_screen.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index bf0bb88e0c..b99bc5a397 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -38,8 +38,11 @@ u8 gCrashScreenCharToGlyph[128] = { 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, }; +#define GLYPH_HEIGHT 7 +#define GLYPH_WIDTH 5 +#define FONT_ROWS 9 // A height of seven pixels for each Character * nine rows of characters + one row unused. -u32 gCrashScreenFont[7 * 9 + 1] = { +u32 gCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; @@ -113,18 +116,18 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { u32 rowMask; s32 i, j; - data = &gCrashScreenFont[glyph / 5 * 7]; + data = &gCrashScreenFont[glyph / GLYPH_WIDTH * GLYPH_HEIGHT]; ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - for (i = 0; i < 7; i++) { - bit = 0x80000000U >> ((glyph % 5) * 6); + for (i = 0; i < GLYPH_HEIGHT; i++) { + bit = 0x80000000U >> ((glyph % (GLYPH_WIDTH)) * (GLYPH_WIDTH + 1)); rowMask = *data++; - for (j = 0; j < 6; j++) { + for (j = 0; j < (GLYPH_WIDTH + 1); j++) { *ptr++ = (bit & rowMask) ? 0xffff : 1; bit >>= 1; } - ptr += gCrashScreen.width - 6; + ptr += gCrashScreen.width - (GLYPH_WIDTH + 1); } } From a7648c84a98f8c96e218a5d421710bf375c37d44 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 16 Jun 2025 21:58:44 -0400 Subject: [PATCH 009/109] Replace crash screen font; reorganize crash screen to use more of the given space --- src/game/crash_screen.c | 143 ++++--- textures/crash_custom/LICENSE | 385 ++---------------- .../crash_custom/crash_screen_font.ia1.png | Bin 739 -> 637 bytes 3 files changed, 103 insertions(+), 425 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index b99bc5a397..a7c40e281a 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -16,7 +16,12 @@ #include "printf.h" +// 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_CONTEXT, @@ -29,20 +34,28 @@ enum crashPages { PAGE_COUNT }; +char *crashPageNames[] = { + [PAGE_CONTEXT] = "(Context)", +#ifdef PUPPYPRINT_DEBUG + [PAGE_LOG] = "(Log)", +#endif + [PAGE_STACKTRACE] = "(Stack Trace)", + [PAGE_DISASM] = "(Disassembly)", + [PAGE_ASSERTS] = "(Assert)", +}; + u8 gCrashScreenCharToGlyph[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, -1, 41, -1, -1, -1, 43, -1, -1, 37, 38, -1, 42, - -1, 39, 44, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 36, -1, -1, -1, -1, 40, -1, 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, -1, -1, -1, -1, -1, -1, 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, -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, -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, }; -#define GLYPH_HEIGHT 7 -#define GLYPH_WIDTH 5 -#define FONT_ROWS 9 -// A height of seven pixels for each Character * nine rows of characters + one row unused. -u32 gCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS + 1] = { +u32 gCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; @@ -101,8 +114,7 @@ void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { - // 0xe738 = 0b1110011100111000 - *ptr = ((*ptr & 0xe738) >> 2) | 1; + *ptr = 0x0001; ptr++; } ptr += gCrashScreen.width - w; @@ -116,18 +128,22 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { u32 rowMask; s32 i, j; - data = &gCrashScreenFont[glyph / GLYPH_WIDTH * GLYPH_HEIGHT]; + if (glyph > 0x7F) return; + + data = &gCrashScreenFont[((glyph&0xF)*GLYPH_HEIGHT * 2) + (glyph >= 64)]; + ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; for (i = 0; i < GLYPH_HEIGHT; i++) { - bit = 0x80000000U >> ((glyph % (GLYPH_WIDTH)) * (GLYPH_WIDTH + 1)); + bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); rowMask = *data++; + data ++; - for (j = 0; j < (GLYPH_WIDTH + 1); j++) { - *ptr++ = (bit & rowMask) ? 0xffff : 1; + for (j = 0; j < (GLYPH_WIDTH); j++) { + *ptr++ = (bit & rowMask) ? 0xFFFF : 1; bit >>= 1; } - ptr += gCrashScreen.width - (GLYPH_WIDTH + 1); + ptr += gCrashScreen.width - (GLYPH_WIDTH); } } @@ -235,43 +251,43 @@ void crash_screen_print_fpcsr(u32 fpcsr) { void draw_crash_context(OSThread *thread, s32 cause) { __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 20, "THREAD:%d (%s)", thread->id, gCauseDesc[cause]); - crash_screen_print(30, 30, "PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); + 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(); if ((u32)parse_map != MAP_PARSER_ADDRESS) { char *fname = parse_map(tc->pc); - crash_screen_print(30, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); + crash_screen_print(LEFT_MARGIN, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); } - crash_screen_print(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); - crash_screen_print(30, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); - crash_screen_print(30, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); - crash_screen_print(30, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); - crash_screen_print(30, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); - crash_screen_print(30, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); - crash_screen_print(30, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); - crash_screen_print(30, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); - crash_screen_print(30, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); - crash_screen_print(30, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); + crash_screen_print(LEFT_MARGIN, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); + crash_screen_print(LEFT_MARGIN, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); + crash_screen_print(LEFT_MARGIN, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); + crash_screen_print(LEFT_MARGIN, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); + crash_screen_print(LEFT_MARGIN, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); + crash_screen_print(LEFT_MARGIN, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); + crash_screen_print(LEFT_MARGIN, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); + crash_screen_print(LEFT_MARGIN, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); + crash_screen_print(LEFT_MARGIN, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); + crash_screen_print(LEFT_MARGIN, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); crash_screen_print_fpcsr(tc->fpcsr); osWritebackDCacheAll(); - crash_screen_print_float_reg( 30, 170, 0, &tc->fp0.f.f_even); - crash_screen_print_float_reg(120, 170, 2, &tc->fp2.f.f_even); - crash_screen_print_float_reg(210, 170, 4, &tc->fp4.f.f_even); - crash_screen_print_float_reg( 30, 180, 6, &tc->fp6.f.f_even); - crash_screen_print_float_reg(120, 180, 8, &tc->fp8.f.f_even); - crash_screen_print_float_reg(210, 180, 10, &tc->fp10.f.f_even); - crash_screen_print_float_reg( 30, 190, 12, &tc->fp12.f.f_even); - crash_screen_print_float_reg(120, 190, 14, &tc->fp14.f.f_even); - crash_screen_print_float_reg(210, 190, 16, &tc->fp16.f.f_even); - crash_screen_print_float_reg( 30, 200, 18, &tc->fp18.f.f_even); - crash_screen_print_float_reg(120, 200, 20, &tc->fp20.f.f_even); - crash_screen_print_float_reg(210, 200, 22, &tc->fp22.f.f_even); - crash_screen_print_float_reg( 30, 210, 24, &tc->fp24.f.f_even); - crash_screen_print_float_reg(120, 210, 26, &tc->fp26.f.f_even); - crash_screen_print_float_reg(210, 210, 28, &tc->fp28.f.f_even); - crash_screen_print_float_reg( 30, 220, 30, &tc->fp30.f.f_even); + 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); } @@ -296,18 +312,18 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { u32 temp_sp = (tc->sp + 0x14); crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 25, "STACK TRACE FROM %08X:", temp_sp); + crash_screen_print(LEFT_MARGIN, 25, "STACK TRACE FROM %08X:", temp_sp); if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(30, 35, "CURRFUNC: NONE"); + crash_screen_print(LEFT_MARGIN, 35, "CURRFUNC: NONE"); } else { - crash_screen_print(30, 35, "CURRFUNC: %s", parse_map(tc->pc)); + crash_screen_print(LEFT_MARGIN, 35, "CURRFUNC: %s", parse_map(tc->pc)); } osWritebackDCacheAll(); for (int i = 0; i < 18; i++) { if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(30, (45 + (i * 10)), "STACK TRACE DISABLED"); + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "STACK TRACE DISABLED"); break; } else { if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { @@ -316,9 +332,9 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { char *fname = find_function_in_stack(&temp_sp); if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - crash_screen_print(30, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); } else { - crash_screen_print(30, (45 + (i * 10)), "%08X: %s", temp_sp, fname); + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X: %s", temp_sp, fname); } } } @@ -334,7 +350,7 @@ void draw_disasm(OSThread *thread) { if (sProgramPosition == 0) { sProgramPosition = (tc->pc - 36); } - crash_screen_print(30, 25, "DISASM %08X", sProgramPosition); + crash_screen_print(LEFT_MARGIN, 25, "DISASM %08X", sProgramPosition); osWritebackDCacheAll(); @@ -342,7 +358,7 @@ void draw_disasm(OSThread *thread) { u32 addr = (sProgramPosition + (i * 4)); u32 toDisasm = *(u32*)(addr); - crash_screen_print(30, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); + crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); } osWritebackDCacheAll(); @@ -351,15 +367,14 @@ void draw_disasm(OSThread *thread) { void draw_assert(UNUSED OSThread *thread) { crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 25, "ASSERT PAGE"); + crash_screen_print(LEFT_MARGIN, 25, "ASSERT PAGE"); if (__n64Assert_Filename != NULL) { - crash_screen_print(30, 45, "FILE: %s", __n64Assert_Filename); - crash_screen_print(30, 55, "LINE: %d", __n64Assert_LineNum); - crash_screen_print(30, 75, "MESSAGE:", __n64Assert_Message); - crash_screen_print_with_newlines(36, 85, 30, __n64Assert_Message); + crash_screen_print(LEFT_MARGIN, 35, "FILE: %s LINE %d", __n64Assert_Filename, __n64Assert_LineNum); + crash_screen_print(LEFT_MARGIN, 55, "MESSAGE:"); + crash_screen_print(LEFT_MARGIN, 70, " %s", __n64Assert_Message); } else { - crash_screen_print(30, 45, "No failed assert to report."); + crash_screen_print(LEFT_MARGIN, 35, "no failed assert to report."); } osWritebackDCacheAll(); @@ -400,8 +415,8 @@ void draw_crash_screen(OSThread *thread) { crashPage = (PAGE_COUNT - 1); } if (updateBuffer) { - crash_screen_draw_rect(25, 8, 270, 12); - crash_screen_print(30, 10, "Page:%02d L/Z: Left R: Right", crashPage); + crash_screen_draw_rect(0, 0, 320, 20); + crash_screen_print(LEFT_MARGIN, 5, "Page:%02d %-14s L/Z: Left R: Right", crashPage, crashPageNames[crashPage]); switch (crashPage) { case PAGE_CONTEXT: draw_crash_context(thread, cause); break; #ifdef PUPPYPRINT_DEBUG diff --git a/textures/crash_custom/LICENSE b/textures/crash_custom/LICENSE index 08baf3098a..514e0d287f 100644 --- a/textures/crash_custom/LICENSE +++ b/textures/crash_custom/LICENSE @@ -1,361 +1,24 @@ -These textures were adapted from http://uzebox.org/wiki/File:Font6x7.png for use with ultrasm64 - -Creative Commons Legal Code - -Attribution-ShareAlike 3.0 Unported - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR - DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - - a. "Adaptation" means a work based upon the Work, or upon the Work and - other pre-existing works, such as a translation, adaptation, - derivative work, arrangement of music or other alterations of a - literary or artistic work, or phonogram or performance and includes - cinematographic adaptations or any other form in which the Work may be - recast, transformed, or adapted including in any form recognizably - derived from the original, except that a work that constitutes a - Collection will not be considered an Adaptation for the purpose of - this License. For the avoidance of doubt, where the Work is a musical - work, performance or phonogram, the synchronization of the Work in - timed-relation with a moving image ("synching") will be considered an - Adaptation for the purpose of this License. - b. "Collection" means a collection of literary or artistic works, such as - encyclopedias and anthologies, or performances, phonograms or - broadcasts, or other works or subject matter other than works listed - in Section 1(f) below, which, by reason of the selection and - arrangement of their contents, constitute intellectual creations, in - which the Work is included in its entirety in unmodified form along - with one or more other contributions, each constituting separate and - independent works in themselves, which together are assembled into a - collective whole. A work that constitutes a Collection will not be - considered an Adaptation (as defined below) for the purposes of this - License. - c. "Creative Commons Compatible License" means a license that is listed - at https://creativecommons.org/compatiblelicenses that has been - approved by Creative Commons as being essentially equivalent to this - License, including, at a minimum, because that license: (i) contains - terms that have the same purpose, meaning and effect as the License - Elements of this License; and, (ii) explicitly permits the relicensing - of adaptations of works made available under that license under this - License or a Creative Commons jurisdiction license with the same - License Elements as this License. - d. "Distribute" means to make available to the public the original and - copies of the Work or Adaptation, as appropriate, through sale or - other transfer of ownership. - e. "License Elements" means the following high-level license attributes - as selected by Licensor and indicated in the title of this License: - Attribution, ShareAlike. - f. "Licensor" means the individual, individuals, entity or entities that - offer(s) the Work under the terms of this License. - g. "Original Author" means, in the case of a literary or artistic work, - the individual, individuals, entity or entities who created the Work - or if no individual or entity can be identified, the publisher; and in - addition (i) in the case of a performance the actors, singers, - musicians, dancers, and other persons who act, sing, deliver, declaim, - play in, interpret or otherwise perform literary or artistic works or - expressions of folklore; (ii) in the case of a phonogram the producer - being the person or legal entity who first fixes the sounds of a - performance or other sounds; and, (iii) in the case of broadcasts, the - organization that transmits the broadcast. - h. "Work" means the literary and/or artistic work offered under the terms - of this License including without limitation any production in the - literary, scientific and artistic domain, whatever may be the mode or - form of its expression including digital form, such as a book, - pamphlet and other writing; a lecture, address, sermon or other work - of the same nature; a dramatic or dramatico-musical work; a - choreographic work or entertainment in dumb show; a musical - composition with or without words; a cinematographic work to which are - assimilated works expressed by a process analogous to cinematography; - a work of drawing, painting, architecture, sculpture, engraving or - lithography; a photographic work to which are assimilated works - expressed by a process analogous to photography; a work of applied - art; an illustration, map, plan, sketch or three-dimensional work - relative to geography, topography, architecture or science; a - performance; a broadcast; a phonogram; a compilation of data to the - extent it is protected as a copyrightable work; or a work performed by - a variety or circus performer to the extent it is not otherwise - considered a literary or artistic work. - i. "You" means an individual or entity exercising rights under this - License who has not previously violated the terms of this License with - respect to the Work, or who has received express permission from the - Licensor to exercise rights under this License despite a previous - violation. - j. "Publicly Perform" means to perform public recitations of the Work and - to communicate to the public those public recitations, by any means or - process, including by wire or wireless means or public digital - performances; to make available to the public Works in such a way that - members of the public may access these Works from a place and at a - place individually chosen by them; to perform the Work to the public - by any means or process and the communication to the public of the - performances of the Work, including by public digital performance; to - broadcast and rebroadcast the Work by any means including signs, - sounds or images. - k. "Reproduce" means to make copies of the Work by any means including - without limitation by sound or visual recordings and the right of - fixation and reproducing fixations of the Work, including storage of a - protected performance or phonogram in digital form or other electronic - medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - - a. to Reproduce the Work, to incorporate the Work into one or more - Collections, and to Reproduce the Work as incorporated in the - Collections; - b. to create and Reproduce Adaptations provided that any such Adaptation, - including any translation in any medium, takes reasonable steps to - clearly label, demarcate or otherwise identify that changes were made - to the original Work. For example, a translation could be marked "The - original work was translated from English to Spanish," or a - modification could indicate "The original work has been modified."; - c. to Distribute and Publicly Perform the Work including as incorporated - in Collections; and, - d. to Distribute and Publicly Perform Adaptations. - e. For the avoidance of doubt: - - i. Non-waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme cannot be waived, the Licensor - reserves the exclusive right to collect such royalties for any - exercise by You of the rights granted under this License; - ii. Waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme can be waived, the Licensor waives the - exclusive right to collect such royalties for any exercise by You - of the rights granted under this License; and, - iii. Voluntary License Schemes. The Licensor waives the right to - collect royalties, whether individually or, in the event that the - Licensor is a member of a collecting society that administers - voluntary licensing schemes, via that society, from any exercise - by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - - a. You may Distribute or Publicly Perform the Work only under the terms - of this License. You must include a copy of, or the Uniform Resource - Identifier (URI) for, this License with every copy of the Work You - Distribute or Publicly Perform. You may not offer or impose any terms - on the Work that restrict the terms of this License or the ability of - the recipient of the Work to exercise the rights granted to that - recipient under the terms of the License. You may not sublicense the - Work. You must keep intact all notices that refer to this License and - to the disclaimer of warranties with every copy of the Work You - Distribute or Publicly Perform. When You Distribute or Publicly - Perform the Work, You may not impose any effective technological - measures on the Work that restrict the ability of a recipient of the - Work from You to exercise the rights granted to that recipient under - the terms of the License. This Section 4(a) applies to the Work as - incorporated in a Collection, but this does not require the Collection - apart from the Work itself to be made subject to the terms of this - License. If You create a Collection, upon notice from any Licensor You - must, to the extent practicable, remove from the Collection any credit - as required by Section 4(c), as requested. If You create an - Adaptation, upon notice from any Licensor You must, to the extent - practicable, remove from the Adaptation any credit as required by - Section 4(c), as requested. - b. You may Distribute or Publicly Perform an Adaptation only under the - terms of: (i) this License; (ii) a later version of this License with - the same License Elements as this License; (iii) a Creative Commons - jurisdiction license (either this or a later license version) that - contains the same License Elements as this License (e.g., - Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible - License. If you license the Adaptation under one of the licenses - mentioned in (iv), you must comply with the terms of that license. If - you license the Adaptation under the terms of any of the licenses - mentioned in (i), (ii) or (iii) (the "Applicable License"), you must - comply with the terms of the Applicable License generally and the - following provisions: (I) You must include a copy of, or the URI for, - the Applicable License with every copy of each Adaptation You - Distribute or Publicly Perform; (II) You may not offer or impose any - terms on the Adaptation that restrict the terms of the Applicable - License or the ability of the recipient of the Adaptation to exercise - the rights granted to that recipient under the terms of the Applicable - License; (III) You must keep intact all notices that refer to the - Applicable License and to the disclaimer of warranties with every copy - of the Work as included in the Adaptation You Distribute or Publicly - Perform; (IV) when You Distribute or Publicly Perform the Adaptation, - You may not impose any effective technological measures on the - Adaptation that restrict the ability of a recipient of the Adaptation - from You to exercise the rights granted to that recipient under the - terms of the Applicable License. This Section 4(b) applies to the - Adaptation as incorporated in a Collection, but this does not require - the Collection apart from the Adaptation itself to be made subject to - the terms of the Applicable License. - c. If You Distribute, or Publicly Perform the Work or any Adaptations or - Collections, You must, unless a request has been made pursuant to - Section 4(a), keep intact all copyright notices for the Work and - provide, reasonable to the medium or means You are utilizing: (i) the - name of the Original Author (or pseudonym, if applicable) if supplied, - and/or if the Original Author and/or Licensor designate another party - or parties (e.g., a sponsor institute, publishing entity, journal) for - attribution ("Attribution Parties") in Licensor's copyright notice, - terms of service or by other reasonable means, the name of such party - or parties; (ii) the title of the Work if supplied; (iii) to the - extent reasonably practicable, the URI, if any, that Licensor - specifies to be associated with the Work, unless such URI does not - refer to the copyright notice or licensing information for the Work; - and (iv) , consistent with Ssection 3(b), in the case of an - Adaptation, a credit identifying the use of the Work in the Adaptation - (e.g., "French translation of the Work by Original Author," or - "Screenplay based on original Work by Original Author"). The credit - required by this Section 4(c) may be implemented in any reasonable - manner; provided, however, that in the case of a Adaptation or - Collection, at a minimum such credit will appear, if a credit for all - contributing authors of the Adaptation or Collection appears, then as - part of these credits and in a manner at least as prominent as the - credits for the other contributing authors. For the avoidance of - doubt, You may only use the credit required by this Section for the - purpose of attribution in the manner set out above and, by exercising - Your rights under this License, You may not implicitly or explicitly - assert or imply any connection with, sponsorship or endorsement by the - Original Author, Licensor and/or Attribution Parties, as appropriate, - of You or Your use of the Work, without the separate, express prior - written permission of the Original Author, Licensor and/or Attribution - Parties. - d. Except as otherwise agreed in writing by the Licensor or as may be - otherwise permitted by applicable law, if You Reproduce, Distribute or - Publicly Perform the Work either by itself or as part of any - Adaptations or Collections, You must not distort, mutilate, modify or - take other derogatory action in relation to the Work which would be - prejudicial to the Original Author's honor or reputation. Licensor - agrees that in those jurisdictions (e.g. Japan), in which any exercise - of the right granted in Section 3(b) of this License (the right to - make Adaptations) would be deemed to be a distortion, mutilation, - modification or other derogatory action prejudicial to the Original - Author's honor and reputation, the Licensor will waive or not assert, - as appropriate, this Section, to the fullest extent permitted by the - applicable national law, to enable You to reasonably exercise Your - right under Section 3(b) of this License (right to make Adaptations) - but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate - automatically upon any breach by You of the terms of this License. - Individuals or entities who have received Adaptations or Collections - from You under this License, however, will not have their licenses - terminated provided such individuals or entities remain in full - compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will - survive any termination of this License. - b. Subject to the above terms and conditions, the license granted here is - perpetual (for the duration of the applicable copyright in the Work). - Notwithstanding the above, Licensor reserves the right to release the - Work under different license terms or to stop distributing the Work at - any time; provided, however that any such election will not serve to - withdraw this License (or any other license that has been, or is - required to be, granted under the terms of this License), and this - License will continue in full force and effect unless terminated as - stated above. - -8. Miscellaneous - - a. Each time You Distribute or Publicly Perform the Work or a Collection, - the Licensor offers to the recipient a license to the Work on the same - terms and conditions as the license granted to You under this License. - b. Each time You Distribute or Publicly Perform an Adaptation, Licensor - offers to the recipient a license to the original Work on the same - terms and conditions as the license granted to You under this License. - c. If any provision of this License is invalid or unenforceable under - applicable law, it shall not affect the validity or enforceability of - the remainder of the terms of this License, and without further action - by the parties to this agreement, such provision shall be reformed to - the minimum extent necessary to make such provision valid and - enforceable. - d. No term or provision of this License shall be deemed waived and no - breach consented to unless such waiver or consent shall be in writing - and signed by the party to be charged with such waiver or consent. - e. This License constitutes the entire agreement between the parties with - respect to the Work licensed here. There are no understandings, - agreements or representations with respect to the Work not specified - here. Licensor shall not be bound by any additional provisions that - may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - f. The rights granted under, and the subject matter referenced, in this - License were drafted utilizing the terminology of the Berne Convention - for the Protection of Literary and Artistic Works (as amended on - September 28, 1979), the Rome Convention of 1961, the WIPO Copyright - Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 - and the Universal Copyright Convention (as revised on July 24, 1971). - These rights and subject matter take effect in the relevant - jurisdiction in which the License terms are sought to be enforced - according to the corresponding provisions of the implementation of - those treaty provisions in the applicable national law. If the - standard suite of rights granted under applicable copyright law - includes additional rights not granted under this License, such - additional rights are deemed to be included in the License; this - License is not intended to restrict the license of any rights under - applicable law. - - -Creative Commons Notice - - Creative Commons is not a party to this License, and makes no warranty - whatsoever in connection with the Work. Creative Commons will not be - liable to You or any party on any legal theory for any damages - whatsoever, including without limitation any general, special, - incidental or consequential damages arising in connection to this - license. Notwithstanding the foregoing two (2) sentences, if Creative - Commons has expressly identified itself as the Licensor hereunder, it - shall have all rights and obligations of Licensor. - - Except for the limited purpose of indicating to the public that the - Work is licensed under the CCPL, Creative Commons does not authorize - the use by either party of the trademark "Creative Commons" or any - related trademark or logo of Creative Commons without the prior - written consent of Creative Commons. Any permitted use will be in - compliance with Creative Commons' then-current trademark usage - guidelines, as may be published on its website or otherwise made - available upon request from time to time. For the avoidance of doubt, - this trademark restriction does not form part of the License. - - Creative Commons may be contacted at https://creativecommons.org/. +These textures were adapted from https://tobiasjung.name/profont/ for use with HackerSM64 + +ProFont +MIT License + +Copyright (c) 2014 Carl Osterwald, Stephen C. Gilardi, Andrew Welch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/textures/crash_custom/crash_screen_font.ia1.png b/textures/crash_custom/crash_screen_font.ia1.png index 3a9a899e721f22af060ffbc5e9f3cd64fbb2f950..7191dfcdceb642fc60437c6a0fd159a1ae3069e0 100644 GIT binary patch literal 637 zcmV-@0)qXCP)?DTaw*0nZC^`?4jbCkaKb3yS|VDlL02um~Qj{`vw50@V>Bl7EVckRUe>QA;9@0p2~Pkn!&f1 z8@Ol71T00$Sz0=+%QKfthfoS%Rl=Ud0kv@XM;AMyh}zdE-7TI7d(HJgD4CnblSFkX zDZZ^rWjEoY!2k@U`u;5+7zEA>L+Y7vW8m)jRSj}5z~2SZ&)YoD*q>UpolK-~Da6-zX6Vs`|h85HCe{{O7Sh*MIwx%Ujo`+WCvVch}&`)?{0)Znoc($h*2NiAn z{FwRHMamt*DYHNuzX5Ls$Z5>ioIc#w3R4U3N36k8q%W+%i+QBy9wVN-%6q45WFs0cY4cRQC$!XrA{GBahm_>F~G44xeJ*?kH)Dr??6P(&8PA z8H)xhP*4a4;HyUvskVcr^c=|!GI%qn8RR-fmmy23?MU+ozK5B|-AutZ7TH4K zYC{|{U*vb{PwuK0BkS$H!hmS-jLm+%U59S|yn<&Ps4yg8NPshl?au+fbVAPk{i5&z X0wM7=v)(gp00000NkvXXu0mjfO!^^! literal 739 zcmV<90v!E`P)< zVEF%^0SFiw8NlHGe*_C5j;@A*0j3J79cIk`{}81NDB`GMARbJFfq{tu1;EWk*ns3Z zkWz#TAsSg2@BolK5F=5&fUE_N0myd2jA3L%coynfY<`9~6=4s$8bk;I0~0kC5Kct| z6C*}Uf%L#aiwV;v6vu!LG>L2Jbobd{YT2LSfU-_d;C#{ zY#73~Opp}M$cQT}5Mhm74O(XUk5af`4^>n(EXWxH7Pshm9@QY!_y-3fBO~r~hwvJ9 zV{v)`=3_+sphhXowMc=7nr&hBL1@gdfx8De^l?Qadagz0Ghqv9Vsj!$FAHY901`%c zhfoG#LC=eUerVn3n#b%kP-=d@S#R2s*|B!z?RA& zwxatGq!N^JVNuQmiBoJggFJ+u`Y;2G3AvKM9iF(eJ2*2DE(<`Bja?NJB-!9H0@+R2 z0~i_$F#o~~!kw7WqnZ&_%YS%@L`WQ-f^p?QVsj=*Ng12RuvV^+phI{EcM@knEmC1= z51B^K5e#U0FuZ`A{$Muajbv0wNb3XU0)%b<|0B0cQ1p-*3&`q07EoNE5M=-)EU;HB zcnv`K6%n);hN1ci5qvNfL>@U(;K`~CL;#pG z(P?Z62X9p3GXUZaJkf})euO9ok&uYRo|zdi&Bh+`cpZ;BpzwtciY{W}8(9OgdH`cz V4R>ZL?%4nU002ovPDHLkV1jl3A^ZRU From c2d8188ca6c4c176b85f8cc04b3d1cb6d30e5c2c Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 16 Jun 2025 22:13:20 -0400 Subject: [PATCH 010/109] move elements around; add some lowercase instructions --- src/game/crash_screen.c | 31 ++++++++++++++++++------------- src/game/insn_disasm.c | 10 +++++----- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index a7c40e281a..9bf9a5f13f 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -257,18 +257,23 @@ void draw_crash_context(OSThread *thread, s32 cause) { osWritebackDCacheAll(); if ((u32)parse_map != MAP_PARSER_ADDRESS) { char *fname = parse_map(tc->pc); - crash_screen_print(LEFT_MARGIN, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); + crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); + } + 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); + if ((u32)parse_map != MAP_PARSER_ADDRESS) { + char *fname = parse_map(tc->ra); + crash_screen_print(LEFT_MARGIN, 142, "S8:%08XH RA:%08XH (%s)", (u32) tc->s8, (u32) tc->ra, fname); + } else { + crash_screen_print(LEFT_MARGIN, 142, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); } - crash_screen_print(LEFT_MARGIN, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); - crash_screen_print(LEFT_MARGIN, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); - crash_screen_print(LEFT_MARGIN, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); - crash_screen_print(LEFT_MARGIN, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); - crash_screen_print(LEFT_MARGIN, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); - crash_screen_print(LEFT_MARGIN, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); - crash_screen_print(LEFT_MARGIN, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); - crash_screen_print(LEFT_MARGIN, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); - crash_screen_print(LEFT_MARGIN, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); - crash_screen_print(LEFT_MARGIN, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); crash_screen_print_fpcsr(tc->fpcsr); osWritebackDCacheAll(); @@ -332,7 +337,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { char *fname = find_function_in_stack(&temp_sp); if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X: Unknown", temp_sp); } else { crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X: %s", temp_sp, fname); } @@ -416,7 +421,7 @@ void draw_crash_screen(OSThread *thread) { } if (updateBuffer) { crash_screen_draw_rect(0, 0, 320, 20); - crash_screen_print(LEFT_MARGIN, 5, "Page:%02d %-14s L/Z: Left R: Right", crashPage, crashPageNames[crashPage]); + crash_screen_print(LEFT_MARGIN, 5, "Page:%02d %-22s L/Z: Left R: Right", crashPage, crashPageNames[crashPage]); switch (crashPage) { case PAGE_CONTEXT: draw_crash_context(thread, cause); break; #ifdef PUPPYPRINT_DEBUG diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 69eea13ac8..fb768f20ab 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -10,8 +10,8 @@ enum InsnTypes { I_TYPE, J_TYPE, // BRANCH, - // COP0, - // COP1, + COP0, + COP1, }; enum ParamTypes { @@ -253,9 +253,9 @@ char *insn_disasm(InsnData insn, u32 isPC) { if (insn.d == 0) { // trivial case if (isPC) { - return "NOP <-- CRASH"; + return "nop <-- CRASH"; } else { - return "NOP"; + return "nop"; } } @@ -344,7 +344,7 @@ char *insn_disasm(InsnData insn, u32 isPC) { case PARAM_EMUX: target = (insn.d >> 6) & 0x3FF; if (insn.i.rs == insn.i.rt) { - strp += sprintf(strp, "EMUX %s 0x%02X", + strp += sprintf(strp, "emux %s 0x%02X", registerMaps[insn.i.rs], target ); From e706faa7bc1618e71092142a641d4ce83d3d3979 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 16 Jun 2025 23:03:28 -0400 Subject: [PATCH 011/109] COP1 disasm from libdragon --- src/game/crash_screen.c | 11 +- src/game/insn_disasm.c | 393 ++++++++++++++++++++++++---------------- src/game/mario.c | 8 - 3 files changed, 242 insertions(+), 170 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index 9bf9a5f13f..bca96434a8 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -140,7 +140,11 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { data ++; for (j = 0; j < (GLYPH_WIDTH); j++) { - *ptr++ = (bit & rowMask) ? 0xFFFF : 1; + // *ptr++ = (bit & rowMask) ? 0xFFFF : 1; + if (bit & rowMask) { + *ptr = 0xFFFF; + } + ptr++; bit >>= 1; } ptr += gCrashScreen.width - (GLYPH_WIDTH); @@ -349,9 +353,8 @@ extern char *insn_disasm(u32 insn, u32 isPC); static u32 sProgramPosition = 0; void draw_disasm(OSThread *thread) { __OSThreadContext *tc = &thread->context; - // u32 insn = *(u32*)tc->pc; - crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_draw_rect(0, 20, 320, 240); if (sProgramPosition == 0) { sProgramPosition = (tc->pc - 36); } @@ -363,7 +366,7 @@ void draw_disasm(OSThread *thread) { u32 addr = (sProgramPosition + (i * 4)); u32 toDisasm = *(u32*)(addr); - crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); + crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%s", insn_disasm(addr, (addr == tc->pc))); } osWritebackDCacheAll(); diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index fb768f20ab..1393f333f9 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -9,7 +9,6 @@ enum InsnTypes { R_TYPE, I_TYPE, J_TYPE, - // BRANCH, COP0, COP1, }; @@ -92,161 +91,237 @@ typedef struct PACKED { InsnTemplate insn_db[] = { // arithmetic - {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"}, - - {I_TYPE, PARAM_NONE, 0b001010, 0, "SLTI"}, - {I_TYPE, PARAM_NONE, 0b001011, 0, "SLTIU"}, + {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"}, + + {I_TYPE, PARAM_NONE, 0b001010, 0, "slti"}, + {I_TYPE, PARAM_NONE, 0b001011, 0, "sltiu"}, // bitwise ops - {R_TYPE, PARAM_NONE, 0, 0b100100, "AND"}, - {I_TYPE, PARAM_NONE, 0b001100, 0, "ANDI"}, - {R_TYPE, PARAM_NONE, 0, 0b100101, "OR"}, - {I_TYPE, PARAM_NONE, 0b001101, 0, "ORI"}, - {R_TYPE, PARAM_NONE, 0, 0b100110, "XOR"}, - {I_TYPE, PARAM_NONE, 0b001110, 0, "XORI"}, - {R_TYPE, PARAM_BITSHIFT, 0, 0b100110, "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, 0b100100, "and"}, + {I_TYPE, PARAM_NONE, 0b001100, 0, "andi"}, + {R_TYPE, PARAM_NONE, 0, 0b100101, "or"}, + {I_TYPE, PARAM_NONE, 0b001101, 0, "ori"}, + {R_TYPE, PARAM_NONE, 0, 0b100110, "xor"}, + {I_TYPE, PARAM_NONE, 0b001110, 0, "xori"}, + {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"}, // 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"}, + {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"}, + {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"}, + {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"}, - {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"}, + {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"}, + {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"}, // jal (special) - {J_TYPE, PARAM_JAL, 0b000011, 0, "JAL"}, - {J_TYPE, PARAM_JUMP, 0b000010, 0, "J"}, + {J_TYPE, PARAM_JAL, 0b000011, 0, "jal"}, + {J_TYPE, PARAM_JUMP, 0b000010, 0, "j"}, // instructions involving doubles (deprioritized on the list) - {R_TYPE, PARAM_NONE, 0, 0b101101, "DADDU"}, - {I_TYPE, PARAM_SWAP_RS_IMM, 0b011000, 0, "DADDI"}, - {I_TYPE, PARAM_SWAP_RS_IMM, 0b011001, 0, "DADDIU"}, - {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"}, + {R_TYPE, PARAM_NONE, 0, 0b101101, "daddu"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011000, 0, "daddi"}, + {I_TYPE, PARAM_SWAP_RS_IMM, 0b011001, 0, "daddiu"}, + {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 registerMaps[][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", +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 registerMapFloat[][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", +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", }; -char *c0_insn_disasm(UNUSED InsnData insn, UNUSED u32 isPC) { - return "COP0 UNIMPL"; +char* __symbolize(void *vaddr, char *buf, int size) { + return parse_map((u32)vaddr); } -char *c1_insn_disasm(UNUSED InsnData insn, UNUSED u32 isPC) { - // char *strp = &insn_as_string[0]; - // for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; - return "COP1 UNIMPL"; +// Last Resort C0/C1 disassembler, from libdragon +static void c1_disasm(u32 *ptr, char *out, int n) { + 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) { + /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x <%s>", opn+1, tgt26, __symbolize((void*)tgt26, symbuf, sizeof(symbuf))); break; + /* 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, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x <%s>", opn+1, rs, rt, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf))); break; + /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x <%s>", opn+1, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf))); 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, u32 isPC) { + char *strp = &insn_as_string[0]; + + c1_disasm((u32 *)pc, insn_as_string, 100); + + return insn_as_string; } char *branch_insn_disasm(InsnData insn, u32 isPC) { static char *insn_names[] = { - [0b00001] = "BGEZ", - [0b00011] = "BGEZL", - [0b10001] = "BGEZAL", - [0b10011] = "BGEZALL", - [0b00000] = "BLTZ", - [0b00010] = "BLTZL", - [0b10000] = "BLTZAL", - [0b10010] = "BLTZALL", + [0b00001] = "bgez", + [0b00011] = "bgezl", + [0b10001] = "bgezal", + [0b10011] = "bgezall", + [0b00000] = "bltz", + [0b00010] = "bltzl", + [0b10000] = "bltzal", + [0b10010] = "bltzall", }; char *strp = &insn_as_string[0]; - char *rs = registerMaps[insn.b.rs]; + 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, "%-8s %s %04X %s", insn_names[insn.b.sub], rs, offset, isPC ? "<-- CRASH" : ""); + sprintf(strp, "%-9s %s %04X %s", insn_names[insn.b.sub], rs, offset, isPC ? "<-- CRASH" : ""); return insn_as_string; } -char *insn_disasm(InsnData insn, u32 isPC) { +char *insn_disasm(InsnData *addr, u32 isPC) { + InsnData insn = *addr; char *strp = &insn_as_string[0]; int successful_print = 0; u32 target; @@ -263,10 +338,10 @@ char *insn_disasm(InsnData insn, u32 isPC) { return branch_insn_disasm(insn, isPC); } if (insn.i.opcode == OP_COP0) { - return c0_insn_disasm(insn, isPC); + return "cop0 (UNIMPL)"; } if (insn.i.opcode == OP_COP1) { - return c1_insn_disasm(insn, isPC); + return cop1_insn_disasm(addr, isPC); } for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; @@ -275,97 +350,99 @@ char *insn_disasm(InsnData insn, u32 isPC) { 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, "%-8s %s %s %04X", insn_db[i].name, - registerMaps[insn.i.rt], - registerMaps[insn.i.rs], + 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, "%-8s %s %04X", insn_db[i].name, - registerMaps[insn.i.rt], + 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); if ((u32)parse_map != MAP_PARSER_ADDRESS) { - strp += sprintf(strp, "%-8s %s", insn_db[i].name, + strp += sprintf(strp, "%-9s %s", insn_db[i].name, parse_map(target) ); } else { - strp += sprintf(strp, "%-8s %08X", insn_db[i].name, + strp += sprintf(strp, "%-9s %08X", insn_db[i].name, target ); } break; case PARAM_JUMP: target = 0x80000000 | (insn.d & 0x0FFFFFFF); - strp += sprintf(strp, "%-8s %08X", insn_db[i].name, + strp += sprintf(strp, "%-9s %08X", insn_db[i].name, target ); break; case PARAM_FLOAT_RT: - strp += sprintf(strp, "%-8s %s, %04X (%s)", insn_db[i].name, - registerMapFloat[insn.i.rt], + strp += sprintf(strp, "%-9s %s, %04X (%s)", insn_db[i].name, + __mips_fpreg[insn.i.rt], insn.i.immediate, - registerMaps[insn.i.rs] + __mips_gpr[insn.i.rs] ); break; case PARAM_NONE: - strp += sprintf(strp, "%-8s %s %04X (%s)", insn_db[i].name, - registerMaps[insn.i.rt], + strp += sprintf(strp, "%-9s %s %04X (%s)", insn_db[i].name, + __mips_gpr[insn.i.rt], insn.i.immediate, - registerMaps[insn.i.rs] + __mips_gpr[insn.i.rs] ); break; } successful_print = 1; break; - } else if (insn.i.rdata.function != 0 && insn.i.rdata.function == insn_db[i].function) { + } else if ( (insn.i.rdata.function == 0 && insn.i.opcode == 0) + || (insn.i.rdata.function != 0 && insn.i.rdata.function == insn_db[i].function) + ) { switch (insn_db[i].arbitraryParam) { case PARAM_BITSHIFT: - strp += sprintf(strp, "%-8s %s %s %04X", insn_db[i].name, - registerMaps[insn.i.rdata.rd], - registerMaps[insn.i.rt], + 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, "%-8s %s %s %s", insn_db[i].name, - registerMaps[insn.i.rdata.rd], - registerMaps[insn.i.rt], - registerMaps[insn.i.rs] + 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, "%-8s %s", insn_db[i].name, - registerMaps[insn.i.rdata.rd] + 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, "emux %s 0x%02X", - registerMaps[insn.i.rs], + strp += sprintf(strp, "%-9s %s 0x%02X", "emux", + __mips_gpr[insn.i.rs], target ); } else { - strp += sprintf(strp, "%-8s %s %s", insn_db[i].name, - registerMaps[insn.i.rs], - registerMaps[insn.i.rt] + 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, "%-8s %s %s", insn_db[i].name, - registerMaps[insn.i.rs], - registerMaps[insn.i.rt] + strp += sprintf(strp, "%-9s %s %s", insn_db[i].name, + __mips_gpr[insn.i.rs], + __mips_gpr[insn.i.rt] ); break; case PARAM_NONE: - strp += sprintf(strp, "%-8s %s %s %s", insn_db[i].name, - registerMaps[insn.i.rdata.rd], - registerMaps[insn.i.rs], - registerMaps[insn.i.rt] + 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; diff --git a/src/game/mario.c b/src/game/mario.c index 8db0eb846c..af26781131 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -1710,14 +1710,6 @@ s32 execute_mario_action(UNUSED struct Object *obj) { vec3f_get_lateral_dist(gMarioState->prevPos, gMarioState->pos, &gMarioState->lateralSpeed); vec3f_copy(gMarioState->prevPos, gMarioState->pos); - if (gPlayer1Controller->buttonPressed & L_TRIG) { - if (gMarioState->pos[1] == 65526.0f) { - return 0; - } else { - *(vs8*)0=0; - } - } - if (gMarioState->action) { #ifdef ENABLE_DEBUG_FREE_MOVE if ( From 4aeca68fef1d75f54bc904c698c976704c4b1824 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 16 Jun 2025 23:21:15 -0400 Subject: [PATCH 012/109] add jal address to disasm; off-by-one error in map parsing if the address matched exactly --- src/game/insn_disasm.c | 4 ++-- src/game/map_parser.c | 5 ++++- tools/mapPacker.py | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 1393f333f9..8a7cd29af0 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -364,8 +364,8 @@ char *insn_disasm(InsnData *addr, u32 isPC) { case PARAM_JAL: target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); if ((u32)parse_map != MAP_PARSER_ADDRESS) { - strp += sprintf(strp, "%-9s %s", insn_db[i].name, - parse_map(target) + strp += sprintf(strp, "%-9s %s(%08X)", insn_db[i].name, + parse_map(target), target ); } else { strp += sprintf(strp, "%-9s %08X", insn_db[i].name, diff --git a/src/game/map_parser.c b/src/game/map_parser.c index dc07a0d21f..0d9b646abf 100644 --- a/src/game/map_parser.c +++ b/src/game/map_parser.c @@ -45,7 +45,10 @@ char *parse_map(u32 pc) { u32 i; for (i = 0; i < gMapEntrySize; i++) { - if (gMapEntries[i].addr >= pc) break; + if (gMapEntries[i].addr == pc) { + return (char*) ((u32)gMapStrings + gMapEntries[i].nm_offset); + } + else if (gMapEntries[i].addr > pc) break; } if (i == gMapEntrySize - 1) { diff --git a/tools/mapPacker.py b/tools/mapPacker.py index 4ac4b66930..4225db7374 100644 --- a/tools/mapPacker.py +++ b/tools/mapPacker.py @@ -26,8 +26,11 @@ def __repr__(self): tokens = line.split() if len(tokens) >= 3 and len(tokens[-2]) == 1: addr = int(tokens[0], 16) - if addr & 0x80000000 and tokens[-2].lower() == "t": - symNames.append(MapEntry(tokens[-1], addr)) + if addr & 0x80000000: + if (tokens[-2].lower() == "t"): + if (".part." not in tokens[-1]): + if (".constprop." not in tokens[-1]): + symNames.append(MapEntry(tokens[-1], addr)) From 28bc67d77fdf120a3dd3ccf5bbe17037ccde713c Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 16 Jun 2025 23:34:27 -0400 Subject: [PATCH 013/109] give ra's funcname more space; actually draw rectangles on all pages --- src/game/crash_screen.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index bca96434a8..c0bbc3bab0 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -243,10 +243,10 @@ void crash_screen_print_fpcsr(u32 fpcsr) { s32 i; u32 bit = BIT(17); - crash_screen_print(30, 155, "FPCSR:%08XH", fpcsr); + crash_screen_print(100, 220, "FPCSR:%08XH", fpcsr); for (i = 0; i < 6; i++) { if (fpcsr & bit) { - crash_screen_print(132, 155, "(%s)", gFpcsrDesc[i]); + crash_screen_print(222, 220, "(%s)", gFpcsrDesc[i]); return; } bit >>= 1; @@ -272,12 +272,12 @@ void draw_crash_context(OSThread *thread, s32 cause) { 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); if ((u32)parse_map != MAP_PARSER_ADDRESS) { char *fname = parse_map(tc->ra); - crash_screen_print(LEFT_MARGIN, 142, "S8:%08XH RA:%08XH (%s)", (u32) tc->s8, (u32) tc->ra, fname); - } else { - crash_screen_print(LEFT_MARGIN, 142, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); + crash_screen_print(LEFT_MARGIN, 152, "RA at: %s", fname); } + crash_screen_print_fpcsr(tc->fpcsr); osWritebackDCacheAll(); @@ -303,11 +303,11 @@ void draw_crash_context(OSThread *thread, s32 cause) { #ifdef PUPPYPRINT_DEBUG void draw_crash_log(void) { s32 i; - crash_screen_draw_rect(25, 20, 270, 210); + 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(30, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); + crash_screen_print(LEFT_MARGIN, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); } #undef LINE_HEIGHT } @@ -320,7 +320,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; u32 temp_sp = (tc->sp + 0x14); - crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_draw_rect(0, 20, 320, 210); crash_screen_print(LEFT_MARGIN, 25, "STACK TRACE FROM %08X:", temp_sp); if ((u32) parse_map == MAP_PARSER_ADDRESS) { crash_screen_print(LEFT_MARGIN, 35, "CURRFUNC: NONE"); @@ -373,7 +373,7 @@ void draw_disasm(OSThread *thread) { } void draw_assert(UNUSED OSThread *thread) { - crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_draw_rect(0, 20, 320, 210); crash_screen_print(LEFT_MARGIN, 25, "ASSERT PAGE"); From 3f7858e265741461562681782f9a1ff3b8529b1e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 16 Jun 2025 23:46:28 -0400 Subject: [PATCH 014/109] Add failsafe for instruction disasm --- src/game/crash_screen.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index c0bbc3bab0..b9391e499e 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -358,15 +358,20 @@ void draw_disasm(OSThread *thread) { if (sProgramPosition == 0) { sProgramPosition = (tc->pc - 36); } - crash_screen_print(LEFT_MARGIN, 25, "DISASM %08X", sProgramPosition); + crash_screen_print(LEFT_MARGIN, 25, "Program Counter: %08X", sProgramPosition); osWritebackDCacheAll(); for (int i = 0; i < 19; i++) { u32 addr = (sProgramPosition + (i * 4)); - u32 toDisasm = *(u32*)(addr); - crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%s", insn_disasm(addr, (addr == tc->pc))); + char *disasm = insn_disasm(addr, (addr == tc->pc)); + if (disasm[0] == 0) { + crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%08X", addr); + } else { + crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%s", disasm); + } + } osWritebackDCacheAll(); From 8ab428a8ccd010ec8cda74823cc20c28f25b281f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 17 Jun 2025 22:54:19 -0400 Subject: [PATCH 015/109] start on porting libdragon backtrace (does not work) --- Makefile | 8 +- asm/debug/map.s | 17 - sm64.ld | 5 +- src/game/backtrace.h | 180 ++++++ src/game/cop0.h | 348 +++++++++++ src/game/crash_screen.c | 42 +- src/game/insn_disasm.c | 98 ++-- src/game/map_parser.c | 868 ++++++++++++++++++++++++++-- src/game/mario_actions_stationary.c | 3 + src/game/stack_trace.c | 0 10 files changed, 1420 insertions(+), 149 deletions(-) delete mode 100644 asm/debug/map.s create mode 100644 src/game/backtrace.h create mode 100644 src/game/cop0.h create mode 100644 src/game/stack_trace.c diff --git a/Makefile b/Makefile index ffc0395f28..040cb482e9 100644 --- a/Makefile +++ b/Makefile @@ -225,7 +225,7 @@ endif # allowing for usage of CEN64 (and possibly Project64) to print messages to terminal. # 1 - includes code in ROM # 0 - does not -ISVPRINT ?= 0 +ISVPRINT ?= 1 $(eval $(call validate-option,ISVPRINT,0 1)) ifeq ($(ISVPRINT),1) DEFINES += ISVPRINT=1 @@ -956,10 +956,10 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(call print,Getting Goddard size...) $(V)python3 tools/getGoddardSize.py $(BUILD_DIR)/sm64_prelim.map $(VERSION) -$(BUILD_DIR)/asm/debug/map.o: asm/debug/map.s $(BUILD_DIR)/sm64_prelim.elf +$(BUILD_DIR)/asm/debug/map.o: $(BUILD_DIR)/sm64_prelim.elf $(call print,Assembling:,$<,$@) - $(V)python3 tools/mapPacker.py $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/bin/addr.bin $(BUILD_DIR)/bin/name.bin - $(V)$(CROSS)gcc -c $(ASMFLAGS) $(foreach i,$(INCLUDE_DIRS),-Wa,-I$(i)) -x assembler-with-cpp -MMD -MF $(BUILD_DIR)/$*.d -o $@ $< + $(V)tools/n64sym $(BUILD_DIR)/sm64_prelim.elf + $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym # Link SM64 ELF file $(ELF): $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/asm/debug/map.o $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) $(BUILD_DIR)/libz.a $(BUILD_DIR)/libgoddard.a diff --git a/asm/debug/map.s b/asm/debug/map.s deleted file mode 100644 index fc88194287..0000000000 --- a/asm/debug/map.s +++ /dev/null @@ -1,17 +0,0 @@ -.include "macros.inc" -.section .data -.balign 16 -glabel gMapEntries -.incbin "bin/addr.bin" -glabel gMapEntryEnd - -.balign 16 -glabel gMapStrings -.incbin "bin/name.bin" -glabel gMapStringsEnd - -.balign 16 -glabel gMapEntrySize -.word (gMapEntryEnd - gMapEntries) / 4 -glabel gMapStringSize -.word (gMapStringsEnd - gMapStrings) diff --git a/sm64.ld b/sm64.ld index 1374a2c750..51e5285e99 100755 --- a/sm64.ld +++ b/sm64.ld @@ -148,9 +148,6 @@ SECTIONS /* hardcoded symbols to satisfy preliminary link for map parser */ #ifndef DEBUG_MAP_STACKTRACE _mapDataSegmentRomStart = 0; - gMapEntries = 0; - gMapEntrySize = 0; - gMapStrings = 0; #endif BEGIN_SEG(main, .) SUBALIGN(16) @@ -529,7 +526,7 @@ SECTIONS #endif #ifdef DEBUG_MAP_STACKTRACE - BEGIN_SEG(mapData, (RAM_END - 0x00100000)) { + BEGIN_SEG(mapData, (RAM_END - 0x00100000)) ALIGN(16) { KEEP(BUILD_DIR/asm/debug/map.o(.data*)); } END_SEG(mapData) diff --git a/src/game/backtrace.h b/src/game/backtrace.h new file mode 100644 index 0000000000..576a777471 --- /dev/null +++ b/src/game/backtrace.h @@ -0,0 +1,180 @@ +#ifndef __LIBDRAGON_BACKTRACE_INTERNAL_H +#define __LIBDRAGON_BACKTRACE_INTERNAL_H + +/** @brief The "type" of funciton as categorized by the backtrace heuristic (__bt_analyze_func) */ +typedef enum { + BT_FUNCTION, ///< Regular function with a stack frame + BT_FUNCTION_FRAMEPOINTER, ///< The function uses the register fp as frame pointer (normally, this happens only when the function uses alloca) + BT_EXCEPTION, ///< This is an exception handler (inthandler.S) + BT_LEAF ///< Leaf function (no calls), no stack frame allocated, sp/ra not modified +} bt_func_type; + +/** @brief Description of a function for the purpose of backtracing (filled by __bt_analyze_func) */ +typedef struct { + bt_func_type type; ///< Type of the function + int stack_size; ///< Size of the stack frame + int ra_offset; ///< Offset of the return address from the top of the stack frame + int fp_offset; ///< Offset of the saved fp from the top of the stack frame; this is != 0 only if the function modifies fp (maybe as a frame pointer, but not necessarily) +} bt_func_t; + +u8 __bt_analyze_func(bt_func_t *func, u32 *ptr, u32 func_start, u8 from_exception); + +/** @brief Like #backtrace, but start from an arbitrary context. Useful for backtracing a thread */ +int __backtrace_from(void **buffer, int size, u32 *pc, u32 *sp, u32 *fp, u32 *exception_ra); + +/** + * @brief Return the symbol associated to a given address. + * + * This function inspect the symbol table (if any) to search for the + * specified address. It returns the function name the address belongs + * to, and the offset within the function as a string in the format + * "function_name+0x1234". + * + * If the symbol table is not found in the rompack or the address is not found, + * the return string is "???". + * + * @param vaddr Address to symbolize + * @param buf Buffer where to store the result + * @param size Size of the buffer + * @return char* Pointer to the return string. This is within the provided + * buffer, but not necessarily at the beginning because of DMA + * alignment constraints. + */ +char* __symbolize(void *vaddr, char *buf, int size); + +#endif + +/** + * @file backtrace.h + * @brief Backtrace (call stack) support + * @ingroup backtrace + */ + +/** + * @defgroup backtrace Backtrace (call stack) support + * @ingroup lowlevel + * @brief Implementation of functions to walk the stack and dump a backtrace + * + * This module implements two POSIX/GNU standard functions to help walking + * the stack and providing the current execution context: backtrace() and + * backtrace_symbols(). + * + * The functions have an API fully compatible with the standard ones. The + * implementation is however optimized for the MIPS/N64 case, and with + * standard compilation settings. See the documentation in backtrace.c + * for implementation details. + * + * You can call the functions to inspect the current call stack. For + * a higher level function that just prints the current call stack + * on the debug channels, see #debug_backtrace. + * + * @{ + */ + +#ifndef __LIBDRAGON_BACKTRACE_H +#define __LIBDRAGON_BACKTRACE_H + +/** + * @brief A stack frame, part of a backtrace + */ +typedef struct { + u32 addr; ///< PC address of the frame (MIPS virtual address) + + const char *func; ///< Name of the function (this should always be present) + u32 func_offset; ///< Byte offset of the address within the function + + const char *source_file; ///< Name of the source file (if known, or "???" otherwise) + int source_line; ///< Line number in the source file (if known, or 0 otherwise) + + u8 is_inline; ///< True if this frame refers to an inlined function +} backtrace_frame_t; + +/** + * @brief Walk the stack and return the current call stack + * + * This function will analyze the current execution context, + * walking the stack and returning informations on the active + * call frames. + * + * This function adheres to POSIX specification. It does not + * allocate memory so it is safe to be called even in the + * context of low memory conditions or possibly corrupted heap. + * + * If called within an interrupt or exception handler, the function + * is able to correctly walk backward the interrupt handler and + * show the context even before the exception was triggered. + * + * @param buffer Empty array of pointers. This will be populated with pointers + * to the return addresses for each call frame. + * @param size Size of the buffer, that is, maximum number of call frames + * that will be walked by the function. + * @return Number of call frames walked (at most, size). + */ +int backtrace(void **buffer, int size); + +/** + * @brief Translate the buffer returned by #backtrace into a list of strings + * + * This function symbolizes the buffer returned by #backtrace, translating + * return addresses into function names and source code locations. + * + * The user-readable strings are allocated on the heap and must be freed by + * the caller (via a single free() call). There is no need to free each + * of the returned strings: a single free() call is enough, as they are + * allocated in a single contiguous block. + * + * This function adheres to POSIX specification. + * + * This function also handles inlined functions. In general, inlined function + * do not have a real stack frame because they are expanded in place; so for + * instance a single stack frame (as returned by #backtrace) can correspond + * to multiple symbolized stack frames, one per each inlined function. Since + * the POSIX API requires this function to return an array of the same size + * of the input array, all inlined functions are collapsed into a single + * string, separated by newlines. + * + * @param buffer Array of return addresses, populated by #backtrace + * @param size Size of the provided buffer, in number of pointers. + * @return Array of strings, one for each call frame. The array + * must be freed by the caller with a single free() call. + * + * @see #backtrace_symbols_cb + */ +char** backtrace_symbols(void **buffer, int size); + +/** + * @brief Symbolize the buffer returned by #backtrace, calling a callback for each frame + * + * This function is similar to #backtrace_symbols, but instead of formatting strings + * into a heap-allocated buffer, it invokes a callback for each symbolized stack + * frame. This allows to skip the memory allocation if not required, and also allows + * for custom processing / formatting of the backtrace by the caller. + * + * The callback will receive an opaque argument (cb_arg) and a pointer to a + * stack frame descriptor (#backtrace_frame_t). The descriptor and all its + * contents (including strings) is valid only for the duration of the call, + * so the callback must (deep-)copy any data it needs to keep. + * + * The callback implementation might find useful to call #backtrace_frame_print + * or #backtrace_frame_print_compact to print the frame information. + * + * @param buffer Array of return addresses, populated by #backtrace + * @param size Size of the provided buffer, in number of pointers. + * @param flags Flags to control the symbolization process. Use 0. + * @param cb Callback function to invoke for each symbolized frame + * @param cb_arg Opaque argument to pass to the callback function + * @return True if the symbolization was successful, false otherwise. + * Notice that the function returns true even if some frames + * were not symbolized; false is only used when the function + * had to abort before even calling the callback once (eg: + * no symbol table was found). + * + * @see #backtrace_symbols + */ +u8 backtrace_symbols_cb(void **buffer, int size, u32 flags, + void (*cb)(void *, backtrace_frame_t*), void *cb_arg); + +/** @} */ + +#endif + diff --git a/src/game/cop0.h b/src/game/cop0.h new file mode 100644 index 0000000000..e311e32e72 --- /dev/null +++ b/src/game/cop0.h @@ -0,0 +1,348 @@ +/** + * @file cop0.h + * @brief N64 COP0 Interface + * @ingroup n64sys + */ + +/** + * @addtogroup n64sys + * @{ + */ + +#ifndef __LIBDRAGON_COP0_H +#define __LIBDRAGON_COP0_H + +/** @brief Read the COP0 Count register (see also TICKS_READ). */ +#define C0_COUNT() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$9":"=r"(x)); \ + x; \ +}) + +/** @brief Write the COP0 Count register. */ +#define C0_WRITE_COUNT(x) ({ \ + asm volatile("mtc0 %0,$9"::"r"(x)); \ +}) + + +/** @brief Read the COP0 Compare register. */ +#define C0_COMPARE() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$11":"=r"(x)); \ + x; \ +}) + +/** @brief Write the COP0 Compare register. */ +#define C0_WRITE_COMPARE(x) ({ \ + asm volatile("mtc0 %0,$11"::"r"(x)); \ +}) + + +/** @brief Read the COP0 Status register. */ +#define C0_STATUS() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$12":"=r"(x)); \ + x; \ +}) + +/** @brief Write the COP0 Status register. */ +#define C0_WRITE_STATUS(x) ({ \ + asm volatile("mtc0 %0,$12"::"r"(x)); \ +}) + +/** + * @brief Returns the COP0 register $13 (Cause Register) + * + * The coprocessor 0 (system control coprocessor - COP0) register $13 is a read + * write register keeping pending interrupts, exception code, coprocessor unit + * number referenced for a coprocessor unusable exception. + * + * @see #C0_GET_CAUSE_EXC_CODE, #C0_GET_CAUSE_CE and #C0_CAUSE_BD + */ +#define C0_CAUSE() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$13" : "=r" (x) : ); \ + x; \ +}) + +/** + * @brief Write the COP0 register $13 (Cause register) + * + * Use this to update it for a custom exception handler. + * */ +#define C0_WRITE_CAUSE(x) ({ \ + asm volatile("mtc0 %0,$13"::"r"(x)); \ +}) + +/** @cond */ +/* Alternative version with different naming */ +#define C0_CR() C0_CAUSE() +#define C0_WRITE_CR(x) C0_WRITE_CAUSE(x) +/** @endcond */ + +/** + * @brief Returns the COP0 register $8 (BadVAddr) + * + * The coprocessor 0 (system control coprocessor - COP0) register $8 is a read + * only register holding the last virtual address to be translated which became + * invalid, or a virtual address for which an addressing error occurred. + */ +#define C0_BADVADDR() ({ \ + uint64_t x; \ + asm volatile("dmfc0 %0,$8" : "=r" (x) : ); \ + x; \ +}) + +/** + * @brief Read the COP0 register $14 (EPC) + * + * The coprocessor 0 (system control coprocessor - COP0) register $14 is the + * return from exception program counter. For asynchronous exceptions it points + * to the place to continue execution whereas for synchronous (caused by code) + * exceptions, point to the instruction causing the fault condition, which + * needs correction in the exception handler. This macro is for reading its + * value. + */ +#define C0_EPC() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$14" : "=r" (x) : ); \ + x; \ +}) + +/** + * @brief Read the COP0 INDEX register + * + * This register is used during TLB programming. It holds the index of the TLB + * entry being accessed (0-31). + */ +#define C0_INDEX() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$0":"=r"(x)); \ + x; \ +}) + +/** + * @brief Write the COP0 INDEX register + * + * This register is used during TLB programming. It holds the index of the TLB + * entry being accessed (0-31). + */ +#define C0_WRITE_INDEX(x) asm volatile("mtc0 %0,$0; nop; nop"::"r"(x)) + + +/** + * @brief Read the COP0 ENTRYHI register + * + * This register is used during TLB programming. It holds the configuration + * of the virtual memory entry for the TLB slot being accessed. + */ +#define C0_ENTRYHI() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$10":"=r"(x)); \ + x; \ +}) + +/** + * @brief Write the COP0 ENTRYHI register + * + * This register is used during TLB programming. + */ +#define C0_WRITE_ENTRYHI(x) asm volatile("mtc0 %0,$10; nop; nop"::"r"(x)) + +/** + * @brief Read the COP0 ENTRYLO0 register + * + * This register is used during TLB programming. It holds the configuration + * of the physical memory entry (even bank) for the TLB slot being accessed. + */ +#define C0_ENTRYLO0() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$2":"=r"(x)); \ + x; \ +}) + +/** + * @brief Write the COP0 ENTRYLO0 register + * + * This register is used during TLB programming. It holds the configuration + * of the physical memory entry (even bank) for the TLB slot being accessed. + */ +#define C0_WRITE_ENTRYLO0(x) asm volatile("mtc0 %0,$2; nop; nop"::"r"(x)) + +/** + * @brief Read the COP0 ENTRYLO1 register + * + * This register is used during TLB programming. It holds the configuration + * of the physical memory entry (odd bank) for the TLB slot being accessed. + */ +#define C0_ENTRYLO1() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$3":"=r"(x)); \ + x; \ +}) + +/** + * @brief Write the COP0 ENTRYLO1 register + * + * This register is used during TLB programming. It holds the configuration + * of the physical memory entry (even bank) for the TLB slot being accessed. + */ +#define C0_WRITE_ENTRYLO1(x) asm volatile("mtc0 %0,$3; nop; nop"::"r"(x)) + + +/** + * @brief Read the COP0 PAGEMASK register + * + * This register is used during TLB programming. It holds the bitmask that + * configures the page size of the TLB slot being accessed. + */ +#define C0_PAGEMASK() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$5":"=r"(x)); \ + x; \ +}) + +/** + * @brief Write the COP0 PAGEMASK register + * + * This register is used during TLB programming. It holds the bitmask that + * configures the page size of the TLB slot being accessed. + */ +#define C0_WRITE_PAGEMASK(x) asm volatile("mtc0 %0,$5; nop; nop"::"r"(x)) + + +/** + * @brief Read the COP0 WIRED register + * + * This register is used during TLB programming. It allows to partition TLB + * slots between fixed slots and random slots. The fixed slot pool is the + * range [0..WIRED[ and the random pool is the range [WIRED..32[ + */ +#define C0_WIRED() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$6":"=r"(x)); \ + x; \ +}) + +/** + * @brief Read the COP0 WATCHLO register + * + * This register is used during watchpoint programming. It allows to trigger + * an exception when a memory access occurs on a specific memory location. + */ +#define C0_WATCHLO() ({ \ + u32 x; \ + asm volatile("mfc0 %0,$18":"=r"(x)); \ + x; \ +}) + +/** + * @brief Write the COP0 WATCHLO register + * + * This register is used during watchpoint programming. It allows to trigger + * an exception when a memory access occurs on a specific memory location. + */ +#define C0_WRITE_WATCHLO(x) asm volatile("mtc0 %0,$18"::"r"(x)) + +/** + * @brief Write the COP0 WIRED register + * + * This register is used during TLB programming. It allows to partition TLB + * slots between fixed slots and random slots. The fixed slot pool is the + * range [0..WIRED[ and the random pool is the range [WIRED..32[ + */ +#define C0_WRITE_WIRED(x) asm volatile("mtc0 %0,$6; nop; nop"::"r"(x)) + +/** @cond */ +/* Deprecated version of macros with wrong naming that include "READ" */ +#define C0_READ_CR() C0_CAUSE() +#define C0_READ_EPC() C0_EPC() +#define C0_READ_BADVADDR() C0_BADVADDR() +/** @endcond */ + +/* COP0 Status bits definition. Please refer to MIPS R4300 manual. */ +#define C0_STATUS_IE 0x00000001 ///< Status: interrupt enable +#define C0_STATUS_EXL 0x00000002 ///< Status: within exception +#define C0_STATUS_ERL 0x00000004 ///< Status: within error + +/* COP0 Cause bits definition. Please refer to MIPS R4300 manual. */ +#define C0_CAUSE_BD 0x80000000 ///< Cause: exception triggered in delay slot +#define C0_CAUSE_CE 0x30000000 ///< Cause: coprocessor exception +#define C0_CAUSE_EXC_CODE 0x0000007C ///< Cause: exception code + +/* COP0 interrupt bits definition. These are compatible bothwith mask and pending bits. */ +#define C0_INTERRUPT_0 0x00000100 ///< Status/Cause: SW interrupt 0 +#define C0_INTERRUPT_1 0x00000200 ///< Status/Cause: SW interrupt 1 +#define C0_INTERRUPT_2 0x00000400 ///< Status/Cause: HW interrupt 2 (RCP) +#define C0_INTERRUPT_3 0x00000800 ///< Status/Cause: HW interrupt 3 (CART) +#define C0_INTERRUPT_4 0x00001000 ///< Status/Cause: HW interrupt 4 (PRENMI) +#define C0_INTERRUPT_5 0x00002000 ///< Status/Cause: HW interrupt 5 +#define C0_INTERRUPT_6 0x00004000 ///< Status/Cause: HW interrupt 6 +#define C0_INTERRUPT_7 0x00008000 ///< Status/Cause: HW interrupt 7 (Timer) + +#define C0_INTERRUPT_RCP C0_INTERRUPT_2 ///< Status/Cause: HW interrupt 2 (RCP) +#define C0_INTERRUPT_CART C0_INTERRUPT_3 ///< Status/Cause: HW interrupt 3 (CART) +#define C0_INTERRUPT_PRENMI C0_INTERRUPT_4 ///< Status/Cause: HW interrupt 4 (PRENMI) +#define C0_INTERRUPT_TIMER C0_INTERRUPT_7 ///< Status/Cause: HW interrupt 7 (Timer) + +/** + * @brief Get the CE value from the COP0 status register + * + * Gets the Coprocessor unit number referenced by a coprocessor unusable + * exception from the given COP0 Status register value. + */ +#define C0_GET_CAUSE_CE(cr) (((cr) & C0_CAUSE_CE) >> 28) + +/** + * @brief Get the exception code value from the COP0 status register value + */ +#define C0_GET_CAUSE_EXC_CODE(sr) (((sr) & C0_CAUSE_EXC_CODE) >> 2) + +/* Flag bits valid for COP0 ENTRYLO0/ENTRYLO1 registers */ +#define C0_ENTRYLO_GLOBAL (1<<0) ///< ENTRYLO: mapping is global (all ASIDs) +#define C0_ENTRYLO_VALID (1<<1) ///< ENTRYLO: mapping is active (not disabled) +#define C0_ENTRYLO_DIRTY (1<<2) ///< ENTRYLO: mapping is writable + +/* Flag bits valid for COP0 INDEX register */ +#define C0_INDEX_PROBE_FAILED (1<<31) ///< INDEX: set when a TLBP probe failed to find a match + + +/** + * @brief COP0 TLBWI opcode. + * + * This opcode is used during TLB programming. It writes the TLB slot referenced + * by INDEX with the contents of PAGEMASK, ENTRYHI, ENTRYLO0, ENTRYLO1. + */ +#define C0_TLBWI() asm volatile("tlbwi; nop; nop; nop; nop") + +/** + * @brief COP0 TLBWR opcode. + * + * This opcode is used during TLB programming. It writes a random TLB slot with + * the contents of PAGEMASK, ENTRYHI, ENTRYLO0, ENTRYLO1. THe slot is selected + * in the random pool (slots in the range from WIRED to 31). + */ +#define C0_TLBWR() asm volatile("tlbwr; nop; nop; nop; nop") + +/** + * @brief COP0 TLBR opcode. + * + * This opcode is used during TLB programming. It reads the contents of the TLB + * slot referenced by INDEX into the registers PAGEMASK, ENTRYHI, ENTRYLO0 and + * ENTRYLO1. + */ +#define C0_TLBR() asm volatile("tlbr; nop; nop; nop; nop") + +/** + * @brief COP0 TLBP opcode. + * + * This opcode is used during TLB programming. It probes the current TLB slots + * using ENTRYHI (virtual address) to find a matching slot. If it finds, it + * loads its index into INDEX. Otherwise, it sets the C0_INDEX_PROBE_FAILED bit + * in INDEX. + */ +#define C0_TLBP() asm volatile("tlbp; nop; nop; nop; nop") + +/** @} */ + +#endif \ No newline at end of file diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index b9391e499e..4e88eca876 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -314,38 +314,26 @@ void draw_crash_log(void) { #endif -// prints any function pointers it finds in the stack format: -// SP address: function name +extern void inspector_print_backtrace(void *bt, int n, int bt_skip); +int backtrace(void **buffer, int size); + void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; u32 temp_sp = (tc->sp + 0x14); crash_screen_draw_rect(0, 20, 320, 210); - crash_screen_print(LEFT_MARGIN, 25, "STACK TRACE FROM %08X:", temp_sp); - if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, 35, "CURRFUNC: NONE"); - } else { - crash_screen_print(LEFT_MARGIN, 35, "CURRFUNC: %s", parse_map(tc->pc)); - } + crash_screen_print(LEFT_MARGIN, 25, "Backtrace from %08X:", temp_sp); osWritebackDCacheAll(); + if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { + crash_screen_print(LEFT_MARGIN, (45), "STACK TRACE DISABLED"); + } else { + #define LINE_COUNT 10 + void *lines[LINE_COUNT]; + int bt_skip = 0; // how many frames to skip + backtrace(lines, LINE_COUNT); - for (int i = 0; i < 18; i++) { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "STACK TRACE DISABLED"); - break; - } else { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - return; - } - - char *fname = find_function_in_stack(&temp_sp); - if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X: Unknown", temp_sp); - } else { - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X: %s", temp_sp, fname); - } - } + inspector_print_backtrace(lines, LINE_COUNT, bt_skip); } } @@ -478,9 +466,9 @@ void thread2_crash_screen(UNUSED void *arg) { thread = get_crashed_thread(); gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; if (thread) { - if ((u32) map_data_init != MAP_PARSER_ADDRESS) { - map_data_init(); - } + // if ((u32) map_data_init != MAP_PARSER_ADDRESS) { + // map_data_init(); + // } gCrashScreen.thread.priority = 15; stop_sounds_in_continuous_banks(); stop_background_music(sBackgroundMusicQueue[0].seqId); diff --git a/src/game/insn_disasm.c b/src/game/insn_disasm.c index 8a7cd29af0..7134ada907 100644 --- a/src/game/insn_disasm.c +++ b/src/game/insn_disasm.c @@ -90,43 +90,7 @@ typedef struct PACKED { #define OP_BRANCH 0b000001 // technically "REGIMM" InsnTemplate insn_db[] = { - // arithmetic - {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"}, - - {I_TYPE, PARAM_NONE, 0b001010, 0, "slti"}, - {I_TYPE, PARAM_NONE, 0b001011, 0, "sltiu"}, - - - // bitwise ops - {R_TYPE, PARAM_NONE, 0, 0b100100, "and"}, - {I_TYPE, PARAM_NONE, 0b001100, 0, "andi"}, - {R_TYPE, PARAM_NONE, 0, 0b100101, "or"}, - {I_TYPE, PARAM_NONE, 0b001101, 0, "ori"}, - {R_TYPE, PARAM_NONE, 0, 0b100110, "xor"}, - {I_TYPE, PARAM_NONE, 0b001110, 0, "xori"}, - {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"}, + // We want instructions with opcodes first (prioritized) // load/store {I_TYPE, PARAM_LUI, 0b001111, 0, "lui"}, @@ -144,6 +108,7 @@ InsnTemplate insn_db[] = { {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"}, @@ -153,7 +118,6 @@ InsnTemplate insn_db[] = { {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"}, @@ -163,19 +127,59 @@ InsnTemplate insn_db[] = { {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"}, - {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"}, + {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"}, - // instructions involving doubles (deprioritized on the list) - {R_TYPE, PARAM_NONE, 0, 0b101101, "daddu"}, + // 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"}, + + // 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"}, @@ -214,9 +218,7 @@ char __mips_fpreg[][5] = { "$f28", "$f29", "$f30", "$f31", }; -char* __symbolize(void *vaddr, char *buf, int size) { - return parse_map((u32)vaddr); -} +extern char* __symbolize(void *vaddr, char *buf, int size); // Last Resort C0/C1 disassembler, from libdragon static void c1_disasm(u32 *ptr, char *out, int n) { @@ -395,7 +397,7 @@ char *insn_disasm(InsnData *addr, u32 isPC) { } successful_print = 1; break; - } else if ( (insn.i.rdata.function == 0 && insn.i.opcode == 0) + } 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) { diff --git a/src/game/map_parser.c b/src/game/map_parser.c index 0d9b646abf..d4c8c79e66 100644 --- a/src/game/map_parser.c +++ b/src/game/map_parser.c @@ -1,30 +1,59 @@ #include +#include #include #include #include #include "segments.h" +#include "memory.h" +#include "backtrace.h" +#include "cop0.h" -#define STACK_TRAVERSAL_LIMIT 100 +extern u32 strstr(char *, char *); -struct MapEntry { - u32 addr; - u32 nm_offset; - u32 nm_len; - u32 pad; -}; -extern u8 gMapStrings[]; -extern struct MapEntry gMapEntries[]; -extern u32 gMapEntrySize; -extern u8 _mapDataSegmentRomStart[]; +/** + * @brief Exception codes + */ +typedef enum { + EXCEPTION_CODE_INTERRUPT = 0, + EXCEPTION_CODE_TLB_MODIFICATION = 1, + EXCEPTION_CODE_TLB_LOAD_I_MISS = 2, + EXCEPTION_CODE_TLB_STORE_MISS = 3, + EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR = 4, + EXCEPTION_CODE_STORE_ADDRESS_ERROR = 5, + EXCEPTION_CODE_I_BUS_ERROR = 6, + EXCEPTION_CODE_D_BUS_ERROR = 7, + EXCEPTION_CODE_SYS_CALL = 8, + EXCEPTION_CODE_BREAKPOINT = 9, + EXCEPTION_CODE_RESERVED_INSTRUCTION = 10, + EXCEPTION_CODE_COPROCESSOR_UNUSABLE = 11, + EXCEPTION_CODE_ARITHMETIC_OVERFLOW = 12, + EXCEPTION_CODE_TRAP = 13, + EXCEPTION_CODE_FLOATING_POINT = 15, + EXCEPTION_CODE_WATCH = 23, +} exception_code_t; + +/** @brief Check if addr is a valid PC address */ +static u8 is_valid_address(u32 addr) +{ + // TODO: for now we only handle RAM (cached access). This should be extended to handle + // TLB-mapped addresses for instance. + return addr >= 0x80000400 && addr < 0x80800000 && (addr & 3) == 0; +} + +#define offsetof(type, member) __builtin_offsetof (type, member) +// 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); - } + 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); @@ -35,28 +64,32 @@ static u32 headless_pi_status(void) } // end of code provided by Wiseguy +/** @brief Address of the SYMT symbol table in the rompak. */ +static u32 SYMT_ROM = 0xFFFFFFFF; + +/** @brief Placeholder used in frames where symbols are not available */ +static const char *UNKNOWN_SYMBOL = "???"; + +extern void dma_read(void *dest, void *srcStart, void *srcEnd); -void map_data_init(void) { - headless_dma((u32)_mapDataSegmentRomStart, (u32*)(RAM_END - 0x100000), 0x100000); - while (headless_pi_status() & (PI_STATUS_DMA_BUSY | PI_STATUS_ERROR)); +void map_parser_dma(void *dst, void *src, size_t size) { + // char dm[500]; + // sprintf(dm, "DMA [%08X] <-- [%08X %08X] (%08X)", dst, src, size, SYMT_ROM); + // osSyncPrintf(dm); + // dma_read(dst, src, src+ALIGN8(size)); + headless_dma((u32)src, dst, size); + while (headless_pi_status() & PI_STATUS_IO_BUSY); } -char *parse_map(u32 pc) { - u32 i; +#define STACK_TRAVERSAL_LIMIT 100 - for (i = 0; i < gMapEntrySize; i++) { - if (gMapEntries[i].addr == pc) { - return (char*) ((u32)gMapStrings + gMapEntries[i].nm_offset); - } - else if (gMapEntries[i].addr > pc) break; - } +/** @brief Function alignment enfored by the compiler (-falign-functions). + * + * @note This must be kept in sync with n64.mk. + */ +#define FUNCTION_ALIGNMENT 32 - if (i == gMapEntrySize - 1) { - return NULL; - } else { - return (char*) ((u32)gMapStrings + gMapEntries[i - 1].nm_offset); - } -} +extern u8 _mapDataSegmentRomStart[]; extern u8 _mainSegmentStart[]; extern u8 _mainSegmentTextEnd[]; @@ -65,25 +98,762 @@ extern u8 _engineSegmentTextEnd[]; extern u8 _goddardSegmentStart[]; extern u8 _goddardSegmentTextEnd[]; +/** + * @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; + +void print_header(symtable_header_t *h) { + char tt[1000]; + char *tp = tt; + + tp += sprintf(tp, "HEAD: %s\n", h->head); + tp += sprintf(tp, "VER: %08X\n", h->version); + #define PRINT(field) tp += sprintf(tp, #field ": %08X\n", field); + PRINT(h->addrtab_off); + PRINT(h->addrtab_size); + PRINT(h->symtab_off); + PRINT(h->symtab_size); + PRINT(h->strtab_off); + PRINT(h->strtab_size); + + osSyncPrintf(tt); +} + +u8 symHeap[0x4000]; +u32 symIdx = 0; +void *malloc(size_t size) { + return &symHeap[symIdx += ALIGN16(size)]; +} + +/** @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; + +void print_entry(symtable_entry_t *h) { + char tt[1000]; + char *tp = tt; + + #define PRINT(field) tp += sprintf(tp, #field ": %08X\n", field); + PRINT(h->func_sidx); + PRINT(h->file_sidx); + PRINT(h->func_len); + PRINT(h->file_len); + PRINT(h->line); + PRINT(h->func_off); + + osSyncPrintf(tt); +} + +/** + * @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 + +/** + * @brief Open the SYMT symbol table in the rompak. + * + * If not found, return a null header. + */ +static symtable_header_t symt_open(void *addr) { + SYMT_ROM = (u32)_mapDataSegmentRomStart; + + // We don't _need_ to pass by value for this right + 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("backtrace: invalid symbol table found at 0x%08lx\n", SYMT_ROM); + SYMT_ROM = 0; + return (symtable_header_t){0}; + } + if (symt_header.version != 2) { + osSyncPrintf("backtrace: 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 + */ +static 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) + */ +static 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) + */ +static 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 + */ +static 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 +static 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 +static char* symt_entry_file(symtable_header_t *symt, symtable_entry_t *entry, u32 addr, char *buf, int size) +{ + return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); +} + +symtable_entry_t ALIGNED16 entry; +char* __symbolize(void *vaddr, char *buf, int size) { + symtable_header_t symt = symt_open(vaddr); + 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); + } + + // Read the symbol name + symt_entry_fetch(&symt, &entry, idx); + char *func = symt_entry_func(&symt, &entry, addr, buf, size-12); + char lbuf[12]; + sprintf(lbuf, "+0x%lx", addr - ADDRENTRY_ADDR(a)); + return strcat(func, lbuf); + } + sprintf(buf, "%s", UNKNOWN_SYMBOL); + return buf; +} + +char *parse_map(u32 addr) { + static char map_name[64] ALIGNED16; + __symbolize((u32*)addr, map_name, sizeof(map_name)); + + return map_name; +} + +char *get_filename(u32 addr) { + static char file_name[64] ALIGNED16; + + return file_name; +} + char *find_function_in_stack(u32 *sp) { - for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { - u32 val = *sp; - val = *(u32 *)val; - *sp += 4; - - if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { - return parse_map(val); - } - else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { - return parse_map(val); - } - else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { - return parse_map(val); - } - - - } - return NULL; + for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { + u32 val = *sp; + val = *(u32 *)val; + *sp += 4; + + if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { + return parse_map(val); + } + else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { + return parse_map(val); + } + else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { + return parse_map(val); + } + } + return NULL; +} + + + +/** + * @brief Analyze a function to find out its stack frame layout and properties (useful for backtracing). + * + * This function implements the core heuristic used by the backtrace engine. It analyzes the actual + * code of a function in memory instruction by instruction, trying to find out whether the function + * uses a stack frame or not, whether it uses a frame pointer, and where the return address is stored. + * + * Since we do not have DWARF informations or similar metadata, we can just do educated guesses. A + * mistake in the heuristic will result probably in a wrong backtrace from this point on. + * + * The heuristic works as follows: + * + * * Most functions do have a stack frame. In fact, 99.99% of the functions you can find in a call stack + * must have a stack frame, because the only functions without a stack frame are leaf functions (functions + * that do not call other functions), which in turns can never be part of a stack trace. + * * The heuristic walks the function code backwards, looking for the stack frame. Specifically, it looks + * for an instruction saving the RA register to the stack (eg: `sd $ra, nn($sp)`), and an instruction + * creating the stack frame (eg: `addiu $sp, $sp, -nn`). Once both are found, the heuristic knows how to + * fill in `.stack_size` and `.ra_offset` fields of the function description structure, and it can stop. + * * Some functions also modify $fp (the frame pointer register): sometimes, they just use it as one additional + * free register, and other times they really use it as frame pointer. If the heuristic finds the + * instruction `move $fp, $sp`, it knows that the function uses $fp as frame pointer, and will mark + * the function as BT_FUNCTION_FRAMEPOINTER. In any case, the field `.fp_offset` will be filled in + * with the offset in the stack where $fp is stored, so that the backtrace engine can track the + * current value of the register in any case. + * * The 0.01% of the functions that do not have a stack frame but appear in the call stack are leaf + * functions interrupted by exceptions. Leaf functions pose two important problems: first, $ra is + * not saved into the stack so there is no way to know where to go back. Second, there is no clear + * indication where the function begins (as we normally stops analysis when we see the stack frame + * creation). So in this case the heuristic would fail. We rely thus on two hints coming from the caller: + * * First, we expect the caller to set from_exception=TRUE, so that we know that we might potentially + * deal with a leaf function. + * * Second, the caller should provide the function start address, so that we stop the analysis when + * we reach it, and mark the function as BT_LEAF. + * * If the function start address is not provided (because e.g. the symbol table was not found and + * thus we have no information about function starts), the last ditch heuristic is to look for + * the nops that are normally used to align the function start to the FUNCTION_ALIGNMENT boundary. + * Obviously this is a very fragile heuristic (it will fail if the function required no nops to be + * properly aligned), but it is the best we can do. Worst case, in this specific case of a leaf + * function interrupted by the exception, the stack trace will be wrong from this point on. + * + * @param func Output function description structure + * @param ptr Pointer to the function code at the point where the backtrace starts. + * This is normally the point where a JAL opcode is found, as we are walking + * up the call stack. + * @param func_start Start of the function being analyzed. This is optional: the heuristic can work + * without this hint, but it is useful in certain situations (eg: to better + * walk up after an exception). + * @param from_exception If TRUE, this function was interrupted by an exception. This is a hint that + * the function *might* even be a leaf function without a stack frame, and that + * we must use special heuristics for it. + * + * @return TRUE if the backtrace can continue, FALSE if must be aborted (eg: we are within invalid memory) + */ +u8 __bt_analyze_func(bt_func_t *func, u32 *ptr, u32 func_start, u8 from_exception) +{ + *func = (bt_func_t){ + .type = is_in_exception(ptr) ? BT_EXCEPTION : BT_FUNCTION, + .stack_size = 0, .ra_offset = 0, .fp_offset = 0 + }; + + u32 addr = (u32)ptr; + while (1) { + // Validate that we can dereference the virtual address without raising an exception + // TODO: enhance this check with more valid ranges. + if (!is_valid_address(addr)) { + // This address is invalid, probably something is corrupted. Avoid looking further. + osSyncPrintf("backtrace: interrupted because of invalid return address 0x%08lx\n", addr); + return FALSE; + } + u32 op = *(u32*)addr; + if (MIPS_OP_ADDIU_SP(op) || MIPS_OP_DADDIU_SP(op)) { + // Extract the stack size only from the start of the function, where the + // stack is allocated (negative value). This is important because the RA + // could point to a leaf basis block at the end of the function (like in the + // assert case), and if we picked the positive ADDIU SP at the end of the + // proper function body, we might miss a fp_offset. + if (op & 0x8000) + func->stack_size = -(s16)(op & 0xFFFF); + } else if (MIPS_OP_SD_RA_SP(op)) { + osSyncPrintf("sw ra,sp"); + func->ra_offset = (s16)(op & 0xFFFF) + 4; // +4 = load low 32 bit of RA + // If we found a stack size, it might be a red herring (an alloca); we need one + // happening "just before" sd ra,xx(sp) + func->stack_size = 0; + } else if (MIPS_OP_SD_FP_SP(op)) { + func->fp_offset = (s16)(op & 0xFFFF) + 4; // +4 = load low 32 bit of FP + } else if (MIPS_OP_LUI_GP(op)) { + // Loading gp is commonly done in _start, so it's useless to go back more + return FALSE; + } else if (MIPS_OP_MOVE_FP_SP(op)) { + // This function uses the frame pointer. Uses that as base of the stack. + // Even with -fomit-frame-pointer (default on our toolchain), the compiler + // still emits a framepointer for functions using a variable stack size + // (eg: using alloca() or VLAs). + func->type = BT_FUNCTION_FRAMEPOINTER; + } + // We found the stack frame size and the offset of the return address in the stack frame + // We can stop looking and process the frame + if (func->stack_size != 0 && func->ra_offset != 0) + break; + if (from_exception) { + // The function we are analyzing was interrupted by an exception, so it might + // potentially be a leaf function (no stack frame). We need to make sure to stop + // at the beginning of the function and mark it as leaf function. Use + // func_start if specified, or try to guess using the nops used to align the function + // (crossing fingers that they're there). + if (addr == func_start) { + // The frame that was interrupted by an interrupt handler is a special case: the + // function could be a leaf function with no stack. If we were able to identify + // the function start (via the symbol table) and we reach it, it means that + // we are in a real leaf function. + func->type = BT_LEAF; + break; + } else if (!func_start && MIPS_OP_NOP(op) && (addr + 4) % FUNCTION_ALIGNMENT == 0) { + // If we are in the frame interrupted by an interrupt handler, and we does not know + // the start of the function (eg: no symbol table), then try to stop by looking for + // a NOP that pads between functions. Obviously the NOP we find can be either a FALSE + // positive or a FALSE negative, but we can't do any better without symbols. + func->type = BT_LEAF; + break; + } + } + addr -= 4; + } + return TRUE; +} + +static void backtrace_foreach(void (*cb)(void *arg, void *ptr), void *arg, u32 *ra, u32 *sp, u32 *fp, u32 *exception_ra) +{ + /* + * This function is called in very risky contexts, for instance as part of an exception + * handler or during an assertion. We try to always provide as much information as + * possible in these cases, with graceful degradation if something more elaborate cannot + * be extracted. Thus, this function: + * + * * Must not use malloc(). The heap might be corrupted or empty. + * * Must not use assert(), because that might trigger recursive assertions. + * * Must avoid raising exceptions. Specifically, it must avoid risky memory accesses + * to wrong addresses. + */ + + // #if BACKTRACE_DEBUG + osSyncPrintf("backtrace: start\n"); + // #endif + + u32 func_start = 0; // Start of the current function (when known) + + // Start calling the callback for the backtrace entry point + cb(arg, ra); + + while (1) { + // Analyze the function pointed by ra, passing information about the previous exception frame if any. + // If the analysis fail (for invalid memory accesses), stop right away. + bt_func_t func; + if (!__bt_analyze_func(&func, ra, func_start, (u8)exception_ra)) + return; + + #if 1 + osSyncPrintf("backtrace: %s, ra=%p, sp=%p, fp=%p ra_offset=%d, fp_offset=%d, stack_size=%d\n", + func.type == BT_FUNCTION ? "BT_FUNCTION" : (func.type == BT_EXCEPTION ? "BT_EXCEPTION" : (func.type == BT_FUNCTION_FRAMEPOINTER ? "BT_FRAMEPOINTER" : "BT_LEAF")), + ra, sp, fp, func.ra_offset, func.fp_offset, func.stack_size); + #endif + + switch (func.type) { + case BT_FUNCTION_FRAMEPOINTER: + if (!func.fp_offset) { + osSyncPrintf("backtrace: framepointer used but not saved onto stack at %p\n", ra); + } else { + // Use the frame pointer to refer to the current frame. + sp = fp; + if (!is_valid_address((u32)sp)) { + osSyncPrintf("backtrace: interrupted because of invalid frame pointer 0x%08lx\n", (u32)sp); + return; + } + } + // FALLTHROUGH! + case BT_FUNCTION: + if (func.fp_offset) + fp = *(u32**)((u32)sp + func.fp_offset); + ra = *(u32**)((u32)sp + func.ra_offset) - 2; + sp = (u32*)((u32)sp + func.stack_size); + exception_ra = NULL; + func_start = 0; + break; + case BT_EXCEPTION: { + // Exception frame. We must return back to EPC, but let's keep the + // RA value. If the interrupted function is a leaf function, we + // will need it to further walk back. + // Notice that FP is a callee-saved register so we don't need to + // recover it from the exception frame (also, it isn't saved there + // during interrupts). + exception_ra = *(u32**)((u32)sp + func.ra_offset); + + // Read EPC from exception frame and adjust it with CAUSE BD bit + ra = *(u32**)((u32)sp + offsetof(__OSThreadContext, pc) + 32); + u32 cause = *(u32*)((u32)sp + offsetof(__OSThreadContext, cause) + 32); + if (cause & C0_CAUSE_BD) ra++; + + sp = (u32*)((u32)sp + func.stack_size); + + // Special case: if the exception is due to an invalid EPC + // (eg: a null function pointer call), we can rely on RA to get + // back to the caller. This assumes that we got there via a function call + // rather than a raw jump, but that's a reasonable assumption. It's anyway + // the best we can do. + if ((C0_GET_CAUSE_EXC_CODE(cause) == EXCEPTION_CODE_TLB_LOAD_I_MISS || + C0_GET_CAUSE_EXC_CODE(cause) == EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR) && + !is_valid_address((u32)ra)) { + + // Store the invalid address in the backtrace, so that it will appear in dumps. + // This makes it easier for the user to understand the reason for the exception. + cb(arg, ra); + + ra = exception_ra - 2; + + // The function that jumped into an invalid PC was not interrupted by the exception: it + // is a regular function + // call now. + exception_ra = NULL; + break; + } + + // The next frame might be a leaf function, for which we will not be able + // to find a stack frame. It is useful to try finding the function start. + // Try to open the symbol table: if we find it, we can search for the start + // address of the function. + symtable_header_t symt = symt_open(ra); + if (symt.head[0]) { + int idx; + addrtable_entry_t entry = symt_addrtab_search(&symt, (u32)ra, &idx); + while (!ADDRENTRY_IS_FUNC(entry)) + entry = symt_addrtab_entry(&symt, --idx); + func_start = ADDRENTRY_ADDR(entry); + } + } break; + case BT_LEAF: + ra = exception_ra - 2; + // A leaf function has no stack. On the other hand, an exception happening at the + // beginning of a standard function (before RA is saved), does have a stack but + // will be marked as a leaf function. In this case, we mus update the stack pointer. + sp = (u32*)((u32)sp + func.stack_size); + exception_ra = NULL; + func_start = 0; + break; + } + + // Call the callback with this stack frame + cb(arg, ra); + } +} + +void _flush_cache() { + // no-op +} + +int backtrace(void **buffer, int size) { + int i = -1; // Skip backtrace() itself + void cb(void *arg, void *ptr) { + if (i >= size) return; + if (i >= 0) + buffer[i] = ptr; + i++; + } + + // Current value of SP/RA/FP registers. + u32 *sp, *fp; + asm volatile ( + "move %0, $sp\n" + "move %1, $fp\n" + : "=r"(sp), "=r"(fp) + ); + + // Start from the backtrace function itself. Put the start pointer + // somewhere after the initial prolog, so that we parse the prolog + // itself to find sp/fp/ra offsets. + // Since we don't come from an exception, exception_ra must be NULL. + u32 *pc = (u32*)backtrace + 24; + + backtrace_foreach(cb, NULL, pc, sp, fp, NULL); + return i; +} + +int __backtrace_from(void **buffer, int size, u32 *pc, u32 *sp, u32 *fp, u32 *exception_ra) +{ + int i = 0; + void cb(void *arg, void *ptr) { + if (i >= size) return; + if (i >= 0) + buffer[i] = ptr; + i++; + } + + backtrace_foreach(cb, NULL, pc, sp, fp, exception_ra); + return i; +} + + +static void format_entry(void (*cb)(void *, backtrace_frame_t *), void *cb_arg, + symtable_header_t *symt, int idx, u32 addr, u32 offset, u8 is_func, u8 is_inline) +{ + symtable_entry_t ALIGNED8 entry; + symt_entry_fetch(symt, &entry, idx); + + char ALIGNED8 file_buf[entry.file_len + 2]; + char ALIGNED8 func_buf[MAX(entry.func_len + 2, 32)]; + + cb(cb_arg, &(backtrace_frame_t){ + .addr = addr, + .func_offset = offset ? offset : entry.func_off, + .func = symt_entry_func(symt, &entry, addr, func_buf, sizeof(func_buf)), + .source_file = symt_entry_file(symt, &entry, addr, file_buf, sizeof(file_buf)), + .source_line = is_func ? 0 : entry.line, + .is_inline = is_inline, + }); +} + +u8 backtrace_symbols_cb(void **buffer, int size, u32 flags, + void (*cb)(void *, backtrace_frame_t *), void *cb_arg) +{ + for (int i=0; i" : "", + .source_file = UNKNOWN_SYMBOL, .source_line = 0, .is_inline = FALSE + }); + continue; + } + if (!has_symt) { + // No symbol table. Call the callback with a dummy entry which just contains the address + u8 exc = is_in_exception(needle); + cb(cb_arg, &(backtrace_frame_t){ + .addr = needle, + .func = exc ? "" : UNKNOWN_SYMBOL, .func_offset = 0, + .source_file = UNKNOWN_SYMBOL, .source_line = 0, .is_inline = FALSE + }); + continue; + } + int idx; addrtable_entry_t a; + a = symt_addrtab_search(&symt_header, needle, &idx); + + if (ADDRENTRY_ADDR(a) == needle) { + // Found an entry at this address. Go through all inlines for this address. + while (1) { + format_entry(cb, cb_arg, &symt_header, idx, needle, 0, FALSE, ADDRENTRY_IS_INLINE(a)); + if (!ADDRENTRY_IS_INLINE(a)) break; + a = symt_addrtab_entry(&symt_header, ++idx); + } + } else { + // Search the containing function + while (!ADDRENTRY_IS_FUNC(a)) + a = symt_addrtab_entry(&symt_header, --idx); + format_entry(cb, cb_arg, &symt_header, idx, needle, needle - ADDRENTRY_ADDR(a), TRUE, FALSE); + } + } + return TRUE; +} + +char** backtrace_symbols(void **buffer, int size) +{ + const int MAX_FILE_LEN = 120; + const int MAX_FUNC_LEN = 120; + const int MAX_SYM_LEN = MAX_FILE_LEN + MAX_FUNC_LEN + 24; + char **syms = malloc(2 * size * (sizeof(char*) + MAX_SYM_LEN)); + char *out = (char*)syms + size*sizeof(char*); + int level = 0; + + void cb(void *arg, backtrace_frame_t *frame) { + int n = sprintf(out, + "%s+0x%lx (%s:%d) [0x%08lx]", frame->func, frame->func_offset, frame->source_file, frame->source_line, frame->addr); + if (frame->is_inline) + out[-1] = '\n'; + else + syms[level++] = out; + out += n + 1; + } + + backtrace_symbols_cb(buffer, size, 0, cb, NULL); + return syms; +} + +void crash_screen_print(s32 x, s32 y, const char *fmt, ...); + +void backtrace_frame_print_compact(backtrace_frame_t *frame, char *out, int width) { + const char *source_file = frame->source_file; + int len = strlen(frame->func) + strlen(source_file); + u8 ellipsed = FALSE; + if (len > width && source_file) { + source_file += len - (width - 8); + ellipsed = TRUE; + } + if (frame->func != UNKNOWN_SYMBOL) out += sprintf(out, "%s ", frame->func); + if (source_file != UNKNOWN_SYMBOL) out += sprintf(out, "(%s%s:%d)", ellipsed ? "..." : "", source_file, frame->source_line); + if (frame->func == UNKNOWN_SYMBOL || source_file == UNKNOWN_SYMBOL) + out += sprintf(out, "[0x%08lx]", frame->addr); + // out += sprintf(out, "\n"); +} + + +void inspector_print_backtrace(void *bt, int n, int bt_skip) { + crash_screen_print(10, 45, "Backtrace:"); + char func[128]; + u8 skip = TRUE; + static int yBase = 55; + void cb(void *arg, backtrace_frame_t *frame) { + if (skip) { + if (strstr(frame->func, "")) + skip = FALSE; + return; + } + if (bt_skip > 0) { + bt_skip--; + return; + } + // crash_screen_print(10, " "); + sprintf(func, "%s", frame->func); + frame->func = func; + char stdout[400]; + backtrace_frame_print_compact(frame, stdout, 40); + crash_screen_print(10, yBase, stdout); + yBase += 10; + } + backtrace_symbols_cb(bt, n, 0, cb, NULL); + if (skip) { + // we didn't find the exception handler for some reason (eg: missing symbols) + // so just print the whole thing + skip = FALSE; + backtrace_symbols_cb(bt, n, 0, cb, NULL); + } } diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index b0692e54aa..1fe1011f0b 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -104,6 +104,9 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { + if(m->controller->buttonPressed&L_TRIG){ + *(vs8*)0=m->controller->buttonPressed << 2; + } if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } diff --git a/src/game/stack_trace.c b/src/game/stack_trace.c new file mode 100644 index 0000000000..e69de29bb2 From 916f3911fea6a563b41fdde8370d1ed3e19bef5f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 17 Jun 2025 23:11:34 -0400 Subject: [PATCH 016/109] remove segmented addresses from sym file --- tools/n64sym.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/n64sym.c b/tools/n64sym.c index 9bb85f2642..4ad507ef12 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -204,12 +204,18 @@ bool elf_find_callsites(const char *elf) // Find the functions if (strstr(line, ">:")) { uint32_t addr = strtoul(line, NULL, 16); - symbol_add(elf, addr, true); + // Prevent segmented addresses for now + if ((addr & 0xFF000000) == 0x80000000) { + symbol_add(elf, addr, true); + } } // Find the callsites if (strstr(line, "\tjal\t") || strstr(line, "\tjalr\t") || strstr(line, "\tsyscall")) { uint32_t addr = strtoul(line, NULL, 16); - symbol_add(elf, addr, false); + // Prevent segmented addresses for now + if ((addr & 0xFF000000) == 0x80000000) { + symbol_add(elf, addr, false); + } } } free(line); From 8ebae9fda49fcb7eaa3037267d7326a723110a2e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 18 Jun 2025 21:46:15 -0400 Subject: [PATCH 017/109] don't dox user in n64sym, actually get filenames --- Makefile | 2 +- sm64.ld | 3 +++ src/game/crash_screen.c | 30 ++++++++++++++++++++++++++---- src/game/map_parser.c | 32 ++++++++++++++++++++++++++++++-- tools/n64sym.c | 31 +++++++++++++++++++------------ 5 files changed, 79 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 040cb482e9..e786c69d37 100644 --- a/Makefile +++ b/Makefile @@ -958,7 +958,7 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/asm/debug/map.o: $(BUILD_DIR)/sm64_prelim.elf $(call print,Assembling:,$<,$@) - $(V)tools/n64sym $(BUILD_DIR)/sm64_prelim.elf + $(V)tools/n64sym --cross $(CROSS) $(BUILD_DIR)/sm64_prelim.elf $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym # Link SM64 ELF file diff --git a/sm64.ld b/sm64.ld index 51e5285e99..cd56dcec64 100755 --- a/sm64.ld +++ b/sm64.ld @@ -541,6 +541,9 @@ SECTIONS } .gcc_except_table : { *(.gcc_except_table*) } .jcr : { KEEP (*(.jcr)) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_line_str 0 : { *(.debug_line_str) } /* DWARF 1 */ .debug 0 : { *(.debug) } .line 0 : { *(.line) } diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index 4e88eca876..c9a6995388 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -16,6 +16,9 @@ #include "printf.h" +extern void inspector_print_backtrace(void *bt, int n, int bt_skip); +int backtrace(void **buffer, int size); + // Configurable Defines #define X_KERNING 6 #define GLYPH_WIDTH 8 @@ -24,6 +27,7 @@ #define LEFT_MARGIN 10 // for crash screen prints enum crashPages { + PAGE_SIMPLE, PAGE_CONTEXT, #ifdef PUPPYPRINT_DEBUG PAGE_LOG, @@ -35,6 +39,7 @@ enum crashPages { }; char *crashPageNames[] = { + [PAGE_SIMPLE] = "(Overview)", [PAGE_CONTEXT] = "(Context)", #ifdef PUPPYPRINT_DEBUG [PAGE_LOG] = "(Log)", @@ -253,6 +258,26 @@ void crash_screen_print_fpcsr(u32 fpcsr) { } } +void draw_crash_overview(OSThread *thread, s32 cause) { + __OSThreadContext *tc = &thread->context; + + crash_screen_draw_rect(0, 20, 320, 240); + + if ((u32)parse_map != MAP_PARSER_ADDRESS) { + char *fname = parse_map(tc->pc); + crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); + } + + char *search_symbol(u32 vaddr, int *line); + + int line = -1; + char *t = search_symbol(tc->pc, &line); + + crash_screen_print(LEFT_MARGIN, 60, t); + crash_screen_print(LEFT_MARGIN, 72, "Line %d", line); + +} + void draw_crash_context(OSThread *thread, s32 cause) { __OSThreadContext *tc = &thread->context; crash_screen_draw_rect(0, 20, 320, 240); @@ -313,10 +338,6 @@ void draw_crash_log(void) { } #endif - -extern void inspector_print_backtrace(void *bt, int n, int bt_skip); -int backtrace(void **buffer, int size); - void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; u32 temp_sp = (tc->sp + 0x14); @@ -419,6 +440,7 @@ void draw_crash_screen(OSThread *thread) { 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; diff --git a/src/game/map_parser.c b/src/game/map_parser.c index d4c8c79e66..30ca8d2b2c 100644 --- a/src/game/map_parser.c +++ b/src/game/map_parser.c @@ -361,7 +361,6 @@ static char* symt_entry_file(symtable_header_t *symt, symtable_entry_t *entry, u return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); } -symtable_entry_t ALIGNED16 entry; char* __symbolize(void *vaddr, char *buf, int size) { symtable_header_t symt = symt_open(vaddr); if (symt.head[0]) { @@ -372,6 +371,7 @@ char* __symbolize(void *vaddr, char *buf, int size) { 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); @@ -576,9 +576,11 @@ static void backtrace_foreach(void (*cb)(void *arg, void *ptr), void *arg, u32 * return; #if 1 - osSyncPrintf("backtrace: %s, ra=%p, sp=%p, fp=%p ra_offset=%d, fp_offset=%d, stack_size=%d\n", + char debug[200]; + sprintf(debug, "backtrace: %s, ra=%p, sp=%p, fp=%p ra_offset=%d, fp_offset=%d, stack_size=%d\n", func.type == BT_FUNCTION ? "BT_FUNCTION" : (func.type == BT_EXCEPTION ? "BT_EXCEPTION" : (func.type == BT_FUNCTION_FRAMEPOINTER ? "BT_FRAMEPOINTER" : "BT_LEAF")), ra, sp, fp, func.ra_offset, func.fp_offset, func.stack_size); + osSyncPrintf(debug); #endif switch (func.type) { @@ -823,6 +825,32 @@ void backtrace_frame_print_compact(backtrace_frame_t *frame, char *out, int widt // out += sprintf(out, "\n"); } +char *search_symbol(void *vaddr, int *line) { + static char filebuf[100]; + + symtable_header_t symt = symt_open(vaddr); + 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 + + filebuf[0] = 0; + symt_entry_fetch(&symt, &entry, idx); + *line = entry.line; + char *file = symt_entry_file(&symt, &entry, addr, filebuf, 100); + // char *ret = file; + // file += sprintf(file, ":%d", entry.line); + return file; + } + return UNKNOWN_SYMBOL; +} + void inspector_print_backtrace(void *bt, int n, int bt_skip) { crash_screen_print(10, 45, "Backtrace:"); diff --git a/tools/n64sym.c b/tools/n64sym.c index 4ad507ef12..9740d5c0b1 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -16,6 +16,7 @@ bool flag_verbose = false; int flag_max_sym_len = 64; bool flag_inlines = true; const char *n64_inst = NULL; +const char *cross_prefix = NULL; // Printf if verbose void verbose(const char *fmt, ...) { @@ -34,11 +35,12 @@ void usage(const char *progname) fprintf(stderr, "Usage: %s [flags] []\n", progname); fprintf(stderr, "\n"); fprintf(stderr, "Command-line flags:\n"); + fprintf(stderr, " -c/--cross Cross compilation toolchain (e.g. `mips-linux-gnu-`)\n"); fprintf(stderr, " -v/--verbose Verbose output\n"); fprintf(stderr, " -m/--max-len Maximum symbol length (default: 64)\n"); fprintf(stderr, " --no-inlines Do not export inlined symbols\n"); fprintf(stderr, "\n"); - fprintf(stderr, "This program requires a libdragon toolchain installed in $N64_INST.\n"); + // fprintf(stderr, "This program requires a libdragon toolchain installed in $N64_INST.\n"); } char *stringtable = NULL; @@ -109,7 +111,7 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) cur_elf = NULL; addr2line_r = addr2line_w = NULL; } if (!addrbin) - asprintf(&addrbin, "%s/bin/mips64-elf-addr2line", n64_inst); + asprintf(&addrbin, "/usr/bin/%saddr2line", cross_prefix); const char *cmd_addr[16] = {0}; int i = 0; cmd_addr[i++] = addrbin; @@ -163,6 +165,14 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) assert(ret != -1); char *colon = strrchr(line_buf, ':'); char *file = strndup(line_buf, colon - line_buf); + char *backup = file; + file = strstr(file, "/src/"); + if (file == NULL) { + file = strstr(backup, "/asm/"); + if (file == NULL) { + file = backup; + } + } int line = atoi(colon + 1); // Add the callsite to the list @@ -190,7 +200,7 @@ bool elf_find_callsites(const char *elf) { // Start objdump to parse the disassembly of the ELF file char *cmd = NULL; - asprintf(&cmd, "%s/bin/mips64-elf-objdump -d %s", n64_inst, elf); + asprintf(&cmd, "/usr/bin/%sobjdump -d %s", cross_prefix, elf); verbose("Running: %s\n", cmd); FILE *disasm = popen(cmd, "r"); if (!disasm) { @@ -375,6 +385,12 @@ int main(int argc, char *argv[]) return 1; } flag_max_sym_len = atoi(argv[i]); + } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cross")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + cross_prefix = argv[i]; } else { fprintf(stderr, "invalid flag: %s\n", argv[i]); return 1; @@ -386,15 +402,6 @@ int main(int argc, char *argv[]) return 1; } - // Find n64 installation directory - n64_inst = n64_toolchain_dir(); - if (!n64_inst) { - // Do not mention N64_GCCPREFIX in the error message, since it is - // a seldom used configuration. - fprintf(stderr, "Error: N64_INST environment variable not set\n"); - return 1; - } - const char *infn = argv[i]; if (i < argc-1) outfn = argv[i+1]; From 7c8f95be4dafe51af09423dcaabc730023e0c036 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 18 Jun 2025 22:34:53 -0400 Subject: [PATCH 018/109] remove unnecessary debug sections; pretty up page 0 --- sm64.ld | 9 --------- src/game/crash_screen.c | 22 +++++++++++++++------- src/game/map_parser.c | 6 +++--- src/game/mario_actions_stationary.c | 6 +++--- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/sm64.ld b/sm64.ld index cd56dcec64..218bddd74c 100755 --- a/sm64.ld +++ b/sm64.ld @@ -534,16 +534,7 @@ SECTIONS /* DWARF debug sections. Symbols in the DWARF debugging sections are relative to the beginning of the section so we begin them at 0. */ - .eh_frame_hdr : { *(.eh_frame_hdr) } - .eh_frame : { - __EH_FRAME_BEGIN__ = .; /* Define symbol for accessing eh_frame section */ - KEEP (*(.eh_frame)) - } - .gcc_except_table : { *(.gcc_except_table*) } - .jcr : { KEEP (*(.jcr)) } - .debug_loclists 0 : { *(.debug_loclists) } .debug_rnglists 0 : { *(.debug_rnglists) } - .debug_line_str 0 : { *(.debug_line_str) } /* DWARF 1 */ .debug 0 : { *(.debug) } .line 0 : { *(.line) } diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index c9a6995388..d5fec2f092 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -16,6 +16,8 @@ #include "printf.h" +extern char *strstr(char *, char *); +extern char *search_symbol(u32 vaddr, int *line); extern void inspector_print_backtrace(void *bt, int n, int bt_skip); int backtrace(void **buffer, int size); @@ -263,18 +265,17 @@ void draw_crash_overview(OSThread *thread, s32 cause) { crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_print(LEFT_MARGIN, 20, "Thread %d (%s)", thread->id, gCauseDesc[cause]); + if ((u32)parse_map != MAP_PARSER_ADDRESS) { char *fname = parse_map(tc->pc); crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); } - char *search_symbol(u32 vaddr, int *line); - int line = -1; char *t = search_symbol(tc->pc, &line); - - crash_screen_print(LEFT_MARGIN, 60, t); - crash_screen_print(LEFT_MARGIN, 72, "Line %d", line); + crash_screen_print(LEFT_MARGIN, 60, "File: %s", t+1); + crash_screen_print(LEFT_MARGIN, 72, "Address: 0x%08X", tc->pc); } @@ -376,9 +377,16 @@ void draw_disasm(OSThread *thread) { char *disasm = insn_disasm(addr, (addr == tc->pc)); if (disasm[0] == 0) { - crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%08X", addr); + crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); } else { - crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%s", disasm); + if (strstr(disasm, "j")) { + int line = -1; + search_symbol(addr, &line); + if (line != -1) { + crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%d:", line); + } + } + crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%s", disasm); } } diff --git a/src/game/map_parser.c b/src/game/map_parser.c index 30ca8d2b2c..c853a1851a 100644 --- a/src/game/map_parser.c +++ b/src/game/map_parser.c @@ -833,9 +833,9 @@ char *search_symbol(void *vaddr, int *line) { 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); - } + // while (!ADDRENTRY_IS_FUNC(a)) { + // a = symt_addrtab_entry(&symt, --idx); + // } symtable_entry_t ALIGNED16 entry; // Read the symbol name diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index 1fe1011f0b..883834f128 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -104,9 +104,6 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { - if(m->controller->buttonPressed&L_TRIG){ - *(vs8*)0=m->controller->buttonPressed << 2; - } if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } @@ -118,6 +115,9 @@ s32 act_idle(struct MarioState *m) { if (!(m->actionArg & 1) && m->health < 0x300) { return set_mario_action(m, ACT_PANTING, 0); } + if(m->controller->buttonPressed&L_TRIG){ + *(vs8*)0=m->controller->buttonPressed << 2; + } if (check_common_idle_cancels(m)) { return TRUE; From ac51866797924f870e65d1fee1d9befbe33d4892 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 18 Jun 2025 22:47:42 -0400 Subject: [PATCH 019/109] try the old heuristic --- src/game/crash_screen.c | 46 ++++++++++++++++++++++++++++------------- src/game/map_parser.c | 26 +++++++++++++++++------ 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index d5fec2f092..a06d1bfd57 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -102,7 +102,7 @@ char *gFpcsrDesc[6] = { extern u64 osClockRate; extern far char *parse_map(u32 pc); extern far void map_data_init(void); -extern far char *find_function_in_stack(u32 *sp); +extern far char *find_function_in_stack(u32 *sp, int *line); struct { OSThread thread; @@ -270,13 +270,17 @@ void draw_crash_overview(OSThread *thread, s32 cause) { if ((u32)parse_map != MAP_PARSER_ADDRESS) { char *fname = parse_map(tc->pc); crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); + + int line = -1; + char *t = search_symbol(tc->pc, &line); + if (line != -1) { + crash_screen_print(LEFT_MARGIN, 60, "File: %s", t+1); + } } - int line = -1; - char *t = search_symbol(tc->pc, &line); - crash_screen_print(LEFT_MARGIN, 60, "File: %s", t+1); crash_screen_print(LEFT_MARGIN, 72, "Address: 0x%08X", tc->pc); + // do a lil backtrace } void draw_crash_context(OSThread *thread, s32 cause) { @@ -343,19 +347,33 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; u32 temp_sp = (tc->sp + 0x14); - crash_screen_draw_rect(0, 20, 320, 210); - crash_screen_print(LEFT_MARGIN, 25, "Backtrace from %08X:", temp_sp); + crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", temp_sp); + if ((u32) parse_map == MAP_PARSER_ADDRESS) { + crash_screen_print(LEFT_MARGIN, 35, "Curr Func: None"); + } else { + crash_screen_print(LEFT_MARGIN, 35, "Curr Func: %s", parse_map(tc->pc)); + } osWritebackDCacheAll(); - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, (45), "STACK TRACE DISABLED"); - } else { - #define LINE_COUNT 10 - void *lines[LINE_COUNT]; - int bt_skip = 0; // how many frames to skip - backtrace(lines, LINE_COUNT); - inspector_print_backtrace(lines, LINE_COUNT, bt_skip); + for (int i = 0; i < 18; i++) { + if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "Stack Trace Disabled"); + break; + } else { + if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { + return; + } + + int line = -1; + char *fname = find_function_in_stack(&temp_sp, &line); + if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%08X)", temp_sp, *(u32*)temp_sp); + } else { + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%s:%d)", temp_sp, fname, line); + } + } } } diff --git a/src/game/map_parser.c b/src/game/map_parser.c index c853a1851a..03d9e2a4a7 100644 --- a/src/game/map_parser.c +++ b/src/game/map_parser.c @@ -9,6 +9,7 @@ #include "cop0.h" extern u32 strstr(char *, char *); +char *search_symbol(void *vaddr, int *line); /** * @brief Exception codes @@ -396,23 +397,36 @@ char *get_filename(u32 addr) { return file_name; } -char *find_function_in_stack(u32 *sp) { +char *find_function_in_stack(u32 *sp, int *line) { + char *ret = NULL; + u32 val = *sp; + + #define CALLSITE_OFFSET 0 for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { - u32 val = *sp; + val = *sp; val = *(u32 *)val; *sp += 4; + if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { - return parse_map(val); + ret = parse_map(val + CALLSITE_OFFSET); + break; } else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { - return parse_map(val); + ret = parse_map(val + CALLSITE_OFFSET); + break; } else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { - return parse_map(val); + ret = parse_map(val + CALLSITE_OFFSET); + break; } } - return NULL; + + if (ret) { + search_symbol((void*)(val + CALLSITE_OFFSET), line); + } + + return ret; } From 0aa4cc82f1a3ef4a6dc75a8e1854ddbb1f33647d Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 18 Jun 2025 22:58:08 -0400 Subject: [PATCH 020/109] ok maybe i do need a new heuristic --- src/game/crash_screen.c | 11 ++++++----- src/game/map_parser.c | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index a06d1bfd57..c835e9b521 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -345,7 +345,7 @@ void draw_crash_log(void) { void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; - u32 temp_sp = (tc->sp + 0x14); + u32 temp_sp = (tc->sp - 0x24); crash_screen_draw_rect(0, 20, 320, 240); crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", temp_sp); @@ -368,11 +368,12 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { int line = -1; char *fname = find_function_in_stack(&temp_sp, &line); - if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%08X)", temp_sp, *(u32*)temp_sp); - } else { - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%s:%d)", temp_sp, fname, line); + while ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { + // crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%08X)", temp_sp, *(u32*)temp_sp); + fname = find_function_in_stack(&temp_sp, &line); } + + crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%s:%d)", temp_sp, fname, line); } } } diff --git a/src/game/map_parser.c b/src/game/map_parser.c index 03d9e2a4a7..cfa2013834 100644 --- a/src/game/map_parser.c +++ b/src/game/map_parser.c @@ -401,7 +401,7 @@ char *find_function_in_stack(u32 *sp, int *line) { char *ret = NULL; u32 val = *sp; - #define CALLSITE_OFFSET 0 + #define CALLSITE_OFFSET 8 for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { val = *sp; val = *(u32 *)val; From c94386d3fca7d8c38899e780d5414ae015e537b0 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 18:46:29 -0400 Subject: [PATCH 021/109] Add line support for all lines --- sm64.ld | 1 + src/game/crash_screen.c | 5 +- tools/n64sym.c | 127 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/sm64.ld b/sm64.ld index 218bddd74c..e9ebdcb1e8 100755 --- a/sm64.ld +++ b/sm64.ld @@ -535,6 +535,7 @@ SECTIONS Symbols in the DWARF debugging sections are relative to the beginning of the section so we begin them at 0. */ .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_line_str 0 : { *(.debug_line_str) } /* DWARF 1 */ .debug 0 : { *(.debug) } .line 0 : { *(.line) } diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index c835e9b521..8af2af45e8 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -274,11 +274,12 @@ void draw_crash_overview(OSThread *thread, s32 cause) { int line = -1; char *t = search_symbol(tc->pc, &line); if (line != -1) { - crash_screen_print(LEFT_MARGIN, 60, "File: %s", t+1); + crash_screen_print(LEFT_MARGIN, 60, "File: %s", t); + crash_screen_print(LEFT_MARGIN, 72, "Line: %d", line); } } - crash_screen_print(LEFT_MARGIN, 72, "Address: 0x%08X", tc->pc); + crash_screen_print(LEFT_MARGIN, 84, "Address: 0x%08X", tc->pc); // do a lil backtrace } diff --git a/tools/n64sym.c b/tools/n64sym.c index 9740d5c0b1..e931268977 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "common/subprocess.h" #include "common/polyfill.h" @@ -45,6 +46,8 @@ void usage(const char *progname) char *stringtable = NULL; struct { char *key; int value; } *string_hash = NULL; +// For non-callsites, make sure only one entry per line gets in +struct { char *key; int value; } *linedb = NULL; int stringtable_add(char *word) { @@ -196,6 +199,120 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) getline(&line_buf, &line_buf_size, addr2line_r); } +void line_add(const char *elf, uint32_t addr) { + // We keep one addr2line process open for the last ELF file we processed. + // This allows to convert multiple symbols very fast, avoiding spawning a + // new process for each symbol. + // NOTE: we cannot use popen() here because on some platforms (eg. glibc) + // it only allows a single direction pipe, and we need both directions. + // So we rely on the subprocess library for this. + static char *addrbin = NULL; + static struct subprocess_s subp; + static FILE *addr2line_w = NULL, *addr2line_r = NULL; + static const char *cur_elf = NULL; + static char *line_buf = NULL; + static size_t line_buf_size = 0; + + // Check if this is a new ELF file (or it's the first time we run this function) + if (!cur_elf || strcmp(cur_elf, elf)) { + if (cur_elf) { + subprocess_terminate(&subp); + cur_elf = NULL; addr2line_r = addr2line_w = NULL; + } + if (!addrbin) + asprintf(&addrbin, "/usr/bin/%saddr2line", cross_prefix); + + const char *cmd_addr[16] = {0}; int i = 0; + cmd_addr[i++] = addrbin; + cmd_addr[i++] = "--addresses"; + cmd_addr[i++] = "--functions"; + cmd_addr[i++] = "--demangle"; + if (flag_inlines) cmd_addr[i++] = "--inlines"; + cmd_addr[i++] = "--exe"; + cmd_addr[i++] = elf; + + if (subprocess_create(cmd_addr, subprocess_option_no_window, &subp) != 0) { + fprintf(stderr, "Error: cannot run: %s\n", addrbin); + exit(1); + } + addr2line_w = subprocess_stdin(&subp); + addr2line_r = subprocess_stdout(&subp); + cur_elf = elf; + } + + // Send the address to addr2line and fetch back the symbol and the function name + // Since we activated the "--inlines" option, addr2line produces an unknown number + // of output lines. This is a problem with pipes, as we don't know when to stop. + // Thus, we always add a dummy second address (0xffffffff) so that we stop when we see the + // reply for it. NOTE: we can't use 0x0 as dummy address as DSOs are partially + // linked so there are really symbols at 0. + fprintf(addr2line_w, "%08x\n0xffffffff\n", addr); + fflush(addr2line_w); + + // First line is the address. It's just an echo, so ignore it. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + assert(n >= 2 && strncmp(line_buf, "0x", 2) == 0); + + // Add one symbol for each inlined function + bool at_least_one = false; + while (1) { + // First line is the function name. If instead it's the dummy 0x0 address, + // it means that we're done. + int n = getline(&line_buf, &line_buf_size, addr2line_r); + if (strncmp(line_buf, "0xffffffff", 10) == 0) break; + n--; + if (line_buf[n-1] == '\r') n--; // Remove trailing \r (Windows) + + // If the function of name is longer than 64 bytes, truncate it. This also + // avoid paradoxically long function names like in C++ that can even be + // several thousands of characters long. + char *func = strndup(line_buf, MIN(n, flag_max_sym_len)); + if (n > flag_max_sym_len) strcpy(&func[flag_max_sym_len-3], "..."); + + // Second line is the file name and line number + int ret = getline(&line_buf, &line_buf_size, addr2line_r); + assert(ret != -1); + char *colon = strrchr(line_buf, ':'); + char *file = strndup(line_buf, colon - line_buf); + char *backup = file; + file = strstr(file, "src/"); + if (file == NULL) { + file = strstr(backup, "asm/"); + if (file == NULL) { + file = backup; + } + } + int line = atoi(colon + 1); + + char fileline_key[PATH_MAX]; + sprintf(fileline_key, "%s:%d", file, line); + + // Add the addr to the list, only if that line entry doesn't already exist + if (stbds_shgetp_null(linedb, fileline_key) == NULL) { + verbose("New line: %d\n", line); + stbds_arrput(symtable, ((struct symtable_s) { + .uuid = stbds_arrlen(symtable), + .addr = addr, + .func = func, + .file = file, + .line = line, + .is_func = false, + .is_inline = true, + })); + + stbds_shput(linedb, fileline_key, line); + } + at_least_one = true; + } + assert(at_least_one); + symtable[stbds_arrlen(symtable)-1].is_inline = false; + + // Read and skip the two remaining lines (function and file position) + // that refers to the dummy 0x0 address + getline(&line_buf, &line_buf_size, addr2line_r); + getline(&line_buf, &line_buf_size, addr2line_r); +} + bool elf_find_callsites(const char *elf) { // Start objdump to parse the disassembly of the ELF file @@ -212,6 +329,8 @@ bool elf_find_callsites(const char *elf) char *line = NULL; size_t line_size = 0; while (getline(&line, &line_size, disasm) != -1) { // Find the functions + // format: + // 807fe800 : if (strstr(line, ">:")) { uint32_t addr = strtoul(line, NULL, 16); // Prevent segmented addresses for now @@ -227,6 +346,14 @@ bool elf_find_callsites(const char *elf) symbol_add(elf, addr, false); } } + // See if adding every single text address is worth + if (line[8] == ':') { + uint32_t addr = strtoul(line, NULL, 16); + // Prevent segmented addresses for now + if ((addr & 0xFF000000) == 0x80000000) { + line_add(elf, addr); + } + } } free(line); free(cmd); From 23a009b9f743f3248f83bc92943b5af9246e274e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 20:46:36 -0400 Subject: [PATCH 022/109] start moving files to src/debugger --- src/{game/insn_disasm.c => debugger/disasm.c} | 113 +-- src/debugger/disasm.h | 86 ++ src/debugger/map_parser.c | 187 ++++ src/debugger/map_parser.h | 3 + src/debugger/symtable.c | 168 ++++ src/debugger/symtable.h | 69 ++ src/game/map_parser.c | 901 ------------------ 7 files changed, 523 insertions(+), 1004 deletions(-) rename src/{game/insn_disasm.c => debugger/disasm.c} (88%) create mode 100644 src/debugger/disasm.h create mode 100644 src/debugger/map_parser.c create mode 100644 src/debugger/map_parser.h create mode 100644 src/debugger/symtable.c create mode 100644 src/debugger/symtable.h delete mode 100644 src/game/map_parser.c diff --git a/src/game/insn_disasm.c b/src/debugger/disasm.c similarity index 88% rename from src/game/insn_disasm.c rename to src/debugger/disasm.c index 7134ada907..0d109b6276 100644 --- a/src/game/insn_disasm.c +++ b/src/debugger/disasm.c @@ -4,90 +4,7 @@ #include "sm64.h" #include "macros.h" #include "farcall.h" - -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, -}; - -extern far char *parse_map(u32 pc); -static char insn_as_string[100]; - -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" +#include "disasm.h" InsnTemplate insn_db[] = { // We want instructions with opcodes first (prioritized) @@ -221,7 +138,7 @@ char __mips_fpreg[][5] = { extern char* __symbolize(void *vaddr, char *buf, int size); // Last Resort C0/C1 disassembler, from libdragon -static void c1_disasm(u32 *ptr, char *out, int n) { +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", @@ -292,15 +209,13 @@ static void c1_disasm(u32 *ptr, char *out, int n) { } } -char *cop1_insn_disasm(InsnData *pc, u32 isPC) { - char *strp = &insn_as_string[0]; - - c1_disasm((u32 *)pc, insn_as_string, 100); +char *cop1_insn_disasm(InsnData *pc) { + c1_disasm((u32 *)pc, insn_as_string); return insn_as_string; } -char *branch_insn_disasm(InsnData insn, u32 isPC) { +char *branch_insn_disasm(InsnData insn) { static char *insn_names[] = { [0b00001] = "bgez", [0b00011] = "bgezl", @@ -317,33 +232,29 @@ char *branch_insn_disasm(InsnData insn, u32 isPC) { for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; - sprintf(strp, "%-9s %s %04X %s", insn_names[insn.b.sub], rs, offset, isPC ? "<-- CRASH" : ""); + sprintf(strp, "%-9s %s %04X", insn_names[insn.b.sub], rs, offset); return insn_as_string; } -char *insn_disasm(InsnData *addr, u32 isPC) { +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 - if (isPC) { - return "nop <-- CRASH"; - } else { - return "nop"; - } + return "nop"; } if (insn.i.opcode == OP_BRANCH) { - return branch_insn_disasm(insn, isPC); + 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, isPC); + return cop1_insn_disasm(addr); } for (int i = 0; i < ARRAY_COUNT(insn_as_string); i++) insn_as_string[i] = 0; @@ -457,9 +368,5 @@ char *insn_disasm(InsnData *addr, u32 isPC) { strp += sprintf(strp, "unimpl %08X", insn.d); } - if (isPC) { - sprintf(strp, " <-- CRASH"); - } - return insn_as_string; } diff --git a/src/debugger/disasm.h b/src/debugger/disasm.h new file mode 100644 index 0000000000..010fb92f25 --- /dev/null +++ b/src/debugger/disasm.h @@ -0,0 +1,86 @@ +#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, +}; + +extern far char *parse_map(u32 pc); +static char insn_as_string[100]; + +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" + diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c new file mode 100644 index 0000000000..23ae93375e --- /dev/null +++ b/src/debugger/map_parser.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include "segments.h" +#include "memory.h" +#include "backtrace.h" +#include "cop0.h" + +/** + * @brief Exception codes + */ +typedef enum { + EXCEPTION_CODE_INTERRUPT = 0, + EXCEPTION_CODE_TLB_MODIFICATION = 1, + EXCEPTION_CODE_TLB_LOAD_I_MISS = 2, + EXCEPTION_CODE_TLB_STORE_MISS = 3, + EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR = 4, + EXCEPTION_CODE_STORE_ADDRESS_ERROR = 5, + EXCEPTION_CODE_I_BUS_ERROR = 6, + EXCEPTION_CODE_D_BUS_ERROR = 7, + EXCEPTION_CODE_SYS_CALL = 8, + EXCEPTION_CODE_BREAKPOINT = 9, + EXCEPTION_CODE_RESERVED_INSTRUCTION = 10, + EXCEPTION_CODE_COPROCESSOR_UNUSABLE = 11, + EXCEPTION_CODE_ARITHMETIC_OVERFLOW = 12, + EXCEPTION_CODE_TRAP = 13, + EXCEPTION_CODE_FLOATING_POINT = 15, + EXCEPTION_CODE_WATCH = 23, +} exception_code_t; + +/** @brief Check if addr is a valid PC address */ +static u8 is_valid_address(u32 addr) +{ + // TODO: for now we only handle RAM (cached access). This should be extended to handle + // TLB-mapped addresses for instance. + return addr >= 0x80000400 && addr < 0x80800000 && (addr & 3) == 0; +} + +#define offsetof(type, member) __builtin_offsetof (type, member) + +// 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 + +/** @brief Address of the SYMT symbol table in the rompak. */ +static u32 SYMT_ROM = 0xFFFFFFFF; + +/** @brief Placeholder used in frames where symbols are not available */ +static const char *UNKNOWN_SYMBOL = "???"; + +extern void dma_read(void *dest, void *srcStart, void *srcEnd); + +void map_parser_dma(void *dst, void *src, size_t size) { + // char dm[500]; + // sprintf(dm, "DMA [%08X] <-- [%08X %08X] (%08X)", dst, src, size, SYMT_ROM); + // osSyncPrintf(dm); + // dma_read(dst, src, src+ALIGN8(size)); + headless_dma((u32)src, dst, size); + while (headless_pi_status() & PI_STATUS_IO_BUSY); +} + +#define STACK_TRAVERSAL_LIMIT 100 + +/** @brief Function alignment enfored by the compiler (-falign-functions). + * + * @note This must be kept in sync with n64.mk. + */ +#define FUNCTION_ALIGNMENT 32 + +extern u8 _mapDataSegmentRomStart[]; + +extern u8 _mainSegmentStart[]; +extern u8 _mainSegmentTextEnd[]; +extern u8 _engineSegmentStart[]; +extern u8 _engineSegmentTextEnd[]; +extern u8 _goddardSegmentStart[]; +extern u8 _goddardSegmentTextEnd[]; + +char* __symbolize(void *vaddr, char *buf, int size) { + symtable_header_t symt = symt_open(vaddr); + 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]; + sprintf(lbuf, "+0x%lx", addr - ADDRENTRY_ADDR(a)); + return strcat(func, lbuf); + } + sprintf(buf, "%s", UNKNOWN_SYMBOL); + return buf; +} + +char *parse_map(u32 addr) { + static char map_name[64] ALIGNED16; + __symbolize((u32*)addr, map_name, sizeof(map_name)); + + return map_name; +} + +char *get_filename(u32 addr) { + static char file_name[64] ALIGNED16; + + return file_name; +} + +char *find_function_in_stack(u32 *sp, int *line) { + char *ret = NULL; + u32 val = *sp; + + #define CALLSITE_OFFSET 8 + for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { + val = *sp; + val = *(u32 *)val; + *sp += 4; + + + if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { + ret = parse_map(val + CALLSITE_OFFSET); + break; + } + else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { + ret = parse_map(val + CALLSITE_OFFSET); + break; + } + else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { + ret = parse_map(val + CALLSITE_OFFSET); + break; + } + } + + if (ret) { + search_symbol((void*)(val + CALLSITE_OFFSET), line); + } + + return ret; +} + +void crash_screen_print(s32 x, s32 y, const char *fmt, ...); + +char *search_symbol(void *vaddr, int *line) { + static char filebuf[100]; + + symtable_header_t symt = symt_open(vaddr); + if (symt.head[0]) { + u32 addr = (u32)vaddr; + int idx = 0; + addrtable_entry_t a = symt_addrtab_search(&symt, addr, &idx); + + symtable_entry_t ALIGNED16 entry; + + // Read the symbol name + filebuf[0] = 0; + symt_entry_fetch(&symt, &entry, idx); + *line = entry.line; + char *file = symt_entry_file(&symt, &entry, addr, filebuf, sizeof(filebuf)); + return file; + } + return UNKNOWN_SYMBOL; +} diff --git a/src/debugger/map_parser.h b/src/debugger/map_parser.h new file mode 100644 index 0000000000..53962305f3 --- /dev/null +++ b/src/debugger/map_parser.h @@ -0,0 +1,3 @@ +#pragma once + +char *search_symbol(void *vaddr, int *line); diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c new file mode 100644 index 0000000000..0a52e52341 --- /dev/null +++ b/src/debugger/symtable.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include "segments.h" +#include "memory.h" +#include "backtrace.h" +#include "cop0.h" + +#include "symtable.h" + +/** + * @brief Open the SYMT symbol table in the rompak. + * + * If not found, return a null header. + */ +static symtable_header_t symt_open(void *addr) { + SYMT_ROM = (u32)_mapDataSegmentRomStart; + + // We don't _need_ to pass by value for this right + 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("backtrace: invalid symbol table found at 0x%08lx\n", SYMT_ROM); + SYMT_ROM = 0; + return (symtable_header_t){0}; + } + if (symt_header.version != 2) { + osSyncPrintf("backtrace: 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 + */ +static 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) + */ +static 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) + */ +static 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 + */ +static 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 +static 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, u32 addr, char *buf, int size) { + return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); +} diff --git a/src/debugger/symtable.h b/src/debugger/symtable.h new file mode 100644 index 0000000000..65a60ecccc --- /dev/null +++ b/src/debugger/symtable.h @@ -0,0 +1,69 @@ +#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 + diff --git a/src/game/map_parser.c b/src/game/map_parser.c deleted file mode 100644 index cfa2013834..0000000000 --- a/src/game/map_parser.c +++ /dev/null @@ -1,901 +0,0 @@ -#include -#include -#include -#include -#include -#include "segments.h" -#include "memory.h" -#include "backtrace.h" -#include "cop0.h" - -extern u32 strstr(char *, char *); -char *search_symbol(void *vaddr, int *line); - -/** - * @brief Exception codes - */ -typedef enum { - EXCEPTION_CODE_INTERRUPT = 0, - EXCEPTION_CODE_TLB_MODIFICATION = 1, - EXCEPTION_CODE_TLB_LOAD_I_MISS = 2, - EXCEPTION_CODE_TLB_STORE_MISS = 3, - EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR = 4, - EXCEPTION_CODE_STORE_ADDRESS_ERROR = 5, - EXCEPTION_CODE_I_BUS_ERROR = 6, - EXCEPTION_CODE_D_BUS_ERROR = 7, - EXCEPTION_CODE_SYS_CALL = 8, - EXCEPTION_CODE_BREAKPOINT = 9, - EXCEPTION_CODE_RESERVED_INSTRUCTION = 10, - EXCEPTION_CODE_COPROCESSOR_UNUSABLE = 11, - EXCEPTION_CODE_ARITHMETIC_OVERFLOW = 12, - EXCEPTION_CODE_TRAP = 13, - EXCEPTION_CODE_FLOATING_POINT = 15, - EXCEPTION_CODE_WATCH = 23, -} exception_code_t; - -/** @brief Check if addr is a valid PC address */ -static u8 is_valid_address(u32 addr) -{ - // TODO: for now we only handle RAM (cached access). This should be extended to handle - // TLB-mapped addresses for instance. - return addr >= 0x80000400 && addr < 0x80800000 && (addr & 3) == 0; -} - -#define offsetof(type, member) __builtin_offsetof (type, member) - -// 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 - -/** @brief Address of the SYMT symbol table in the rompak. */ -static u32 SYMT_ROM = 0xFFFFFFFF; - -/** @brief Placeholder used in frames where symbols are not available */ -static const char *UNKNOWN_SYMBOL = "???"; - -extern void dma_read(void *dest, void *srcStart, void *srcEnd); - -void map_parser_dma(void *dst, void *src, size_t size) { - // char dm[500]; - // sprintf(dm, "DMA [%08X] <-- [%08X %08X] (%08X)", dst, src, size, SYMT_ROM); - // osSyncPrintf(dm); - // dma_read(dst, src, src+ALIGN8(size)); - headless_dma((u32)src, dst, size); - while (headless_pi_status() & PI_STATUS_IO_BUSY); -} - -#define STACK_TRAVERSAL_LIMIT 100 - -/** @brief Function alignment enfored by the compiler (-falign-functions). - * - * @note This must be kept in sync with n64.mk. - */ -#define FUNCTION_ALIGNMENT 32 - -extern u8 _mapDataSegmentRomStart[]; - -extern u8 _mainSegmentStart[]; -extern u8 _mainSegmentTextEnd[]; -extern u8 _engineSegmentStart[]; -extern u8 _engineSegmentTextEnd[]; -extern u8 _goddardSegmentStart[]; -extern u8 _goddardSegmentTextEnd[]; - -/** - * @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; - -void print_header(symtable_header_t *h) { - char tt[1000]; - char *tp = tt; - - tp += sprintf(tp, "HEAD: %s\n", h->head); - tp += sprintf(tp, "VER: %08X\n", h->version); - #define PRINT(field) tp += sprintf(tp, #field ": %08X\n", field); - PRINT(h->addrtab_off); - PRINT(h->addrtab_size); - PRINT(h->symtab_off); - PRINT(h->symtab_size); - PRINT(h->strtab_off); - PRINT(h->strtab_size); - - osSyncPrintf(tt); -} - -u8 symHeap[0x4000]; -u32 symIdx = 0; -void *malloc(size_t size) { - return &symHeap[symIdx += ALIGN16(size)]; -} - -/** @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; - -void print_entry(symtable_entry_t *h) { - char tt[1000]; - char *tp = tt; - - #define PRINT(field) tp += sprintf(tp, #field ": %08X\n", field); - PRINT(h->func_sidx); - PRINT(h->file_sidx); - PRINT(h->func_len); - PRINT(h->file_len); - PRINT(h->line); - PRINT(h->func_off); - - osSyncPrintf(tt); -} - -/** - * @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 - -/** - * @brief Open the SYMT symbol table in the rompak. - * - * If not found, return a null header. - */ -static symtable_header_t symt_open(void *addr) { - SYMT_ROM = (u32)_mapDataSegmentRomStart; - - // We don't _need_ to pass by value for this right - 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("backtrace: invalid symbol table found at 0x%08lx\n", SYMT_ROM); - SYMT_ROM = 0; - return (symtable_header_t){0}; - } - if (symt_header.version != 2) { - osSyncPrintf("backtrace: 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 - */ -static 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) - */ -static 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) - */ -static 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 - */ -static 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 -static 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 -static char* symt_entry_file(symtable_header_t *symt, symtable_entry_t *entry, u32 addr, char *buf, int size) -{ - return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); -} - -char* __symbolize(void *vaddr, char *buf, int size) { - symtable_header_t symt = symt_open(vaddr); - 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]; - sprintf(lbuf, "+0x%lx", addr - ADDRENTRY_ADDR(a)); - return strcat(func, lbuf); - } - sprintf(buf, "%s", UNKNOWN_SYMBOL); - return buf; -} - -char *parse_map(u32 addr) { - static char map_name[64] ALIGNED16; - __symbolize((u32*)addr, map_name, sizeof(map_name)); - - return map_name; -} - -char *get_filename(u32 addr) { - static char file_name[64] ALIGNED16; - - return file_name; -} - -char *find_function_in_stack(u32 *sp, int *line) { - char *ret = NULL; - u32 val = *sp; - - #define CALLSITE_OFFSET 8 - for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { - val = *sp; - val = *(u32 *)val; - *sp += 4; - - - if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET); - break; - } - else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET); - break; - } - else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET); - break; - } - } - - if (ret) { - search_symbol((void*)(val + CALLSITE_OFFSET), line); - } - - return ret; -} - - - -/** - * @brief Analyze a function to find out its stack frame layout and properties (useful for backtracing). - * - * This function implements the core heuristic used by the backtrace engine. It analyzes the actual - * code of a function in memory instruction by instruction, trying to find out whether the function - * uses a stack frame or not, whether it uses a frame pointer, and where the return address is stored. - * - * Since we do not have DWARF informations or similar metadata, we can just do educated guesses. A - * mistake in the heuristic will result probably in a wrong backtrace from this point on. - * - * The heuristic works as follows: - * - * * Most functions do have a stack frame. In fact, 99.99% of the functions you can find in a call stack - * must have a stack frame, because the only functions without a stack frame are leaf functions (functions - * that do not call other functions), which in turns can never be part of a stack trace. - * * The heuristic walks the function code backwards, looking for the stack frame. Specifically, it looks - * for an instruction saving the RA register to the stack (eg: `sd $ra, nn($sp)`), and an instruction - * creating the stack frame (eg: `addiu $sp, $sp, -nn`). Once both are found, the heuristic knows how to - * fill in `.stack_size` and `.ra_offset` fields of the function description structure, and it can stop. - * * Some functions also modify $fp (the frame pointer register): sometimes, they just use it as one additional - * free register, and other times they really use it as frame pointer. If the heuristic finds the - * instruction `move $fp, $sp`, it knows that the function uses $fp as frame pointer, and will mark - * the function as BT_FUNCTION_FRAMEPOINTER. In any case, the field `.fp_offset` will be filled in - * with the offset in the stack where $fp is stored, so that the backtrace engine can track the - * current value of the register in any case. - * * The 0.01% of the functions that do not have a stack frame but appear in the call stack are leaf - * functions interrupted by exceptions. Leaf functions pose two important problems: first, $ra is - * not saved into the stack so there is no way to know where to go back. Second, there is no clear - * indication where the function begins (as we normally stops analysis when we see the stack frame - * creation). So in this case the heuristic would fail. We rely thus on two hints coming from the caller: - * * First, we expect the caller to set from_exception=TRUE, so that we know that we might potentially - * deal with a leaf function. - * * Second, the caller should provide the function start address, so that we stop the analysis when - * we reach it, and mark the function as BT_LEAF. - * * If the function start address is not provided (because e.g. the symbol table was not found and - * thus we have no information about function starts), the last ditch heuristic is to look for - * the nops that are normally used to align the function start to the FUNCTION_ALIGNMENT boundary. - * Obviously this is a very fragile heuristic (it will fail if the function required no nops to be - * properly aligned), but it is the best we can do. Worst case, in this specific case of a leaf - * function interrupted by the exception, the stack trace will be wrong from this point on. - * - * @param func Output function description structure - * @param ptr Pointer to the function code at the point where the backtrace starts. - * This is normally the point where a JAL opcode is found, as we are walking - * up the call stack. - * @param func_start Start of the function being analyzed. This is optional: the heuristic can work - * without this hint, but it is useful in certain situations (eg: to better - * walk up after an exception). - * @param from_exception If TRUE, this function was interrupted by an exception. This is a hint that - * the function *might* even be a leaf function without a stack frame, and that - * we must use special heuristics for it. - * - * @return TRUE if the backtrace can continue, FALSE if must be aborted (eg: we are within invalid memory) - */ -u8 __bt_analyze_func(bt_func_t *func, u32 *ptr, u32 func_start, u8 from_exception) -{ - *func = (bt_func_t){ - .type = is_in_exception(ptr) ? BT_EXCEPTION : BT_FUNCTION, - .stack_size = 0, .ra_offset = 0, .fp_offset = 0 - }; - - u32 addr = (u32)ptr; - while (1) { - // Validate that we can dereference the virtual address without raising an exception - // TODO: enhance this check with more valid ranges. - if (!is_valid_address(addr)) { - // This address is invalid, probably something is corrupted. Avoid looking further. - osSyncPrintf("backtrace: interrupted because of invalid return address 0x%08lx\n", addr); - return FALSE; - } - u32 op = *(u32*)addr; - if (MIPS_OP_ADDIU_SP(op) || MIPS_OP_DADDIU_SP(op)) { - // Extract the stack size only from the start of the function, where the - // stack is allocated (negative value). This is important because the RA - // could point to a leaf basis block at the end of the function (like in the - // assert case), and if we picked the positive ADDIU SP at the end of the - // proper function body, we might miss a fp_offset. - if (op & 0x8000) - func->stack_size = -(s16)(op & 0xFFFF); - } else if (MIPS_OP_SD_RA_SP(op)) { - osSyncPrintf("sw ra,sp"); - func->ra_offset = (s16)(op & 0xFFFF) + 4; // +4 = load low 32 bit of RA - // If we found a stack size, it might be a red herring (an alloca); we need one - // happening "just before" sd ra,xx(sp) - func->stack_size = 0; - } else if (MIPS_OP_SD_FP_SP(op)) { - func->fp_offset = (s16)(op & 0xFFFF) + 4; // +4 = load low 32 bit of FP - } else if (MIPS_OP_LUI_GP(op)) { - // Loading gp is commonly done in _start, so it's useless to go back more - return FALSE; - } else if (MIPS_OP_MOVE_FP_SP(op)) { - // This function uses the frame pointer. Uses that as base of the stack. - // Even with -fomit-frame-pointer (default on our toolchain), the compiler - // still emits a framepointer for functions using a variable stack size - // (eg: using alloca() or VLAs). - func->type = BT_FUNCTION_FRAMEPOINTER; - } - // We found the stack frame size and the offset of the return address in the stack frame - // We can stop looking and process the frame - if (func->stack_size != 0 && func->ra_offset != 0) - break; - if (from_exception) { - // The function we are analyzing was interrupted by an exception, so it might - // potentially be a leaf function (no stack frame). We need to make sure to stop - // at the beginning of the function and mark it as leaf function. Use - // func_start if specified, or try to guess using the nops used to align the function - // (crossing fingers that they're there). - if (addr == func_start) { - // The frame that was interrupted by an interrupt handler is a special case: the - // function could be a leaf function with no stack. If we were able to identify - // the function start (via the symbol table) and we reach it, it means that - // we are in a real leaf function. - func->type = BT_LEAF; - break; - } else if (!func_start && MIPS_OP_NOP(op) && (addr + 4) % FUNCTION_ALIGNMENT == 0) { - // If we are in the frame interrupted by an interrupt handler, and we does not know - // the start of the function (eg: no symbol table), then try to stop by looking for - // a NOP that pads between functions. Obviously the NOP we find can be either a FALSE - // positive or a FALSE negative, but we can't do any better without symbols. - func->type = BT_LEAF; - break; - } - } - addr -= 4; - } - return TRUE; -} - -static void backtrace_foreach(void (*cb)(void *arg, void *ptr), void *arg, u32 *ra, u32 *sp, u32 *fp, u32 *exception_ra) -{ - /* - * This function is called in very risky contexts, for instance as part of an exception - * handler or during an assertion. We try to always provide as much information as - * possible in these cases, with graceful degradation if something more elaborate cannot - * be extracted. Thus, this function: - * - * * Must not use malloc(). The heap might be corrupted or empty. - * * Must not use assert(), because that might trigger recursive assertions. - * * Must avoid raising exceptions. Specifically, it must avoid risky memory accesses - * to wrong addresses. - */ - - // #if BACKTRACE_DEBUG - osSyncPrintf("backtrace: start\n"); - // #endif - - u32 func_start = 0; // Start of the current function (when known) - - // Start calling the callback for the backtrace entry point - cb(arg, ra); - - while (1) { - // Analyze the function pointed by ra, passing information about the previous exception frame if any. - // If the analysis fail (for invalid memory accesses), stop right away. - bt_func_t func; - if (!__bt_analyze_func(&func, ra, func_start, (u8)exception_ra)) - return; - - #if 1 - char debug[200]; - sprintf(debug, "backtrace: %s, ra=%p, sp=%p, fp=%p ra_offset=%d, fp_offset=%d, stack_size=%d\n", - func.type == BT_FUNCTION ? "BT_FUNCTION" : (func.type == BT_EXCEPTION ? "BT_EXCEPTION" : (func.type == BT_FUNCTION_FRAMEPOINTER ? "BT_FRAMEPOINTER" : "BT_LEAF")), - ra, sp, fp, func.ra_offset, func.fp_offset, func.stack_size); - osSyncPrintf(debug); - #endif - - switch (func.type) { - case BT_FUNCTION_FRAMEPOINTER: - if (!func.fp_offset) { - osSyncPrintf("backtrace: framepointer used but not saved onto stack at %p\n", ra); - } else { - // Use the frame pointer to refer to the current frame. - sp = fp; - if (!is_valid_address((u32)sp)) { - osSyncPrintf("backtrace: interrupted because of invalid frame pointer 0x%08lx\n", (u32)sp); - return; - } - } - // FALLTHROUGH! - case BT_FUNCTION: - if (func.fp_offset) - fp = *(u32**)((u32)sp + func.fp_offset); - ra = *(u32**)((u32)sp + func.ra_offset) - 2; - sp = (u32*)((u32)sp + func.stack_size); - exception_ra = NULL; - func_start = 0; - break; - case BT_EXCEPTION: { - // Exception frame. We must return back to EPC, but let's keep the - // RA value. If the interrupted function is a leaf function, we - // will need it to further walk back. - // Notice that FP is a callee-saved register so we don't need to - // recover it from the exception frame (also, it isn't saved there - // during interrupts). - exception_ra = *(u32**)((u32)sp + func.ra_offset); - - // Read EPC from exception frame and adjust it with CAUSE BD bit - ra = *(u32**)((u32)sp + offsetof(__OSThreadContext, pc) + 32); - u32 cause = *(u32*)((u32)sp + offsetof(__OSThreadContext, cause) + 32); - if (cause & C0_CAUSE_BD) ra++; - - sp = (u32*)((u32)sp + func.stack_size); - - // Special case: if the exception is due to an invalid EPC - // (eg: a null function pointer call), we can rely on RA to get - // back to the caller. This assumes that we got there via a function call - // rather than a raw jump, but that's a reasonable assumption. It's anyway - // the best we can do. - if ((C0_GET_CAUSE_EXC_CODE(cause) == EXCEPTION_CODE_TLB_LOAD_I_MISS || - C0_GET_CAUSE_EXC_CODE(cause) == EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR) && - !is_valid_address((u32)ra)) { - - // Store the invalid address in the backtrace, so that it will appear in dumps. - // This makes it easier for the user to understand the reason for the exception. - cb(arg, ra); - - ra = exception_ra - 2; - - // The function that jumped into an invalid PC was not interrupted by the exception: it - // is a regular function - // call now. - exception_ra = NULL; - break; - } - - // The next frame might be a leaf function, for which we will not be able - // to find a stack frame. It is useful to try finding the function start. - // Try to open the symbol table: if we find it, we can search for the start - // address of the function. - symtable_header_t symt = symt_open(ra); - if (symt.head[0]) { - int idx; - addrtable_entry_t entry = symt_addrtab_search(&symt, (u32)ra, &idx); - while (!ADDRENTRY_IS_FUNC(entry)) - entry = symt_addrtab_entry(&symt, --idx); - func_start = ADDRENTRY_ADDR(entry); - } - } break; - case BT_LEAF: - ra = exception_ra - 2; - // A leaf function has no stack. On the other hand, an exception happening at the - // beginning of a standard function (before RA is saved), does have a stack but - // will be marked as a leaf function. In this case, we mus update the stack pointer. - sp = (u32*)((u32)sp + func.stack_size); - exception_ra = NULL; - func_start = 0; - break; - } - - // Call the callback with this stack frame - cb(arg, ra); - } -} - -void _flush_cache() { - // no-op -} - -int backtrace(void **buffer, int size) { - int i = -1; // Skip backtrace() itself - void cb(void *arg, void *ptr) { - if (i >= size) return; - if (i >= 0) - buffer[i] = ptr; - i++; - } - - // Current value of SP/RA/FP registers. - u32 *sp, *fp; - asm volatile ( - "move %0, $sp\n" - "move %1, $fp\n" - : "=r"(sp), "=r"(fp) - ); - - // Start from the backtrace function itself. Put the start pointer - // somewhere after the initial prolog, so that we parse the prolog - // itself to find sp/fp/ra offsets. - // Since we don't come from an exception, exception_ra must be NULL. - u32 *pc = (u32*)backtrace + 24; - - backtrace_foreach(cb, NULL, pc, sp, fp, NULL); - return i; -} - -int __backtrace_from(void **buffer, int size, u32 *pc, u32 *sp, u32 *fp, u32 *exception_ra) -{ - int i = 0; - void cb(void *arg, void *ptr) { - if (i >= size) return; - if (i >= 0) - buffer[i] = ptr; - i++; - } - - backtrace_foreach(cb, NULL, pc, sp, fp, exception_ra); - return i; -} - - -static void format_entry(void (*cb)(void *, backtrace_frame_t *), void *cb_arg, - symtable_header_t *symt, int idx, u32 addr, u32 offset, u8 is_func, u8 is_inline) -{ - symtable_entry_t ALIGNED8 entry; - symt_entry_fetch(symt, &entry, idx); - - char ALIGNED8 file_buf[entry.file_len + 2]; - char ALIGNED8 func_buf[MAX(entry.func_len + 2, 32)]; - - cb(cb_arg, &(backtrace_frame_t){ - .addr = addr, - .func_offset = offset ? offset : entry.func_off, - .func = symt_entry_func(symt, &entry, addr, func_buf, sizeof(func_buf)), - .source_file = symt_entry_file(symt, &entry, addr, file_buf, sizeof(file_buf)), - .source_line = is_func ? 0 : entry.line, - .is_inline = is_inline, - }); -} - -u8 backtrace_symbols_cb(void **buffer, int size, u32 flags, - void (*cb)(void *, backtrace_frame_t *), void *cb_arg) -{ - for (int i=0; i" : "", - .source_file = UNKNOWN_SYMBOL, .source_line = 0, .is_inline = FALSE - }); - continue; - } - if (!has_symt) { - // No symbol table. Call the callback with a dummy entry which just contains the address - u8 exc = is_in_exception(needle); - cb(cb_arg, &(backtrace_frame_t){ - .addr = needle, - .func = exc ? "" : UNKNOWN_SYMBOL, .func_offset = 0, - .source_file = UNKNOWN_SYMBOL, .source_line = 0, .is_inline = FALSE - }); - continue; - } - int idx; addrtable_entry_t a; - a = symt_addrtab_search(&symt_header, needle, &idx); - - if (ADDRENTRY_ADDR(a) == needle) { - // Found an entry at this address. Go through all inlines for this address. - while (1) { - format_entry(cb, cb_arg, &symt_header, idx, needle, 0, FALSE, ADDRENTRY_IS_INLINE(a)); - if (!ADDRENTRY_IS_INLINE(a)) break; - a = symt_addrtab_entry(&symt_header, ++idx); - } - } else { - // Search the containing function - while (!ADDRENTRY_IS_FUNC(a)) - a = symt_addrtab_entry(&symt_header, --idx); - format_entry(cb, cb_arg, &symt_header, idx, needle, needle - ADDRENTRY_ADDR(a), TRUE, FALSE); - } - } - return TRUE; -} - -char** backtrace_symbols(void **buffer, int size) -{ - const int MAX_FILE_LEN = 120; - const int MAX_FUNC_LEN = 120; - const int MAX_SYM_LEN = MAX_FILE_LEN + MAX_FUNC_LEN + 24; - char **syms = malloc(2 * size * (sizeof(char*) + MAX_SYM_LEN)); - char *out = (char*)syms + size*sizeof(char*); - int level = 0; - - void cb(void *arg, backtrace_frame_t *frame) { - int n = sprintf(out, - "%s+0x%lx (%s:%d) [0x%08lx]", frame->func, frame->func_offset, frame->source_file, frame->source_line, frame->addr); - if (frame->is_inline) - out[-1] = '\n'; - else - syms[level++] = out; - out += n + 1; - } - - backtrace_symbols_cb(buffer, size, 0, cb, NULL); - return syms; -} - -void crash_screen_print(s32 x, s32 y, const char *fmt, ...); - -void backtrace_frame_print_compact(backtrace_frame_t *frame, char *out, int width) { - const char *source_file = frame->source_file; - int len = strlen(frame->func) + strlen(source_file); - u8 ellipsed = FALSE; - if (len > width && source_file) { - source_file += len - (width - 8); - ellipsed = TRUE; - } - if (frame->func != UNKNOWN_SYMBOL) out += sprintf(out, "%s ", frame->func); - if (source_file != UNKNOWN_SYMBOL) out += sprintf(out, "(%s%s:%d)", ellipsed ? "..." : "", source_file, frame->source_line); - if (frame->func == UNKNOWN_SYMBOL || source_file == UNKNOWN_SYMBOL) - out += sprintf(out, "[0x%08lx]", frame->addr); - // out += sprintf(out, "\n"); -} - -char *search_symbol(void *vaddr, int *line) { - static char filebuf[100]; - - symtable_header_t symt = symt_open(vaddr); - 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 - - filebuf[0] = 0; - symt_entry_fetch(&symt, &entry, idx); - *line = entry.line; - char *file = symt_entry_file(&symt, &entry, addr, filebuf, 100); - // char *ret = file; - // file += sprintf(file, ":%d", entry.line); - return file; - } - return UNKNOWN_SYMBOL; -} - - -void inspector_print_backtrace(void *bt, int n, int bt_skip) { - crash_screen_print(10, 45, "Backtrace:"); - char func[128]; - u8 skip = TRUE; - static int yBase = 55; - void cb(void *arg, backtrace_frame_t *frame) { - if (skip) { - if (strstr(frame->func, "")) - skip = FALSE; - return; - } - if (bt_skip > 0) { - bt_skip--; - return; - } - // crash_screen_print(10, " "); - sprintf(func, "%s", frame->func); - frame->func = func; - char stdout[400]; - backtrace_frame_print_compact(frame, stdout, 40); - crash_screen_print(10, yBase, stdout); - yBase += 10; - } - backtrace_symbols_cb(bt, n, 0, cb, NULL); - if (skip) { - // we didn't find the exception handler for some reason (eg: missing symbols) - // so just print the whole thing - skip = FALSE; - backtrace_symbols_cb(bt, n, 0, cb, NULL); - } -} - - From af3a9776cd06c496a3b7609afe9b7648866c79e1 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 21:27:48 -0400 Subject: [PATCH 023/109] split out symtable and map_parser --- Makefile | 25 +++++++--- sm64.ld | 10 ++-- src/debugger/map_parser.c | 99 +++------------------------------------ src/debugger/symtable.c | 55 +++++++++++++++------- src/debugger/symtable.h | 7 +++ tools/n64sym.c | 21 +++++---- 6 files changed, 91 insertions(+), 126 deletions(-) diff --git a/Makefile b/Makefile index e786c69d37..30d040dce1 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,16 @@ endif # FIXLIGHTS - converts light objects to light color commands for assets, needed for vanilla-style lighting FIXLIGHTS ?= 1 -DEBUG_MAP_STACKTRACE_FLAG := -D DEBUG_MAP_STACKTRACE +# Debug Switches - Comment these out to remove the functionality + +# Export symbols and stack trace data for crash screen +DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS +# Include line data, at the cost of a significantly longer link time +# DEBUG_EXPORT_ALL_LINES := -D DEBUG_EXPORT_ALL_LINES + +ifneq ($(DEBUG_EXPORT_ALL_LINES),) + DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines +endif TARGET := sm64 @@ -384,7 +393,7 @@ ACTOR_DIR := actors LEVEL_DIRS := $(patsubst levels/%,%,$(dir $(wildcard levels/*/header.h))) # Directories containing source files -SRC_DIRS += src src/boot src/game src/engine src/audio src/menu src/buffers lib/librtc actors levels bin data assets asm lib sound +SRC_DIRS += src src/boot src/game src/engine src/audio src/menu src/buffers src/debugger lib/librtc actors levels bin data assets asm lib sound LIBZ_SRC_DIRS := src/libz GODDARD_SRC_DIRS := src/goddard src/goddard/dynlists BIN_DIRS := bin bin/$(VERSION) @@ -731,7 +740,11 @@ $(BUILD_DIR)/src/game/rendering_graph_node.o: OPT_FLAGS := $(GRAPH_NODE_OPT_FLAG # $(info MATH_UTIL_OPT_FLAGS: $(MATH_UTIL_OPT_FLAGS)) # $(info GRAPH_NODE_OPT_FLAGS: $(GRAPH_NODE_OPT_FLAGS)) +<<<<<<< HEAD ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) asm/debug $(GODDARD_SRC_DIRS) $(LIBZ_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(TEXT_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) rsp include) $(YAY0_DIR) $(addprefix $(YAY0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION) +======= +ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) $(GODDARD_SRC_DIRS) $(LIBZ_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) $(addprefix actors/vanilla_actors/,$(VNL_ACTRS_DIRS)) rsp include) $(YAY0_DIR) $(addprefix $(YAY0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION) +>>>>>>> 624553c8b (split out symtable and map_parser) # Make sure build directory exists before compiling anything DUMMY != mkdir -p $(ALL_DIRS) @@ -931,7 +944,7 @@ $(BUILD_DIR)/rsp/%.bin $(BUILD_DIR)/rsp/%_data.bin: rsp/%.s # Run linker script through the C preprocessor $(BUILD_DIR)/$(LD_SCRIPT): $(LD_SCRIPT) $(BUILD_DIR)/goddard.txt $(call print,Preprocessing linker script:,$<,$@) - $(V)$(CPP) $(CPPFLAGS) -DBUILD_DIR=$(BUILD_DIR) -DULTRALIB=lib$(ULTRALIB) $(DEBUG_MAP_STACKTRACE_FLAG) -MMD -MP -MT $@ -MF $@.d -o $@ $< + $(V)$(CPP) $(CPPFLAGS) -DBUILD_DIR=$(BUILD_DIR) -DULTRALIB=lib$(ULTRALIB) $(DEBUG_EXPORT_SYMBOLS_FLAG) -MMD -MP -MT $@ -MF $@.d -o $@ $< # Link libgoddard $(BUILD_DIR)/libgoddard.a: $(GODDARD_O_FILES) @@ -956,13 +969,13 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(call print,Getting Goddard size...) $(V)python3 tools/getGoddardSize.py $(BUILD_DIR)/sm64_prelim.map $(VERSION) -$(BUILD_DIR)/asm/debug/map.o: $(BUILD_DIR)/sm64_prelim.elf +$(BUILD_DIR)/sm64.sym.o: $(BUILD_DIR)/sm64_prelim.elf $(call print,Assembling:,$<,$@) - $(V)tools/n64sym --cross $(CROSS) $(BUILD_DIR)/sm64_prelim.elf + $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --cross $(CROSS) $(BUILD_DIR)/sm64_prelim.elf $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym # Link SM64 ELF file -$(ELF): $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/asm/debug/map.o $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) $(BUILD_DIR)/libz.a $(BUILD_DIR)/libgoddard.a +$(ELF): $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/sm64.sym.o $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) $(BUILD_DIR)/libz.a $(BUILD_DIR)/libgoddard.a @$(PRINT) "$(GREEN)Linking ELF file: $(BLUE)$@ $(NO_COL)\n" $(V)$(LD) --gc-sections -L $(BUILD_DIR) -T $(BUILD_DIR)/$(LD_SCRIPT) -T goddard.txt -Map $(BUILD_DIR)/sm64.$(VERSION).map --no-check-sections $(addprefix -R ,$(SEG_FILES)) -o $@ $(O_FILES) -L$(LIBS_DIR) -l$(ULTRALIB) -Llib $(LINK_LIBRARIES) -u sprintf -u osMapTLB -Llib/gcclib/$(LIBGCCDIR) -lgcc diff --git a/sm64.ld b/sm64.ld index e9ebdcb1e8..a0ebd48cee 100755 --- a/sm64.ld +++ b/sm64.ld @@ -146,7 +146,7 @@ SECTIONS #endif /* hardcoded symbols to satisfy preliminary link for map parser */ -#ifndef DEBUG_MAP_STACKTRACE +#ifndef DEBUG_EXPORT_SYMBOLS _mapDataSegmentRomStart = 0; #endif @@ -164,6 +164,7 @@ SECTIONS BUILD_DIR/src/hvqm*.o(.text*); BUILD_DIR/src/usb*.o(.text*); BUILD_DIR/src/audio*.o(.text*); + BUILD_DIR/src/debugger*.o(.text*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.text*); #endif @@ -187,6 +188,7 @@ SECTIONS BUILD_DIR/asm/n64_assert.o(.*data*); BUILD_DIR/src/boot*.o(.*data*); BUILD_DIR/src/audio*.o(.*data*); + BUILD_DIR/src/debugger*.o(.*data*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.*data*); #endif @@ -206,6 +208,7 @@ SECTIONS BUILD_DIR/src/boot*.o(.rodata*); BUILD_DIR/src/usb*.o(.rodata*); BUILD_DIR/src/audio*.o(.rodata*); + BUILD_DIR/src/debugger*.o(.rodata*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.rodata*); #endif @@ -226,6 +229,7 @@ SECTIONS BUILD_DIR/src/hvqm*.o(.*bss*); BUILD_DIR/src/usb*.o(.*bss*); BUILD_DIR/src/audio*.o(.*bss*); + BUILD_DIR/src/debugger*.o(.*bss*); #ifdef S2DEX_TEXT_ENGINE BUILD_DIR/src/s2d_engine*.o(.*bss*); #endif @@ -525,9 +529,9 @@ SECTIONS END_SEG(capcom) #endif -#ifdef DEBUG_MAP_STACKTRACE +#ifdef DEBUG_EXPORT_SYMBOLS BEGIN_SEG(mapData, (RAM_END - 0x00100000)) ALIGN(16) { - KEEP(BUILD_DIR/asm/debug/map.o(.data*)); + KEEP(BUILD_DIR/sm64.sym.o(.data*)); } END_SEG(mapData) #endif diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c index 23ae93375e..0f008fcc41 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -4,90 +4,13 @@ #include #include #include "segments.h" -#include "memory.h" -#include "backtrace.h" -#include "cop0.h" - -/** - * @brief Exception codes - */ -typedef enum { - EXCEPTION_CODE_INTERRUPT = 0, - EXCEPTION_CODE_TLB_MODIFICATION = 1, - EXCEPTION_CODE_TLB_LOAD_I_MISS = 2, - EXCEPTION_CODE_TLB_STORE_MISS = 3, - EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR = 4, - EXCEPTION_CODE_STORE_ADDRESS_ERROR = 5, - EXCEPTION_CODE_I_BUS_ERROR = 6, - EXCEPTION_CODE_D_BUS_ERROR = 7, - EXCEPTION_CODE_SYS_CALL = 8, - EXCEPTION_CODE_BREAKPOINT = 9, - EXCEPTION_CODE_RESERVED_INSTRUCTION = 10, - EXCEPTION_CODE_COPROCESSOR_UNUSABLE = 11, - EXCEPTION_CODE_ARITHMETIC_OVERFLOW = 12, - EXCEPTION_CODE_TRAP = 13, - EXCEPTION_CODE_FLOATING_POINT = 15, - EXCEPTION_CODE_WATCH = 23, -} exception_code_t; - -/** @brief Check if addr is a valid PC address */ -static u8 is_valid_address(u32 addr) -{ - // TODO: for now we only handle RAM (cached access). This should be extended to handle - // TLB-mapped addresses for instance. - return addr >= 0x80000400 && addr < 0x80800000 && (addr & 3) == 0; -} - -#define offsetof(type, member) __builtin_offsetof (type, member) - -// 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 - -/** @brief Address of the SYMT symbol table in the rompak. */ -static u32 SYMT_ROM = 0xFFFFFFFF; - -/** @brief Placeholder used in frames where symbols are not available */ -static const char *UNKNOWN_SYMBOL = "???"; +#include "game/memory.h" -extern void dma_read(void *dest, void *srcStart, void *srcEnd); - -void map_parser_dma(void *dst, void *src, size_t size) { - // char dm[500]; - // sprintf(dm, "DMA [%08X] <-- [%08X %08X] (%08X)", dst, src, size, SYMT_ROM); - // osSyncPrintf(dm); - // dma_read(dst, src, src+ALIGN8(size)); - headless_dma((u32)src, dst, size); - while (headless_pi_status() & PI_STATUS_IO_BUSY); -} +#include "map_parser.h" +#include "symtable.h" #define STACK_TRAVERSAL_LIMIT 100 - -/** @brief Function alignment enfored by the compiler (-falign-functions). - * - * @note This must be kept in sync with n64.mk. - */ -#define FUNCTION_ALIGNMENT 32 - -extern u8 _mapDataSegmentRomStart[]; +#define UNKNOWN_SYMBOL "???" extern u8 _mainSegmentStart[]; extern u8 _mainSegmentTextEnd[]; @@ -97,7 +20,7 @@ extern u8 _goddardSegmentStart[]; extern u8 _goddardSegmentTextEnd[]; char* __symbolize(void *vaddr, char *buf, int size) { - symtable_header_t symt = symt_open(vaddr); + symtable_header_t symt = symt_open(); if (symt.head[0]) { u32 addr = (u32)vaddr; int idx = 0; @@ -125,12 +48,6 @@ char *parse_map(u32 addr) { return map_name; } -char *get_filename(u32 addr) { - static char file_name[64] ALIGNED16; - - return file_name; -} - char *find_function_in_stack(u32 *sp, int *line) { char *ret = NULL; u32 val = *sp; @@ -163,16 +80,14 @@ char *find_function_in_stack(u32 *sp, int *line) { return ret; } -void crash_screen_print(s32 x, s32 y, const char *fmt, ...); - char *search_symbol(void *vaddr, int *line) { static char filebuf[100]; - symtable_header_t symt = symt_open(vaddr); + 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); + symt_addrtab_search(&symt, addr, &idx); symtable_entry_t ALIGNED16 entry; diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c index 0a52e52341..6bf3b14153 100644 --- a/src/debugger/symtable.c +++ b/src/debugger/symtable.c @@ -4,18 +4,44 @@ #include #include #include "segments.h" -#include "memory.h" -#include "backtrace.h" -#include "cop0.h" +#include "game/memory.h" #include "symtable.h" +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. */ -static symtable_header_t symt_open(void *addr) { +symtable_header_t symt_open(void) { SYMT_ROM = (u32)_mapDataSegmentRomStart; // We don't _need_ to pass by value for this right @@ -33,12 +59,12 @@ static symtable_header_t symt_open(void *addr) { ); if (symt_header.head[0] != 'S' || symt_header.head[1] != 'Y' || symt_header.head[2] != 'M' || symt_header.head[3] != 'T') { - osSyncPrintf("backtrace: invalid symbol table found at 0x%08lx\n", SYMT_ROM); + 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("backtrace: unsupported symbol table version %ld -- please update your n64sym tool\n", symt_header.version); + 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}; } @@ -53,8 +79,7 @@ static symtable_header_t symt_open(void *addr) { * @param idx Index of the entry to return * @return addrtable_entry_t Entry of the address table */ -static addrtable_entry_t symt_addrtab_entry(symtable_header_t *symt, int idx) -{ +addrtable_entry_t symt_addrtab_entry(symtable_header_t *symt, int idx) { return IO_READ(0xB0000000 | (SYMT_ROM + symt->addrtab_off + idx * 4)); } @@ -72,8 +97,7 @@ static addrtable_entry_t symt_addrtab_entry(symtable_header_t *symt, int idx) * @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) */ -static addrtable_entry_t symt_addrtab_search(symtable_header_t *symt, u32 addr, int *idx) -{ +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) { @@ -106,8 +130,7 @@ static addrtable_entry_t symt_addrtab_search(symtable_header_t *symt, u32 addr, * @param size Size of the destination buffer * @return char* Fetched string within the destination buffer (might not be at offset 0 for alignment reasons) */ -static char* symt_string(symtable_header_t *symt, int sidx, int slen, char *buf, int size) -{ +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; @@ -135,8 +158,7 @@ static char* symt_string(symtable_header_t *symt, int sidx, int slen, char *buf, * @param entry Output entry pointer * @param idx Index of the entry to fetch */ -static void symt_entry_fetch(symtable_header_t *symt, symtable_entry_t *entry, int idx) -{ +void symt_entry_fetch(symtable_header_t *symt, symtable_entry_t *entry, int idx) { osWritebackDCache(entry, sizeof(symtable_entry_t)); // char dbg[100]; @@ -150,8 +172,7 @@ static void symt_entry_fetch(symtable_header_t *symt, symtable_entry_t *entry, i } // Fetch the function name of an entry -static char* symt_entry_func(symtable_header_t *symt, symtable_entry_t *entry, u32 addr, char *buf, int size) -{ +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 @@ -163,6 +184,6 @@ static char* symt_entry_func(symtable_header_t *symt, symtable_entry_t *entry, u } // Fetch the file name of an entry -char* symt_entry_file(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, u32 addr, char *buf, int size) { return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); } diff --git a/src/debugger/symtable.h b/src/debugger/symtable.h index 65a60ecccc..4f163f168f 100644 --- a/src/debugger/symtable.h +++ b/src/debugger/symtable.h @@ -67,3 +67,10 @@ typedef u32 addrtable_entry_t; #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, u32 addr, char *buf, int size); diff --git a/tools/n64sym.c b/tools/n64sym.c index e931268977..d0fafc401c 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -16,6 +16,7 @@ bool flag_verbose = false; int flag_max_sym_len = 64; bool flag_inlines = true; +bool flag_all_lines = false; const char *n64_inst = NULL; const char *cross_prefix = NULL; @@ -39,6 +40,7 @@ void usage(const char *progname) fprintf(stderr, " -c/--cross Cross compilation toolchain (e.g. `mips-linux-gnu-`)\n"); fprintf(stderr, " -v/--verbose Verbose output\n"); fprintf(stderr, " -m/--max-len Maximum symbol length (default: 64)\n"); + fprintf(stderr, " -a/--all-lines Generate full address->line info (Can take seconds on its own)\n"); fprintf(stderr, " --no-inlines Do not export inlined symbols\n"); fprintf(stderr, "\n"); // fprintf(stderr, "This program requires a libdragon toolchain installed in $N64_INST.\n"); @@ -199,7 +201,7 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) getline(&line_buf, &line_buf_size, addr2line_r); } -void line_add(const char *elf, uint32_t addr) { +void address_add(const char *elf, uint32_t addr) { // We keep one addr2line process open for the last ELF file we processed. // This allows to convert multiple symbols very fast, avoiding spawning a // new process for each symbol. @@ -289,7 +291,6 @@ void line_add(const char *elf, uint32_t addr) { // Add the addr to the list, only if that line entry doesn't already exist if (stbds_shgetp_null(linedb, fileline_key) == NULL) { - verbose("New line: %d\n", line); stbds_arrput(symtable, ((struct symtable_s) { .uuid = stbds_arrlen(symtable), .addr = addr, @@ -346,12 +347,14 @@ bool elf_find_callsites(const char *elf) symbol_add(elf, addr, false); } } - // See if adding every single text address is worth - if (line[8] == ':') { - uint32_t addr = strtoul(line, NULL, 16); - // Prevent segmented addresses for now - if ((addr & 0xFF000000) == 0x80000000) { - line_add(elf, addr); + + if (flag_all_lines) { + if (line[8] == ':') { + uint32_t addr = strtoul(line, NULL, 16); + // Prevent segmented addresses for now + if ((addr & 0xFF000000) == 0x80000000) { + address_add(elf, addr); + } } } } @@ -500,6 +503,8 @@ int main(int argc, char *argv[]) flag_verbose = true; } else if (!strcmp(argv[i], "--no-inlines")) { flag_inlines = false; + } else if (!strcmp(argv[i], "-a") || !strcmp(argv[i], "--all-lines")) { + flag_all_lines = true; } else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) { if (++i == argc) { fprintf(stderr, "missing argument for %s\n", argv[i-1]); From 1dbb9583c0aab8e9d962d4aaed528aff308ce75a Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 21:30:53 -0400 Subject: [PATCH 024/109] diff and warning reduction; finally move crash_screen over --- src/{game => debugger}/crash_screen.c | 15 +- src/debugger/map_parser.c | 2 +- src/debugger/symtable.c | 2 +- src/debugger/symtable.h | 2 +- src/game/backtrace.h | 180 ------------- src/game/cop0.h | 348 -------------------------- src/game/stack_trace.c | 0 7 files changed, 10 insertions(+), 539 deletions(-) rename src/{game => debugger}/crash_screen.c (98%) delete mode 100644 src/game/backtrace.h delete mode 100644 src/game/cop0.h delete mode 100644 src/game/stack_trace.c diff --git a/src/game/crash_screen.c b/src/debugger/crash_screen.c similarity index 98% rename from src/game/crash_screen.c rename to src/debugger/crash_screen.c index 8af2af45e8..ae85850c46 100644 --- a/src/game/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -4,18 +4,17 @@ #include #include "buffers/framebuffers.h" #include "types.h" -#include "puppyprint.h" +#include "game/puppyprint.h" #include "audio/external.h" #include "farcall.h" -#include "game_init.h" -#include "main.h" -#include "debug.h" -#include "rumble_init.h" +#include "game/game_init.h" +#include "game/main.h" +#include "game/debug.h" +#include "game/rumble_init.h" +#include "game/printf.h" #include "sm64.h" -#include "printf.h" - extern char *strstr(char *, char *); extern char *search_symbol(u32 vaddr, int *line); extern void inspector_print_backtrace(void *bt, int n, int bt_skip); @@ -399,7 +398,7 @@ void draw_disasm(OSThread *thread) { if (disasm[0] == 0) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); } else { - if (strstr(disasm, "j")) { + if (disasm[0] == 'j') { int line = -1; search_symbol(addr, &line); if (line != -1) { diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c index 0f008fcc41..e59a3f26da 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -95,7 +95,7 @@ char *search_symbol(void *vaddr, int *line) { filebuf[0] = 0; symt_entry_fetch(&symt, &entry, idx); *line = entry.line; - char *file = symt_entry_file(&symt, &entry, addr, filebuf, sizeof(filebuf)); + char *file = symt_entry_file(&symt, &entry, filebuf, sizeof(filebuf)); return file; } return UNKNOWN_SYMBOL; diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c index 6bf3b14153..d46fdf3bb9 100644 --- a/src/debugger/symtable.c +++ b/src/debugger/symtable.c @@ -184,6 +184,6 @@ char *symt_entry_func(symtable_header_t *symt, symtable_entry_t *entry, u32 addr } // Fetch the file name of an entry -char *symt_entry_file(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) { return symt_string(symt, entry->file_sidx, entry->file_len, buf, size); } diff --git a/src/debugger/symtable.h b/src/debugger/symtable.h index 4f163f168f..1f1d3eba3c 100644 --- a/src/debugger/symtable.h +++ b/src/debugger/symtable.h @@ -73,4 +73,4 @@ addrtable_entry_t symt_addrtab_search(symtable_header_t *symt, u32 addr, int *id 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, u32 addr, char *buf, int size); +char *symt_entry_file(symtable_header_t *symt, symtable_entry_t *entry, char *buf, int size); diff --git a/src/game/backtrace.h b/src/game/backtrace.h deleted file mode 100644 index 576a777471..0000000000 --- a/src/game/backtrace.h +++ /dev/null @@ -1,180 +0,0 @@ -#ifndef __LIBDRAGON_BACKTRACE_INTERNAL_H -#define __LIBDRAGON_BACKTRACE_INTERNAL_H - -/** @brief The "type" of funciton as categorized by the backtrace heuristic (__bt_analyze_func) */ -typedef enum { - BT_FUNCTION, ///< Regular function with a stack frame - BT_FUNCTION_FRAMEPOINTER, ///< The function uses the register fp as frame pointer (normally, this happens only when the function uses alloca) - BT_EXCEPTION, ///< This is an exception handler (inthandler.S) - BT_LEAF ///< Leaf function (no calls), no stack frame allocated, sp/ra not modified -} bt_func_type; - -/** @brief Description of a function for the purpose of backtracing (filled by __bt_analyze_func) */ -typedef struct { - bt_func_type type; ///< Type of the function - int stack_size; ///< Size of the stack frame - int ra_offset; ///< Offset of the return address from the top of the stack frame - int fp_offset; ///< Offset of the saved fp from the top of the stack frame; this is != 0 only if the function modifies fp (maybe as a frame pointer, but not necessarily) -} bt_func_t; - -u8 __bt_analyze_func(bt_func_t *func, u32 *ptr, u32 func_start, u8 from_exception); - -/** @brief Like #backtrace, but start from an arbitrary context. Useful for backtracing a thread */ -int __backtrace_from(void **buffer, int size, u32 *pc, u32 *sp, u32 *fp, u32 *exception_ra); - -/** - * @brief Return the symbol associated to a given address. - * - * This function inspect the symbol table (if any) to search for the - * specified address. It returns the function name the address belongs - * to, and the offset within the function as a string in the format - * "function_name+0x1234". - * - * If the symbol table is not found in the rompack or the address is not found, - * the return string is "???". - * - * @param vaddr Address to symbolize - * @param buf Buffer where to store the result - * @param size Size of the buffer - * @return char* Pointer to the return string. This is within the provided - * buffer, but not necessarily at the beginning because of DMA - * alignment constraints. - */ -char* __symbolize(void *vaddr, char *buf, int size); - -#endif - -/** - * @file backtrace.h - * @brief Backtrace (call stack) support - * @ingroup backtrace - */ - -/** - * @defgroup backtrace Backtrace (call stack) support - * @ingroup lowlevel - * @brief Implementation of functions to walk the stack and dump a backtrace - * - * This module implements two POSIX/GNU standard functions to help walking - * the stack and providing the current execution context: backtrace() and - * backtrace_symbols(). - * - * The functions have an API fully compatible with the standard ones. The - * implementation is however optimized for the MIPS/N64 case, and with - * standard compilation settings. See the documentation in backtrace.c - * for implementation details. - * - * You can call the functions to inspect the current call stack. For - * a higher level function that just prints the current call stack - * on the debug channels, see #debug_backtrace. - * - * @{ - */ - -#ifndef __LIBDRAGON_BACKTRACE_H -#define __LIBDRAGON_BACKTRACE_H - -/** - * @brief A stack frame, part of a backtrace - */ -typedef struct { - u32 addr; ///< PC address of the frame (MIPS virtual address) - - const char *func; ///< Name of the function (this should always be present) - u32 func_offset; ///< Byte offset of the address within the function - - const char *source_file; ///< Name of the source file (if known, or "???" otherwise) - int source_line; ///< Line number in the source file (if known, or 0 otherwise) - - u8 is_inline; ///< True if this frame refers to an inlined function -} backtrace_frame_t; - -/** - * @brief Walk the stack and return the current call stack - * - * This function will analyze the current execution context, - * walking the stack and returning informations on the active - * call frames. - * - * This function adheres to POSIX specification. It does not - * allocate memory so it is safe to be called even in the - * context of low memory conditions or possibly corrupted heap. - * - * If called within an interrupt or exception handler, the function - * is able to correctly walk backward the interrupt handler and - * show the context even before the exception was triggered. - * - * @param buffer Empty array of pointers. This will be populated with pointers - * to the return addresses for each call frame. - * @param size Size of the buffer, that is, maximum number of call frames - * that will be walked by the function. - * @return Number of call frames walked (at most, size). - */ -int backtrace(void **buffer, int size); - -/** - * @brief Translate the buffer returned by #backtrace into a list of strings - * - * This function symbolizes the buffer returned by #backtrace, translating - * return addresses into function names and source code locations. - * - * The user-readable strings are allocated on the heap and must be freed by - * the caller (via a single free() call). There is no need to free each - * of the returned strings: a single free() call is enough, as they are - * allocated in a single contiguous block. - * - * This function adheres to POSIX specification. - * - * This function also handles inlined functions. In general, inlined function - * do not have a real stack frame because they are expanded in place; so for - * instance a single stack frame (as returned by #backtrace) can correspond - * to multiple symbolized stack frames, one per each inlined function. Since - * the POSIX API requires this function to return an array of the same size - * of the input array, all inlined functions are collapsed into a single - * string, separated by newlines. - * - * @param buffer Array of return addresses, populated by #backtrace - * @param size Size of the provided buffer, in number of pointers. - * @return Array of strings, one for each call frame. The array - * must be freed by the caller with a single free() call. - * - * @see #backtrace_symbols_cb - */ -char** backtrace_symbols(void **buffer, int size); - -/** - * @brief Symbolize the buffer returned by #backtrace, calling a callback for each frame - * - * This function is similar to #backtrace_symbols, but instead of formatting strings - * into a heap-allocated buffer, it invokes a callback for each symbolized stack - * frame. This allows to skip the memory allocation if not required, and also allows - * for custom processing / formatting of the backtrace by the caller. - * - * The callback will receive an opaque argument (cb_arg) and a pointer to a - * stack frame descriptor (#backtrace_frame_t). The descriptor and all its - * contents (including strings) is valid only for the duration of the call, - * so the callback must (deep-)copy any data it needs to keep. - * - * The callback implementation might find useful to call #backtrace_frame_print - * or #backtrace_frame_print_compact to print the frame information. - * - * @param buffer Array of return addresses, populated by #backtrace - * @param size Size of the provided buffer, in number of pointers. - * @param flags Flags to control the symbolization process. Use 0. - * @param cb Callback function to invoke for each symbolized frame - * @param cb_arg Opaque argument to pass to the callback function - * @return True if the symbolization was successful, false otherwise. - * Notice that the function returns true even if some frames - * were not symbolized; false is only used when the function - * had to abort before even calling the callback once (eg: - * no symbol table was found). - * - * @see #backtrace_symbols - */ -u8 backtrace_symbols_cb(void **buffer, int size, u32 flags, - void (*cb)(void *, backtrace_frame_t*), void *cb_arg); - -/** @} */ - -#endif - diff --git a/src/game/cop0.h b/src/game/cop0.h deleted file mode 100644 index e311e32e72..0000000000 --- a/src/game/cop0.h +++ /dev/null @@ -1,348 +0,0 @@ -/** - * @file cop0.h - * @brief N64 COP0 Interface - * @ingroup n64sys - */ - -/** - * @addtogroup n64sys - * @{ - */ - -#ifndef __LIBDRAGON_COP0_H -#define __LIBDRAGON_COP0_H - -/** @brief Read the COP0 Count register (see also TICKS_READ). */ -#define C0_COUNT() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$9":"=r"(x)); \ - x; \ -}) - -/** @brief Write the COP0 Count register. */ -#define C0_WRITE_COUNT(x) ({ \ - asm volatile("mtc0 %0,$9"::"r"(x)); \ -}) - - -/** @brief Read the COP0 Compare register. */ -#define C0_COMPARE() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$11":"=r"(x)); \ - x; \ -}) - -/** @brief Write the COP0 Compare register. */ -#define C0_WRITE_COMPARE(x) ({ \ - asm volatile("mtc0 %0,$11"::"r"(x)); \ -}) - - -/** @brief Read the COP0 Status register. */ -#define C0_STATUS() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$12":"=r"(x)); \ - x; \ -}) - -/** @brief Write the COP0 Status register. */ -#define C0_WRITE_STATUS(x) ({ \ - asm volatile("mtc0 %0,$12"::"r"(x)); \ -}) - -/** - * @brief Returns the COP0 register $13 (Cause Register) - * - * The coprocessor 0 (system control coprocessor - COP0) register $13 is a read - * write register keeping pending interrupts, exception code, coprocessor unit - * number referenced for a coprocessor unusable exception. - * - * @see #C0_GET_CAUSE_EXC_CODE, #C0_GET_CAUSE_CE and #C0_CAUSE_BD - */ -#define C0_CAUSE() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$13" : "=r" (x) : ); \ - x; \ -}) - -/** - * @brief Write the COP0 register $13 (Cause register) - * - * Use this to update it for a custom exception handler. - * */ -#define C0_WRITE_CAUSE(x) ({ \ - asm volatile("mtc0 %0,$13"::"r"(x)); \ -}) - -/** @cond */ -/* Alternative version with different naming */ -#define C0_CR() C0_CAUSE() -#define C0_WRITE_CR(x) C0_WRITE_CAUSE(x) -/** @endcond */ - -/** - * @brief Returns the COP0 register $8 (BadVAddr) - * - * The coprocessor 0 (system control coprocessor - COP0) register $8 is a read - * only register holding the last virtual address to be translated which became - * invalid, or a virtual address for which an addressing error occurred. - */ -#define C0_BADVADDR() ({ \ - uint64_t x; \ - asm volatile("dmfc0 %0,$8" : "=r" (x) : ); \ - x; \ -}) - -/** - * @brief Read the COP0 register $14 (EPC) - * - * The coprocessor 0 (system control coprocessor - COP0) register $14 is the - * return from exception program counter. For asynchronous exceptions it points - * to the place to continue execution whereas for synchronous (caused by code) - * exceptions, point to the instruction causing the fault condition, which - * needs correction in the exception handler. This macro is for reading its - * value. - */ -#define C0_EPC() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$14" : "=r" (x) : ); \ - x; \ -}) - -/** - * @brief Read the COP0 INDEX register - * - * This register is used during TLB programming. It holds the index of the TLB - * entry being accessed (0-31). - */ -#define C0_INDEX() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$0":"=r"(x)); \ - x; \ -}) - -/** - * @brief Write the COP0 INDEX register - * - * This register is used during TLB programming. It holds the index of the TLB - * entry being accessed (0-31). - */ -#define C0_WRITE_INDEX(x) asm volatile("mtc0 %0,$0; nop; nop"::"r"(x)) - - -/** - * @brief Read the COP0 ENTRYHI register - * - * This register is used during TLB programming. It holds the configuration - * of the virtual memory entry for the TLB slot being accessed. - */ -#define C0_ENTRYHI() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$10":"=r"(x)); \ - x; \ -}) - -/** - * @brief Write the COP0 ENTRYHI register - * - * This register is used during TLB programming. - */ -#define C0_WRITE_ENTRYHI(x) asm volatile("mtc0 %0,$10; nop; nop"::"r"(x)) - -/** - * @brief Read the COP0 ENTRYLO0 register - * - * This register is used during TLB programming. It holds the configuration - * of the physical memory entry (even bank) for the TLB slot being accessed. - */ -#define C0_ENTRYLO0() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$2":"=r"(x)); \ - x; \ -}) - -/** - * @brief Write the COP0 ENTRYLO0 register - * - * This register is used during TLB programming. It holds the configuration - * of the physical memory entry (even bank) for the TLB slot being accessed. - */ -#define C0_WRITE_ENTRYLO0(x) asm volatile("mtc0 %0,$2; nop; nop"::"r"(x)) - -/** - * @brief Read the COP0 ENTRYLO1 register - * - * This register is used during TLB programming. It holds the configuration - * of the physical memory entry (odd bank) for the TLB slot being accessed. - */ -#define C0_ENTRYLO1() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$3":"=r"(x)); \ - x; \ -}) - -/** - * @brief Write the COP0 ENTRYLO1 register - * - * This register is used during TLB programming. It holds the configuration - * of the physical memory entry (even bank) for the TLB slot being accessed. - */ -#define C0_WRITE_ENTRYLO1(x) asm volatile("mtc0 %0,$3; nop; nop"::"r"(x)) - - -/** - * @brief Read the COP0 PAGEMASK register - * - * This register is used during TLB programming. It holds the bitmask that - * configures the page size of the TLB slot being accessed. - */ -#define C0_PAGEMASK() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$5":"=r"(x)); \ - x; \ -}) - -/** - * @brief Write the COP0 PAGEMASK register - * - * This register is used during TLB programming. It holds the bitmask that - * configures the page size of the TLB slot being accessed. - */ -#define C0_WRITE_PAGEMASK(x) asm volatile("mtc0 %0,$5; nop; nop"::"r"(x)) - - -/** - * @brief Read the COP0 WIRED register - * - * This register is used during TLB programming. It allows to partition TLB - * slots between fixed slots and random slots. The fixed slot pool is the - * range [0..WIRED[ and the random pool is the range [WIRED..32[ - */ -#define C0_WIRED() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$6":"=r"(x)); \ - x; \ -}) - -/** - * @brief Read the COP0 WATCHLO register - * - * This register is used during watchpoint programming. It allows to trigger - * an exception when a memory access occurs on a specific memory location. - */ -#define C0_WATCHLO() ({ \ - u32 x; \ - asm volatile("mfc0 %0,$18":"=r"(x)); \ - x; \ -}) - -/** - * @brief Write the COP0 WATCHLO register - * - * This register is used during watchpoint programming. It allows to trigger - * an exception when a memory access occurs on a specific memory location. - */ -#define C0_WRITE_WATCHLO(x) asm volatile("mtc0 %0,$18"::"r"(x)) - -/** - * @brief Write the COP0 WIRED register - * - * This register is used during TLB programming. It allows to partition TLB - * slots between fixed slots and random slots. The fixed slot pool is the - * range [0..WIRED[ and the random pool is the range [WIRED..32[ - */ -#define C0_WRITE_WIRED(x) asm volatile("mtc0 %0,$6; nop; nop"::"r"(x)) - -/** @cond */ -/* Deprecated version of macros with wrong naming that include "READ" */ -#define C0_READ_CR() C0_CAUSE() -#define C0_READ_EPC() C0_EPC() -#define C0_READ_BADVADDR() C0_BADVADDR() -/** @endcond */ - -/* COP0 Status bits definition. Please refer to MIPS R4300 manual. */ -#define C0_STATUS_IE 0x00000001 ///< Status: interrupt enable -#define C0_STATUS_EXL 0x00000002 ///< Status: within exception -#define C0_STATUS_ERL 0x00000004 ///< Status: within error - -/* COP0 Cause bits definition. Please refer to MIPS R4300 manual. */ -#define C0_CAUSE_BD 0x80000000 ///< Cause: exception triggered in delay slot -#define C0_CAUSE_CE 0x30000000 ///< Cause: coprocessor exception -#define C0_CAUSE_EXC_CODE 0x0000007C ///< Cause: exception code - -/* COP0 interrupt bits definition. These are compatible bothwith mask and pending bits. */ -#define C0_INTERRUPT_0 0x00000100 ///< Status/Cause: SW interrupt 0 -#define C0_INTERRUPT_1 0x00000200 ///< Status/Cause: SW interrupt 1 -#define C0_INTERRUPT_2 0x00000400 ///< Status/Cause: HW interrupt 2 (RCP) -#define C0_INTERRUPT_3 0x00000800 ///< Status/Cause: HW interrupt 3 (CART) -#define C0_INTERRUPT_4 0x00001000 ///< Status/Cause: HW interrupt 4 (PRENMI) -#define C0_INTERRUPT_5 0x00002000 ///< Status/Cause: HW interrupt 5 -#define C0_INTERRUPT_6 0x00004000 ///< Status/Cause: HW interrupt 6 -#define C0_INTERRUPT_7 0x00008000 ///< Status/Cause: HW interrupt 7 (Timer) - -#define C0_INTERRUPT_RCP C0_INTERRUPT_2 ///< Status/Cause: HW interrupt 2 (RCP) -#define C0_INTERRUPT_CART C0_INTERRUPT_3 ///< Status/Cause: HW interrupt 3 (CART) -#define C0_INTERRUPT_PRENMI C0_INTERRUPT_4 ///< Status/Cause: HW interrupt 4 (PRENMI) -#define C0_INTERRUPT_TIMER C0_INTERRUPT_7 ///< Status/Cause: HW interrupt 7 (Timer) - -/** - * @brief Get the CE value from the COP0 status register - * - * Gets the Coprocessor unit number referenced by a coprocessor unusable - * exception from the given COP0 Status register value. - */ -#define C0_GET_CAUSE_CE(cr) (((cr) & C0_CAUSE_CE) >> 28) - -/** - * @brief Get the exception code value from the COP0 status register value - */ -#define C0_GET_CAUSE_EXC_CODE(sr) (((sr) & C0_CAUSE_EXC_CODE) >> 2) - -/* Flag bits valid for COP0 ENTRYLO0/ENTRYLO1 registers */ -#define C0_ENTRYLO_GLOBAL (1<<0) ///< ENTRYLO: mapping is global (all ASIDs) -#define C0_ENTRYLO_VALID (1<<1) ///< ENTRYLO: mapping is active (not disabled) -#define C0_ENTRYLO_DIRTY (1<<2) ///< ENTRYLO: mapping is writable - -/* Flag bits valid for COP0 INDEX register */ -#define C0_INDEX_PROBE_FAILED (1<<31) ///< INDEX: set when a TLBP probe failed to find a match - - -/** - * @brief COP0 TLBWI opcode. - * - * This opcode is used during TLB programming. It writes the TLB slot referenced - * by INDEX with the contents of PAGEMASK, ENTRYHI, ENTRYLO0, ENTRYLO1. - */ -#define C0_TLBWI() asm volatile("tlbwi; nop; nop; nop; nop") - -/** - * @brief COP0 TLBWR opcode. - * - * This opcode is used during TLB programming. It writes a random TLB slot with - * the contents of PAGEMASK, ENTRYHI, ENTRYLO0, ENTRYLO1. THe slot is selected - * in the random pool (slots in the range from WIRED to 31). - */ -#define C0_TLBWR() asm volatile("tlbwr; nop; nop; nop; nop") - -/** - * @brief COP0 TLBR opcode. - * - * This opcode is used during TLB programming. It reads the contents of the TLB - * slot referenced by INDEX into the registers PAGEMASK, ENTRYHI, ENTRYLO0 and - * ENTRYLO1. - */ -#define C0_TLBR() asm volatile("tlbr; nop; nop; nop; nop") - -/** - * @brief COP0 TLBP opcode. - * - * This opcode is used during TLB programming. It probes the current TLB slots - * using ENTRYHI (virtual address) to find a matching slot. If it finds, it - * loads its index into INDEX. Otherwise, it sets the C0_INDEX_PROBE_FAILED bit - * in INDEX. - */ -#define C0_TLBP() asm volatile("tlbp; nop; nop; nop; nop") - -/** @} */ - -#endif \ No newline at end of file diff --git a/src/game/stack_trace.c b/src/game/stack_trace.c deleted file mode 100644 index e69de29bb2..0000000000 From 39142c38fd5361d2bb516355bfc9e3306b89e54a Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 22:15:24 -0400 Subject: [PATCH 025/109] --all-lines work --- Makefile | 13 +++--- src/debugger/crash_screen.c | 79 +++++++++++++++++++++---------------- src/debugger/disasm.c | 5 ++- src/debugger/disasm.h | 5 +-- src/debugger/map_parser.c | 23 ++++++----- src/debugger/map_parser.h | 6 ++- 6 files changed, 74 insertions(+), 57 deletions(-) diff --git a/Makefile b/Makefile index 30d040dce1..0b0ee809d2 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ FIXLIGHTS ?= 1 # Export symbols and stack trace data for crash screen DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS # Include line data, at the cost of a significantly longer link time -# DEBUG_EXPORT_ALL_LINES := -D DEBUG_EXPORT_ALL_LINES +DEBUG_EXPORT_ALL_LINES := -D DEBUG_EXPORT_ALL_LINES ifneq ($(DEBUG_EXPORT_ALL_LINES),) DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines @@ -682,8 +682,8 @@ patch: $(ROM) # Extra object file dependencies $(BUILD_DIR)/asm/ipl3.o: $(IPL3_RAW_FILES) -$(BUILD_DIR)/src/game/crash_screen.o: $(CRASH_TEXTURE_C_FILES) $(BUILD_DIR)/src/game/fasttext.o: $(FASTTEXT_TEXTURE_C_FILES) +$(BUILD_DIR)/src/debugger/crash_screen.o: $(CRASH_TEXTURE_C_FILES) $(BUILD_DIR)/src/game/version.o: $(BUILD_DIR)/src/game/version_data.h $(BUILD_DIR)/lib/aspMain.o: $(BUILD_DIR)/rsp/audio.bin $(SOUND_BIN_DIR)/sound_data.o: $(SOUND_BIN_DIR)/sound_data.ctl $(SOUND_BIN_DIR)/sound_data.tbl $(SOUND_BIN_DIR)/sequences.bin $(SOUND_BIN_DIR)/bank_sets @@ -740,11 +740,7 @@ $(BUILD_DIR)/src/game/rendering_graph_node.o: OPT_FLAGS := $(GRAPH_NODE_OPT_FLAG # $(info MATH_UTIL_OPT_FLAGS: $(MATH_UTIL_OPT_FLAGS)) # $(info GRAPH_NODE_OPT_FLAGS: $(GRAPH_NODE_OPT_FLAGS)) -<<<<<<< HEAD -ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) asm/debug $(GODDARD_SRC_DIRS) $(LIBZ_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(TEXT_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) rsp include) $(YAY0_DIR) $(addprefix $(YAY0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION) -======= -ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) $(GODDARD_SRC_DIRS) $(LIBZ_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) $(addprefix actors/vanilla_actors/,$(VNL_ACTRS_DIRS)) rsp include) $(YAY0_DIR) $(addprefix $(YAY0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION) ->>>>>>> 624553c8b (split out symtable and map_parser) +ALL_DIRS := $(BUILD_DIR) $(addprefix $(BUILD_DIR)/,$(SRC_DIRS) $(GODDARD_SRC_DIRS) $(LIBZ_SRC_DIRS) $(ULTRA_BIN_DIRS) $(BIN_DIRS) $(TEXTURE_DIRS) $(TEXT_DIRS) $(SOUND_SAMPLE_DIRS) $(addprefix levels/,$(LEVEL_DIRS)) rsp include) $(YAY0_DIR) $(addprefix $(YAY0_DIR)/,$(VERSION)) $(SOUND_BIN_DIR) $(SOUND_BIN_DIR)/sequences/$(VERSION) # Make sure build directory exists before compiling anything DUMMY != mkdir -p $(ALL_DIRS) @@ -970,8 +966,9 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(V)python3 tools/getGoddardSize.py $(BUILD_DIR)/sm64_prelim.map $(VERSION) $(BUILD_DIR)/sm64.sym.o: $(BUILD_DIR)/sm64_prelim.elf - $(call print,Assembling:,$<,$@) + $(call print,Generating symbol table:,$(@F)) $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --cross $(CROSS) $(BUILD_DIR)/sm64_prelim.elf + $(call print,Assembling:,$@) $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym # Link SM64 ELF file diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index ae85850c46..a083e63d63 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -13,10 +13,12 @@ #include "game/rumble_init.h" #include "game/printf.h" +#include "map_parser.h" +#include "disasm.h" + #include "sm64.h" extern char *strstr(char *, char *); -extern char *search_symbol(u32 vaddr, int *line); extern void inspector_print_backtrace(void *bt, int n, int bt_skip); int backtrace(void **buffer, int size); @@ -99,9 +101,6 @@ char *gFpcsrDesc[6] = { extern u64 osClockRate; -extern far char *parse_map(u32 pc); -extern far void map_data_init(void); -extern far char *find_function_in_stack(u32 *sp, int *line); struct { OSThread thread; @@ -267,7 +266,7 @@ void draw_crash_overview(OSThread *thread, s32 cause) { crash_screen_print(LEFT_MARGIN, 20, "Thread %d (%s)", thread->id, gCauseDesc[cause]); if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->pc); + char *fname = parse_map(tc->pc, TRUE); crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); int line = -1; @@ -279,18 +278,16 @@ void draw_crash_overview(OSThread *thread, s32 cause) { } crash_screen_print(LEFT_MARGIN, 84, "Address: 0x%08X", tc->pc); - - // do a lil backtrace } 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, 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(); if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->pc); + char *fname = parse_map(tc->pc, TRUE); crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); } crash_screen_print(LEFT_MARGIN, 52, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); @@ -304,7 +301,7 @@ void draw_crash_context(OSThread *thread, s32 cause) { 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); if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->ra); + char *fname = parse_map(tc->ra, TRUE); crash_screen_print(LEFT_MARGIN, 152, "RA at: %s", fname); } @@ -345,21 +342,35 @@ void draw_crash_log(void) { void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; - u32 temp_sp = (tc->sp - 0x24); + u32 temp_sp = (tc->sp + 0x24); crash_screen_draw_rect(0, 20, 320, 240); crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", temp_sp); + + // Current Func (EPC) + if ((u32) parse_map == MAP_PARSER_ADDRESS) { + crash_screen_print(LEFT_MARGIN, 35, "%08X", tc->pc); + } else { + crash_screen_print(LEFT_MARGIN, 35, "%08X (%s)", tc->pc, parse_map(tc->pc, TRUE)); + } + + // Previous Func (RA) if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, 35, "Curr Func: None"); + crash_screen_print(LEFT_MARGIN, 45, "0x%08X", tc->ra); } else { - crash_screen_print(LEFT_MARGIN, 35, "Curr Func: %s", parse_map(tc->pc)); + u32 ra = tc->ra; + int line = -1; + char *fname = parse_map(ra, FALSE); + + search_symbol(ra, &line); + crash_screen_print(LEFT_MARGIN, 45, "%08X (%s:%d)", ra, fname, line); } osWritebackDCacheAll(); - for (int i = 0; i < 18; i++) { + for (int i = 0; i < 17; i++) { if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "Stack Trace Disabled"); + crash_screen_print(LEFT_MARGIN, (55 + (i * 10)), "Stack Trace Disabled"); break; } else { if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { @@ -369,16 +380,14 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { int line = -1; char *fname = find_function_in_stack(&temp_sp, &line); while ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - // crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%08X)", temp_sp, *(u32*)temp_sp); fname = find_function_in_stack(&temp_sp, &line); } - crash_screen_print(LEFT_MARGIN, (45 + (i * 10)), "%08X (%s:%d)", temp_sp, fname, line); + crash_screen_print(LEFT_MARGIN, (55 + (i * 10)), "%08X (%s:%d)", temp_sp, fname, line); } } } -extern char *insn_disasm(u32 insn, u32 isPC); static u32 sProgramPosition = 0; void draw_disasm(OSThread *thread) { __OSThreadContext *tc = &thread->context; @@ -394,17 +403,17 @@ void draw_disasm(OSThread *thread) { for (int i = 0; i < 19; i++) { u32 addr = (sProgramPosition + (i * 4)); - char *disasm = insn_disasm(addr, (addr == tc->pc)); + char *disasm = insn_disasm((InsnData *)addr); if (disasm[0] == 0) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); } else { - if (disasm[0] == 'j') { + // if (disasm[0] == 'j') { int line = -1; search_symbol(addr, &line); if (line != -1) { crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%d:", line); } - } + // } crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%s", disasm); } @@ -416,14 +425,14 @@ void draw_disasm(OSThread *thread) { void draw_assert(UNUSED OSThread *thread) { crash_screen_draw_rect(0, 20, 320, 210); - crash_screen_print(LEFT_MARGIN, 25, "ASSERT PAGE"); + crash_screen_print(LEFT_MARGIN, 25, "Assert Page"); if (__n64Assert_Filename != NULL) { - crash_screen_print(LEFT_MARGIN, 35, "FILE: %s LINE %d", __n64Assert_Filename, __n64Assert_LineNum); - crash_screen_print(LEFT_MARGIN, 55, "MESSAGE:"); + crash_screen_print(LEFT_MARGIN, 35, "File: %s Line %d", __n64Assert_Filename, __n64Assert_LineNum); + crash_screen_print(LEFT_MARGIN, 55, "Message:"); crash_screen_print(LEFT_MARGIN, 70, " %s", __n64Assert_Message); } else { - crash_screen_print(LEFT_MARGIN, 35, "no failed assert to report."); + crash_screen_print(LEFT_MARGIN, 35, "No failed assert to report."); } osWritebackDCacheAll(); @@ -448,13 +457,16 @@ void draw_crash_screen(OSThread *thread) { crashPage--; updateBuffer = TRUE; } - if (gPlayer1Controller->buttonDown & D_CBUTTONS) { - sProgramPosition += 4; - updateBuffer = TRUE; - } - if (gPlayer1Controller->buttonDown & U_CBUTTONS) { - sProgramPosition -= 4; - 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)) { @@ -515,9 +527,6 @@ void thread2_crash_screen(UNUSED void *arg) { thread = get_crashed_thread(); gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; if (thread) { - // if ((u32) map_data_init != MAP_PARSER_ADDRESS) { - // map_data_init(); - // } gCrashScreen.thread.priority = 15; stop_sounds_in_continuous_banks(); stop_background_music(sBackgroundMusicQueue[0].seqId); diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c index 0d109b6276..eb8a8bb834 100644 --- a/src/debugger/disasm.c +++ b/src/debugger/disasm.c @@ -5,6 +5,9 @@ #include "macros.h" #include "farcall.h" #include "disasm.h" +#include "map_parser.h" + +static char insn_as_string[100]; InsnTemplate insn_db[] = { // We want instructions with opcodes first (prioritized) @@ -278,7 +281,7 @@ char *insn_disasm(InsnData *addr) { target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); if ((u32)parse_map != MAP_PARSER_ADDRESS) { strp += sprintf(strp, "%-9s %s(%08X)", insn_db[i].name, - parse_map(target), target + parse_map(target, FALSE), target ); } else { strp += sprintf(strp, "%-9s %08X", insn_db[i].name, diff --git a/src/debugger/disasm.h b/src/debugger/disasm.h index 010fb92f25..9fc667c2b3 100644 --- a/src/debugger/disasm.h +++ b/src/debugger/disasm.h @@ -23,9 +23,6 @@ enum ParamTypes { PARAM_EMUX, }; -extern far char *parse_map(u32 pc); -static char insn_as_string[100]; - typedef struct PACKED { u16 rd : 5; u16 shift_amt : 5; @@ -84,3 +81,5 @@ typedef struct PACKED { #define OP_COP1 0b010001 #define OP_BRANCH 0b000001 // technically "REGIMM" +extern char *insn_disasm(InsnData *insn); + diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c index e59a3f26da..ab0dfa515b 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -19,7 +19,7 @@ extern u8 _engineSegmentTextEnd[]; extern u8 _goddardSegmentStart[]; extern u8 _goddardSegmentTextEnd[]; -char* __symbolize(void *vaddr, char *buf, int size) { +char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset) { symtable_header_t symt = symt_open(); if (symt.head[0]) { u32 addr = (u32)vaddr; @@ -34,16 +34,20 @@ char* __symbolize(void *vaddr, char *buf, int size) { symt_entry_fetch(&symt, &entry, idx); char *func = symt_entry_func(&symt, &entry, addr, buf, size-12); char lbuf[12]; - sprintf(lbuf, "+0x%lx", addr - ADDRENTRY_ADDR(a)); + if (andOffset) { + sprintf(lbuf, "+0x%lx", addr - ADDRENTRY_ADDR(a)); + } else { + lbuf[0] = 0; + } return strcat(func, lbuf); } sprintf(buf, "%s", UNKNOWN_SYMBOL); return buf; } -char *parse_map(u32 addr) { +char *parse_map(u32 addr, u32 andOffset) { static char map_name[64] ALIGNED16; - __symbolize((u32*)addr, map_name, sizeof(map_name)); + __symbolize((u32*)addr, map_name, sizeof(map_name), andOffset); return map_name; } @@ -60,28 +64,29 @@ char *find_function_in_stack(u32 *sp, int *line) { if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET); + ret = parse_map(val + CALLSITE_OFFSET, FALSE); break; } else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET); + ret = parse_map(val + CALLSITE_OFFSET, FALSE); break; } else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET); + ret = parse_map(val + CALLSITE_OFFSET, FALSE); break; } } if (ret) { - search_symbol((void*)(val + CALLSITE_OFFSET), line); + search_symbol(val + CALLSITE_OFFSET, line); } return ret; } -char *search_symbol(void *vaddr, int *line) { +char *search_symbol(u32 addr, int *line) { static char filebuf[100]; + void *vaddr = (void *)addr; symtable_header_t symt = symt_open(); if (symt.head[0]) { diff --git a/src/debugger/map_parser.h b/src/debugger/map_parser.h index 53962305f3..a61aab514d 100644 --- a/src/debugger/map_parser.h +++ b/src/debugger/map_parser.h @@ -1,3 +1,7 @@ #pragma once -char *search_symbol(void *vaddr, int *line); +#include "farcall.h" + +char *search_symbol(u32 vaddr, int *line); +extern far char *parse_map(u32 pc, u32 andOffset); +extern far char *find_function_in_stack(u32 *sp, int *line); From d221c98e5cbc67a3d7946368b64ae8dce99754fc Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 22:17:18 -0400 Subject: [PATCH 026/109] properly guard --all-lines in the code itself --- Makefile | 2 +- src/debugger/crash_screen.c | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 0b0ee809d2..fcde357fc9 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ FIXLIGHTS ?= 1 # Export symbols and stack trace data for crash screen DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS # Include line data, at the cost of a significantly longer link time -DEBUG_EXPORT_ALL_LINES := -D DEBUG_EXPORT_ALL_LINES +# DEBUG_EXPORT_ALL_LINES := -D DEBUG_EXPORT_ALL_LINES ifneq ($(DEBUG_EXPORT_ALL_LINES),) DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index a083e63d63..b71a7720ae 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -407,13 +407,17 @@ void draw_disasm(OSThread *thread) { if (disasm[0] == 0) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); } else { - // if (disasm[0] == 'j') { +#ifndef DEBUG_EXPORT_ALL_LINES + if (disasm[0] == 'j') { +#endif // DEBUG_EXPORT_ALL_LINES int line = -1; search_symbol(addr, &line); if (line != -1) { crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%d:", line); } - // } +#ifndef DEBUG_EXPORT_ALL_LINES + } +#endif // DEBUG_EXPORT_ALL_LINES crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%s", disasm); } From 2f9e4a3d8131fcde7a578d4214f063a3e2a9139e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 22:26:22 -0400 Subject: [PATCH 027/109] add text color; fix J disasm --- src/debugger/crash_screen.c | 20 ++++++++++++++++++-- src/debugger/disasm.c | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index b71a7720ae..3ee26ec27d 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -112,6 +112,15 @@ struct { u16 height; } gCrashScreen; +static u16 gCrashScreenColor = 0xFFFF; + +static void set_text_color(u32 r, u32 g, u32 b) { + gCrashScreenColor = GPACK_RGBA5551(r, g, b, 255); +} +static void reset_text_color(void) { + gCrashScreenColor = 0xFFFF; +} + void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { u16 *ptr; s32 i, j; @@ -139,15 +148,16 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; + u16 color = gCrashScreenColor; + for (i = 0; i < GLYPH_HEIGHT; i++) { bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); rowMask = *data++; data ++; for (j = 0; j < (GLYPH_WIDTH); j++) { - // *ptr++ = (bit & rowMask) ? 0xFFFF : 1; if (bit & rowMask) { - *ptr = 0xFFFF; + *ptr = color; } ptr++; bit >>= 1; @@ -404,6 +414,11 @@ void draw_disasm(OSThread *thread) { u32 addr = (sProgramPosition + (i * 4)); char *disasm = insn_disasm((InsnData *)addr); + + if (addr == tc->pc) { + set_text_color(255, 0, 0); + } + if (disasm[0] == 0) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); } else { @@ -421,6 +436,7 @@ void draw_disasm(OSThread *thread) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%s", disasm); } + reset_text_color(); } osWritebackDCacheAll(); diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c index eb8a8bb834..6a1ee4421f 100644 --- a/src/debugger/disasm.c +++ b/src/debugger/disasm.c @@ -290,7 +290,7 @@ char *insn_disasm(InsnData *addr) { } break; case PARAM_JUMP: - target = 0x80000000 | (insn.d & 0x0FFFFFFF); + target = 0x80000000 | (insn.d & 0x03FFFFFF); strp += sprintf(strp, "%-9s %08X", insn_db[i].name, target ); From 3d576aa89930ebe5230cd85962ef30e38ed3ead0 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 19 Jun 2025 22:31:20 -0400 Subject: [PATCH 028/109] fix the makefile switch; decontrast the line numbers in disasm --- Makefile | 5 +++-- src/debugger/crash_screen.c | 12 ++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index fcde357fc9..d3d2d0790d 100644 --- a/Makefile +++ b/Makefile @@ -95,10 +95,11 @@ FIXLIGHTS ?= 1 # Export symbols and stack trace data for crash screen DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS # Include line data, at the cost of a significantly longer link time -# DEBUG_EXPORT_ALL_LINES := -D DEBUG_EXPORT_ALL_LINES +DEBUG_EXPORT_ALL_LINES := 1 -ifneq ($(DEBUG_EXPORT_ALL_LINES),) +ifeq ($(DEBUG_EXPORT_ALL_LINES),1) DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines + DEFINES += DEBUG_EXPORT_ALL_LINES=1 endif TARGET := sm64 diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 3ee26ec27d..8e7559f3b9 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -415,9 +415,6 @@ void draw_disasm(OSThread *thread) { char *disasm = insn_disasm((InsnData *)addr); - if (addr == tc->pc) { - set_text_color(255, 0, 0); - } if (disasm[0] == 0) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); @@ -428,17 +425,24 @@ void draw_disasm(OSThread *thread) { int line = -1; search_symbol(addr, &line); if (line != -1) { + set_text_color(200, 200, 200); crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%d:", line); + reset_text_color(); } #ifndef DEBUG_EXPORT_ALL_LINES } #endif // DEBUG_EXPORT_ALL_LINES + if (addr == tc->pc) { + set_text_color(255, 0, 0); + } else { + reset_text_color(); + } crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%s", disasm); } - reset_text_color(); } + reset_text_color(); osWritebackDCacheAll(); } From 5b2d8cb2dc7bf753b5661abe700041ed822c2196 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Fri, 20 Jun 2025 19:22:27 -0400 Subject: [PATCH 029/109] diff reductions --- Makefile | 2 +- src/debugger/crash_screen.c | 3 +- src/game/mario_actions_stationary.c | 3 -- tools/mapPacker.py | 53 ----------------------------- 4 files changed, 3 insertions(+), 58 deletions(-) delete mode 100644 tools/mapPacker.py diff --git a/Makefile b/Makefile index d3d2d0790d..639f89ad13 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ FIXLIGHTS ?= 1 # Export symbols and stack trace data for crash screen DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS # Include line data, at the cost of a significantly longer link time -DEBUG_EXPORT_ALL_LINES := 1 +DEBUG_EXPORT_ALL_LINES := 0 ifeq ($(DEBUG_EXPORT_ALL_LINES),1) DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 8e7559f3b9..6ef81b9854 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -420,7 +420,8 @@ void draw_disasm(OSThread *thread) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); } else { #ifndef DEBUG_EXPORT_ALL_LINES - if (disasm[0] == 'j') { + // catch `jal` and `jalr` callsites + if (disasm[0] == 'j' && disasm[1] == 'a') { #endif // DEBUG_EXPORT_ALL_LINES int line = -1; search_symbol(addr, &line); diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index 883834f128..b0692e54aa 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -115,9 +115,6 @@ s32 act_idle(struct MarioState *m) { if (!(m->actionArg & 1) && m->health < 0x300) { return set_mario_action(m, ACT_PANTING, 0); } - if(m->controller->buttonPressed&L_TRIG){ - *(vs8*)0=m->controller->buttonPressed << 2; - } if (check_common_idle_cancels(m)) { return TRUE; diff --git a/tools/mapPacker.py b/tools/mapPacker.py deleted file mode 100644 index 4225db7374..0000000000 --- a/tools/mapPacker.py +++ /dev/null @@ -1,53 +0,0 @@ -import sys, struct, subprocess - -class MapEntry(): - def __init__(self, nm, addr): - self.name = nm - self.addr = addr - self.strlen = (len(nm) + 4) & (~3) - def __str__(self): - return "%s %s %d" % (self.addr, self.name, self.strlen) - def __repr__(self): - return "%s %s %d" % (self.addr, self.name, self.strlen) - - -structDef = ">LLLL" - -symNames = [] - - -proc = subprocess.Popen(["nm", "-S", sys.argv[1]], stdout=subprocess.PIPE) - -symbols = proc.communicate()[0].decode('ascii').split("\n") -for line in symbols: - # format: - # 80153210 000000f8 T global_sym - # 80153210 t static_sym - tokens = line.split() - if len(tokens) >= 3 and len(tokens[-2]) == 1: - addr = int(tokens[0], 16) - if addr & 0x80000000: - if (tokens[-2].lower() == "t"): - if (".part." not in tokens[-1]): - if (".constprop." not in tokens[-1]): - symNames.append(MapEntry(tokens[-1], addr)) - - - -f1 = open(sys.argv[2], "wb+") -f2 = open(sys.argv[3], "wb+") - -symNames.sort(key=lambda x: x.addr) - -off = 0 -for x in symNames: - f1.write(struct.pack(structDef, x.addr, off, len(x.name), 0)) - f2.write(struct.pack(">%ds" % x.strlen, bytes(x.name, encoding="ascii"))) - off += x.strlen - - -f1.close() -f2.close() - -# print('\n'.join([str(hex(x.addr)) + " " + x.name for x in symNames])) - From 7b1177245eb645e4a86d81d2c904ee36ddc87b77 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Fri, 20 Jun 2025 19:42:51 -0400 Subject: [PATCH 030/109] make search_symbol return all info instead --- src/debugger/crash_screen.c | 29 ++++++++++++++--------------- src/debugger/map_parser.c | 20 +++++++++++++------- src/debugger/map_parser.h | 9 ++++++++- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 6ef81b9854..40ad516ee1 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -276,14 +276,15 @@ void draw_crash_overview(OSThread *thread, s32 cause) { crash_screen_print(LEFT_MARGIN, 20, "Thread %d (%s)", thread->id, gCauseDesc[cause]); if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->pc, TRUE); - crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); + symtable_info_t info = get_symbol_info(tc->pc); - int line = -1; - char *t = search_symbol(tc->pc, &line); - if (line != -1) { - crash_screen_print(LEFT_MARGIN, 60, "File: %s", t); - crash_screen_print(LEFT_MARGIN, 72, "Line: %d", line); + 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); +#ifndef 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 } } @@ -369,11 +370,9 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { crash_screen_print(LEFT_MARGIN, 45, "0x%08X", tc->ra); } else { u32 ra = tc->ra; - int line = -1; - char *fname = parse_map(ra, FALSE); + symtable_info_t info = get_symbol_info(ra); - search_symbol(ra, &line); - crash_screen_print(LEFT_MARGIN, 45, "%08X (%s:%d)", ra, fname, line); + crash_screen_print(LEFT_MARGIN, 45, "%08X (%s:%d)", ra, info.func, info.line); } osWritebackDCacheAll(); @@ -419,15 +418,15 @@ void draw_disasm(OSThread *thread) { if (disasm[0] == 0) { crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); } else { + symtable_info_t info = get_symbol_info(addr); #ifndef DEBUG_EXPORT_ALL_LINES // catch `jal` and `jalr` callsites if (disasm[0] == 'j' && disasm[1] == 'a') { #endif // DEBUG_EXPORT_ALL_LINES - int line = -1; - search_symbol(addr, &line); - if (line != -1) { + + if (info.line != -1) { set_text_color(200, 200, 200); - crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%d:", line); + crash_screen_print(LEFT_MARGIN, (35 + (i * 10)), "%d:", info.line); reset_text_color(); } #ifndef DEBUG_EXPORT_ALL_LINES diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c index ab0dfa515b..7c839f98e5 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -78,18 +78,21 @@ char *find_function_in_stack(u32 *sp, int *line) { } if (ret) { - search_symbol(val + CALLSITE_OFFSET, line); + symtable_info_t info = get_symbol_info(val + CALLSITE_OFFSET); + *line = info.line; } return ret; } -char *search_symbol(u32 addr, int *line) { +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); @@ -99,9 +102,12 @@ char *search_symbol(u32 addr, int *line) { // Read the symbol name filebuf[0] = 0; symt_entry_fetch(&symt, &entry, idx); - *line = entry.line; - char *file = symt_entry_file(&symt, &entry, filebuf, sizeof(filebuf)); - return file; + info.line = entry.line; + info.func_offset = entry.func_off; + info.file = symt_entry_file(&symt, &entry, filebuf, sizeof(filebuf)); + info.func = parse_map(addr, TRUE); + + return info; } - return UNKNOWN_SYMBOL; + return (symtable_info_t){.line = -1}; } diff --git a/src/debugger/map_parser.h b/src/debugger/map_parser.h index a61aab514d..4bf3573c04 100644 --- a/src/debugger/map_parser.h +++ b/src/debugger/map_parser.h @@ -2,6 +2,13 @@ #include "farcall.h" -char *search_symbol(u32 vaddr, int *line); +typedef struct { + char *file; + char *func; + int line; + u16 func_offset; +} symtable_info_t; + +symtable_info_t get_symbol_info(u32 vaddr); extern far char *parse_map(u32 pc, u32 andOffset); extern far char *find_function_in_stack(u32 *sp, int *line); From b2ee29479bb136f170e0518d7f21b94330eb648e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Fri, 20 Jun 2025 23:11:38 -0400 Subject: [PATCH 031/109] add func names to starts in disasm; more robust make switches --- Makefile | 10 +++++++--- src/debugger/crash_screen.c | 19 ++++++++++++++----- src/debugger/map_parser.c | 14 ++++++++++---- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 639f89ad13..c3fff5ccc1 100644 --- a/Makefile +++ b/Makefile @@ -90,16 +90,20 @@ endif # FIXLIGHTS - converts light objects to light color commands for assets, needed for vanilla-style lighting FIXLIGHTS ?= 1 -# Debug Switches - Comment these out to remove the functionality - +# Debug Switches - Comment these out to remove the functionality, then run `make clean` # Export symbols and stack trace data for crash screen -DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS +DEBUG_EXPORT_SYMBOLS := 1 # Include line data, at the cost of a significantly longer link time DEBUG_EXPORT_ALL_LINES := 0 ifeq ($(DEBUG_EXPORT_ALL_LINES),1) DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines DEFINES += DEBUG_EXPORT_ALL_LINES=1 + DEFAULT_OPT_FLAGS += -g -gdwarf-4 +endif +ifeq ($(DEBUG_EXPORT_SYMBOLS),1) + DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS + DEFAULT_OPT_FLAGS += -gdwarf-4 endif TARGET := sm64 diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 40ad516ee1..35ddbcc748 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -281,7 +281,7 @@ void draw_crash_overview(OSThread *thread, s32 cause) { 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); -#ifndef DEBUG_EXPORT_ALL_LINES +#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 @@ -408,6 +408,8 @@ void draw_disasm(OSThread *thread) { crash_screen_print(LEFT_MARGIN, 25, "Program Counter: %08X", sProgramPosition); osWritebackDCacheAll(); + int skiplines = 0; + int currline = 0; for (int i = 0; i < 19; i++) { u32 addr = (sProgramPosition + (i * 4)); @@ -416,17 +418,24 @@ void draw_disasm(OSThread *thread) { if (disasm[0] == 0) { - crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%08X", addr); + crash_screen_print(LEFT_MARGIN + 22, 35 + (skiplines * 10) + (i * 10), "%08X", addr); } else { symtable_info_t info = get_symbol_info(addr); + + if (info.func_offset == 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 + (i * 10)), "%d:", info.line); + crash_screen_print(LEFT_MARGIN, 35 + (skiplines * 10) + (i * 10), "%d:", info.line); reset_text_color(); } #ifndef DEBUG_EXPORT_ALL_LINES @@ -437,7 +446,7 @@ void draw_disasm(OSThread *thread) { } else { reset_text_color(); } - crash_screen_print(LEFT_MARGIN + 22, (35 + (i * 10)), "%s", disasm); + crash_screen_print(LEFT_MARGIN + 22, 35 + (skiplines * 10) + (i * 10), "%s", disasm); } } diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c index 7c839f98e5..7f58e038fd 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -39,6 +39,8 @@ char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset) { } else { lbuf[0] = 0; } + entry.func_off = addr - ADDRENTRY_ADDR(a); + return strcat(func, lbuf); } sprintf(buf, "%s", UNKNOWN_SYMBOL); @@ -47,16 +49,21 @@ char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset) { 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); - return map_name; + if (ret[0] == ' ') { + ret++; + } + return ret; } char *find_function_in_stack(u32 *sp, int *line) { char *ret = NULL; u32 val = *sp; - #define CALLSITE_OFFSET 8 + #define CALLSITE_OFFSET 0 for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { val = *sp; val = *(u32 *)val; @@ -90,7 +97,6 @@ symtable_info_t get_symbol_info(u32 addr) { void *vaddr = (void *)addr; symtable_header_t symt = symt_open(); - if (symt.head[0]) { symtable_info_t info; u32 addr = (u32)vaddr; @@ -105,7 +111,7 @@ symtable_info_t get_symbol_info(u32 addr) { info.line = entry.line; info.func_offset = entry.func_off; info.file = symt_entry_file(&symt, &entry, filebuf, sizeof(filebuf)); - info.func = parse_map(addr, TRUE); + info.func = parse_map(addr, (info.func_offset != 0)); return info; } From 17904c6be8d8b2cf0359e2b5dbc7b1fcadc7d9a5 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sat, 21 Jun 2025 00:52:22 -0400 Subject: [PATCH 032/109] actually generate line tables when generating symbols; fix function name heuristic in disasm --- Makefile | 4 ++-- src/debugger/crash_screen.c | 47 ++++++++++++++++++------------------- src/debugger/map_parser.c | 4 +++- src/debugger/map_parser.h | 1 + 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index c3fff5ccc1..a730fb1f58 100644 --- a/Makefile +++ b/Makefile @@ -99,11 +99,11 @@ DEBUG_EXPORT_ALL_LINES := 0 ifeq ($(DEBUG_EXPORT_ALL_LINES),1) DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines DEFINES += DEBUG_EXPORT_ALL_LINES=1 - DEFAULT_OPT_FLAGS += -g -gdwarf-4 + DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 endif ifeq ($(DEBUG_EXPORT_SYMBOLS),1) DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS - DEFAULT_OPT_FLAGS += -gdwarf-4 + DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 endif TARGET := sm64 diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 35ddbcc748..30706cc791 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "buffers/framebuffers.h" @@ -18,9 +19,12 @@ #include "sm64.h" +extern u16 sRenderedFramebuffer; +extern struct SequenceQueueItem sBackgroundMusicQueue[6]; +extern void audio_signal_game_loop_tick(void); +extern void stop_sounds_in_continuous_banks(void); +extern void read_controller_inputs(s32 threadID); extern char *strstr(char *, char *); -extern void inspector_print_backtrace(void *bt, int n, int bt_skip); -int backtrace(void **buffer, int size); // Configurable Defines #define X_KERNING 6 @@ -41,7 +45,7 @@ enum crashPages { PAGE_COUNT }; -char *crashPageNames[] = { +static char *crashPageNames[] = { [PAGE_SIMPLE] = "(Overview)", [PAGE_CONTEXT] = "(Context)", #ifdef PUPPYPRINT_DEBUG @@ -52,7 +56,7 @@ char *crashPageNames[] = { [PAGE_ASSERTS] = "(Assert)", }; -u8 gCrashScreenCharToGlyph[128] = { +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, @@ -63,14 +67,19 @@ u8 gCrashScreenCharToGlyph[128] = { 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, }; -u32 gCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { +static u32 sCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; +<<<<<<< HEAD u8 crashPage = 0; u8 updateBuffer = TRUE; static char crashScreenBuf[0x200]; +======= +static u8 crashPage = 0; +static u8 updateBuffer = TRUE; +>>>>>>> 108056920 (actually generate line tables when generating symbols; fix function name heuristic in disasm) char *gCauseDesc[18] = { "Interrupt", @@ -98,11 +107,9 @@ char *gFpcsrDesc[6] = { "Inexact operation", }; - - -extern u64 osClockRate; - -struct { +static u32 sProgramPosition = 0; +static u16 gCrashScreenTextColor = 0xFFFF; +static struct { OSThread thread; u64 stack[THREAD2_STACK / sizeof(u64)]; OSMesgQueue mesgQueue; @@ -112,13 +119,12 @@ struct { u16 height; } gCrashScreen; -static u16 gCrashScreenColor = 0xFFFF; - static void set_text_color(u32 r, u32 g, u32 b) { - gCrashScreenColor = GPACK_RGBA5551(r, g, b, 255); + gCrashScreenTextColor = GPACK_RGBA5551(r, g, b, 255); } + static void reset_text_color(void) { - gCrashScreenColor = 0xFFFF; + gCrashScreenTextColor = 0xFFFF; } void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { @@ -144,11 +150,11 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { if (glyph > 0x7F) return; - data = &gCrashScreenFont[((glyph&0xF)*GLYPH_HEIGHT * 2) + (glyph >= 64)]; + data = &sCrashScreenFont[((glyph&0xF)*GLYPH_HEIGHT * 2) + (glyph >= 64)]; ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - u16 color = gCrashScreenColor; + u16 color = gCrashScreenTextColor; for (i = 0; i < GLYPH_HEIGHT; i++) { bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); @@ -397,7 +403,6 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { } } -static u32 sProgramPosition = 0; void draw_disasm(OSThread *thread) { __OSThreadContext *tc = &thread->context; @@ -422,7 +427,7 @@ void draw_disasm(OSThread *thread) { } else { symtable_info_t info = get_symbol_info(addr); - if (info.func_offset == 0 && currline != info.line) { + 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); @@ -542,12 +547,6 @@ OSThread *get_crashed_thread(void) { return NULL; } -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); -extern struct SequenceQueueItem sBackgroundMusicQueue[6]; - void thread2_crash_screen(UNUSED void *arg) { OSMesg mesg; OSThread *thread = NULL; diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c index 7f58e038fd..a606809362 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -110,8 +110,10 @@ symtable_info_t get_symbol_info(u32 addr) { 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, (info.func_offset != 0)); + info.func = parse_map(addr, (info.distance != 0)); return info; } diff --git a/src/debugger/map_parser.h b/src/debugger/map_parser.h index 4bf3573c04..44d1eda5d9 100644 --- a/src/debugger/map_parser.h +++ b/src/debugger/map_parser.h @@ -6,6 +6,7 @@ typedef struct { char *file; char *func; int line; + u16 distance; u16 func_offset; } symtable_info_t; From 3a69ecc32b5d5c33fb6cffc08afc845cbf0c41c7 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sat, 21 Jun 2025 01:25:24 -0400 Subject: [PATCH 033/109] I thought assertf was in the repo already :thinking: --- asm/n64_assert.s | 7 +++- src/audio/external.c | 3 +- src/audio/heap.c | 2 +- src/boot/main.c | 11 ++--- src/debugger/assert.h | 62 +++++++++++++++++++++++++++++ src/debugger/crash_screen.c | 18 +++++++-- src/engine/geo_layout.c | 3 +- src/engine/level_script.c | 7 ++-- src/engine/surface_load.c | 1 + src/game/debug.h | 52 ------------------------ src/game/level_update.c | 1 + src/game/mario_actions_stationary.c | 5 +++ src/game/object_list_processor.c | 3 +- 13 files changed, 105 insertions(+), 70 deletions(-) create mode 100644 src/debugger/assert.h diff --git a/asm/n64_assert.s b/asm/n64_assert.s index 33f991093f..eaedbaa355 100644 --- a/asm/n64_assert.s +++ b/asm/n64_assert.s @@ -1,19 +1,22 @@ .include "macros.inc" +#include "src/debugger/assert.h" .section .data glabel __n64Assert_Filename .skip 4 glabel __n64Assert_LineNum .skip 4 -glabel __n64Assert_Message +glabel __n64Assert_Condition .skip 4 +glabel __n64Assert_MessageBuf +.skip ASSERT_MESGBUF_SIZE .section .text glabel __n64Assert sw $a0, __n64Assert_Filename sw $a1, __n64Assert_LineNum -sw $a2, __n64Assert_Message +sw $a2, __n64Assert_Condition syscall nop diff --git a/src/audio/external.c b/src/audio/external.c index af117f47d1..5c5885ba70 100644 --- a/src/audio/external.c +++ b/src/audio/external.c @@ -7,6 +7,7 @@ #include "external.h" #include "playback.h" #include "synthesis.h" +#include "debugger/assert.h" #include "game/debug.h" #include "game/main.h" #include "game/level_update.h" @@ -698,7 +699,7 @@ struct SPTask *create_next_audio_frame_task(void) { * Called from threads: thread5_game_loop */ void play_sound(s32 soundBits, f32 *pos) { - assert(((soundBits & SOUNDARGS_MASK_SOUNDID) >> SOUNDARGS_SHIFT_SOUNDID) != 0xff, "Sfx tables do not support a sound id of 0xff!"); + assertf(((soundBits & SOUNDARGS_MASK_SOUNDID) >> SOUNDARGS_SHIFT_SOUNDID) != 0xff, "Sfx tables do not support a sound id of 0xff!"); sSoundRequests[sSoundRequestCount].soundBits = soundBits; sSoundRequests[sSoundRequestCount].position = pos; sSoundRequestCount++; diff --git a/src/audio/heap.c b/src/audio/heap.c index 75161172d8..0c027d9292 100644 --- a/src/audio/heap.c +++ b/src/audio/heap.c @@ -1058,7 +1058,7 @@ void init_reverb_us(s32 presetId) { #endif aggress(gBetterReverbPresetCount > 0, "No BETTER_REVERB presets exist!"); - assert(gBetterReverbPresetValue < gBetterReverbPresetCount, "BETTER_REVERB preset value exceeds total number of available presets!"); + assertf(gBetterReverbPresetValue < gBetterReverbPresetCount, "BETTER_REVERB preset value exceeds total number of available presets!"); betterReverbPreset = &gBetterReverbSettings[0]; } diff --git a/src/boot/main.c b/src/boot/main.c index a239eecb1e..dd6322e3ed 100644 --- a/src/boot/main.c +++ b/src/boot/main.c @@ -6,6 +6,7 @@ #include "sm64.h" #include "audio/external.h" +#include "debugger/assert.h" #include "game/game_init.h" #include "game/debug.h" #include "game/memory.h" @@ -324,20 +325,20 @@ void alert_rcp_hung_up(void) { void check_stack_validity(void) { gIdleThreadStack[0]++; gIdleThreadStack[THREAD1_STACK - 1]++; - assert(gIdleThreadStack[0] == gIdleThreadStack[THREAD1_STACK - 1], "Thread 1 stack overflow.") + assertf(gIdleThreadStack[0] == gIdleThreadStack[THREAD1_STACK - 1], "Thread 1 stack overflow."); gThread3Stack[0]++; gThread3Stack[THREAD3_STACK - 1]++; - assert(gThread3Stack[0] == gThread3Stack[THREAD3_STACK - 1], "Thread 3 stack overflow.") + assertf(gThread3Stack[0] == gThread3Stack[THREAD3_STACK - 1], "Thread 3 stack overflow."); gThread4Stack[0]++; gThread4Stack[THREAD4_STACK - 1]++; - assert(gThread4Stack[0] == gThread4Stack[THREAD4_STACK - 1], "Thread 4 stack overflow.") + assertf(gThread4Stack[0] == gThread4Stack[THREAD4_STACK - 1], "Thread 4 stack overflow."); gThread5Stack[0]++; gThread5Stack[THREAD5_STACK - 1]++; - assert(gThread5Stack[0] == gThread5Stack[THREAD5_STACK - 1], "Thread 5 stack overflow.") + assertf(gThread5Stack[0] == gThread5Stack[THREAD5_STACK - 1], "Thread 5 stack overflow."); #if ENABLE_RUMBLE gThread6Stack[0]++; gThread6Stack[THREAD6_STACK - 1]++; - assert(gThread6Stack[0] == gThread6Stack[THREAD6_STACK - 1], "Thread 6 stack overflow.") + assertf(gThread6Stack[0] == gThread6Stack[THREAD6_STACK - 1], "Thread 6 stack overflow."); #endif } #endif diff --git a/src/debugger/assert.h b/src/debugger/assert.h new file mode 100644 index 0000000000..7c77112c50 --- /dev/null +++ b/src/debugger/assert.h @@ -0,0 +1,62 @@ +#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 error(message) { \ + sprintf(__n64Assert_MessageBuf, message); \ + __n64Assert(__FILE__, __LINE__, "Unconditional"); \ +} + +/** + * Wrapper for assert/aggress + */ +#define __assert_wrapper(cond) __n64Assert(__FILE__, __LINE__, (cond)) + +/** + * Will always cause a crash if cond is not true (handle with care) + */ +#define aggress(cond, message) do {\ + if ((cond) == FALSE) { \ + sprintf(__n64Assert_MessageBuf, message); \ + __assert_wrapper(#cond); \ + } \ +} while (0); + +/** + * Will cause a crash if cond is not true, and DEBUG is defined (allows for quick removal of littered asserts) + */ +#ifdef DEBUG_ASSERTIONS +#define assertf(cond, ...) do {\ + if ((cond) == FALSE) { \ + sprintf(__n64Assert_MessageBuf, __VA_ARGS__); \ + __assert_wrapper(#cond); \ + } \ +} while (0); +#else +#define assertf(cond, ...) if ((cond) == FALSE) { __builtin_unreachable(); } +#endif + +#ifdef DEBUG_ASSERTIONS +#define assert(cond) do {\ + if ((cond) == FALSE) { \ + __n64Assert_MessageBuf[0] = 0; \ + __assert_wrapper(#cond); \ + } \ +} while (0); +#else +#define assert(cond) if ((cond) == FALSE) { __builtin_unreachable(); } +#endif + +#endif // ASSEMBLER diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 30706cc791..a6ba7a4fcd 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -16,6 +16,7 @@ #include "map_parser.h" #include "disasm.h" +#include "assert.h" #include "sm64.h" @@ -463,12 +464,15 @@ void draw_disasm(OSThread *thread) { void draw_assert(UNUSED OSThread *thread) { crash_screen_draw_rect(0, 20, 320, 210); - crash_screen_print(LEFT_MARGIN, 25, "Assert Page"); + crash_screen_print(LEFT_MARGIN, 25, "Assert"); if (__n64Assert_Filename != NULL) { - crash_screen_print(LEFT_MARGIN, 35, "File: %s Line %d", __n64Assert_Filename, __n64Assert_LineNum); - crash_screen_print(LEFT_MARGIN, 55, "Message:"); - crash_screen_print(LEFT_MARGIN, 70, " %s", __n64Assert_Message); + 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); + 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."); } @@ -489,10 +493,12 @@ void draw_crash_screen(OSThread *thread) { 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; } @@ -512,6 +518,7 @@ void draw_crash_screen(OSThread *thread) { } 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); @@ -567,6 +574,9 @@ void thread2_crash_screen(UNUSED void *arg) { play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); audio_signal_game_loop_tick(); crash_screen_sleep(200); + if (thread->context.cause == EXC_SYSCALL) { + crashPage = PAGE_ASSERTS; + } continue; } } else { diff --git a/src/engine/geo_layout.c b/src/engine/geo_layout.c index cf217a936d..c677b6d089 100644 --- a/src/engine/geo_layout.c +++ b/src/engine/geo_layout.c @@ -4,6 +4,7 @@ #include "geo_layout.h" #include "math_util.h" #include "game/memory.h" +#include "debugger/assert.h" #include "graph_node.h" #include "game/debug.h" @@ -784,7 +785,7 @@ struct GraphNode *process_geo_layout(struct AllocOnlyPool *pool, void *segptr) { gGeoLayoutStack[1] = 0; while (gGeoLayoutCommand != NULL) { - assert((gGeoLayoutCommand[0x00] < GEO_CMD_COUNT), "Invalid or unloaded geo layout detected."); + assertf((gGeoLayoutCommand[0x00] < GEO_CMD_COUNT), "Invalid or unloaded geo layout detected."); GeoLayoutJumpTable[gGeoLayoutCommand[0x00]](); } diff --git a/src/engine/level_script.c b/src/engine/level_script.c index a6246c0dc4..c62c35b575 100644 --- a/src/engine/level_script.c +++ b/src/engine/level_script.c @@ -8,6 +8,7 @@ #include "audio/synthesis.h" #include "buffers/framebuffers.h" #include "buffers/zbuffer.h" +#include "debugger/assert.h" #include "game/area.h" #include "game/debug.h" #include "game/game_init.h" @@ -439,7 +440,7 @@ static void level_cmd_load_model_from_dl(void) { s16 layer = CMD_GET(u16, 0x8); void *dl_ptr = CMD_GET(void *, 4); - assert_args(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); + assertf(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); if (model < MODEL_ID_COUNT) { gLoadedGraphNodes[model] = (struct GraphNode *) init_graph_node_display_list(sLevelPool, 0, layer, dl_ptr); @@ -452,7 +453,7 @@ static void level_cmd_load_model_from_geo(void) { ModelID16 model = CMD_GET(ModelID16, 2); void *geo = CMD_GET(void *, 4); - assert_args(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); + assertf(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); if (model < MODEL_ID_COUNT) { gLoadedGraphNodes[model] = process_geo_layout(sLevelPool, geo); } @@ -466,7 +467,7 @@ static void level_cmd_23(void) { void *dl = CMD_GET(void *, 4); s32 scale = CMD_GET(s32, 8); - assert_args(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); + assertf(model < MODEL_ID_COUNT, "Tried to load an invalid model ID: 0x%04X", model); if (model < MODEL_ID_COUNT) { // GraphNodeScale has a GraphNode at the top. This // is being stored to the array, so cast the pointer. diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c index 0134e23a56..70d6040ba3 100644 --- a/src/engine/surface_load.c +++ b/src/engine/surface_load.c @@ -5,6 +5,7 @@ #include "graph_node.h" #include "behavior_script.h" #include "behavior_data.h" +#include "debugger/assert.h" #include "game/memory.h" #include "game/object_helpers.h" #include "game/macro_special_objects.h" diff --git a/src/game/debug.h b/src/game/debug.h index f7e75b3599..0a3e82887b 100644 --- a/src/game/debug.h +++ b/src/game/debug.h @@ -40,56 +40,4 @@ void try_print_debug_mario_level_info(void); #define try_print_debug_mario_level_info() #endif -extern char *__n64Assert_Filename; -extern u32 __n64Assert_LineNum; -extern char *__n64Assert_Message; -extern void __n64Assert(char *fileName, u32 lineNum, char *message); - -extern char gAssertionStr[0x200]; - -/** - * Will always cause a crash with your message of choice - */ -#define error(message) __n64Assert(__FILE__, __LINE__, (message)) - -/** - * Will always cause a crash if cond is not true (handle with care) - */ -#define aggress(cond, message) do {\ - if ((cond) == FALSE) { \ - error(message); \ - } \ -} while (0); - -#define aggress_args(cond, ...) do {\ - if ((cond) == FALSE) { \ - sprintf(gAssertionStr, __VA_ARGS__); \ - error(gAssertionStr); \ - } \ -} while (0); - -/** - * Will cause a crash if cond is not true, and DEBUG is defined (allows for quick removal of littered asserts) - */ -#ifdef DEBUG_ASSERTIONS - -#define assert(cond, message) do {\ - if ((cond) == FALSE) { \ - error(message); \ - } \ -} while (0); - -#define assert_args(cond, ...) do {\ - if ((cond) == FALSE) { \ - sprintf(gAssertionStr, __VA_ARGS__); \ - error(gAssertionStr); \ - } \ -} while (0); - -#else -#define assert(cond, message) -#define assert_args(cond, ...) -#endif - - #endif // DEBUG_H diff --git a/src/game/level_update.c b/src/game/level_update.c index 579fc69a8a..bc77210b06 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -5,6 +5,7 @@ #include "dialog_ids.h" #include "audio/external.h" #include "audio/synthesis.h" +#include "debugger/assert.h" #include "level_update.h" #include "game_init.h" #include "level_update.h" diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index b0692e54aa..a0cdc37ba5 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -101,9 +101,14 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { return FALSE; } +#include "debugger/assert.h" //! TODO: actionArg names s32 act_idle(struct MarioState *m) { + if (m->controller->buttonPressed & L_TRIG) { + assertf(0, "Mario Pos {%f %f %f}", m->pos[0], m->pos[1], m->pos[2]); + // *(vs8*)0=0; + } if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } diff --git a/src/game/object_list_processor.c b/src/game/object_list_processor.c index 87407dd955..a63a0cae01 100644 --- a/src/game/object_list_processor.c +++ b/src/game/object_list_processor.c @@ -5,6 +5,7 @@ #include "behavior_data.h" #include "camera.h" #include "debug.h" +#include "debugger/assert.h" #include "engine/behavior_script.h" #include "engine/graph_node.h" #include "engine/surface_collision.h" @@ -550,7 +551,7 @@ void update_terrain_objects(void) { profiler_update(PROFILER_TIME_DYNAMIC, profiler_get_delta(PROFILER_DELTA_COLLISION) - first); // If the dynamic surface pool has overflowed, throw an error. - assert((uintptr_t)gDynamicSurfacePoolEnd <= (uintptr_t)gDynamicSurfacePool + DYNAMIC_SURFACE_POOL_SIZE, "Dynamic surface pool size exceeded!"); + assertf((uintptr_t)gDynamicSurfacePoolEnd <= (uintptr_t)gDynamicSurfacePool + DYNAMIC_SURFACE_POOL_SIZE, "Dynamic surface pool size exceeded!"); } /** From b2d8658f3a15ed79d86b949e426959085259c457 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sat, 21 Jun 2025 01:33:41 -0400 Subject: [PATCH 034/109] Assert/f and aggress/f tested --- src/audio/heap.c | 2 +- src/audio/synthesis.c | 2 +- src/debugger/assert.h | 12 +++++++++--- src/debugger/crash_screen.c | 8 +++++--- src/game/mario_actions_stationary.c | 4 ---- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/audio/heap.c b/src/audio/heap.c index 0c027d9292..adb30faea2 100644 --- a/src/audio/heap.c +++ b/src/audio/heap.c @@ -1056,7 +1056,7 @@ void init_reverb_us(s32 presetId) { #else if (gBetterReverbPresetValue >= gBetterReverbPresetCount) { #endif - aggress(gBetterReverbPresetCount > 0, "No BETTER_REVERB presets exist!"); + aggressf(gBetterReverbPresetCount > 0, "No BETTER_REVERB presets exist!"); assertf(gBetterReverbPresetValue < gBetterReverbPresetCount, "BETTER_REVERB preset value exceeds total number of available presets!"); betterReverbPreset = &gBetterReverbSettings[0]; diff --git a/src/audio/synthesis.c b/src/audio/synthesis.c index 10f027c01e..e1780c5e8b 100644 --- a/src/audio/synthesis.c +++ b/src/audio/synthesis.c @@ -206,7 +206,7 @@ void set_better_reverb_buffers(u32 *inputDelaysL, u32 *inputDelaysR) { } } - aggress_args(bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE <= BETTER_REVERB_SIZE, "BETTER_REVERB_SIZE is too small for this preset!\nSize should be at least 0x%06x!", bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE); + aggressf(bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE <= BETTER_REVERB_SIZE, "BETTER_REVERB_SIZE is too small for this preset!\nSize should be at least 0x%06x!", bufOffset * sizeof(s16) + BETTER_REVERB_PTR_SIZE); bzero(allpassIdx, sizeof(allpassIdx)); } diff --git a/src/debugger/assert.h b/src/debugger/assert.h index 7c77112c50..49b3168aed 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -25,11 +25,17 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); #define __assert_wrapper(cond) __n64Assert(__FILE__, __LINE__, (cond)) /** - * Will always cause a crash if cond is not true (handle with care) + * `aggress` and `aggressf` will always cause a crash if `cond` is not true (handle with care) */ -#define aggress(cond, message) do {\ +#define aggressf(cond, ...) do {\ if ((cond) == FALSE) { \ - sprintf(__n64Assert_MessageBuf, message); \ + 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); diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index a6ba7a4fcd..72604dd14c 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -462,7 +462,7 @@ void draw_disasm(OSThread *thread) { } void draw_assert(UNUSED OSThread *thread) { - crash_screen_draw_rect(0, 20, 320, 210); + crash_screen_draw_rect(0, 20, 320, 240); crash_screen_print(LEFT_MARGIN, 25, "Assert"); @@ -471,8 +471,10 @@ void draw_assert(UNUSED OSThread *thread) { 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); - crash_screen_print(LEFT_MARGIN, 75, "Message:"); - crash_screen_print(LEFT_MARGIN, 85, " %s", __n64Assert_MessageBuf); + 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."); } diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index a0cdc37ba5..f35de69539 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -105,10 +105,6 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { - if (m->controller->buttonPressed & L_TRIG) { - assertf(0, "Mario Pos {%f %f %f}", m->pos[0], m->pos[1], m->pos[2]); - // *(vs8*)0=0; - } if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } From 856455a3b874f891838da51faf69a5723c73a16e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 22 Jun 2025 22:57:30 -0400 Subject: [PATCH 035/109] new stack trace that verifies entries --- Makefile | 4 +- include/segment_symbols.h | 3 + src/debugger/assert.h | 4 +- src/debugger/crash_screen.c | 29 +++---- src/debugger/disasm.c | 14 +++- src/debugger/disasm.h | 2 + src/debugger/map_parser.c | 44 +--------- src/debugger/map_parser.h | 3 +- src/debugger/stacktrace.c | 122 ++++++++++++++++++++++++++++ src/debugger/stacktrace.h | 21 +++++ src/debugger/symtable.c | 2 - src/game/mario_actions_stationary.c | 5 ++ 12 files changed, 183 insertions(+), 70 deletions(-) create mode 100644 src/debugger/stacktrace.c create mode 100644 src/debugger/stacktrace.h diff --git a/Makefile b/Makefile index a730fb1f58..aa42ae6e1e 100644 --- a/Makefile +++ b/Makefile @@ -102,7 +102,7 @@ ifeq ($(DEBUG_EXPORT_ALL_LINES),1) DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 endif ifeq ($(DEBUG_EXPORT_SYMBOLS),1) - DEBUG_EXPORT_SYMBOLS_FLAG := -D DEBUG_EXPORT_SYMBOLS + DEFINES += DEBUG_EXPORT_SYMBOLS=1 DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 endif @@ -945,7 +945,7 @@ $(BUILD_DIR)/rsp/%.bin $(BUILD_DIR)/rsp/%_data.bin: rsp/%.s # Run linker script through the C preprocessor $(BUILD_DIR)/$(LD_SCRIPT): $(LD_SCRIPT) $(BUILD_DIR)/goddard.txt $(call print,Preprocessing linker script:,$<,$@) - $(V)$(CPP) $(CPPFLAGS) -DBUILD_DIR=$(BUILD_DIR) -DULTRALIB=lib$(ULTRALIB) $(DEBUG_EXPORT_SYMBOLS_FLAG) -MMD -MP -MT $@ -MF $@.d -o $@ $< + $(V)$(CPP) $(CPPFLAGS) -DBUILD_DIR=$(BUILD_DIR) -DULTRALIB=lib$(ULTRALIB) -MMD -MP -MT $@ -MF $@.d -o $@ $< # Link libgoddard $(BUILD_DIR)/libgoddard.a: $(GODDARD_O_FILES) diff --git a/include/segment_symbols.h b/include/segment_symbols.h index e664c475a8..89fddc01c4 100644 --- a/include/segment_symbols.h +++ b/include/segment_symbols.h @@ -51,10 +51,13 @@ DECLARE_SEGMENT(goddard) DECLARE_SEGMENT(framebuffers) DECLARE_SEGMENT(assets) extern u8 _goddardSegmentStart[]; +extern u8 _goddardSegmentTextEnd[]; extern u8 _goddardSegmentEnd[]; extern u8 _engineSegmentStart[]; +extern u8 _engineSegmentTextEnd[]; extern u8 _engineSegmentBssEnd[]; extern u8 _mainSegmentStart[]; +extern u8 _mainSegmentTextEnd[]; extern u8 _mainSegmentEnd[]; extern u8 _engineSegmentEnd[]; extern u8 _framebuffersSegmentBssStart[]; diff --git a/src/debugger/assert.h b/src/debugger/assert.h index 49b3168aed..3dd6a1c38a 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -41,7 +41,9 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); } while (0); /** - * Will cause a crash if cond is not true, and DEBUG is defined (allows for quick removal of littered asserts) + * 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 {\ diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 72604dd14c..4bfd5f31fc 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -17,6 +17,7 @@ #include "map_parser.h" #include "disasm.h" #include "assert.h" +#include "stacktrace.h" #include "sm64.h" @@ -360,10 +361,9 @@ void draw_crash_log(void) { void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; - u32 temp_sp = (tc->sp + 0x24); crash_screen_draw_rect(0, 20, 320, 240); - crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", temp_sp); + crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", tc->sp); // Current Func (EPC) if ((u32) parse_map == MAP_PARSER_ADDRESS) { @@ -384,22 +384,17 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { osWritebackDCacheAll(); - for (int i = 0; i < 17; i++) { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, (55 + (i * 10)), "Stack Trace Disabled"); - break; - } else { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - return; - } - - int line = -1; - char *fname = find_function_in_stack(&temp_sp, &line); - while ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - fname = find_function_in_stack(&temp_sp, &line); - } + if ((u32) generate_stack == MAP_PARSER_ADDRESS) { + crash_screen_print(LEFT_MARGIN, 55, "Stack Trace Disabled"); + } else { + static u32 generated = 0; - crash_screen_print(LEFT_MARGIN, (55 + (i * 10)), "%08X (%s:%d)", temp_sp, fname, line); + 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)); } } } diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c index 6a1ee4421f..a7dc30d38e 100644 --- a/src/debugger/disasm.c +++ b/src/debugger/disasm.c @@ -138,7 +138,13 @@ char __mips_fpreg[][5] = { "$f28", "$f29", "$f30", "$f31", }; -extern char* __symbolize(void *vaddr, char *buf, int size); +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) { @@ -190,7 +196,7 @@ static void c1_disasm(u32 *ptr, char *out) { } } switch (*opn) { - /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x <%s>", opn+1, tgt26, __symbolize((void*)tgt26, symbuf, sizeof(symbuf))); break; + /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x <%s>", opn+1, tgt26, __symbolize((void*)tgt26, symbuf, sizeof(symbuf), FALSE)); break; /* 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; @@ -198,8 +204,8 @@ static void c1_disasm(u32 *ptr, char *out) { /* 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, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x <%s>", opn+1, rs, rt, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf))); break; - /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x <%s>", opn+1, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf))); break; + /* op rs, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x <%s>", opn+1, rs, rt, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf), TRUE)); break; + /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x <%s>", opn+1, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf), TRUE)); 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; diff --git a/src/debugger/disasm.h b/src/debugger/disasm.h index 9fc667c2b3..f4e6fc6195 100644 --- a/src/debugger/disasm.h +++ b/src/debugger/disasm.h @@ -82,4 +82,6 @@ typedef struct PACKED { #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/debugger/map_parser.c b/src/debugger/map_parser.c index a606809362..248d5718fd 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -3,22 +3,13 @@ #include #include #include -#include "segments.h" #include "game/memory.h" #include "map_parser.h" #include "symtable.h" -#define STACK_TRAVERSAL_LIMIT 100 #define UNKNOWN_SYMBOL "???" -extern u8 _mainSegmentStart[]; -extern u8 _mainSegmentTextEnd[]; -extern u8 _engineSegmentStart[]; -extern u8 _engineSegmentTextEnd[]; -extern u8 _goddardSegmentStart[]; -extern u8 _goddardSegmentTextEnd[]; - char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset) { symtable_header_t symt = symt_open(); if (symt.head[0]) { @@ -59,39 +50,6 @@ char *parse_map(u32 addr, u32 andOffset) { return ret; } -char *find_function_in_stack(u32 *sp, int *line) { - char *ret = NULL; - u32 val = *sp; - - #define CALLSITE_OFFSET 0 - for (int i = 0; i < STACK_TRAVERSAL_LIMIT; i++) { - val = *sp; - val = *(u32 *)val; - *sp += 4; - - - if ((val >= (u32)_mainSegmentStart) && (val <= (u32)_mainSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET, FALSE); - break; - } - else if ((val >= (u32)_engineSegmentStart) && (val <= (u32)_engineSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET, FALSE); - break; - } - else if ((val >= (u32)_goddardSegmentStart) && (val <= (u32)_goddardSegmentTextEnd)) { - ret = parse_map(val + CALLSITE_OFFSET, FALSE); - break; - } - } - - if (ret) { - symtable_info_t info = get_symbol_info(val + CALLSITE_OFFSET); - *line = info.line; - } - - return ret; -} - symtable_info_t get_symbol_info(u32 addr) { static char filebuf[100]; void *vaddr = (void *)addr; @@ -113,7 +71,7 @@ symtable_info_t get_symbol_info(u32 addr) { 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, (info.distance != 0)); + info.func = parse_map(addr, FALSE); return info; } diff --git a/src/debugger/map_parser.h b/src/debugger/map_parser.h index 44d1eda5d9..21f02de718 100644 --- a/src/debugger/map_parser.h +++ b/src/debugger/map_parser.h @@ -12,4 +12,5 @@ typedef struct { symtable_info_t get_symbol_info(u32 vaddr); extern far char *parse_map(u32 pc, u32 andOffset); -extern far char *find_function_in_stack(u32 *sp, int *line); +extern far symtable_info_t walk_stack(u32 *addr); +extern far char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset); diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c new file mode 100644 index 0000000000..fe9f6d36d3 --- /dev/null +++ b/src/debugger/stacktrace.c @@ -0,0 +1,122 @@ +#include +#include + +#include "map_parser.h" +#include "symtable.h" +#include "segment_symbols.h" +#include "stacktrace.h" +#include "disasm.h" + +static StackFrame stack[STACK_LINE_COUNT]; +static u32 stackIdx; +u32 stackTraceGenerated = FALSE; +extern void __osCleanupThread(); +extern void thread5_game_loop(void *); +extern void __osDispatchThread(); + +static u8 is_top_of_stack(u32 ra) { + return (ra == ((u32)__osCleanupThread)); +} + +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[100]; + sprintf(stackbuf, "%08X: %s:%d", stack[idx].func, stack[idx].funcname, stack[idx].line); + + return stackbuf; +} + +/** + * HOW TO TRACE THE STACK + * - Store breadcrumb starting with RA + * - walk back + * - Find address + * - Poke (address-8) and get info to see if it's a valid callsite + * - get the frame func address by doing RA - func_offset + * - if the function JAL points to the func address, we have a chain! + * - set RA to this address + * - if it's a JalR: + * - record register + * - walk back to find what the address is loaded to + * - read memory and then check the func address + **/ + +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++; + + u32 val = *(u32*)sp; + + if (is_text_addr(val + CALLSITE_OFFSET)) { + int idx = 0; + symtable_info_t info = get_symbol_info(val + CALLSITE_OFFSET); + // look for the func in RA + addrtable_entry_t a = 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); + add_entry_to_stack(val + CALLSITE_OFFSET, breadcrumb, &info); + breadcrumb = val; + return stackIdx; + } + + while (!ADDRENTRY_IS_FUNC(a)) { + a = symt_addrtab_entry(&symt, --idx); + } + + if (info.distance == 0) { + u32 jal = *(u32*)(val + CALLSITE_OFFSET); + + if (insn_is_jal((Insn *) &jal)) { + jal = 0x80000000 | ((jal & 0x03FFFFFF) * 4); + if (jal == ADDRENTRY_ADDR(a)) { + 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/debugger/stacktrace.h b/src/debugger/stacktrace.h new file mode 100644 index 0000000000..06e2cc50db --- /dev/null +++ b/src/debugger/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 far u32 generate_stack(OSThread *); +extern far char *get_stack_entry(u32 idx); diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c index d46fdf3bb9..1214a93048 100644 --- a/src/debugger/symtable.c +++ b/src/debugger/symtable.c @@ -44,7 +44,6 @@ void map_parser_dma(void *dst, void *src, size_t size) { symtable_header_t symt_open(void) { SYMT_ROM = (u32)_mapDataSegmentRomStart; - // We don't _need_ to pass by value for this right symtable_header_t ALIGNED8 symt_header; if (SYMT_ROM == 0) { @@ -119,7 +118,6 @@ addrtable_entry_t symt_addrtab_search(symtable_header_t *symt, u32 addr, int *id return entry; } - /** * @brief Fetch a string from the string table * diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index f35de69539..2db74dca4d 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -105,6 +105,11 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { + if (m->controller->buttonPressed & L_TRIG) { + aggress(m->controller->buttonPressed & A_BUTTON); + assert(0); + // *(vs8*)0=0; + } if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } From d29ba9f4f601e21add1b850a142b5b99f4e84e0d Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 22 Jun 2025 23:20:28 -0400 Subject: [PATCH 036/109] label the end of stack; add syscall --- src/debugger/crash_screen.c | 2 +- src/debugger/disasm.c | 7 +++++++ src/debugger/disasm.h | 1 + src/debugger/stacktrace.c | 41 +++++++++++++++---------------------- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 4bfd5f31fc..32e8b282a8 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -363,7 +363,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; crash_screen_draw_rect(0, 20, 320, 240); - crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", tc->sp); + crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", (u32) tc->sp); // Current Func (EPC) if ((u32) parse_map == MAP_PARSER_ADDRESS) { diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c index a7dc30d38e..a6a4a7c56e 100644 --- a/src/debugger/disasm.c +++ b/src/debugger/disasm.c @@ -98,6 +98,8 @@ InsnTemplate insn_db[] = { {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"}, @@ -360,6 +362,11 @@ char *insn_disasm(InsnData *addr) { __mips_gpr[insn.i.rt] ); break; + case PARAM_SYSCALL: + strp += sprintf(strp, "%-9s %d", 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], diff --git a/src/debugger/disasm.h b/src/debugger/disasm.h index f4e6fc6195..609ed17561 100644 --- a/src/debugger/disasm.h +++ b/src/debugger/disasm.h @@ -21,6 +21,7 @@ enum ParamTypes { PARAM_MULT_MOVE, PARAM_TRAP, PARAM_EMUX, + PARAM_SYSCALL, }; typedef struct PACKED { diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c index fe9f6d36d3..0895322bfa 100644 --- a/src/debugger/stacktrace.c +++ b/src/debugger/stacktrace.c @@ -1,5 +1,6 @@ #include #include +#include #include "map_parser.h" #include "symtable.h" @@ -7,12 +8,13 @@ #include "stacktrace.h" #include "disasm.h" +extern void __osCleanupThread(); + static StackFrame stack[STACK_LINE_COUNT]; static u32 stackIdx; u32 stackTraceGenerated = FALSE; -extern void __osCleanupThread(); -extern void thread5_game_loop(void *); -extern void __osDispatchThread(); + +#define STACK_END_STR "[End of stack]" static u8 is_top_of_stack(u32 ra) { return (ra == ((u32)__osCleanupThread)); @@ -44,26 +46,12 @@ static void add_entry_to_stack(u32 addr, u32 ra, symtable_info_t *info) { char *get_stack_entry(u32 idx) { static char stackbuf[100]; + sprintf(stackbuf, "%08X: %s:%d", stack[idx].func, stack[idx].funcname, stack[idx].line); return stackbuf; } -/** - * HOW TO TRACE THE STACK - * - Store breadcrumb starting with RA - * - walk back - * - Find address - * - Poke (address-8) and get info to see if it's a valid callsite - * - get the frame func address by doing RA - func_offset - * - if the function JAL points to the func address, we have a chain! - * - set RA to this address - * - if it's a JalR: - * - record register - * - walk back to find what the address is loaded to - * - read memory and then check the func address - **/ - u32 generate_stack(OSThread *thread) { static u32 breadcrumb = 0; symtable_header_t symt = symt_open(); @@ -78,30 +66,35 @@ u32 generate_stack(OSThread *thread) { 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); - // look for the func in RA - addrtable_entry_t a = symt_addrtab_search(&symt, breadcrumb, &idx); + 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; } - while (!ADDRENTRY_IS_FUNC(a)) { - a = symt_addrtab_entry(&symt, --idx); + // get the start of the current frame's func + while (!ADDRENTRY_IS_FUNC(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)) { - jal = 0x80000000 | ((jal & 0x03FFFFFF) * 4); - if (jal == ADDRENTRY_ADDR(a)) { + 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; } From 915ad4339f4c41461c468d7ad9c02191150da559 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 00:47:55 -0400 Subject: [PATCH 037/109] fix prelim link --- sm64.ld | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sm64.ld b/sm64.ld index a0ebd48cee..88e436525f 100755 --- a/sm64.ld +++ b/sm64.ld @@ -531,7 +531,9 @@ SECTIONS #ifdef DEBUG_EXPORT_SYMBOLS BEGIN_SEG(mapData, (RAM_END - 0x00100000)) ALIGN(16) { +#ifndef PRELIMINARY KEEP(BUILD_DIR/sm64.sym.o(.data*)); +#endif } END_SEG(mapData) #endif From b8cb13c1bbb9d572c32cb04f82873326cace1afc Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 00:54:20 -0400 Subject: [PATCH 038/109] diff reduction --- src/game/mario_actions_stationary.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index 2db74dca4d..b0692e54aa 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -101,15 +101,9 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { return FALSE; } -#include "debugger/assert.h" //! TODO: actionArg names s32 act_idle(struct MarioState *m) { - if (m->controller->buttonPressed & L_TRIG) { - aggress(m->controller->buttonPressed & A_BUTTON); - assert(0); - // *(vs8*)0=0; - } if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } From c00f64cd9ba37bacb1052303e08dea80d653e321 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 00:56:50 -0400 Subject: [PATCH 039/109] turn off isv --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index aa42ae6e1e..ec317b618a 100644 --- a/Makefile +++ b/Makefile @@ -239,7 +239,7 @@ endif # allowing for usage of CEN64 (and possibly Project64) to print messages to terminal. # 1 - includes code in ROM # 0 - does not -ISVPRINT ?= 1 +ISVPRINT ?= 0 $(eval $(call validate-option,ISVPRINT,0 1)) ifeq ($(ISVPRINT),1) DEFINES += ISVPRINT=1 From f1c2660e1baacc3a7a000e160ed910cc70eb0ae2 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 20:34:43 -0400 Subject: [PATCH 040/109] allow stack trace to be disabled --- include/config/config_debug.h | 6 ++++++ src/debugger/crash_screen.c | 12 ++++++++++++ src/debugger/stacktrace.c | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/include/config/config_debug.h b/include/config/config_debug.h index 7ed67cac93..02c56b25c4 100644 --- a/include/config/config_debug.h +++ b/include/config/config_debug.h @@ -94,3 +94,9 @@ * Intentionally crash the game whenever a runtime assertion fails (also invoked by the DEBUG define in the Makefile). */ #define DEBUG_ASSERTIONS + +/** + * Crash Screen: enable symbol-assisted stack trace + * If disabled, stack trace will just dump memory, and you can figure it out yourself. + */ +#define DEBUG_FULL_STACK_TRACE diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 32e8b282a8..c67725b32e 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -3,6 +3,7 @@ #include #include #include +#include "config/config_debug.h" #include "buffers/framebuffers.h" #include "types.h" #include "game/puppyprint.h" @@ -365,6 +366,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { crash_screen_draw_rect(0, 20, 320, 240); crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", (u32) tc->sp); +#ifdef DEBUG_FULL_STACK_TRACE // Current Func (EPC) if ((u32) parse_map == MAP_PARSER_ADDRESS) { crash_screen_print(LEFT_MARGIN, 35, "%08X", tc->pc); @@ -397,6 +399,15 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { crash_screen_print(LEFT_MARGIN, 55 + (i * 10), get_stack_entry(i)); } } +#else // 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 // DEBUG_FULL_STACK_TRACE } void draw_disasm(OSThread *thread) { @@ -571,6 +582,7 @@ void thread2_crash_screen(UNUSED void *arg) { play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); 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; } diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c index 0895322bfa..dd718ed680 100644 --- a/src/debugger/stacktrace.c +++ b/src/debugger/stacktrace.c @@ -2,6 +2,8 @@ #include #include +#include "config/config_debug.h" + #include "map_parser.h" #include "symtable.h" #include "segment_symbols.h" @@ -10,6 +12,8 @@ extern void __osCleanupThread(); +#ifdef DEBUG_FULL_STACK_TRACE + static StackFrame stack[STACK_LINE_COUNT]; static u32 stackIdx; u32 stackTraceGenerated = FALSE; @@ -113,3 +117,5 @@ u32 generate_stack(OSThread *thread) { return stackIdx; } + +#endif // DEBUG_FULL_STACK_TRACE From 6bf198b464536752eeef1f419ad3c540e5feab76 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 21:49:58 -0400 Subject: [PATCH 041/109] fully handle disabling export_symbols --- Makefile | 9 ++-- src/debugger/crash_screen.c | 75 +++++++++++++---------------- src/debugger/disasm.c | 18 ++++--- src/debugger/map_parser.c | 4 ++ src/debugger/stacktrace.c | 4 +- src/debugger/symtable.c | 4 ++ src/game/mario_actions_stationary.c | 1 + 7 files changed, 60 insertions(+), 55 deletions(-) diff --git a/Makefile b/Makefile index ec317b618a..77b9d102c0 100644 --- a/Makefile +++ b/Makefile @@ -96,12 +96,11 @@ DEBUG_EXPORT_SYMBOLS := 1 # Include line data, at the cost of a significantly longer link time DEBUG_EXPORT_ALL_LINES := 0 -ifeq ($(DEBUG_EXPORT_ALL_LINES),1) - DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines - DEFINES += DEBUG_EXPORT_ALL_LINES=1 - DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 -endif ifeq ($(DEBUG_EXPORT_SYMBOLS),1) + ifeq ($(DEBUG_EXPORT_ALL_LINES),1) + DEBUG_EXPORT_ALL_LINES_FLAG += --all-lines + DEFINES += DEBUG_EXPORT_ALL_LINES=1 + endif DEFINES += DEBUG_EXPORT_SYMBOLS=1 DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 endif diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index c67725b32e..b58d58c4b4 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -284,18 +284,18 @@ void draw_crash_overview(OSThread *thread, s32 cause) { crash_screen_print(LEFT_MARGIN, 20, "Thread %d (%s)", thread->id, gCauseDesc[cause]); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { - symtable_info_t info = get_symbol_info(tc->pc); +#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); + 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); + // 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); } @@ -306,10 +306,10 @@ void draw_crash_context(OSThread *thread, s32 cause) { 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(); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->pc, TRUE); - crash_screen_print(LEFT_MARGIN, 40, "Crash at: %s", fname == NULL ? "Unknown" : fname); - } +#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); @@ -320,10 +320,10 @@ void draw_crash_context(OSThread *thread, s32 cause) { 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); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->ra, TRUE); - crash_screen_print(LEFT_MARGIN, 152, "RA at: %s", fname); - } +#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); @@ -366,40 +366,28 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { crash_screen_draw_rect(0, 20, 320, 240); crash_screen_print(LEFT_MARGIN, 25, "Stack Trace from %08X:", (u32) tc->sp); -#ifdef DEBUG_FULL_STACK_TRACE +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) // Current Func (EPC) - if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, 35, "%08X", tc->pc); - } else { - crash_screen_print(LEFT_MARGIN, 35, "%08X (%s)", tc->pc, parse_map(tc->pc, TRUE)); - } + crash_screen_print(LEFT_MARGIN, 35, "%08X (%s)", tc->pc, parse_map(tc->pc, TRUE)); // Previous Func (RA) - if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, 45, "0x%08X", tc->ra); - } else { - u32 ra = tc->ra; - symtable_info_t info = get_symbol_info(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); - } + crash_screen_print(LEFT_MARGIN, 45, "%08X (%s:%d)", ra, info.func, info.line); osWritebackDCacheAll(); - if ((u32) generate_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(LEFT_MARGIN, 55, "Stack Trace Disabled"); - } else { - static u32 generated = 0; + 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)); - } + 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 // DEBUG_FULL_STACK_TRACE +#else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) // simple stack trace u32 sp = tc->sp; @@ -407,7 +395,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { 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 // DEBUG_FULL_STACK_TRACE +#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) } void draw_disasm(OSThread *thread) { @@ -432,6 +420,7 @@ void draw_disasm(OSThread *thread) { 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) { @@ -453,6 +442,8 @@ void draw_disasm(OSThread *thread) { #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 { diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c index a6a4a7c56e..cf2dd21ee5 100644 --- a/src/debugger/disasm.c +++ b/src/debugger/disasm.c @@ -198,7 +198,15 @@ static void c1_disasm(u32 *ptr, char *out) { } } switch (*opn) { - /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x <%s>", opn+1, tgt26, __symbolize((void*)tgt26, symbuf, sizeof(symbuf), FALSE)); break; +#ifdef DEBUG_EXPORT_SYMBOLS + /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x <%s>", opn+1, tgt26, parse_map(tgt26, FALSE)); break; + /* op rs, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x <%s>", opn+1, rs, rt, tgt16, parse_map(tgt16, TRUE)); break; + /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x <%s>", opn+1, tgt16, parse_map(tgt16, TRUE)); break; +#else + /* op tgt26 */ case 'j': sprintf(out, "%-9s %08x", opn+1, tgt26); break; + /* op rs, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x", opn+1, rs, rt, tgt16); break; + /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x", 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; @@ -206,8 +214,6 @@ static void c1_disasm(u32 *ptr, char *out) { /* 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, rt, tgt16 */case 'b': sprintf(out, "%-9s %s, %s, %08x <%s>", opn+1, rs, rt, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf), TRUE)); break; - /* op tgt16 */ case 'y': sprintf(out, "%-9s %08x <%s>", opn+1, tgt16, __symbolize((void*)tgt16, symbuf, sizeof(symbuf), TRUE)); 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; @@ -287,15 +293,15 @@ char *insn_disasm(InsnData *addr) { break; case PARAM_JAL: target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { +#ifdef DEBUG_EXPORT_SYMBOLS strp += sprintf(strp, "%-9s %s(%08X)", insn_db[i].name, parse_map(target, FALSE), target ); - } else { +#else strp += sprintf(strp, "%-9s %08X", insn_db[i].name, target ); - } +#endif // DEBUG_EXPORT_SYMBOLS break; case PARAM_JUMP: target = 0x80000000 | (insn.d & 0x03FFFFFF); diff --git a/src/debugger/map_parser.c b/src/debugger/map_parser.c index 248d5718fd..26b72834b9 100644 --- a/src/debugger/map_parser.c +++ b/src/debugger/map_parser.c @@ -8,6 +8,8 @@ #include "map_parser.h" #include "symtable.h" +#ifdef DEBUG_EXPORT_SYMBOLS + #define UNKNOWN_SYMBOL "???" char* __symbolize(void *vaddr, char *buf, int size, u32 andOffset) { @@ -77,3 +79,5 @@ symtable_info_t get_symbol_info(u32 addr) { } return (symtable_info_t){.line = -1}; } + +#endif // DEBUG_EXPORT_SYMBOLS diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c index dd718ed680..321374b85e 100644 --- a/src/debugger/stacktrace.c +++ b/src/debugger/stacktrace.c @@ -12,7 +12,7 @@ extern void __osCleanupThread(); -#ifdef DEBUG_FULL_STACK_TRACE +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) static StackFrame stack[STACK_LINE_COUNT]; static u32 stackIdx; @@ -118,4 +118,4 @@ u32 generate_stack(OSThread *thread) { return stackIdx; } -#endif // DEBUG_FULL_STACK_TRACE +#endif // DEBUG_EXPORT_SYMBOLS && DEBUG_FULL_STACK_TRACE diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c index 1214a93048..0ad11876a8 100644 --- a/src/debugger/symtable.c +++ b/src/debugger/symtable.c @@ -8,6 +8,8 @@ #include "symtable.h" +#ifdef DEBUG_EXPORT_SYMBOLS + u32 SYMT_ROM = 0xFFFFFFFF; extern u8 _mapDataSegmentRomStart[]; // The start and end of the exception vector @@ -185,3 +187,5 @@ char *symt_entry_func(symtable_header_t *symt, symtable_entry_t *entry, u32 addr 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/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index b0692e54aa..ee1877e490 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -104,6 +104,7 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { + *(vs8*)0=0; if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } From 802c9219ffcb039a7a6e29fe772ee8a71f679c28 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 21:52:19 -0400 Subject: [PATCH 042/109] and dont include the sym file in the rom either --- Makefile | 7 ++++--- src/debugger/crash_screen.c | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 77b9d102c0..e81e9e2688 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ FIXLIGHTS ?= 1 # Debug Switches - Comment these out to remove the functionality, then run `make clean` # Export symbols and stack trace data for crash screen -DEBUG_EXPORT_SYMBOLS := 1 +DEBUG_EXPORT_SYMBOLS := 0 # Include line data, at the cost of a significantly longer link time DEBUG_EXPORT_ALL_LINES := 0 @@ -103,6 +103,7 @@ ifeq ($(DEBUG_EXPORT_SYMBOLS),1) endif DEFINES += DEBUG_EXPORT_SYMBOLS=1 DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 + SYMBOL_TABLE = $(BUILD_DIR)/sm64.sym.o endif TARGET := sm64 @@ -969,14 +970,14 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(call print,Getting Goddard size...) $(V)python3 tools/getGoddardSize.py $(BUILD_DIR)/sm64_prelim.map $(VERSION) -$(BUILD_DIR)/sm64.sym.o: $(BUILD_DIR)/sm64_prelim.elf +$(SYMBOL_TABLE): $(BUILD_DIR)/sm64_prelim.elf $(call print,Generating symbol table:,$(@F)) $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --cross $(CROSS) $(BUILD_DIR)/sm64_prelim.elf $(call print,Assembling:,$@) $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym # Link SM64 ELF file -$(ELF): $(BUILD_DIR)/sm64_prelim.elf $(BUILD_DIR)/sm64.sym.o $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) $(BUILD_DIR)/libz.a $(BUILD_DIR)/libgoddard.a +$(ELF): $(BUILD_DIR)/sm64_prelim.elf $(SYMBOL_TABLE) $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) $(BUILD_DIR)/libz.a $(BUILD_DIR)/libgoddard.a @$(PRINT) "$(GREEN)Linking ELF file: $(BLUE)$@ $(NO_COL)\n" $(V)$(LD) --gc-sections -L $(BUILD_DIR) -T $(BUILD_DIR)/$(LD_SCRIPT) -T goddard.txt -Map $(BUILD_DIR)/sm64.$(VERSION).map --no-check-sections $(addprefix -R ,$(SEG_FILES)) -o $@ $(O_FILES) -L$(LIBS_DIR) -l$(ULTRALIB) -Llib $(LINK_LIBRARIES) -u sprintf -u osMapTLB -Llib/gcclib/$(LIBGCCDIR) -lgcc diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index b58d58c4b4..1764bff653 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -409,7 +409,9 @@ void draw_disasm(OSThread *thread) { 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)); From 582126f76e07aef92c32233b6a0f9c00049d2e45 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 22:01:28 -0400 Subject: [PATCH 043/109] oops --- src/game/mario_actions_stationary.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index ee1877e490..b0692e54aa 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -104,7 +104,6 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { - *(vs8*)0=0; if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } From 1e724edec106b83c537c720d3f6db49afe6b8925 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 23 Jun 2025 22:03:10 -0400 Subject: [PATCH 044/109] set my defaults --- Makefile | 2 +- sm64.ld | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e81e9e2688..0007f560d3 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ FIXLIGHTS ?= 1 # Debug Switches - Comment these out to remove the functionality, then run `make clean` # Export symbols and stack trace data for crash screen -DEBUG_EXPORT_SYMBOLS := 0 +DEBUG_EXPORT_SYMBOLS := 1 # Include line data, at the cost of a significantly longer link time DEBUG_EXPORT_ALL_LINES := 0 diff --git a/sm64.ld b/sm64.ld index 88e436525f..14aad99b2c 100755 --- a/sm64.ld +++ b/sm64.ld @@ -530,7 +530,7 @@ SECTIONS #endif #ifdef DEBUG_EXPORT_SYMBOLS - BEGIN_SEG(mapData, (RAM_END - 0x00100000)) ALIGN(16) { + BEGIN_SEG(mapData, 0) ALIGN(16) { #ifndef PRELIMINARY KEEP(BUILD_DIR/sm64.sym.o(.data*)); #endif From 4c0adf338935a8f800ecd4dc780e0e0b4431476f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 24 Jun 2025 19:37:47 -0400 Subject: [PATCH 045/109] fix compile errors --- src/audio/external.c | 2 +- src/debugger/crash_screen.c | 9 ++------- src/engine/geo_layout.c | 2 +- src/game/game_init.c | 3 ++- src/game/level_update.c | 6 +++--- src/game/mario_actions_stationary.c | 1 + 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/audio/external.c b/src/audio/external.c index 5c5885ba70..35dbcf6a4b 100644 --- a/src/audio/external.c +++ b/src/audio/external.c @@ -687,7 +687,7 @@ struct SPTask *create_next_audio_frame_task(void) { task->yield_data_ptr = NULL; task->yield_data_size = 0; - assert_args(writtenCmds <= gMaxAudioCmds, "Audio RSP pool exceeded: %d command(s) over!", (s32) gMaxAudioCmds - (s32) writtenCmds); + assertf(writtenCmds <= gMaxAudioCmds, "Audio RSP pool exceeded: %d command(s) over!", (s32) gMaxAudioCmds - (s32) writtenCmds); decrease_sample_dma_ttls(); diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 1764bff653..1d32c01af3 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -74,15 +74,10 @@ static u32 sCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; -<<<<<<< HEAD u8 crashPage = 0; u8 updateBuffer = TRUE; static char crashScreenBuf[0x200]; -======= -static u8 crashPage = 0; -static u8 updateBuffer = TRUE; ->>>>>>> 108056920 (actually generate line tables when generating symbols; fix function name heuristic in disasm) char *gCauseDesc[18] = { "Interrupt", @@ -199,7 +194,7 @@ void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const ch xOffset = xNewline; } - glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; if (*ptr == '\n') { y += 10; @@ -232,7 +227,7 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { ptr = crashScreenBuf; while (*ptr && size-- > 0) { - glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; if (glyph != 0xff) { crash_screen_draw_glyph(x, y, glyph); diff --git a/src/engine/geo_layout.c b/src/engine/geo_layout.c index c677b6d089..7a0809c7ba 100644 --- a/src/engine/geo_layout.c +++ b/src/engine/geo_layout.c @@ -605,7 +605,7 @@ void geo_layout_cmd_node_billboard(void) { cmdPos = read_vec3s(axis, &cmdPos[0]); if (isCylindrical) { - assert(axis[0] != 0 || axis[1] != 0 || axis[2] != 0, "Axis vector for cylindrical billboard\nmust be non-zero"); + assertf(axis[0] != 0 || axis[1] != 0 || axis[2] != 0, "Axis vector for cylindrical billboard\nmust be non-zero"); } if (params & 0x80) { diff --git a/src/game/game_init.c b/src/game/game_init.c index 2a8de60f8d..f4fa16625e 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -7,6 +7,7 @@ #include "buffers/gfx_output_buffer.h" #include "buffers/framebuffers.h" #include "buffers/zbuffer.h" +#include "debugger/assert.h" #include "engine/level_script.h" #include "engine/math_util.h" #include "game_init.h" @@ -351,7 +352,7 @@ void create_gfx_task_structure(void) { gGfxSPTask->task.t.yield_data_size = OS_YIELD_DATA_SIZE; // NOTE: 'entries' is not representative of the right-side allocations coming from the GFX pool; do not use that variable here. - assert_args((u8*) gDisplayListHead <= gGfxPoolEnd, "GFX pool exceeded: %d command(s) over!", ((s32) gGfxPoolEnd - (s32) gDisplayListHead) / sizeof(Gfx)); + assertf((u8*) gDisplayListHead <= gGfxPoolEnd, "GFX pool exceeded: %d command(s) over!", ((s32) gGfxPoolEnd - (s32) gDisplayListHead) / sizeof(Gfx)); } /** diff --git a/src/game/level_update.c b/src/game/level_update.c index bc77210b06..2c9e76d3a1 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -351,7 +351,7 @@ void set_mario_initial_action(struct MarioState *m, u32 spawnType, u32 actionArg void init_mario_after_warp(void) { struct Object *object = get_destination_warp_object(sWarpDest.nodeId); - assert_args(object, "No dest warp object found for: 0x%02X", sWarpDest.nodeId); + assertf(object, "No dest warp object found for: 0x%02X", sWarpDest.nodeId); u32 marioSpawnType = get_mario_spawn_type(object); @@ -573,7 +573,7 @@ void check_instant_warp(void) { s16 music_unchanged_through_warp(s16 arg) { struct ObjectWarpNode *warpNode = area_get_warp_node(arg); - assert_args(warpNode, "No source warp node found for: 0x%02X", (u8) arg); + assertf(warpNode, "No source warp node found for: 0x%02X", (u8) arg); s16 levelNum = warpNode->node.destLevel & 0x7F; @@ -901,7 +901,7 @@ void initiate_delayed_warp(void) { default: warpNode = area_get_warp_node(sSourceWarpNodeId); - assert_args(warpNode, "No source warp node found for: 0x%02X", (u8) sSourceWarpNodeId); + assertf(warpNode, "No source warp node found for: 0x%02X", (u8) sSourceWarpNodeId); initiate_warp(warpNode->node.destLevel & 0x7F, warpNode->node.destArea, warpNode->node.destNode, sDelayedWarpArg); diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index b0692e54aa..ee1877e490 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -104,6 +104,7 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { + *(vs8*)0=0; if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } From 5e39bf4ea1f37931faf3709b7d89f273bad0f343 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 24 Jun 2025 19:40:12 -0400 Subject: [PATCH 046/109] actually export debug data --- Makefile | 2 +- src/game/mario_actions_stationary.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0007f560d3..5dc096dc6c 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,7 @@ endif #==============================================================================# # Default non-gcc opt flags -DEFAULT_OPT_FLAGS = -Ofast -falign-functions=32 -ffinite-math-only -fno-signed-zeros -fno-math-errno +DEFAULT_OPT_FLAGS += -Ofast -falign-functions=32 -ffinite-math-only -fno-signed-zeros -fno-math-errno # Note: -fno-associative-math is used here to suppress warnings, ideally we would enable this as an optimization but # this conflicts with -ftrapping-math apparently. # TODO: Figure out how to allow -fassociative-math to be enabled diff --git a/src/game/mario_actions_stationary.c b/src/game/mario_actions_stationary.c index ee1877e490..b0692e54aa 100644 --- a/src/game/mario_actions_stationary.c +++ b/src/game/mario_actions_stationary.c @@ -104,7 +104,6 @@ s32 check_common_hold_idle_cancels(struct MarioState *m) { //! TODO: actionArg names s32 act_idle(struct MarioState *m) { - *(vs8*)0=0; if (m->quicksandDepth > 30.0f) { return set_mario_action(m, ACT_IN_QUICKSAND, 0); } From 835c216313384cc4e6f8c6f00562e1b4d3870b9d Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 24 Jun 2025 21:18:28 -0400 Subject: [PATCH 047/109] oops --- tools/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/Makefile b/tools/Makefile index 86ed184db8..219ef3d0cd 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -8,6 +8,7 @@ CXX := g++ CFLAGS := -I. -O2 -s LDFLAGS := -lm ALL_PROGRAMS := armips filesizer lz4tpack rncpack n64graphics n64graphics_ci n64sym mio0 slienc n64cksum textconv aifc_decode aiff_extract_codebook vadpcm_enc tabledesign extract_data_for_mio skyconv +LIBAUDIOFILE := audiofile/libaudiofile.a OS := $(shell uname) From 69d3b8b21d29462fb68daddaf6f388e7c47b20cb Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 26 Jun 2025 19:56:35 -0400 Subject: [PATCH 048/109] aglab gave this patch to anime and games fan, and I edited it so that hopefully every setup works now --- Makefile | 19 ++++++++++++++++++- sm64.ld | 2 +- src/debugger/stacktrace.c | 4 ++-- tools/Makefile | 8 ++++++++ tools/n64sym.c | 26 +++++++++++++++++--------- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 5dc096dc6c..1ec65df3d7 100644 --- a/Makefile +++ b/Makefile @@ -449,6 +449,17 @@ DEP_FILES := $(O_FILES:.o=.d) $(LIBZ_O_FILES:.o=.d) $(GODDARD_O_FILES:.o=.d) $(B # Compiler Options # #==============================================================================# +# Attempt to figure out OS +OS := $(shell uname) + +ifneq (,$(findstring Windows_NT,$(OS))) + OS := Windows_NT +else ifneq (,$(findstring MSYS,$(OS))) + OS := Windows_NT +else ifneq (,$(findstring MINGW,$(OS))) + OS := Windows_NT +endif + # detect prefix for MIPS toolchain ifneq ($(call find-command,mips64-elf-ld),) CROSS := mips64-elf- @@ -502,6 +513,12 @@ endif AR := $(CROSS)ar OBJDUMP := $(CROSS)objdump OBJCOPY := $(CROSS)objcopy +ifeq ($(OS),Windows_NT) + ADDR2LINE := addr2line +else + ADDR2LINE := $(shell which $(CROSS)addr2line) + OBJDUMP_EXE := $(shell which $(OBJDUMP)) +endif ifeq ($(LD), tools/mips64-elf-ld) ifeq ($(shell ls -la tools/mips64-elf-ld | awk '{print $1}' | grep x),) @@ -972,7 +989,7 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(SYMBOL_TABLE): $(BUILD_DIR)/sm64_prelim.elf $(call print,Generating symbol table:,$(@F)) - $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --cross $(CROSS) $(BUILD_DIR)/sm64_prelim.elf + $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --objdump $(OBJDUMP_EXE) --addr2line $(ADDR2LINE) $(BUILD_DIR)/sm64_prelim.elf $(call print,Assembling:,$@) $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym diff --git a/sm64.ld b/sm64.ld index 14aad99b2c..811289e90c 100755 --- a/sm64.ld +++ b/sm64.ld @@ -173,7 +173,7 @@ SECTIONS #endif BUILD_DIR/lib/librtc*.o(.text*); *ULTRALIB.a:*.o(.text*); - */libnustd.a:*.o(.text*); + *libnustd.a:*.o(.text*); *libgcc.a:*.o(.text*); */libz.a:*.o(.text*); */libhvqm2.a:*.o(.text*); diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c index 321374b85e..5e07b2c375 100644 --- a/src/debugger/stacktrace.c +++ b/src/debugger/stacktrace.c @@ -66,7 +66,7 @@ u32 generate_stack(OSThread *thread) { breadcrumb = tc->ra; while (1) { // dont know the end goal yet - sp++; + sp += 4; u32 val = *(u32*)sp; @@ -86,7 +86,7 @@ u32 generate_stack(OSThread *thread) { } // get the start of the current frame's func - while (!ADDRENTRY_IS_FUNC(funcstart)) { + while (!ADDRENTRY_IS_FUNC(funcstart) && !ADDRENTRY_IS_INLINE(funcstart)) { funcstart = symt_addrtab_entry(&symt, --idx); } diff --git a/tools/Makefile b/tools/Makefile index 219ef3d0cd..d64a754717 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -12,6 +12,14 @@ LIBAUDIOFILE := audiofile/libaudiofile.a OS := $(shell uname) +ifneq (,$(findstring Windows_NT,$(OS))) + OS := Windows_NT +else ifneq (,$(findstring MSYS,$(OS))) + OS := Windows_NT +else ifneq (,$(findstring MINGW,$(OS))) + OS := Windows_NT +endif + ifneq ($(OS), Darwin) ALL_PROGRAMS += flips endif diff --git a/tools/n64sym.c b/tools/n64sym.c index d0fafc401c..c0fa4b28ec 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -18,7 +18,8 @@ int flag_max_sym_len = 64; bool flag_inlines = true; bool flag_all_lines = false; const char *n64_inst = NULL; -const char *cross_prefix = NULL; +const char *addr2line_exe = NULL; +const char *objdump_exe = NULL; // Printf if verbose void verbose(const char *fmt, ...) { @@ -37,7 +38,8 @@ void usage(const char *progname) fprintf(stderr, "Usage: %s [flags] []\n", progname); fprintf(stderr, "\n"); fprintf(stderr, "Command-line flags:\n"); - fprintf(stderr, " -c/--cross Cross compilation toolchain (e.g. `mips-linux-gnu-`)\n"); + fprintf(stderr, " -j/--objdump Path to the objdump exe\n"); + fprintf(stderr, " -c/--addr2line Path to the addr2line exe\n"); fprintf(stderr, " -v/--verbose Verbose output\n"); fprintf(stderr, " -m/--max-len Maximum symbol length (default: 64)\n"); fprintf(stderr, " -a/--all-lines Generate full address->line info (Can take seconds on its own)\n"); @@ -116,7 +118,7 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) cur_elf = NULL; addr2line_r = addr2line_w = NULL; } if (!addrbin) - asprintf(&addrbin, "/usr/bin/%saddr2line", cross_prefix); + asprintf(&addrbin, "%s", addr2line_exe); const char *cmd_addr[16] = {0}; int i = 0; cmd_addr[i++] = addrbin; @@ -127,7 +129,7 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) cmd_addr[i++] = "--exe"; cmd_addr[i++] = elf; - if (subprocess_create(cmd_addr, subprocess_option_no_window, &subp) != 0) { + if (subprocess_create(cmd_addr, subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { fprintf(stderr, "Error: cannot run: %s\n", addrbin); exit(1); } @@ -222,7 +224,7 @@ void address_add(const char *elf, uint32_t addr) { cur_elf = NULL; addr2line_r = addr2line_w = NULL; } if (!addrbin) - asprintf(&addrbin, "/usr/bin/%saddr2line", cross_prefix); + asprintf(&addrbin, "%s", addr2line_exe); const char *cmd_addr[16] = {0}; int i = 0; cmd_addr[i++] = addrbin; @@ -233,7 +235,7 @@ void address_add(const char *elf, uint32_t addr) { cmd_addr[i++] = "--exe"; cmd_addr[i++] = elf; - if (subprocess_create(cmd_addr, subprocess_option_no_window, &subp) != 0) { + if (subprocess_create(cmd_addr, subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { fprintf(stderr, "Error: cannot run: %s\n", addrbin); exit(1); } @@ -318,7 +320,7 @@ bool elf_find_callsites(const char *elf) { // Start objdump to parse the disassembly of the ELF file char *cmd = NULL; - asprintf(&cmd, "/usr/bin/%sobjdump -d %s", cross_prefix, elf); + asprintf(&cmd, "%s -d %s", objdump_exe, elf); verbose("Running: %s\n", cmd); FILE *disasm = popen(cmd, "r"); if (!disasm) { @@ -517,12 +519,18 @@ int main(int argc, char *argv[]) return 1; } flag_max_sym_len = atoi(argv[i]); - } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cross")) { + } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--addr2line")) { if (++i == argc) { fprintf(stderr, "missing argument for %s\n", argv[i-1]); return 1; } - cross_prefix = argv[i]; + addr2line_exe = argv[i]; + } else if (!strcmp(argv[i], "-j") || !strcmp(argv[i], "--objdump")) { + if (++i == argc) { + fprintf(stderr, "missing argument for %s\n", argv[i-1]); + return 1; + } + objdump_exe = argv[i]; } else { fprintf(stderr, "invalid flag: %s\n", argv[i]); return 1; From d63a4643ccb776f805aeb0b03d8cd4c9fcee773e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Fri, 27 Jun 2025 20:46:32 -0400 Subject: [PATCH 049/109] subprocess searches the path on linux, no more mingw-specific behavior now hopefully --- Makefile | 19 ++----------------- tools/n64sym.c | 2 +- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 1ec65df3d7..439a0fc300 100644 --- a/Makefile +++ b/Makefile @@ -449,17 +449,6 @@ DEP_FILES := $(O_FILES:.o=.d) $(LIBZ_O_FILES:.o=.d) $(GODDARD_O_FILES:.o=.d) $(B # Compiler Options # #==============================================================================# -# Attempt to figure out OS -OS := $(shell uname) - -ifneq (,$(findstring Windows_NT,$(OS))) - OS := Windows_NT -else ifneq (,$(findstring MSYS,$(OS))) - OS := Windows_NT -else ifneq (,$(findstring MINGW,$(OS))) - OS := Windows_NT -endif - # detect prefix for MIPS toolchain ifneq ($(call find-command,mips64-elf-ld),) CROSS := mips64-elf- @@ -513,12 +502,8 @@ endif AR := $(CROSS)ar OBJDUMP := $(CROSS)objdump OBJCOPY := $(CROSS)objcopy -ifeq ($(OS),Windows_NT) - ADDR2LINE := addr2line -else - ADDR2LINE := $(shell which $(CROSS)addr2line) - OBJDUMP_EXE := $(shell which $(OBJDUMP)) -endif +ADDR2LINE := addr2line +OBJDUMP_EXE := $(OBJDUMP) ifeq ($(LD), tools/mips64-elf-ld) ifeq ($(shell ls -la tools/mips64-elf-ld | awk '{print $1}' | grep x),) diff --git a/tools/n64sym.c b/tools/n64sym.c index c0fa4b28ec..85e4750666 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -129,7 +129,7 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) cmd_addr[i++] = "--exe"; cmd_addr[i++] = elf; - if (subprocess_create(cmd_addr, subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { + if (subprocess_create(cmd_addr, subprocess_option_search_user_path | subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { fprintf(stderr, "Error: cannot run: %s\n", addrbin); exit(1); } From 860bc0ece41953b73e6bbb498536f1efb8bc0d4f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Fri, 27 Jun 2025 20:48:02 -0400 Subject: [PATCH 050/109] dont need objdump_exe anymore --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 439a0fc300..aba9bcabff 100644 --- a/Makefile +++ b/Makefile @@ -503,7 +503,6 @@ AR := $(CROSS)ar OBJDUMP := $(CROSS)objdump OBJCOPY := $(CROSS)objcopy ADDR2LINE := addr2line -OBJDUMP_EXE := $(OBJDUMP) ifeq ($(LD), tools/mips64-elf-ld) ifeq ($(shell ls -la tools/mips64-elf-ld | awk '{print $1}' | grep x),) @@ -974,7 +973,7 @@ $(BUILD_DIR)/goddard.txt: $(BUILD_DIR)/sm64_prelim.elf $(SYMBOL_TABLE): $(BUILD_DIR)/sm64_prelim.elf $(call print,Generating symbol table:,$(@F)) - $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --objdump $(OBJDUMP_EXE) --addr2line $(ADDR2LINE) $(BUILD_DIR)/sm64_prelim.elf + $(V)tools/n64sym $(DEBUG_EXPORT_ALL_LINES_FLAG) --objdump $(OBJDUMP) --addr2line $(ADDR2LINE) $(BUILD_DIR)/sm64_prelim.elf $(call print,Assembling:,$@) $(LD) -r -b binary -o $@ $(BUILD_DIR)/sm64_prelim.sym From 8ddaceaa6d4a65bbac3660ac41e45c0e1fddd438 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 2 Jul 2025 20:14:24 -0400 Subject: [PATCH 051/109] add errorf --- src/debugger/assert.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/debugger/assert.h b/src/debugger/assert.h index 3dd6a1c38a..4e99dd6d84 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -16,8 +16,13 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); */ #define error(message) { \ sprintf(__n64Assert_MessageBuf, message); \ - __n64Assert(__FILE__, __LINE__, "Unconditional"); \ + __n64Assert(__FILE__, __LINE__, " error() called "); \ } +#define errorf(message, ...) { \ + sprintf(__n64Assert_MessageBuf, message, __VA_ARGS__); \ + __n64Assert(__FILE__, __LINE__, " errorf() called "); \ +} + /** * Wrapper for assert/aggress From 4476fb83c07b1dd24bcd9d4768002dd3cdc8b15d Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 2 Jul 2025 21:11:27 -0400 Subject: [PATCH 052/109] asserts no longer unreachable when disabled --- src/debugger/assert.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/debugger/assert.h b/src/debugger/assert.h index 4e99dd6d84..0722e3d137 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -58,7 +58,7 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); } \ } while (0); #else -#define assertf(cond, ...) if ((cond) == FALSE) { __builtin_unreachable(); } +#define assertf(cond, ...) #endif #ifdef DEBUG_ASSERTIONS @@ -69,7 +69,7 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); } \ } while (0); #else -#define assert(cond) if ((cond) == FALSE) { __builtin_unreachable(); } +#define assert(cond) #endif #endif // ASSEMBLER From a8bf89c0d15ce123578b1a5e4966963332e05087 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 3 Jul 2025 20:26:24 -0400 Subject: [PATCH 053/109] fix the case of errorf with one arg --- src/debugger/assert.h | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/debugger/assert.h b/src/debugger/assert.h index 0722e3d137..fcab6940a6 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -11,19 +11,26 @@ extern char *__n64Assert_Condition; extern char __n64Assert_MessageBuf[ASSERT_MESGBUF_SIZE + 1]; extern void __n64Assert(char *fileName, u32 lineNum, char *cond); +/** + * Stops the program + */ +#define abort() { \ + __n64Assert_MessageBuf[0] = 0; \ + __n64Assert(__FILE__, __LINE__, " abort() "); \ +} + /** * Will always cause a crash with your message of choice */ #define error(message) { \ sprintf(__n64Assert_MessageBuf, message); \ - __n64Assert(__FILE__, __LINE__, " error() called "); \ + __n64Assert(__FILE__, __LINE__, " error() "); \ } #define errorf(message, ...) { \ - sprintf(__n64Assert_MessageBuf, message, __VA_ARGS__); \ - __n64Assert(__FILE__, __LINE__, " errorf() called "); \ + sprintf(__n64Assert_MessageBuf, message __VA_OPT__(, __VA_ARGS__)); \ + __n64Assert(__FILE__, __LINE__, " errorf() "); \ } - /** * Wrapper for assert/aggress */ From d96152d8c9c5ddd0e2965245cd10cc41369a6525 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 3 Jul 2025 20:49:52 -0400 Subject: [PATCH 054/109] use cpp concatenation for errorf --- src/debugger/assert.h | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/debugger/assert.h b/src/debugger/assert.h index fcab6940a6..de3f320a3f 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -11,14 +11,6 @@ extern char *__n64Assert_Condition; extern char __n64Assert_MessageBuf[ASSERT_MESGBUF_SIZE + 1]; extern void __n64Assert(char *fileName, u32 lineNum, char *cond); -/** - * Stops the program - */ -#define abort() { \ - __n64Assert_MessageBuf[0] = 0; \ - __n64Assert(__FILE__, __LINE__, " abort() "); \ -} - /** * Will always cause a crash with your message of choice */ @@ -27,7 +19,7 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); __n64Assert(__FILE__, __LINE__, " error() "); \ } #define errorf(message, ...) { \ - sprintf(__n64Assert_MessageBuf, message __VA_OPT__(, __VA_ARGS__)); \ + sprintf(__n64Assert_MessageBuf, message ## __VA_ARGS__); \ __n64Assert(__FILE__, __LINE__, " errorf() "); \ } From c195c271661ba3a72661f41e4d0bb7670d61145a Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sat, 12 Jul 2025 17:12:39 -0400 Subject: [PATCH 055/109] swap macro order --- src/debugger/assert.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/debugger/assert.h b/src/debugger/assert.h index de3f320a3f..c466c48f48 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -14,14 +14,14 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); /** * Will always cause a crash with your message of choice */ -#define error(message) { \ - sprintf(__n64Assert_MessageBuf, message); \ - __n64Assert(__FILE__, __LINE__, " error() "); \ -} #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 From d18faaa68c0c0ff3eb1e8ec704bbd4dfaefac37e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 14 Sep 2025 11:57:30 -0400 Subject: [PATCH 056/109] address feedback --- include/config/config_debug.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/config/config_debug.h b/include/config/config_debug.h index 02c56b25c4..4814831e64 100644 --- a/include/config/config_debug.h +++ b/include/config/config_debug.h @@ -10,7 +10,7 @@ // #define DEBUG_ALL /** - * Disables all debug options (except PUPPYPRINT). + * Disables all debug options (except PUPPYPRINT and DEBUG_FULL_STACK_TRACE). */ // #define DISABLE_ALL @@ -96,7 +96,7 @@ #define DEBUG_ASSERTIONS /** - * Crash Screen: enable symbol-assisted stack trace - * If disabled, stack trace will just dump memory, and you can figure it out yourself. + * Crash Screen: enable symbol-assisted stack trace. Adds a bit more code to (somewhat) intelligently walk the stack. + * If disabled, stack trace will just dump the memory at the stack pointer, from which one can cross-reference with the function map. */ #define DEBUG_FULL_STACK_TRACE From a6546033cdfeb0001c06b404c5c77f4ac30c88a2 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 7 Oct 2025 20:03:12 -0400 Subject: [PATCH 057/109] DEBUG_FULL_STACK_TRACE promoted to a makefile switch --- Makefile | 10 +++++++++- include/config/config_debug.h | 6 ------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index c8d344cafb..3106d8802d 100644 --- a/Makefile +++ b/Makefile @@ -90,10 +90,15 @@ endif # FIXLIGHTS - converts light objects to light color commands for assets, needed for vanilla-style lighting FIXLIGHTS ?= 1 +#---------------- # Debug Switches - Comment these out to remove the functionality, then run `make clean` +#---------------- # Export symbols and stack trace data for crash screen DEBUG_EXPORT_SYMBOLS := 1 -# Include line data, at the cost of a significantly longer link time +# Crash Screen: enable symbol-assisted stack trace. Adds a bit more code to (somewhat) intelligently walk the stack. +# If disabled, stack trace will just dump the memory at the stack pointer, from which one can cross-reference with the function map. +DEBUG_FULL_STACK_TRACE := 1 +# Include line data, at the cost of a significantly longer link time and a few extra megabytes of ROM DEBUG_EXPORT_ALL_LINES := 0 ifeq ($(DEBUG_EXPORT_SYMBOLS),1) @@ -105,6 +110,9 @@ ifeq ($(DEBUG_EXPORT_SYMBOLS),1) DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 SYMBOL_TABLE = $(BUILD_DIR)/sm64.sym.o endif +ifeq ($(DEBUG_FULL_STACK_TRACE),1) + DEFINES += DEBUG_FULL_STACK_TRACE +endif TARGET := sm64 diff --git a/include/config/config_debug.h b/include/config/config_debug.h index 4814831e64..f2ba64b02b 100644 --- a/include/config/config_debug.h +++ b/include/config/config_debug.h @@ -94,9 +94,3 @@ * Intentionally crash the game whenever a runtime assertion fails (also invoked by the DEBUG define in the Makefile). */ #define DEBUG_ASSERTIONS - -/** - * Crash Screen: enable symbol-assisted stack trace. Adds a bit more code to (somewhat) intelligently walk the stack. - * If disabled, stack trace will just dump the memory at the stack pointer, from which one can cross-reference with the function map. - */ -#define DEBUG_FULL_STACK_TRACE From 596cbb7e28b2a71bba19be3398425273093a1d52 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 7 Oct 2025 20:08:46 -0400 Subject: [PATCH 058/109] compile error --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3106d8802d..0bdd450346 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,7 @@ ifeq ($(DEBUG_EXPORT_SYMBOLS),1) SYMBOL_TABLE = $(BUILD_DIR)/sm64.sym.o endif ifeq ($(DEBUG_FULL_STACK_TRACE),1) - DEFINES += DEBUG_FULL_STACK_TRACE + DEFINES += DEBUG_FULL_STACK_TRACE=1 endif TARGET := sm64 From 4ccff73f32fad4f2de16794e0ed8593bf55dc902 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 7 Oct 2025 20:13:01 -0400 Subject: [PATCH 059/109] crash_screen external header --- src/boot/main.c | 2 +- src/debugger/crash_screen.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/debugger/crash_screen.h diff --git a/src/boot/main.c b/src/boot/main.c index dd6322e3ed..640526f625 100644 --- a/src/boot/main.c +++ b/src/boot/main.c @@ -7,6 +7,7 @@ #include "sm64.h" #include "audio/external.h" #include "debugger/assert.h" +#include "debugger/crash_screen.h" #include "game/game_init.h" #include "game/debug.h" #include "game/memory.h" @@ -344,7 +345,6 @@ void check_stack_validity(void) { #endif -extern void crash_screen_init(void); extern OSViMode VI; void thread3_main(UNUSED void *arg) { setup_mesg_queues(); diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h new file mode 100644 index 0000000000..76411b95ab --- /dev/null +++ b/src/debugger/crash_screen.h @@ -0,0 +1,7 @@ +#ifndef CRASH_SCREEN_H +#define CRASH_SCREEN_H + + +void crash_screen_init(void); + +#endif // CRASH_SCREEN_H From b68d787d634eea26eb394e215d40cc301efff380 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 7 Oct 2025 20:28:09 -0400 Subject: [PATCH 060/109] more header moving --- src/debugger/crash_screen.c | 24 +++--------------------- src/debugger/crash_screen.h | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 1d32c01af3..05457d163f 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -15,6 +15,7 @@ #include "game/rumble_init.h" #include "game/printf.h" +#include "crash_screen.h" #include "map_parser.h" #include "disasm.h" #include "assert.h" @@ -29,25 +30,6 @@ extern void stop_sounds_in_continuous_banks(void); extern void read_controller_inputs(s32 threadID); extern char *strstr(char *, char *); -// 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, -#ifdef PUPPYPRINT_DEBUG - PAGE_LOG, -#endif - PAGE_STACKTRACE, - PAGE_DISASM, - PAGE_ASSERTS, - PAGE_COUNT -}; - static char *crashPageNames[] = { [PAGE_SIMPLE] = "(Overview)", [PAGE_CONTEXT] = "(Context)", @@ -74,8 +56,8 @@ static u32 sCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; -u8 crashPage = 0; -u8 updateBuffer = TRUE; +static u8 crashPage = 0; +static u8 updateBuffer = TRUE; static char crashScreenBuf[0x200]; diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 76411b95ab..6dd5658640 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -1,6 +1,24 @@ #ifndef CRASH_SCREEN_H #define CRASH_SCREEN_H +// 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, +#ifdef PUPPYPRINT_DEBUG + PAGE_LOG, +#endif + PAGE_STACKTRACE, + PAGE_DISASM, + PAGE_ASSERTS, + PAGE_COUNT +}; void crash_screen_init(void); From 5fec1336f62a84bc92503a0387d317acf561fd0c Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 8 Oct 2025 19:31:58 -0400 Subject: [PATCH 061/109] C --- src/debugger/crash_screen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 6dd5658640..930a168420 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -8,7 +8,7 @@ #define FONT_ROWS 16 #define LEFT_MARGIN 10 // for crash screen prints -enum crashPages { +enum CrashPages { PAGE_SIMPLE, PAGE_CONTEXT, #ifdef PUPPYPRINT_DEBUG From 105be2374b593e569015a1b9be09fc51ece5646c Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 23 Oct 2025 20:56:25 -0400 Subject: [PATCH 062/109] start diff reduction --- src/game/crash_screen.c | 487 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 src/game/crash_screen.c diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c new file mode 100644 index 0000000000..bf0bb88e0c --- /dev/null +++ b/src/game/crash_screen.c @@ -0,0 +1,487 @@ +#include +#include +#include +#include +#include "buffers/framebuffers.h" +#include "types.h" +#include "puppyprint.h" +#include "audio/external.h" +#include "farcall.h" +#include "game_init.h" +#include "main.h" +#include "debug.h" +#include "rumble_init.h" + +#include "sm64.h" + +#include "printf.h" + +#define X_KERNING 6 + +enum crashPages { + PAGE_CONTEXT, +#ifdef PUPPYPRINT_DEBUG + PAGE_LOG, +#endif + PAGE_STACKTRACE, + PAGE_DISASM, + PAGE_ASSERTS, + PAGE_COUNT +}; + +u8 gCrashScreenCharToGlyph[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, -1, 41, -1, -1, -1, 43, -1, -1, 37, 38, -1, 42, + -1, 39, 44, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 36, -1, -1, -1, -1, 40, -1, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1, +}; + +// A height of seven pixels for each Character * nine rows of characters + one row unused. +u32 gCrashScreenFont[7 * 9 + 1] = { + #include "textures/crash_custom/crash_screen_font.ia1.inc.c" +}; + +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", +}; + + + +extern u64 osClockRate; +extern far char *parse_map(u32 pc); +extern far void map_data_init(void); +extern far char *find_function_in_stack(u32 *sp); + +struct { + OSThread thread; + u64 stack[THREAD2_STACK / sizeof(u64)]; + OSMesgQueue mesgQueue; + OSMesg mesg; + u16 *framebuffer; + u16 width; + u16 height; +} gCrashScreen; + +void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { + u16 *ptr; + s32 i, j; + + ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + // 0xe738 = 0b1110011100111000 + *ptr = ((*ptr & 0xe738) >> 2) | 1; + ptr++; + } + ptr += gCrashScreen.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; + + data = &gCrashScreenFont[glyph / 5 * 7]; + ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; + + for (i = 0; i < 7; i++) { + bit = 0x80000000U >> ((glyph % 5) * 6); + rowMask = *data++; + + for (j = 0; j < 6; j++) { + *ptr++ = (bit & rowMask) ? 0xffff : 1; + bit >>= 1; + } + ptr += gCrashScreen.width - 6; + } +} + +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; + s32 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 >= SCREEN_WIDTH - (xNewline + X_KERNING)) { + y += 10; + xOffset = xNewline; + } + + glyph = gCrashScreenCharToGlyph[*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 = gCrashScreenCharToGlyph[*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 = BIT(17); + + crash_screen_print(30, 155, "FPCSR:%08XH", fpcsr); + for (i = 0; i < 6; i++) { + if (fpcsr & bit) { + crash_screen_print(132, 155, "(%s)", gFpcsrDesc[i]); + return; + } + bit >>= 1; + } +} + +void draw_crash_context(OSThread *thread, s32 cause) { + __OSThreadContext *tc = &thread->context; + crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_print(30, 20, "THREAD:%d (%s)", thread->id, gCauseDesc[cause]); + crash_screen_print(30, 30, "PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); + osWritebackDCacheAll(); + if ((u32)parse_map != MAP_PARSER_ADDRESS) { + char *fname = parse_map(tc->pc); + crash_screen_print(30, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); + } + crash_screen_print(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); + crash_screen_print(30, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); + crash_screen_print(30, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); + crash_screen_print(30, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); + crash_screen_print(30, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); + crash_screen_print(30, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); + crash_screen_print(30, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); + crash_screen_print(30, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); + crash_screen_print(30, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); + crash_screen_print(30, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); + crash_screen_print_fpcsr(tc->fpcsr); + + osWritebackDCacheAll(); + crash_screen_print_float_reg( 30, 170, 0, &tc->fp0.f.f_even); + crash_screen_print_float_reg(120, 170, 2, &tc->fp2.f.f_even); + crash_screen_print_float_reg(210, 170, 4, &tc->fp4.f.f_even); + crash_screen_print_float_reg( 30, 180, 6, &tc->fp6.f.f_even); + crash_screen_print_float_reg(120, 180, 8, &tc->fp8.f.f_even); + crash_screen_print_float_reg(210, 180, 10, &tc->fp10.f.f_even); + crash_screen_print_float_reg( 30, 190, 12, &tc->fp12.f.f_even); + crash_screen_print_float_reg(120, 190, 14, &tc->fp14.f.f_even); + crash_screen_print_float_reg(210, 190, 16, &tc->fp16.f.f_even); + crash_screen_print_float_reg( 30, 200, 18, &tc->fp18.f.f_even); + crash_screen_print_float_reg(120, 200, 20, &tc->fp20.f.f_even); + crash_screen_print_float_reg(210, 200, 22, &tc->fp22.f.f_even); + crash_screen_print_float_reg( 30, 210, 24, &tc->fp24.f.f_even); + crash_screen_print_float_reg(120, 210, 26, &tc->fp26.f.f_even); + crash_screen_print_float_reg(210, 210, 28, &tc->fp28.f.f_even); + crash_screen_print_float_reg( 30, 220, 30, &tc->fp30.f.f_even); +} + + +#ifdef PUPPYPRINT_DEBUG +void draw_crash_log(void) { + s32 i; + crash_screen_draw_rect(25, 20, 270, 210); + osWritebackDCacheAll(); +#define LINE_HEIGHT (25 + ((LOG_BUFFER_SIZE - 1) * 10)) + for (i = 0; i < LOG_BUFFER_SIZE; i++) { + crash_screen_print(30, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); + } +#undef LINE_HEIGHT +} +#endif + + +// prints any function pointers it finds in the stack format: +// SP address: function name +void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { + __OSThreadContext *tc = &thread->context; + u32 temp_sp = (tc->sp + 0x14); + + crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_print(30, 25, "STACK TRACE FROM %08X:", temp_sp); + if ((u32) parse_map == MAP_PARSER_ADDRESS) { + crash_screen_print(30, 35, "CURRFUNC: NONE"); + } else { + crash_screen_print(30, 35, "CURRFUNC: %s", parse_map(tc->pc)); + } + + osWritebackDCacheAll(); + + for (int i = 0; i < 18; i++) { + if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { + crash_screen_print(30, (45 + (i * 10)), "STACK TRACE DISABLED"); + break; + } else { + if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { + return; + } + + char *fname = find_function_in_stack(&temp_sp); + if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { + crash_screen_print(30, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); + } else { + crash_screen_print(30, (45 + (i * 10)), "%08X: %s", temp_sp, fname); + } + } + } +} + +extern char *insn_disasm(u32 insn, u32 isPC); +static u32 sProgramPosition = 0; +void draw_disasm(OSThread *thread) { + __OSThreadContext *tc = &thread->context; + // u32 insn = *(u32*)tc->pc; + + crash_screen_draw_rect(25, 20, 270, 210); + if (sProgramPosition == 0) { + sProgramPosition = (tc->pc - 36); + } + crash_screen_print(30, 25, "DISASM %08X", sProgramPosition); + osWritebackDCacheAll(); + + + for (int i = 0; i < 19; i++) { + u32 addr = (sProgramPosition + (i * 4)); + u32 toDisasm = *(u32*)(addr); + + crash_screen_print(30, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); + } + + osWritebackDCacheAll(); +} + +void draw_assert(UNUSED OSThread *thread) { + crash_screen_draw_rect(25, 20, 270, 210); + + crash_screen_print(30, 25, "ASSERT PAGE"); + + if (__n64Assert_Filename != NULL) { + crash_screen_print(30, 45, "FILE: %s", __n64Assert_Filename); + crash_screen_print(30, 55, "LINE: %d", __n64Assert_LineNum); + crash_screen_print(30, 75, "MESSAGE:", __n64Assert_Message); + crash_screen_print_with_newlines(36, 85, 30, __n64Assert_Message); + } else { + crash_screen_print(30, 45, "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++; + updateBuffer = TRUE; + } + if (gPlayer1Controller->buttonPressed & (L_TRIG | Z_TRIG)) { + crashPage--; + updateBuffer = TRUE; + } + 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 (updateBuffer) { + crash_screen_draw_rect(25, 8, 270, 12); + crash_screen_print(30, 10, "Page:%02d L/Z: Left R: Right", crashPage); + switch (crashPage) { + 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(gCrashScreen.framebuffer); + 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; +} + +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); +extern struct SequenceQueueItem sBackgroundMusicQueue[6]; + +void thread2_crash_screen(UNUSED void *arg) { + OSMesg mesg; + OSThread *thread = NULL; + + osSetEventMesg(OS_EVENT_CPU_BREAK, &gCrashScreen.mesgQueue, (OSMesg) 1); + osSetEventMesg(OS_EVENT_FAULT, &gCrashScreen.mesgQueue, (OSMesg) 2); + while (TRUE) { + if (thread == NULL) { + osRecvMesg(&gCrashScreen.mesgQueue, &mesg, 1); + thread = get_crashed_thread(); + gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; + if (thread) { + if ((u32) map_data_init != MAP_PARSER_ADDRESS) { + map_data_init(); + } + gCrashScreen.thread.priority = 15; + stop_sounds_in_continuous_banks(); + stop_background_music(sBackgroundMusicQueue[0].seqId); + audio_signal_game_loop_tick(); + crash_screen_sleep(200); + play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); + audio_signal_game_loop_tick(); + crash_screen_sleep(200); + continue; + } + } else { + if (gControllerBits) { +#if ENABLE_RUMBLE + block_until_rumble_pak_free(); +#endif + osContStartReadDataEx(&gSIEventMesgQueue); + } + read_controller_inputs(THREAD_2_CRASH_SCREEN); + draw_crash_screen(thread); + } + } +} + +void crash_screen_init(void) { + gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; + gCrashScreen.width = SCREEN_WIDTH; + gCrashScreen.height = SCREEN_HEIGHT; + osCreateMesgQueue(&gCrashScreen.mesgQueue, &gCrashScreen.mesg, 1); + osCreateThread(&gCrashScreen.thread, THREAD_2_CRASH_SCREEN, thread2_crash_screen, NULL, + (u8 *) gCrashScreen.stack + sizeof(gCrashScreen.stack), + OS_PRIORITY_APPMAX + ); + osStartThread(&gCrashScreen.thread); +} + From 8c1db60db77e4919abd6d055801fb03918a80399 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 23 Oct 2025 20:56:41 -0400 Subject: [PATCH 063/109] move the file --- src/debugger/crash_screen.c | 376 ++++++++++------------------ src/game/crash_screen.c | 487 ------------------------------------ 2 files changed, 139 insertions(+), 724 deletions(-) delete mode 100644 src/game/crash_screen.c diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 05457d163f..bf0bb88e0c 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -1,63 +1,50 @@ #include #include -#include #include #include -#include "config/config_debug.h" #include "buffers/framebuffers.h" #include "types.h" -#include "game/puppyprint.h" +#include "puppyprint.h" #include "audio/external.h" #include "farcall.h" -#include "game/game_init.h" -#include "game/main.h" -#include "game/debug.h" -#include "game/rumble_init.h" -#include "game/printf.h" - -#include "crash_screen.h" -#include "map_parser.h" -#include "disasm.h" -#include "assert.h" -#include "stacktrace.h" +#include "game_init.h" +#include "main.h" +#include "debug.h" +#include "rumble_init.h" #include "sm64.h" -extern u16 sRenderedFramebuffer; -extern struct SequenceQueueItem sBackgroundMusicQueue[6]; -extern void audio_signal_game_loop_tick(void); -extern void stop_sounds_in_continuous_banks(void); -extern void read_controller_inputs(s32 threadID); -extern char *strstr(char *, char *); +#include "printf.h" -static char *crashPageNames[] = { - [PAGE_SIMPLE] = "(Overview)", - [PAGE_CONTEXT] = "(Context)", +#define X_KERNING 6 + +enum crashPages { + PAGE_CONTEXT, #ifdef PUPPYPRINT_DEBUG - [PAGE_LOG] = "(Log)", + PAGE_LOG, #endif - [PAGE_STACKTRACE] = "(Stack Trace)", - [PAGE_DISASM] = "(Disassembly)", - [PAGE_ASSERTS] = "(Assert)", + PAGE_STACKTRACE, + PAGE_DISASM, + PAGE_ASSERTS, + PAGE_COUNT }; -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, +u8 gCrashScreenCharToGlyph[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, -1, 41, -1, -1, -1, 43, -1, -1, 37, 38, -1, 42, + -1, 39, 44, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 36, -1, -1, -1, -1, 40, -1, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1, }; -static u32 sCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { +// A height of seven pixels for each Character * nine rows of characters + one row unused. +u32 gCrashScreenFont[7 * 9 + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; -static u8 crashPage = 0; -static u8 updateBuffer = TRUE; +u8 crashPage = 0; +u8 updateBuffer = TRUE; static char crashScreenBuf[0x200]; @@ -87,9 +74,14 @@ char *gFpcsrDesc[6] = { "Inexact operation", }; -static u32 sProgramPosition = 0; -static u16 gCrashScreenTextColor = 0xFFFF; -static struct { + + +extern u64 osClockRate; +extern far char *parse_map(u32 pc); +extern far void map_data_init(void); +extern far char *find_function_in_stack(u32 *sp); + +struct { OSThread thread; u64 stack[THREAD2_STACK / sizeof(u64)]; OSMesgQueue mesgQueue; @@ -99,14 +91,6 @@ static struct { u16 height; } gCrashScreen; -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; @@ -114,7 +98,8 @@ void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { - *ptr = 0x0001; + // 0xe738 = 0b1110011100111000 + *ptr = ((*ptr & 0xe738) >> 2) | 1; ptr++; } ptr += gCrashScreen.width - w; @@ -128,27 +113,18 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { u32 rowMask; s32 i, j; - if (glyph > 0x7F) return; - - data = &sCrashScreenFont[((glyph&0xF)*GLYPH_HEIGHT * 2) + (glyph >= 64)]; - + data = &gCrashScreenFont[glyph / 5 * 7]; ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - u16 color = gCrashScreenTextColor; - - for (i = 0; i < GLYPH_HEIGHT; i++) { - bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); + for (i = 0; i < 7; i++) { + bit = 0x80000000U >> ((glyph % 5) * 6); rowMask = *data++; - data ++; - for (j = 0; j < (GLYPH_WIDTH); j++) { - if (bit & rowMask) { - *ptr = color; - } - ptr++; + for (j = 0; j < 6; j++) { + *ptr++ = (bit & rowMask) ? 0xffff : 1; bit >>= 1; } - ptr += gCrashScreen.width - (GLYPH_WIDTH); + ptr += gCrashScreen.width - 6; } } @@ -176,7 +152,7 @@ void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const ch xOffset = xNewline; } - glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; + glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; if (*ptr == '\n') { y += 10; @@ -209,7 +185,7 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { ptr = crashScreenBuf; while (*ptr && size-- > 0) { - glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; + glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; if (glyph != 0xff) { crash_screen_draw_glyph(x, y, glyph); @@ -244,215 +220,143 @@ void crash_screen_print_fpcsr(u32 fpcsr) { s32 i; u32 bit = BIT(17); - crash_screen_print(100, 220, "FPCSR:%08XH", fpcsr); + crash_screen_print(30, 155, "FPCSR:%08XH", fpcsr); for (i = 0; i < 6; i++) { if (fpcsr & bit) { - crash_screen_print(222, 220, "(%s)", gFpcsrDesc[i]); + crash_screen_print(132, 155, "(%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); + crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_print(30, 20, "THREAD:%d (%s)", thread->id, gCauseDesc[cause]); + crash_screen_print(30, 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 - + if ((u32)parse_map != MAP_PARSER_ADDRESS) { + char *fname = parse_map(tc->pc); + crash_screen_print(30, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); + } + crash_screen_print(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); + crash_screen_print(30, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); + crash_screen_print(30, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); + crash_screen_print(30, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); + crash_screen_print(30, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); + crash_screen_print(30, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); + crash_screen_print(30, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); + crash_screen_print(30, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); + crash_screen_print(30, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); + crash_screen_print(30, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); 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); + crash_screen_print_float_reg( 30, 170, 0, &tc->fp0.f.f_even); + crash_screen_print_float_reg(120, 170, 2, &tc->fp2.f.f_even); + crash_screen_print_float_reg(210, 170, 4, &tc->fp4.f.f_even); + crash_screen_print_float_reg( 30, 180, 6, &tc->fp6.f.f_even); + crash_screen_print_float_reg(120, 180, 8, &tc->fp8.f.f_even); + crash_screen_print_float_reg(210, 180, 10, &tc->fp10.f.f_even); + crash_screen_print_float_reg( 30, 190, 12, &tc->fp12.f.f_even); + crash_screen_print_float_reg(120, 190, 14, &tc->fp14.f.f_even); + crash_screen_print_float_reg(210, 190, 16, &tc->fp16.f.f_even); + crash_screen_print_float_reg( 30, 200, 18, &tc->fp18.f.f_even); + crash_screen_print_float_reg(120, 200, 20, &tc->fp20.f.f_even); + crash_screen_print_float_reg(210, 200, 22, &tc->fp22.f.f_even); + crash_screen_print_float_reg( 30, 210, 24, &tc->fp24.f.f_even); + crash_screen_print_float_reg(120, 210, 26, &tc->fp26.f.f_even); + crash_screen_print_float_reg(210, 210, 28, &tc->fp28.f.f_even); + crash_screen_print_float_reg( 30, 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); + crash_screen_draw_rect(25, 20, 270, 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]); + crash_screen_print(30, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); } #undef LINE_HEIGHT } #endif + +// prints any function pointers it finds in the stack format: +// SP address: function name void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; + u32 temp_sp = (tc->sp + 0x14); - 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); + crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_print(30, 25, "STACK TRACE FROM %08X:", temp_sp); + if ((u32) parse_map == MAP_PARSER_ADDRESS) { + crash_screen_print(30, 35, "CURRFUNC: NONE"); + } else { + crash_screen_print(30, 35, "CURRFUNC: %s", parse_map(tc->pc)); + } 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 < 18; i++) { + if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { + crash_screen_print(30, (45 + (i * 10)), "STACK TRACE DISABLED"); + break; + } else { + if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { + return; + } - 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)))); + char *fname = find_function_in_stack(&temp_sp); + if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { + crash_screen_print(30, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); + } else { + crash_screen_print(30, (45 + (i * 10)), "%08X: %s", temp_sp, fname); + } + } } -#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) } +extern char *insn_disasm(u32 insn, u32 isPC); +static u32 sProgramPosition = 0; void draw_disasm(OSThread *thread) { __OSThreadContext *tc = &thread->context; + // u32 insn = *(u32*)tc->pc; - crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_draw_rect(25, 20, 270, 210); if (sProgramPosition == 0) { sProgramPosition = (tc->pc - 36); } - crash_screen_print(LEFT_MARGIN, 25, "Program Counter: %08X", sProgramPosition); + crash_screen_print(30, 25, "DISASM %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)); + u32 toDisasm = *(u32*)(addr); - 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); - } - + crash_screen_print(30, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); } - reset_text_color(); osWritebackDCacheAll(); } void draw_assert(UNUSED OSThread *thread) { - crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(LEFT_MARGIN, 25, "Assert"); + crash_screen_print(30, 25, "ASSERT PAGE"); 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); - } + crash_screen_print(30, 45, "FILE: %s", __n64Assert_Filename); + crash_screen_print(30, 55, "LINE: %d", __n64Assert_LineNum); + crash_screen_print(30, 75, "MESSAGE:", __n64Assert_Message); + crash_screen_print_with_newlines(36, 85, 30, __n64Assert_Message); } else { - crash_screen_print(LEFT_MARGIN, 35, "No failed assert to report."); + crash_screen_print(30, 45, "No failed assert to report."); } osWritebackDCacheAll(); @@ -471,24 +375,19 @@ void draw_crash_screen(OSThread *thread) { 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 (gPlayer1Controller->buttonDown & D_CBUTTONS) { + sProgramPosition += 4; + updateBuffer = TRUE; + } + if (gPlayer1Controller->buttonDown & U_CBUTTONS) { + sProgramPosition -= 4; + updateBuffer = TRUE; } if ((crashPage >= PAGE_COUNT) && (crashPage != 255)) { @@ -496,13 +395,11 @@ void draw_crash_screen(OSThread *thread) { } 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]); + crash_screen_draw_rect(25, 8, 270, 12); + crash_screen_print(30, 10, "Page:%02d L/Z: Left R: Right", 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; @@ -532,6 +429,12 @@ OSThread *get_crashed_thread(void) { return NULL; } +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); +extern struct SequenceQueueItem sBackgroundMusicQueue[6]; + void thread2_crash_screen(UNUSED void *arg) { OSMesg mesg; OSThread *thread = NULL; @@ -544,6 +447,9 @@ void thread2_crash_screen(UNUSED void *arg) { thread = get_crashed_thread(); gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; if (thread) { + if ((u32) map_data_init != MAP_PARSER_ADDRESS) { + map_data_init(); + } gCrashScreen.thread.priority = 15; stop_sounds_in_continuous_banks(); stop_background_music(sBackgroundMusicQueue[0].seqId); @@ -552,10 +458,6 @@ void thread2_crash_screen(UNUSED void *arg) { play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); 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 { diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c deleted file mode 100644 index bf0bb88e0c..0000000000 --- a/src/game/crash_screen.c +++ /dev/null @@ -1,487 +0,0 @@ -#include -#include -#include -#include -#include "buffers/framebuffers.h" -#include "types.h" -#include "puppyprint.h" -#include "audio/external.h" -#include "farcall.h" -#include "game_init.h" -#include "main.h" -#include "debug.h" -#include "rumble_init.h" - -#include "sm64.h" - -#include "printf.h" - -#define X_KERNING 6 - -enum crashPages { - PAGE_CONTEXT, -#ifdef PUPPYPRINT_DEBUG - PAGE_LOG, -#endif - PAGE_STACKTRACE, - PAGE_DISASM, - PAGE_ASSERTS, - PAGE_COUNT -}; - -u8 gCrashScreenCharToGlyph[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, -1, 41, -1, -1, -1, 43, -1, -1, 37, 38, -1, 42, - -1, 39, 44, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 36, -1, -1, -1, -1, 40, -1, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1, -}; - -// A height of seven pixels for each Character * nine rows of characters + one row unused. -u32 gCrashScreenFont[7 * 9 + 1] = { - #include "textures/crash_custom/crash_screen_font.ia1.inc.c" -}; - -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", -}; - - - -extern u64 osClockRate; -extern far char *parse_map(u32 pc); -extern far void map_data_init(void); -extern far char *find_function_in_stack(u32 *sp); - -struct { - OSThread thread; - u64 stack[THREAD2_STACK / sizeof(u64)]; - OSMesgQueue mesgQueue; - OSMesg mesg; - u16 *framebuffer; - u16 width; - u16 height; -} gCrashScreen; - -void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { - u16 *ptr; - s32 i, j; - - ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - for (i = 0; i < h; i++) { - for (j = 0; j < w; j++) { - // 0xe738 = 0b1110011100111000 - *ptr = ((*ptr & 0xe738) >> 2) | 1; - ptr++; - } - ptr += gCrashScreen.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; - - data = &gCrashScreenFont[glyph / 5 * 7]; - ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - - for (i = 0; i < 7; i++) { - bit = 0x80000000U >> ((glyph % 5) * 6); - rowMask = *data++; - - for (j = 0; j < 6; j++) { - *ptr++ = (bit & rowMask) ? 0xffff : 1; - bit >>= 1; - } - ptr += gCrashScreen.width - 6; - } -} - -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; - s32 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 >= SCREEN_WIDTH - (xNewline + X_KERNING)) { - y += 10; - xOffset = xNewline; - } - - glyph = gCrashScreenCharToGlyph[*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 = gCrashScreenCharToGlyph[*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 = BIT(17); - - crash_screen_print(30, 155, "FPCSR:%08XH", fpcsr); - for (i = 0; i < 6; i++) { - if (fpcsr & bit) { - crash_screen_print(132, 155, "(%s)", gFpcsrDesc[i]); - return; - } - bit >>= 1; - } -} - -void draw_crash_context(OSThread *thread, s32 cause) { - __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 20, "THREAD:%d (%s)", thread->id, gCauseDesc[cause]); - crash_screen_print(30, 30, "PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); - osWritebackDCacheAll(); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->pc); - crash_screen_print(30, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); - } - crash_screen_print(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); - crash_screen_print(30, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); - crash_screen_print(30, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); - crash_screen_print(30, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); - crash_screen_print(30, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); - crash_screen_print(30, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); - crash_screen_print(30, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); - crash_screen_print(30, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); - crash_screen_print(30, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); - crash_screen_print(30, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); - crash_screen_print_fpcsr(tc->fpcsr); - - osWritebackDCacheAll(); - crash_screen_print_float_reg( 30, 170, 0, &tc->fp0.f.f_even); - crash_screen_print_float_reg(120, 170, 2, &tc->fp2.f.f_even); - crash_screen_print_float_reg(210, 170, 4, &tc->fp4.f.f_even); - crash_screen_print_float_reg( 30, 180, 6, &tc->fp6.f.f_even); - crash_screen_print_float_reg(120, 180, 8, &tc->fp8.f.f_even); - crash_screen_print_float_reg(210, 180, 10, &tc->fp10.f.f_even); - crash_screen_print_float_reg( 30, 190, 12, &tc->fp12.f.f_even); - crash_screen_print_float_reg(120, 190, 14, &tc->fp14.f.f_even); - crash_screen_print_float_reg(210, 190, 16, &tc->fp16.f.f_even); - crash_screen_print_float_reg( 30, 200, 18, &tc->fp18.f.f_even); - crash_screen_print_float_reg(120, 200, 20, &tc->fp20.f.f_even); - crash_screen_print_float_reg(210, 200, 22, &tc->fp22.f.f_even); - crash_screen_print_float_reg( 30, 210, 24, &tc->fp24.f.f_even); - crash_screen_print_float_reg(120, 210, 26, &tc->fp26.f.f_even); - crash_screen_print_float_reg(210, 210, 28, &tc->fp28.f.f_even); - crash_screen_print_float_reg( 30, 220, 30, &tc->fp30.f.f_even); -} - - -#ifdef PUPPYPRINT_DEBUG -void draw_crash_log(void) { - s32 i; - crash_screen_draw_rect(25, 20, 270, 210); - osWritebackDCacheAll(); -#define LINE_HEIGHT (25 + ((LOG_BUFFER_SIZE - 1) * 10)) - for (i = 0; i < LOG_BUFFER_SIZE; i++) { - crash_screen_print(30, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); - } -#undef LINE_HEIGHT -} -#endif - - -// prints any function pointers it finds in the stack format: -// SP address: function name -void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { - __OSThreadContext *tc = &thread->context; - u32 temp_sp = (tc->sp + 0x14); - - crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 25, "STACK TRACE FROM %08X:", temp_sp); - if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(30, 35, "CURRFUNC: NONE"); - } else { - crash_screen_print(30, 35, "CURRFUNC: %s", parse_map(tc->pc)); - } - - osWritebackDCacheAll(); - - for (int i = 0; i < 18; i++) { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(30, (45 + (i * 10)), "STACK TRACE DISABLED"); - break; - } else { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - return; - } - - char *fname = find_function_in_stack(&temp_sp); - if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - crash_screen_print(30, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); - } else { - crash_screen_print(30, (45 + (i * 10)), "%08X: %s", temp_sp, fname); - } - } - } -} - -extern char *insn_disasm(u32 insn, u32 isPC); -static u32 sProgramPosition = 0; -void draw_disasm(OSThread *thread) { - __OSThreadContext *tc = &thread->context; - // u32 insn = *(u32*)tc->pc; - - crash_screen_draw_rect(25, 20, 270, 210); - if (sProgramPosition == 0) { - sProgramPosition = (tc->pc - 36); - } - crash_screen_print(30, 25, "DISASM %08X", sProgramPosition); - osWritebackDCacheAll(); - - - for (int i = 0; i < 19; i++) { - u32 addr = (sProgramPosition + (i * 4)); - u32 toDisasm = *(u32*)(addr); - - crash_screen_print(30, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); - } - - osWritebackDCacheAll(); -} - -void draw_assert(UNUSED OSThread *thread) { - crash_screen_draw_rect(25, 20, 270, 210); - - crash_screen_print(30, 25, "ASSERT PAGE"); - - if (__n64Assert_Filename != NULL) { - crash_screen_print(30, 45, "FILE: %s", __n64Assert_Filename); - crash_screen_print(30, 55, "LINE: %d", __n64Assert_LineNum); - crash_screen_print(30, 75, "MESSAGE:", __n64Assert_Message); - crash_screen_print_with_newlines(36, 85, 30, __n64Assert_Message); - } else { - crash_screen_print(30, 45, "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++; - updateBuffer = TRUE; - } - if (gPlayer1Controller->buttonPressed & (L_TRIG | Z_TRIG)) { - crashPage--; - updateBuffer = TRUE; - } - 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 (updateBuffer) { - crash_screen_draw_rect(25, 8, 270, 12); - crash_screen_print(30, 10, "Page:%02d L/Z: Left R: Right", crashPage); - switch (crashPage) { - 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(gCrashScreen.framebuffer); - 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; -} - -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); -extern struct SequenceQueueItem sBackgroundMusicQueue[6]; - -void thread2_crash_screen(UNUSED void *arg) { - OSMesg mesg; - OSThread *thread = NULL; - - osSetEventMesg(OS_EVENT_CPU_BREAK, &gCrashScreen.mesgQueue, (OSMesg) 1); - osSetEventMesg(OS_EVENT_FAULT, &gCrashScreen.mesgQueue, (OSMesg) 2); - while (TRUE) { - if (thread == NULL) { - osRecvMesg(&gCrashScreen.mesgQueue, &mesg, 1); - thread = get_crashed_thread(); - gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; - if (thread) { - if ((u32) map_data_init != MAP_PARSER_ADDRESS) { - map_data_init(); - } - gCrashScreen.thread.priority = 15; - stop_sounds_in_continuous_banks(); - stop_background_music(sBackgroundMusicQueue[0].seqId); - audio_signal_game_loop_tick(); - crash_screen_sleep(200); - play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); - audio_signal_game_loop_tick(); - crash_screen_sleep(200); - continue; - } - } else { - if (gControllerBits) { -#if ENABLE_RUMBLE - block_until_rumble_pak_free(); -#endif - osContStartReadDataEx(&gSIEventMesgQueue); - } - read_controller_inputs(THREAD_2_CRASH_SCREEN); - draw_crash_screen(thread); - } - } -} - -void crash_screen_init(void) { - gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; - gCrashScreen.width = SCREEN_WIDTH; - gCrashScreen.height = SCREEN_HEIGHT; - osCreateMesgQueue(&gCrashScreen.mesgQueue, &gCrashScreen.mesg, 1); - osCreateThread(&gCrashScreen.thread, THREAD_2_CRASH_SCREEN, thread2_crash_screen, NULL, - (u8 *) gCrashScreen.stack + sizeof(gCrashScreen.stack), - OS_PRIORITY_APPMAX - ); - osStartThread(&gCrashScreen.thread); -} - From 20e0a5c45e7196446499398aa6f00e457d57bcf3 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 23 Oct 2025 20:56:49 -0400 Subject: [PATCH 064/109] get the diffs --- src/debugger/crash_screen.c | 376 +++++++++++++++++++++++------------- 1 file changed, 237 insertions(+), 139 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index bf0bb88e0c..05457d163f 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -1,50 +1,63 @@ #include #include +#include #include #include +#include "config/config_debug.h" #include "buffers/framebuffers.h" #include "types.h" -#include "puppyprint.h" +#include "game/puppyprint.h" #include "audio/external.h" #include "farcall.h" -#include "game_init.h" -#include "main.h" -#include "debug.h" -#include "rumble_init.h" +#include "game/game_init.h" +#include "game/main.h" +#include "game/debug.h" +#include "game/rumble_init.h" +#include "game/printf.h" + +#include "crash_screen.h" +#include "map_parser.h" +#include "disasm.h" +#include "assert.h" +#include "stacktrace.h" #include "sm64.h" -#include "printf.h" - -#define X_KERNING 6 +extern u16 sRenderedFramebuffer; +extern struct SequenceQueueItem sBackgroundMusicQueue[6]; +extern void audio_signal_game_loop_tick(void); +extern void stop_sounds_in_continuous_banks(void); +extern void read_controller_inputs(s32 threadID); +extern char *strstr(char *, char *); -enum crashPages { - PAGE_CONTEXT, +static char *crashPageNames[] = { + [PAGE_SIMPLE] = "(Overview)", + [PAGE_CONTEXT] = "(Context)", #ifdef PUPPYPRINT_DEBUG - PAGE_LOG, + [PAGE_LOG] = "(Log)", #endif - PAGE_STACKTRACE, - PAGE_DISASM, - PAGE_ASSERTS, - PAGE_COUNT + [PAGE_STACKTRACE] = "(Stack Trace)", + [PAGE_DISASM] = "(Disassembly)", + [PAGE_ASSERTS] = "(Assert)", }; -u8 gCrashScreenCharToGlyph[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, -1, 41, -1, -1, -1, 43, -1, -1, 37, 38, -1, 42, - -1, 39, 44, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 36, -1, -1, -1, -1, 40, -1, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1, +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, }; -// A height of seven pixels for each Character * nine rows of characters + one row unused. -u32 gCrashScreenFont[7 * 9 + 1] = { +static u32 sCrashScreenFont[GLYPH_HEIGHT * FONT_ROWS * 2 + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; -u8 crashPage = 0; -u8 updateBuffer = TRUE; +static u8 crashPage = 0; +static u8 updateBuffer = TRUE; static char crashScreenBuf[0x200]; @@ -74,14 +87,9 @@ char *gFpcsrDesc[6] = { "Inexact operation", }; - - -extern u64 osClockRate; -extern far char *parse_map(u32 pc); -extern far void map_data_init(void); -extern far char *find_function_in_stack(u32 *sp); - -struct { +static u32 sProgramPosition = 0; +static u16 gCrashScreenTextColor = 0xFFFF; +static struct { OSThread thread; u64 stack[THREAD2_STACK / sizeof(u64)]; OSMesgQueue mesgQueue; @@ -91,6 +99,14 @@ struct { u16 height; } gCrashScreen; +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; @@ -98,8 +114,7 @@ void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { - // 0xe738 = 0b1110011100111000 - *ptr = ((*ptr & 0xe738) >> 2) | 1; + *ptr = 0x0001; ptr++; } ptr += gCrashScreen.width - w; @@ -113,18 +128,27 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { u32 rowMask; s32 i, j; - data = &gCrashScreenFont[glyph / 5 * 7]; + if (glyph > 0x7F) return; + + data = &sCrashScreenFont[((glyph&0xF)*GLYPH_HEIGHT * 2) + (glyph >= 64)]; + ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; - for (i = 0; i < 7; i++) { - bit = 0x80000000U >> ((glyph % 5) * 6); + u16 color = gCrashScreenTextColor; + + for (i = 0; i < GLYPH_HEIGHT; i++) { + bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); rowMask = *data++; + data ++; - for (j = 0; j < 6; j++) { - *ptr++ = (bit & rowMask) ? 0xffff : 1; + for (j = 0; j < (GLYPH_WIDTH); j++) { + if (bit & rowMask) { + *ptr = color; + } + ptr++; bit >>= 1; } - ptr += gCrashScreen.width - 6; + ptr += gCrashScreen.width - (GLYPH_WIDTH); } } @@ -152,7 +176,7 @@ void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const ch xOffset = xNewline; } - glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; if (*ptr == '\n') { y += 10; @@ -185,7 +209,7 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { ptr = crashScreenBuf; while (*ptr && size-- > 0) { - glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; if (glyph != 0xff) { crash_screen_draw_glyph(x, y, glyph); @@ -220,143 +244,215 @@ void crash_screen_print_fpcsr(u32 fpcsr) { s32 i; u32 bit = BIT(17); - crash_screen_print(30, 155, "FPCSR:%08XH", fpcsr); + crash_screen_print(100, 220, "FPCSR:%08XH", fpcsr); for (i = 0; i < 6; i++) { if (fpcsr & bit) { - crash_screen_print(132, 155, "(%s)", gFpcsrDesc[i]); + 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(25, 20, 270, 210); - crash_screen_print(30, 20, "THREAD:%d (%s)", thread->id, gCauseDesc[cause]); - crash_screen_print(30, 30, "PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); + 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(); - if ((u32)parse_map != MAP_PARSER_ADDRESS) { - char *fname = parse_map(tc->pc); - crash_screen_print(30, 40, "CRASH AT: %s", fname == NULL ? "UNKNOWN" : fname); - } - crash_screen_print(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); - crash_screen_print(30, 60, "A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); - crash_screen_print(30, 70, "A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); - crash_screen_print(30, 80, "T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); - crash_screen_print(30, 90, "T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); - crash_screen_print(30, 100, "S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); - crash_screen_print(30, 110, "S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); - crash_screen_print(30, 120, "S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); - crash_screen_print(30, 130, "T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); - crash_screen_print(30, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); +#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( 30, 170, 0, &tc->fp0.f.f_even); - crash_screen_print_float_reg(120, 170, 2, &tc->fp2.f.f_even); - crash_screen_print_float_reg(210, 170, 4, &tc->fp4.f.f_even); - crash_screen_print_float_reg( 30, 180, 6, &tc->fp6.f.f_even); - crash_screen_print_float_reg(120, 180, 8, &tc->fp8.f.f_even); - crash_screen_print_float_reg(210, 180, 10, &tc->fp10.f.f_even); - crash_screen_print_float_reg( 30, 190, 12, &tc->fp12.f.f_even); - crash_screen_print_float_reg(120, 190, 14, &tc->fp14.f.f_even); - crash_screen_print_float_reg(210, 190, 16, &tc->fp16.f.f_even); - crash_screen_print_float_reg( 30, 200, 18, &tc->fp18.f.f_even); - crash_screen_print_float_reg(120, 200, 20, &tc->fp20.f.f_even); - crash_screen_print_float_reg(210, 200, 22, &tc->fp22.f.f_even); - crash_screen_print_float_reg( 30, 210, 24, &tc->fp24.f.f_even); - crash_screen_print_float_reg(120, 210, 26, &tc->fp26.f.f_even); - crash_screen_print_float_reg(210, 210, 28, &tc->fp28.f.f_even); - crash_screen_print_float_reg( 30, 220, 30, &tc->fp30.f.f_even); + 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(25, 20, 270, 210); + 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(30, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); + crash_screen_print(LEFT_MARGIN, (LINE_HEIGHT - (i * 10)), consoleLogTable[i]); } #undef LINE_HEIGHT } #endif - -// prints any function pointers it finds in the stack format: -// SP address: function name void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; - u32 temp_sp = (tc->sp + 0x14); - crash_screen_draw_rect(25, 20, 270, 210); - crash_screen_print(30, 25, "STACK TRACE FROM %08X:", temp_sp); - if ((u32) parse_map == MAP_PARSER_ADDRESS) { - crash_screen_print(30, 35, "CURRFUNC: NONE"); - } else { - crash_screen_print(30, 35, "CURRFUNC: %s", parse_map(tc->pc)); - } + 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(); - for (int i = 0; i < 18; i++) { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - crash_screen_print(30, (45 + (i * 10)), "STACK TRACE DISABLED"); - break; - } else { - if ((u32) find_function_in_stack == MAP_PARSER_ADDRESS) { - return; - } + static u32 generated = 0; - char *fname = find_function_in_stack(&temp_sp); - if ((fname == NULL) || ((*(u32*)temp_sp & 0x80000000) == 0)) { - crash_screen_print(30, (45 + (i * 10)), "%08X: UNKNOWN", temp_sp); - } else { - crash_screen_print(30, (45 + (i * 10)), "%08X: %s", temp_sp, fname); - } - } + 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) } -extern char *insn_disasm(u32 insn, u32 isPC); -static u32 sProgramPosition = 0; void draw_disasm(OSThread *thread) { __OSThreadContext *tc = &thread->context; - // u32 insn = *(u32*)tc->pc; - crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_draw_rect(0, 20, 320, 240); if (sProgramPosition == 0) { sProgramPosition = (tc->pc - 36); } - crash_screen_print(30, 25, "DISASM %08X", sProgramPosition); + 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)); - u32 toDisasm = *(u32*)(addr); - crash_screen_print(30, (35 + (i * 10)), "%s", insn_disasm(toDisasm, (addr == tc->pc))); + 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(UNUSED OSThread *thread) { - crash_screen_draw_rect(25, 20, 270, 210); + crash_screen_draw_rect(0, 20, 320, 240); - crash_screen_print(30, 25, "ASSERT PAGE"); + crash_screen_print(LEFT_MARGIN, 25, "Assert"); if (__n64Assert_Filename != NULL) { - crash_screen_print(30, 45, "FILE: %s", __n64Assert_Filename); - crash_screen_print(30, 55, "LINE: %d", __n64Assert_LineNum); - crash_screen_print(30, 75, "MESSAGE:", __n64Assert_Message); - crash_screen_print_with_newlines(36, 85, 30, __n64Assert_Message); + 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(30, 45, "No failed assert to report."); + crash_screen_print(LEFT_MARGIN, 35, "No failed assert to report."); } osWritebackDCacheAll(); @@ -375,19 +471,24 @@ void draw_crash_screen(OSThread *thread) { 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 (gPlayer1Controller->buttonDown & D_CBUTTONS) { - sProgramPosition += 4; - updateBuffer = TRUE; - } - if (gPlayer1Controller->buttonDown & U_CBUTTONS) { - sProgramPosition -= 4; - 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)) { @@ -395,11 +496,13 @@ void draw_crash_screen(OSThread *thread) { } if (crashPage == 255) { crashPage = (PAGE_COUNT - 1); + if (crashPage == PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; } if (updateBuffer) { - crash_screen_draw_rect(25, 8, 270, 12); - crash_screen_print(30, 10, "Page:%02d L/Z: Left R: Right", crashPage); + 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; @@ -429,12 +532,6 @@ OSThread *get_crashed_thread(void) { return NULL; } -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); -extern struct SequenceQueueItem sBackgroundMusicQueue[6]; - void thread2_crash_screen(UNUSED void *arg) { OSMesg mesg; OSThread *thread = NULL; @@ -447,9 +544,6 @@ void thread2_crash_screen(UNUSED void *arg) { thread = get_crashed_thread(); gCrashScreen.framebuffer = (RGBA16 *) gFramebuffers[sRenderedFramebuffer]; if (thread) { - if ((u32) map_data_init != MAP_PARSER_ADDRESS) { - map_data_init(); - } gCrashScreen.thread.priority = 15; stop_sounds_in_continuous_banks(); stop_background_music(sBackgroundMusicQueue[0].seqId); @@ -458,6 +552,10 @@ void thread2_crash_screen(UNUSED void *arg) { play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); 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 { From 8797a8c4286c321f5dc60f1e5f948c46f85da643 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 10:38:30 -0500 Subject: [PATCH 065/109] start addressing feedback --- src/boot/deflate/decompress_template.h | 5 +++-- src/debugger/assert.h | 2 +- src/debugger/crash_screen.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/boot/deflate/decompress_template.h b/src/boot/deflate/decompress_template.h index 02e92ddc8a..4cac1946b4 100644 --- a/src/boot/deflate/decompress_template.h +++ b/src/boot/deflate/decompress_template.h @@ -249,7 +249,8 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, SAFETY_CHECK(i == num_litlen_syms + num_offset_syms); } else if (block_type == DEFLATE_BLOCKTYPE_UNCOMPRESSED) { - u16 len, nlen; + u16 len; + // u16 nlen; /* * Uncompressed block: copy 'len' bytes literally from the input @@ -274,7 +275,7 @@ FUNCNAME(struct libdeflate_decompressor * restrict d, SAFETY_CHECK(in_end - in_next >= 4); len = get_unaligned_le16(in_next); - nlen = get_unaligned_le16(in_next + 2); + // nlen = get_unaligned_le16(in_next + 2); in_next += 4; SAFETY_CHECK(len <= in_end - in_next); diff --git a/src/debugger/assert.h b/src/debugger/assert.h index c466c48f48..1b9b12f278 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -1,6 +1,6 @@ #pragma once -#define ASSERT_MESGBUF_SIZE 256 +#define ASSERT_MESGBUF_SIZE 512 #ifndef __ASSEMBLER__ diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 930a168420..89e7112519 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -6,7 +6,7 @@ #define GLYPH_WIDTH 8 #define GLYPH_HEIGHT 12 #define FONT_ROWS 16 -#define LEFT_MARGIN 10 // for crash screen prints +#define LEFT_MARGIN 16 // for crash screen prints enum CrashPages { PAGE_SIMPLE, From 4123c820f47528e784884e3e103f4fccf9a3e636 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 10:44:14 -0500 Subject: [PATCH 066/109] tool warnings (keep n64sym changes less involved so that we can pull in upstream if necessary) --- tools/Makefile | 1 + tools/common/binout.c | 8 +++++++- tools/common/binout.h | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/Makefile b/tools/Makefile index d51462c2f3..17bfc05cc6 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -68,6 +68,7 @@ n64cksum_CFLAGS := -DN64CKSUM_STANDALONE textconv_SOURCES := textconv.c utf8.c hashtable.c n64sym_SOURCES := n64sym.c +n64sym_CFLAGS := -Wno-unused-result patch_elf_32bit_SOURCES := patch_elf_32bit.c diff --git a/tools/common/binout.c b/tools/common/binout.c index 534666300a..b244370eac 100644 --- a/tools/common/binout.c +++ b/tools/common/binout.c @@ -108,7 +108,13 @@ struct placeholder_data *__placeholder_get_data(const char *name) } ptrdiff_t index = stbds_shgeti(placeholder_hash, name); if(index == -1) { - struct placeholder_data default_value = {-1, NULL}; + struct placeholder_data default_value = { + .offset = -1, + .pending_offsets_64 = NULL, + .pending_offsets_32 = NULL, + .pending_offsets_16 = NULL, + .pending_offsets_8 = NULL, + }; index = stbds_shlen(placeholder_hash); stbds_shput(placeholder_hash, name, default_value); } diff --git a/tools/common/binout.h b/tools/common/binout.h index 433177f76a..1da8cb386a 100644 --- a/tools/common/binout.h +++ b/tools/common/binout.h @@ -18,9 +18,9 @@ typeof(v) _v = (v); \ if (sizeof(type) < sizeof(_v)) { \ int64_t ext = (int64_t)_v >> (sizeof(type) * 8 - 1) >> 1; \ - if (ext != 0 && ext != (uint64_t)-1) { \ + if (ext != 0 && ext != -1) { \ fprintf(stderr, "fatal: truncating value %lld to %s (ext=%lld)\n", (long long)_v, #type, (long long)ext); \ - assert(ext == 0 || ext == (uint64_t)-1); \ + assert(ext == 0 || ext == -1); \ } \ } \ (type)_v; \ From 994c762a1071b6ec5dadda5b85c293c83dd3e5c7 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 10:57:52 -0500 Subject: [PATCH 067/109] assert fix --- src/debugger/crash_screen.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 05457d163f..57b4c35baa 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -156,11 +156,19 @@ 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, ...) { +/** + * crash_screen_print, but automatically places a newline + * if text is xNewline units away from the right edge of the screen. + * Also respects newline characters in the evaluated string. + * + * Returns the number of newlines that were in the string, or created. + */ +int crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const char *fmt, ...) { char *ptr; u32 glyph; s32 size; s32 xOffset = x; + int numNewlines = 0; va_list args; va_start(args, fmt); @@ -174,6 +182,7 @@ void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const ch if (xOffset >= SCREEN_WIDTH - (xNewline + X_KERNING)) { y += 10; xOffset = xNewline; + numNewlines += 1; } glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; @@ -182,6 +191,7 @@ void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const ch y += 10; xOffset = x; ptr++; + numNewlines += 1; continue; } else if (glyph != 0xff) { crash_screen_draw_glyph(xOffset, y, glyph); @@ -193,6 +203,8 @@ void crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const ch } va_end(args); + + return numNewlines; } void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { @@ -440,16 +452,25 @@ void draw_disasm(OSThread *thread) { void draw_assert(UNUSED OSThread *thread) { crash_screen_draw_rect(0, 20, 320, 240); - crash_screen_print(LEFT_MARGIN, 25, "Assert"); + set_text_color(0xFF, 0, 0); + crash_screen_print(LEFT_MARGIN, 25, "Assert Failed!"); + reset_text_color(); 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); + crash_screen_print(LEFT_MARGIN, 60, "Condition:"); + crash_screen_print(LEFT_MARGIN + 32, 70, "(%s)", __n64Assert_Condition); if (__n64Assert_MessageBuf[0] != 0) { - crash_screen_print(LEFT_MARGIN, 75, "Message:"); - crash_screen_print(LEFT_MARGIN, 85, " %s", __n64Assert_MessageBuf); + crash_screen_print(LEFT_MARGIN, 85, "Message:"); + UNUSED int numNewlines = + crash_screen_print_with_newlines( + LEFT_MARGIN + 32, + 95, + LEFT_MARGIN, + "%s", + __n64Assert_MessageBuf + ); } } else { crash_screen_print(LEFT_MARGIN, 35, "No failed assert to report."); From a458dde1b77a0503af3e8a259468c656380abf1f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 11:00:31 -0500 Subject: [PATCH 068/109] make the condition use the newline func too --- src/debugger/crash_screen.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 57b4c35baa..441e24efd1 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -460,13 +460,20 @@ void draw_assert(UNUSED OSThread *thread) { 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, 60, "Condition:"); - crash_screen_print(LEFT_MARGIN + 32, 70, "(%s)", __n64Assert_Condition); + int numNewlines = crash_screen_print_with_newlines( + LEFT_MARGIN + 32, + 70, + LEFT_MARGIN, + "(%s)", + __n64Assert_Condition + ); + if (__n64Assert_MessageBuf[0] != 0) { - crash_screen_print(LEFT_MARGIN, 85, "Message:"); - UNUSED int numNewlines = + crash_screen_print(LEFT_MARGIN, 85 + (numNewlines * GLYPH_HEIGHT), "Message:"); + UNUSED int _newlines = crash_screen_print_with_newlines( LEFT_MARGIN + 32, - 95, + 95 + (numNewlines * GLYPH_HEIGHT), LEFT_MARGIN, "%s", __n64Assert_MessageBuf From 3ae791005d1c73acea4896c27ae88fb06ebc9081 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 11:06:55 -0500 Subject: [PATCH 069/109] assert page colored --- src/debugger/crash_screen.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 441e24efd1..cd29063b36 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -459,7 +459,11 @@ void draw_assert(UNUSED OSThread *thread) { if (__n64Assert_Filename != NULL) { crash_screen_print(LEFT_MARGIN, 35, "File: %s", __n64Assert_Filename); crash_screen_print(LEFT_MARGIN, 45, "Line %d", __n64Assert_LineNum); + + // Print the assert condition that failed. + set_text_color(241, 196, 15); crash_screen_print(LEFT_MARGIN, 60, "Condition:"); + reset_text_color(); int numNewlines = crash_screen_print_with_newlines( LEFT_MARGIN + 32, 70, @@ -468,8 +472,11 @@ void draw_assert(UNUSED OSThread *thread) { __n64Assert_Condition ); + // Print the message, if assertf/aggressf/errorf were used. if (__n64Assert_MessageBuf[0] != 0) { + set_text_color(241, 196, 15); crash_screen_print(LEFT_MARGIN, 85 + (numNewlines * GLYPH_HEIGHT), "Message:"); + reset_text_color(); UNUSED int _newlines = crash_screen_print_with_newlines( LEFT_MARGIN + 32, From 00350b2a1f5a8405b3d9578cb5c6e389dd38cd5e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 11:10:54 -0500 Subject: [PATCH 070/109] now assert page is fully colored --- src/debugger/crash_screen.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index cd29063b36..98fd4a69b6 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -457,8 +457,18 @@ void draw_assert(UNUSED OSThread *thread) { reset_text_color(); if (__n64Assert_Filename != NULL) { - crash_screen_print(LEFT_MARGIN, 35, "File: %s", __n64Assert_Filename); - crash_screen_print(LEFT_MARGIN, 45, "Line %d", __n64Assert_LineNum); + set_text_color(241, 196, 15); + crash_screen_print(LEFT_MARGIN, 35, "File: "); + reset_text_color(); + // print this on the same line as `File: ` but to its right + crash_screen_print(LEFT_MARGIN + (6 * GLYPH_WIDTH), 35, "%s", __n64Assert_Filename); + + + set_text_color(241, 196, 15); + crash_screen_print(LEFT_MARGIN, 45, "Line: "); + reset_text_color(); + // print this on the same line as `Line: ` but to its right + crash_screen_print(LEFT_MARGIN + (6 * GLYPH_WIDTH), 45, "%d", __n64Assert_LineNum); // Print the assert condition that failed. set_text_color(241, 196, 15); From c4fa4f902d2de442b61814a54ce82b086cdfd24b Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 11:17:51 -0500 Subject: [PATCH 071/109] add string ... function --- src/debugger/crash_screen.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 98fd4a69b6..fc5dad1f3a 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -99,6 +99,15 @@ static struct { u16 height; } gCrashScreen; +static void crash_screen_truncate_string(char *str, u32 maxLen) { + if (strlen(__n64Assert_Filename) > maxLen) { + __n64Assert_Filename[maxLen - 3] = '.'; + __n64Assert_Filename[maxLen - 2] = '.'; + __n64Assert_Filename[maxLen - 1] = '.'; + __n64Assert_Filename[maxLen] = 0; + } +} + static void set_text_color(u32 r, u32 g, u32 b) { gCrashScreenTextColor = GPACK_RGBA5551(r, g, b, 255); } @@ -461,6 +470,7 @@ void draw_assert(UNUSED OSThread *thread) { crash_screen_print(LEFT_MARGIN, 35, "File: "); reset_text_color(); // print this on the same line as `File: ` but to its right + crash_screen_truncate_string(__n64Assert_Filename, 42); crash_screen_print(LEFT_MARGIN + (6 * GLYPH_WIDTH), 35, "%s", __n64Assert_Filename); From de6cf121ba7a1990c930d27fe14bbcca050b2ca8 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 4 Nov 2025 11:26:45 -0500 Subject: [PATCH 072/109] truncate a bit more --- src/debugger/crash_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index fc5dad1f3a..8c559560ef 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -470,7 +470,7 @@ void draw_assert(UNUSED OSThread *thread) { crash_screen_print(LEFT_MARGIN, 35, "File: "); reset_text_color(); // print this on the same line as `File: ` but to its right - crash_screen_truncate_string(__n64Assert_Filename, 42); + crash_screen_truncate_string(__n64Assert_Filename, 41); crash_screen_print(LEFT_MARGIN + (6 * GLYPH_WIDTH), 35, "%s", __n64Assert_Filename); From aa2b355ebcf7e14f92b6e19b8497f7bb3fbc7e3a Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 5 Nov 2025 20:57:06 -0500 Subject: [PATCH 073/109] add a margin to just about everything --- src/debugger/crash_screen.c | 211 ++++++++++++++++++++++++------------ src/debugger/crash_screen.h | 8 +- 2 files changed, 147 insertions(+), 72 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 8c559560ef..d470365e36 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -89,6 +89,8 @@ char *gFpcsrDesc[6] = { static u32 sProgramPosition = 0; static u16 gCrashScreenTextColor = 0xFFFF; +static u32 sCrashScreenPrintRow_Pixels = 0; +static u32 sCrashScreenPrintLnHeight_Pixels = GLYPH_HEIGHT; static struct { OSThread thread; u64 stack[THREAD2_STACK / sizeof(u64)]; @@ -99,12 +101,17 @@ static struct { u16 height; } gCrashScreen; -static void crash_screen_truncate_string(char *str, u32 maxLen) { - if (strlen(__n64Assert_Filename) > maxLen) { - __n64Assert_Filename[maxLen - 3] = '.'; - __n64Assert_Filename[maxLen - 2] = '.'; - __n64Assert_Filename[maxLen - 1] = '.'; - __n64Assert_Filename[maxLen] = 0; +/** + * Splits a path string by the containing folder and the name of the file itself. + */ +static void crash_screen_split_filepath(char *path, char **folder, char **filename) { + *folder = path; + + for (int i = strlen(path) - 1; i > 0; i--) { + if ((path[i] == '/') || (path[i] == '\\')) { + *filename = &path[i + 1]; + break; + } } } @@ -116,6 +123,22 @@ static void reset_text_color(void) { gCrashScreenTextColor = 0xFFFF; } +/** + * Sets the screen row, in pixels, at which the next print + * will be placed on the Y axis. + */ +static void crash_screen_set_print_top(u32 row) { + sCrashScreenPrintRow_Pixels = row; +} + +static void crash_screen_set_println_height(u32 height_pixels) { + sCrashScreenPrintLnHeight_Pixels = height_pixels; +} + +static void crash_screen_reset_println_height(void) { + sCrashScreenPrintLnHeight_Pixels = GLYPH_HEIGHT; +} + void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { u16 *ptr; s32 i, j; @@ -244,6 +267,37 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { va_end(args); } +void crash_screen_println(const char *fmt, ...) { + char *ptr; + u32 glyph; + s32 size; + s32 x = LEFT_MARGIN; + s32 y = sCrashScreenPrintRow_Pixels; + + 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); + sCrashScreenPrintRow_Pixels += sCrashScreenPrintLnHeight_Pixels; +} + void crash_screen_sleep(s32 ms) { u64 cycles = ms * 1000LL * osClockRate / 1000000ULL; osSetTime(0); @@ -265,7 +319,7 @@ void crash_screen_print_fpcsr(u32 fpcsr) { s32 i; u32 bit = BIT(17); - crash_screen_print(100, 220, "FPCSR:%08XH", fpcsr); + crash_screen_print(LEFT_MARGIN + 90, 220, "FPCSR:%08XH", fpcsr); for (i = 0; i < 6; i++) { if (fpcsr & bit) { crash_screen_print(222, 220, "(%s)", gFpcsrDesc[i]); @@ -278,81 +332,85 @@ void crash_screen_print_fpcsr(u32 fpcsr) { void draw_crash_overview(OSThread *thread, s32 cause) { __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); - crash_screen_print(LEFT_MARGIN, 20, "Thread %d (%s)", thread->id, gCauseDesc[cause]); + crash_screen_println("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); + crash_screen_println("Crash at: %s", info.func == NULL ? "Unknown" : info.func); if (info.line != -1) { - crash_screen_print(LEFT_MARGIN, 60, "File: %s", info.file); + crash_screen_println("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); + crash_screen_println("Line: %d", info.line); #endif // DEBUG_EXPORT_ALL_LINES } #endif // DEBUG_EXPORT_SYMBOLS - crash_screen_print(LEFT_MARGIN, 84, "Address: 0x%08X", tc->pc); + crash_screen_println("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); + crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + + crash_screen_println("Thread:%d (%s)", thread->id, gCauseDesc[cause]); + crash_screen_set_println_height(10); + crash_screen_println("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); + crash_screen_println("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); + crash_screen_println("AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, (u32) tc->v1); + crash_screen_println("A0:%08XH A1:%08XH A2:%08XH", (u32) tc->a0, (u32) tc->a1, (u32) tc->a2); + crash_screen_println("A3:%08XH T0:%08XH T1:%08XH", (u32) tc->a3, (u32) tc->t0, (u32) tc->t1); + crash_screen_println("T2:%08XH T3:%08XH T4:%08XH", (u32) tc->t2, (u32) tc->t3, (u32) tc->t4); + crash_screen_println("T5:%08XH T6:%08XH T7:%08XH", (u32) tc->t5, (u32) tc->t6, (u32) tc->t7); + crash_screen_println("S0:%08XH S1:%08XH S2:%08XH", (u32) tc->s0, (u32) tc->s1, (u32) tc->s2); + crash_screen_println("S3:%08XH S4:%08XH S5:%08XH", (u32) tc->s3, (u32) tc->s4, (u32) tc->s5); + crash_screen_println("S6:%08XH S7:%08XH T8:%08XH", (u32) tc->s6, (u32) tc->s7, (u32) tc->t8); + crash_screen_println("T9:%08XH GP:%08XH SP:%08XH", (u32) tc->t9, (u32) tc->gp, (u32) tc->sp); + crash_screen_println("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); + crash_screen_println("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); + crash_screen_print_float_reg(LEFT_MARGIN + 0, 170, 0, &tc->fp0.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 90, 170, 2, &tc->fp2.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 180, 170, 4, &tc->fp4.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 0, 180, 6, &tc->fp6.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 90, 180, 8, &tc->fp8.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 180, 180, 10, &tc->fp10.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 0, 190, 12, &tc->fp12.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 90, 190, 14, &tc->fp14.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 180, 190, 16, &tc->fp16.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 0, 200, 18, &tc->fp18.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 90, 200, 20, &tc->fp20.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 180, 200, 22, &tc->fp22.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 0, 210, 24, &tc->fp24.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 90, 210, 26, &tc->fp26.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 180, 210, 28, &tc->fp28.f.f_even); + crash_screen_print_float_reg(LEFT_MARGIN + 0, 220, 30, &tc->fp30.f.f_even); + + crash_screen_reset_println_height(); } #ifdef PUPPYPRINT_DEBUG void draw_crash_log(void) { s32 i; - crash_screen_draw_rect(0, 20, 320, 210); + crash_screen_draw_rect(0, 20, SCREEN_WIDTH, 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]); + crash_screen_println(consoleLogTable[i]); } #undef LINE_HEIGHT } @@ -361,18 +419,18 @@ void draw_crash_log(void) { void draw_stacktrace(OSThread *thread, UNUSED 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); + crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_println("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)); + crash_screen_println("%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); + crash_screen_println("%08X (%s:%d)", ra, info.func, info.line); osWritebackDCacheAll(); @@ -383,7 +441,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { stackTraceGenerated = TRUE; } for (u32 i = 0; i < generated; i++) { - crash_screen_print(LEFT_MARGIN, 55 + (i * 10), get_stack_entry(i)); + crash_screen_println(get_stack_entry(i)); } #else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) // simple stack trace @@ -399,11 +457,11 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { void draw_disasm(OSThread *thread) { __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); if (sProgramPosition == 0) { sProgramPosition = (tc->pc - 36); } - crash_screen_print(LEFT_MARGIN, 25, "Program Counter: %08X", sProgramPosition); + crash_screen_println("Program Counter: %08X", sProgramPosition); osWritebackDCacheAll(); int skiplines = 0; @@ -411,6 +469,8 @@ void draw_disasm(OSThread *thread) { int currline = 0; #endif // DEBUG_EXPORT_SYMBOLS + u32 basePositionY = sCrashScreenPrintRow_Pixels; + for (int i = 0; i < 19; i++) { u32 addr = (sProgramPosition + (i * 4)); @@ -418,7 +478,7 @@ void draw_disasm(OSThread *thread) { if (disasm[0] == 0) { - crash_screen_print(LEFT_MARGIN + 22, 35 + (skiplines * 10) + (i * 10), "%08X", addr); + crash_screen_print(LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%08X", addr); } else { #ifdef DEBUG_EXPORT_SYMBOLS symtable_info_t info = get_symbol_info(addr); @@ -426,7 +486,7 @@ void draw_disasm(OSThread *thread) { 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); + crash_screen_print(LEFT_MARGIN, basePositionY + (skiplines * 10) + (i * 10), "<%s:>", info.func); reset_text_color(); skiplines++; } @@ -436,7 +496,7 @@ void draw_disasm(OSThread *thread) { #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); + crash_screen_print(LEFT_MARGIN, basePositionY + (skiplines * 10) + (i * 10), "%d:", info.line); reset_text_color(); } #ifndef DEBUG_EXPORT_ALL_LINES @@ -449,7 +509,7 @@ void draw_disasm(OSThread *thread) { } else { reset_text_color(); } - crash_screen_print(LEFT_MARGIN + 22, 35 + (skiplines * 10) + (i * 10), "%s", disasm); + crash_screen_print(LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%s", disasm); } } @@ -459,55 +519,63 @@ void draw_disasm(OSThread *thread) { } void draw_assert(UNUSED OSThread *thread) { - crash_screen_draw_rect(0, 20, 320, 240); + crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_set_print_top(35); set_text_color(0xFF, 0, 0); - crash_screen_print(LEFT_MARGIN, 25, "Assert Failed!"); + crash_screen_println("Assert Failed!"); reset_text_color(); + if (__n64Assert_Filename != NULL) { set_text_color(241, 196, 15); - crash_screen_print(LEFT_MARGIN, 35, "File: "); + crash_screen_println("File: "); reset_text_color(); // print this on the same line as `File: ` but to its right - crash_screen_truncate_string(__n64Assert_Filename, 41); - crash_screen_print(LEFT_MARGIN + (6 * GLYPH_WIDTH), 35, "%s", __n64Assert_Filename); + char *foldername = NULL; + char *filename = NULL; + crash_screen_split_filepath(__n64Assert_Filename, &foldername, &filename); + if (filename) { + crash_screen_println(" %s", filename); + } set_text_color(241, 196, 15); - crash_screen_print(LEFT_MARGIN, 45, "Line: "); + crash_screen_println("Line: "); reset_text_color(); // print this on the same line as `Line: ` but to its right - crash_screen_print(LEFT_MARGIN + (6 * GLYPH_WIDTH), 45, "%d", __n64Assert_LineNum); + crash_screen_println(" %d", __n64Assert_LineNum); // Print the assert condition that failed. set_text_color(241, 196, 15); - crash_screen_print(LEFT_MARGIN, 60, "Condition:"); + crash_screen_println("Condition:"); reset_text_color(); int numNewlines = crash_screen_print_with_newlines( LEFT_MARGIN + 32, - 70, + sCrashScreenPrintRow_Pixels, LEFT_MARGIN, "(%s)", __n64Assert_Condition ); + sCrashScreenPrintRow_Pixels += ((numNewlines + 1) * GLYPH_HEIGHT); + // Print the message, if assertf/aggressf/errorf were used. if (__n64Assert_MessageBuf[0] != 0) { set_text_color(241, 196, 15); - crash_screen_print(LEFT_MARGIN, 85 + (numNewlines * GLYPH_HEIGHT), "Message:"); + crash_screen_println("Message:"); reset_text_color(); UNUSED int _newlines = crash_screen_print_with_newlines( LEFT_MARGIN + 32, - 95 + (numNewlines * GLYPH_HEIGHT), + sCrashScreenPrintRow_Pixels, LEFT_MARGIN, "%s", __n64Assert_MessageBuf ); } } else { - crash_screen_print(LEFT_MARGIN, 35, "No failed assert to report."); + crash_screen_println("No failed assert to report."); } osWritebackDCacheAll(); @@ -554,8 +622,9 @@ void draw_crash_screen(OSThread *thread) { 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]); + crash_screen_draw_rect(0, 0, SCREEN_WIDTH, RECT_BOUNDARY_Y); + crash_screen_set_print_top(TOP_MARGIN); + crash_screen_println("Page:%02d %-19s 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; diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 89e7112519..ade0da6920 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -6,7 +6,13 @@ #define GLYPH_WIDTH 8 #define GLYPH_HEIGHT 12 #define FONT_ROWS 16 -#define LEFT_MARGIN 16 // for crash screen prints + +// Margins for crash screen prints +#define LEFT_MARGIN 16 +#define TOP_MARGIN 16 + +// Where to stop drawing the Header rectangle and where to start drawing the Body rectangle. +#define RECT_BOUNDARY_Y TOP_MARGIN + GLYPH_HEIGHT + 5 enum CrashPages { PAGE_SIMPLE, From e7837f50010cda32ea564f46445ff8aceeb2edc3 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 5 Nov 2025 21:02:36 -0500 Subject: [PATCH 074/109] split out foldername in path split func --- src/debugger/crash_screen.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index d470365e36..fc00255d45 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -105,11 +105,15 @@ static struct { * Splits a path string by the containing folder and the name of the file itself. */ static void crash_screen_split_filepath(char *path, char **folder, char **filename) { - *folder = path; - + static char folderpath[256]; for (int i = strlen(path) - 1; i > 0; i--) { if ((path[i] == '/') || (path[i] == '\\')) { *filename = &path[i + 1]; + for (int copy = 0; copy <= i; copy++) { + folderpath[copy] = path[copy]; + } + folderpath[i + 1] = 0; + *folder = folderpath; break; } } @@ -528,14 +532,20 @@ void draw_assert(UNUSED OSThread *thread) { if (__n64Assert_Filename != NULL) { - set_text_color(241, 196, 15); - crash_screen_println("File: "); - reset_text_color(); // print this on the same line as `File: ` but to its right char *foldername = NULL; char *filename = NULL; crash_screen_split_filepath(__n64Assert_Filename, &foldername, &filename); + if (foldername) { + set_text_color(241, 196, 15); + crash_screen_println("Folder: "); + reset_text_color(); + crash_screen_println(" %s", foldername); + } if (filename) { + set_text_color(241, 196, 15); + crash_screen_println("File: "); + reset_text_color(); crash_screen_println(" %s", filename); } From d3bb8b6aa4aae5d455b79ec46134bf95635184f1 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 5 Nov 2025 21:31:03 -0500 Subject: [PATCH 075/109] handle (some) cache memes on ares --- src/debugger/crash_screen.c | 1 + src/debugger/symtable.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index fc00255d45..6c2a6d4ceb 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -186,6 +186,7 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { } ptr += gCrashScreen.width - (GLYPH_WIDTH); } + osWritebackDCacheAll(); } static char *write_to_buf(char *buffer, const char *data, size_t size) { diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c index 0ad11876a8..97cf3d37ee 100644 --- a/src/debugger/symtable.c +++ b/src/debugger/symtable.c @@ -34,6 +34,8 @@ static u32 headless_pi_status(void) { // end of code provided by Wiseguy void map_parser_dma(void *dst, void *src, size_t size) { + osWritebackDCacheAll(); + osInvalICache(dst, size); headless_dma((u32)src, dst, size); while (headless_pi_status() & PI_STATUS_IO_BUSY); } From fed332367d698991fec1285d132693fe4601d813 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 5 Nov 2025 21:38:18 -0500 Subject: [PATCH 076/109] remove debug print comment --- src/debugger/symtable.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c index 97cf3d37ee..ff0eb1521a 100644 --- a/src/debugger/symtable.c +++ b/src/debugger/symtable.c @@ -163,9 +163,6 @@ char *symt_string(symtable_header_t *symt, int sidx, int slen, char *buf, int si 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)), From 205fd759e5c10cf60fe1442b36c146b1b01f611b Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 5 Nov 2025 22:18:57 -0500 Subject: [PATCH 077/109] don't let disasm go out of bounds; fast scroll --- src/debugger/crash_screen.c | 14 +++++++++++--- src/debugger/stacktrace.c | 17 ----------------- src/debugger/stacktrace.h | 19 +++++++++++++++++++ src/debugger/symtable.c | 4 ++-- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 6c2a6d4ceb..2d6c5195c7 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -615,12 +615,20 @@ void draw_crash_screen(OSThread *thread) { } if (crashPage == PAGE_DISASM) { + u32 sNewProgramPosition = sProgramPosition; if (gPlayer1Controller->buttonDown & D_CBUTTONS) { - sProgramPosition += 4; - updateBuffer = TRUE; + sNewProgramPosition += 4; } if (gPlayer1Controller->buttonDown & U_CBUTTONS) { - sProgramPosition -= 4; + sNewProgramPosition -= 4; + } + + if (is_text_addr(sNewProgramPosition) && (sNewProgramPosition != sProgramPosition)) { + // Hold B to speed up scrolling + if (!(gPlayer1Controller->buttonDown & B_BUTTON)) { + crash_screen_sleep(30); + } + sProgramPosition = sNewProgramPosition; updateBuffer = TRUE; } } diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c index 5e07b2c375..d772a3dc1e 100644 --- a/src/debugger/stacktrace.c +++ b/src/debugger/stacktrace.c @@ -6,12 +6,9 @@ #include "map_parser.h" #include "symtable.h" -#include "segment_symbols.h" #include "stacktrace.h" #include "disasm.h" -extern void __osCleanupThread(); - #if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) static StackFrame stack[STACK_LINE_COUNT]; @@ -24,20 +21,6 @@ static u8 is_top_of_stack(u32 ra) { return (ra == ((u32)__osCleanupThread)); } -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++]; diff --git a/src/debugger/stacktrace.h b/src/debugger/stacktrace.h index 06e2cc50db..521f627485 100644 --- a/src/debugger/stacktrace.h +++ b/src/debugger/stacktrace.h @@ -2,6 +2,8 @@ #include +#include "segment_symbols.h" + typedef struct { u32 func; u32 offset; @@ -17,5 +19,22 @@ typedef struct { extern u32 stackTraceGenerated; +// libultra import +extern void __osCleanupThread(); + extern far u32 generate_stack(OSThread *); extern far char *get_stack_entry(u32 idx); + +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; +} diff --git a/src/debugger/symtable.c b/src/debugger/symtable.c index ff0eb1521a..5047799df5 100644 --- a/src/debugger/symtable.c +++ b/src/debugger/symtable.c @@ -35,9 +35,9 @@ static u32 headless_pi_status(void) { void map_parser_dma(void *dst, void *src, size_t size) { osWritebackDCacheAll(); - osInvalICache(dst, size); headless_dma((u32)src, dst, size); while (headless_pi_status() & PI_STATUS_IO_BUSY); + osInvalICache(dst, size); } /** @@ -143,7 +143,7 @@ char *symt_string(symtable_header_t *symt, int sidx, int slen, char *buf, int si map_parser_dma( func, (uintptr_t *)(SYMT_ROM + symt->strtab_off + sidx), - nbytes + size ); func[nbytes] = 0; From a30313233e056095d805ff9beafd6948bd519094 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 08:04:15 -0500 Subject: [PATCH 078/109] overview --- src/debugger/crash_screen.c | 58 ++++++++++++++++++++++++++++++------- tools/n64sym.c | 4 +-- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 2d6c5195c7..a924f6a7f1 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -88,6 +88,8 @@ char *gFpcsrDesc[6] = { }; static u32 sProgramPosition = 0; +static u32 sCrashScreenStackTraceCount = 0; + static u16 gCrashScreenTextColor = 0xFFFF; static u32 sCrashScreenPrintRow_Pixels = 0; static u32 sCrashScreenPrintLnHeight_Pixels = GLYPH_HEIGHT; @@ -101,6 +103,8 @@ static struct { u16 height; } gCrashScreen; + + /** * Splits a path string by the containing folder and the name of the file itself. */ @@ -339,14 +343,31 @@ void draw_crash_overview(OSThread *thread, s32 cause) { crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); - crash_screen_println("Thread %d (%s)", thread->id, gCauseDesc[cause]); + set_text_color(0xFF, 0, 0); + crash_screen_println("Game crashed!"); + reset_text_color(); + + crash_screen_println("Thread: %d", thread->id); + crash_screen_println("Cause: %s", gCauseDesc[cause]); #ifdef DEBUG_EXPORT_SYMBOLS symtable_info_t info = get_symbol_info(tc->pc); - crash_screen_println("Crash at: %s", info.func == NULL ? "Unknown" : info.func); if (info.line != -1) { - crash_screen_println("File: %s", info.file); + char *filename, *foldername; + crash_screen_split_filepath(info.file, &foldername, &filename); + if (foldername) { + set_text_color(241, 196, 15); + crash_screen_println("Folder: "); + reset_text_color(); + crash_screen_println(" %s", foldername); + } + if (filename) { + set_text_color(241, 196, 15); + crash_screen_println("File: "); + reset_text_color(); + crash_screen_println(" %s", filename); + } #ifdef DEBUG_EXPORT_ALL_LINES // This line only shows the correct value if every line is in the sym file crash_screen_println("Line: %d", info.line); @@ -354,7 +375,26 @@ void draw_crash_overview(OSThread *thread, s32 cause) { } #endif // DEBUG_EXPORT_SYMBOLS +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + if (stackTraceGenerated) { + set_text_color(241, 196, 15); + crash_screen_println("Stack Trace: "); + reset_text_color(); + + // print current func + crash_screen_println("%08X: %s", tc->pc, info.func == NULL ? "Unknown" : info.func); + // print last func + u32 ret_addr = tc->ra; + symtable_info_t ra_info = get_symbol_info(ret_addr); + crash_screen_println("%08X: %s:%d", ret_addr, ra_info.func == NULL ? "Unknown" : ra_info.func, ra_info.line); + // print up to 3 more + for (u32 i = 0; i < MIN(3, sCrashScreenStackTraceCount); i++) { + crash_screen_println(get_stack_entry(i)); + } + } +#else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) crash_screen_println("Address: 0x%08X", tc->pc); +#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) } void draw_crash_context(OSThread *thread, s32 cause) { @@ -439,13 +479,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { osWritebackDCacheAll(); - static u32 generated = 0; - - if (stackTraceGenerated == FALSE) { - generated = generate_stack(thread); - stackTraceGenerated = TRUE; - } - for (u32 i = 0; i < generated; i++) { + for (u32 i = 0; i < sCrashScreenStackTraceCount; i++) { crash_screen_println(get_stack_entry(i)); } #else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) @@ -695,6 +729,10 @@ void thread2_crash_screen(UNUSED void *arg) { play_sound(SOUND_MARIO_WAAAOOOW, gGlobalSoundSource); audio_signal_game_loop_tick(); crash_screen_sleep(200); + if (stackTraceGenerated == FALSE) { + sCrashScreenStackTraceCount = generate_stack(thread); + stackTraceGenerated = TRUE; + } // If an assert happened, go straight to that page if (thread->context.cause == EXC_SYSCALL) { crashPage = PAGE_ASSERTS; diff --git a/tools/n64sym.c b/tools/n64sym.c index 85e4750666..75d63b8488 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -173,9 +173,9 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) char *colon = strrchr(line_buf, ':'); char *file = strndup(line_buf, colon - line_buf); char *backup = file; - file = strstr(file, "/src/"); + file = strstr(file, "src/"); if (file == NULL) { - file = strstr(backup, "/asm/"); + file = strstr(backup, "asm/"); if (file == NULL) { file = backup; } From 198418108b5b456931fdcfc75d6d57cb0769a7a0 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 08:09:27 -0500 Subject: [PATCH 079/109] assert page with more --- src/debugger/crash_screen.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index a924f6a7f1..902eb51567 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -557,7 +557,8 @@ void draw_disasm(OSThread *thread) { osWritebackDCacheAll(); } -void draw_assert(UNUSED OSThread *thread) { +void draw_assert(OSThread *thread) { + __OSThreadContext *tc = &thread->context; crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); crash_screen_set_print_top(35); @@ -610,7 +611,7 @@ void draw_assert(UNUSED OSThread *thread) { set_text_color(241, 196, 15); crash_screen_println("Message:"); reset_text_color(); - UNUSED int _newlines = + numNewlines += crash_screen_print_with_newlines( LEFT_MARGIN + 32, sCrashScreenPrintRow_Pixels, @@ -619,6 +620,30 @@ void draw_assert(UNUSED OSThread *thread) { __n64Assert_MessageBuf ); } + sCrashScreenPrintRow_Pixels += ((numNewlines + 1) * GLYPH_HEIGHT); +#if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + if (stackTraceGenerated) { + set_text_color(241, 196, 15); + crash_screen_println("Stack Trace:"); + reset_text_color(); + + // Print last func (we know the current func is __n64Assert) + u32 ret_addr = tc->ra; + symtable_info_t ra_info = get_symbol_info(ret_addr); + crash_screen_println("%08X: %s:%d", ret_addr, ra_info.func == NULL ? "Unknown" : ra_info.func, ra_info.line); + // Print up to 3 more + for (u32 i = 0; i < MIN(3, sCrashScreenStackTraceCount); i++) { + crash_screen_println(get_stack_entry(i)); + } + } +#else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) + // Print address of last func (we know the current func is __n64Assert) + u32 ret_addr = tc->ra; + set_text_color(241, 196, 15); + crash_screen_println("Called From:"); + reset_text_color(); + crash_screen_println(" 0x%08X", ret_addr); +#endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) } else { crash_screen_println("No failed assert to report."); } From 858145982e67681b9ab327ea4de14d671f7a5c48 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 19:23:08 -0500 Subject: [PATCH 080/109] Add tab support --- src/debugger/crash_screen.c | 30 ++++++++++++++++++++++++------ src/debugger/crash_screen.h | 4 ++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 902eb51567..2cd0e3014c 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -234,6 +234,13 @@ int crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const cha ptr++; numNewlines += 1; continue; + } else if (*ptr == '\t') { + osSyncPrintf("TAB %d ->", xOffset); + xOffset += GLYPH_WIDTH * (TAB_WIDTH_CHARS - 1); + osSyncPrintf("%d\n", xOffset); + + ptr++; + continue; } else if (glyph != 0xff) { crash_screen_draw_glyph(xOffset, y, glyph); } @@ -262,6 +269,11 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { ptr = crashScreenBuf; while (*ptr && size-- > 0) { + if (*ptr == '\t') { + ptr++; + x += GLYPH_WIDTH * (TAB_WIDTH_CHARS - 1); + continue; + } glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; if (glyph != 0xff) { @@ -292,6 +304,12 @@ void crash_screen_println(const char *fmt, ...) { ptr = crashScreenBuf; while (*ptr && size-- > 0) { + if (*ptr == '\t') { + ptr++; + x += GLYPH_WIDTH * (TAB_WIDTH_CHARS - 1); + continue; + } + glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; if (glyph != 0xff) { @@ -360,13 +378,13 @@ void draw_crash_overview(OSThread *thread, s32 cause) { set_text_color(241, 196, 15); crash_screen_println("Folder: "); reset_text_color(); - crash_screen_println(" %s", foldername); + crash_screen_println("\t%s", foldername); } if (filename) { set_text_color(241, 196, 15); crash_screen_println("File: "); reset_text_color(); - crash_screen_println(" %s", filename); + crash_screen_println("\t%s", filename); } #ifdef DEBUG_EXPORT_ALL_LINES // This line only shows the correct value if every line is in the sym file @@ -576,13 +594,13 @@ void draw_assert(OSThread *thread) { set_text_color(241, 196, 15); crash_screen_println("Folder: "); reset_text_color(); - crash_screen_println(" %s", foldername); + crash_screen_println("\t%s", foldername); } if (filename) { set_text_color(241, 196, 15); crash_screen_println("File: "); reset_text_color(); - crash_screen_println(" %s", filename); + crash_screen_println("\t%s", filename); } @@ -590,7 +608,7 @@ void draw_assert(OSThread *thread) { crash_screen_println("Line: "); reset_text_color(); // print this on the same line as `Line: ` but to its right - crash_screen_println(" %d", __n64Assert_LineNum); + crash_screen_println("\t%d", __n64Assert_LineNum); // Print the assert condition that failed. set_text_color(241, 196, 15); @@ -642,7 +660,7 @@ void draw_assert(OSThread *thread) { set_text_color(241, 196, 15); crash_screen_println("Called From:"); reset_text_color(); - crash_screen_println(" 0x%08X", ret_addr); + crash_screen_println("\t0x%08X", ret_addr); #endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) } else { crash_screen_println("No failed assert to report."); diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index ade0da6920..9292ec4b58 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -11,6 +11,10 @@ #define LEFT_MARGIN 16 #define TOP_MARGIN 16 +// Tab width ('\t') for crash screen prints, in characters. +// Note that we only treat tabs as a wide space. +#define TAB_WIDTH_CHARS 4 + // Where to stop drawing the Header rectangle and where to start drawing the Body rectangle. #define RECT_BOUNDARY_Y TOP_MARGIN + GLYPH_HEIGHT + 5 From c132532512a1e7ada3330f51cef83a00dda89d95 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 19:56:53 -0500 Subject: [PATCH 081/109] bring back the truncation --- src/debugger/crash_screen.c | 73 +++++++++++++++---------------------- src/debugger/crash_screen.h | 5 +++ 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 2cd0e3014c..6209df4ad9 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -108,19 +108,19 @@ static struct { /** * Splits a path string by the containing folder and the name of the file itself. */ -static void crash_screen_split_filepath(char *path, char **folder, char **filename) { - static char folderpath[256]; - for (int i = strlen(path) - 1; i > 0; i--) { - if ((path[i] == '/') || (path[i] == '\\')) { - *filename = &path[i + 1]; - for (int copy = 0; copy <= i; copy++) { - folderpath[copy] = path[copy]; - } - folderpath[i + 1] = 0; - *folder = folderpath; - break; - } +static char *crash_screen_ellide_string(char *str, u32 truncateLength) { + u32 string_length = strlen(str); + + if (truncateLength >= string_length) { + return str; } + + str[string_length - truncateLength - 4] = '('; + str[string_length - truncateLength - 3] = '.'; + str[string_length - truncateLength - 2] = '.'; + str[string_length - truncateLength - 1] = '.'; + str[string_length - truncateLength - 0] = ')'; + return &str[string_length - truncateLength - 4]; } static void set_text_color(u32 r, u32 g, u32 b) { @@ -372,24 +372,19 @@ void draw_crash_overview(OSThread *thread, s32 cause) { symtable_info_t info = get_symbol_info(tc->pc); if (info.line != -1) { - char *filename, *foldername; - crash_screen_split_filepath(info.file, &foldername, &filename); - if (foldername) { - set_text_color(241, 196, 15); - crash_screen_println("Folder: "); - reset_text_color(); - crash_screen_println("\t%s", foldername); - } - if (filename) { - set_text_color(241, 196, 15); - crash_screen_println("File: "); - reset_text_color(); - crash_screen_println("\t%s", filename); - } + char file_line[MAX_PATH]; + + set_text_color(241, 196, 15); #ifdef DEBUG_EXPORT_ALL_LINES - // This line only shows the correct value if every line is in the sym file - crash_screen_println("Line: %d", info.line); + sprintf(file_line, "%s:%d", info.file, info.line); + crash_screen_println("File/Line: "); +#else // DEBUG_EXPORT_ALL_LINES + sprintf(file_line, "%s", info.file); + crash_screen_println("File: "); #endif // DEBUG_EXPORT_ALL_LINES + + reset_text_color(); + crash_screen_println("%s", crash_screen_ellide_string(file_line, ELLISION_LENGTH)); } #endif // DEBUG_EXPORT_SYMBOLS @@ -587,22 +582,12 @@ void draw_assert(OSThread *thread) { if (__n64Assert_Filename != NULL) { // print this on the same line as `File: ` but to its right - char *foldername = NULL; - char *filename = NULL; - crash_screen_split_filepath(__n64Assert_Filename, &foldername, &filename); - if (foldername) { - set_text_color(241, 196, 15); - crash_screen_println("Folder: "); - reset_text_color(); - crash_screen_println("\t%s", foldername); - } - if (filename) { - set_text_color(241, 196, 15); - crash_screen_println("File: "); - reset_text_color(); - crash_screen_println("\t%s", filename); - } - + char file_line[MAX_PATH]; + sprintf(file_line, "%s:%d", __n64Assert_Filename, __n64Assert_LineNum); + set_text_color(241, 196, 15); + crash_screen_println("File/Line: "); + reset_text_color(); + crash_screen_println("%s", crash_screen_ellide_string(file_line, ELLISION_LENGTH)); set_text_color(241, 196, 15); crash_screen_println("Line: "); diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 9292ec4b58..ee2e47166c 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -1,6 +1,8 @@ #ifndef CRASH_SCREEN_H #define CRASH_SCREEN_H +#define MAX_PATH 256 + // Configurable Defines #define X_KERNING 6 #define GLYPH_WIDTH 8 @@ -18,6 +20,9 @@ // Where to stop drawing the Header rectangle and where to start drawing the Body rectangle. #define RECT_BOUNDARY_Y TOP_MARGIN + GLYPH_HEIGHT + 5 +// Max length of string before prepending a "(...)" +#define ELLISION_LENGTH 45 + enum CrashPages { PAGE_SIMPLE, PAGE_CONTEXT, From 55a61bf0228600391180cf0c3f323d167c59356f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 20:02:38 -0500 Subject: [PATCH 082/109] ellide in disasm view too --- src/debugger/crash_screen.c | 2 +- src/debugger/crash_screen.h | 4 ++++ src/debugger/disasm.c | 17 ++++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 6209df4ad9..ebd82b445e 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -108,7 +108,7 @@ static struct { /** * Splits a path string by the containing folder and the name of the file itself. */ -static char *crash_screen_ellide_string(char *str, u32 truncateLength) { +char *crash_screen_ellide_string(char *str, u32 truncateLength) { u32 string_length = strlen(str); if (truncateLength >= string_length) { diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index ee2e47166c..3e2c0118f6 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -35,6 +35,10 @@ enum CrashPages { PAGE_COUNT }; +// Exports for the debugger/ system +char *crash_screen_ellide_string(char *str, u32 truncateLength); + +// Exports for the rest of the game void crash_screen_init(void); #endif // CRASH_SCREEN_H diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c index cf2dd21ee5..bbcab8ba2e 100644 --- a/src/debugger/disasm.c +++ b/src/debugger/disasm.c @@ -4,6 +4,8 @@ #include "sm64.h" #include "macros.h" #include "farcall.h" + +#include "crash_screen.h" #include "disasm.h" #include "map_parser.h" @@ -291,18 +293,19 @@ char *insn_disasm(InsnData *addr) { insn.i.immediate ); break; break; - case PARAM_JAL: + case PARAM_JAL: { target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); #ifdef DEBUG_EXPORT_SYMBOLS - strp += sprintf(strp, "%-9s %s(%08X)", insn_db[i].name, - parse_map(target, FALSE), target - ); + char symBuffer[MAX_PATH]; + sprintf(symBuffer, "%s(%08X)", parse_map(target, FALSE), target); + strp += sprintf(strp, "%-9s %s", insn_db[i].name, crash_screen_ellide_string(symBuffer, 30)); #else - strp += sprintf(strp, "%-9s %08X", insn_db[i].name, - target - ); + strp += sprintf(strp, "%-9s %08X", insn_db[i].name, + target + ); #endif // DEBUG_EXPORT_SYMBOLS break; + } case PARAM_JUMP: target = 0x80000000 | (insn.d & 0x03FFFFFF); strp += sprintf(strp, "%-9s %08X", insn_db[i].name, From 1b683ed2aef08d5170b55099c94a93acf1b33e32 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 20:16:55 -0500 Subject: [PATCH 083/109] dont print file/line and then line --- src/debugger/crash_screen.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index ebd82b445e..ac95f79f47 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -589,12 +589,6 @@ void draw_assert(OSThread *thread) { reset_text_color(); crash_screen_println("%s", crash_screen_ellide_string(file_line, ELLISION_LENGTH)); - set_text_color(241, 196, 15); - crash_screen_println("Line: "); - reset_text_color(); - // print this on the same line as `Line: ` but to its right - crash_screen_println("\t%d", __n64Assert_LineNum); - // Print the assert condition that failed. set_text_color(241, 196, 15); crash_screen_println("Condition:"); From 30a74dcffa58ca4e8d1bfce49a7fdde14d563eb9 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 20:33:50 -0500 Subject: [PATCH 084/109] stuff on same line as descriptor --- src/debugger/crash_screen.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index ac95f79f47..32cab6f70d 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -585,39 +585,46 @@ void draw_assert(OSThread *thread) { char file_line[MAX_PATH]; sprintf(file_line, "%s:%d", __n64Assert_Filename, __n64Assert_LineNum); set_text_color(241, 196, 15); - crash_screen_println("File/Line: "); + crash_screen_println("File/Line:"); reset_text_color(); - crash_screen_println("%s", crash_screen_ellide_string(file_line, ELLISION_LENGTH)); + int numNewlines = crash_screen_print_with_newlines( + LEFT_MARGIN + (GLYPH_WIDTH * 8), + sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, + LEFT_MARGIN, + file_line + ); + + sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); // Print the assert condition that failed. set_text_color(241, 196, 15); crash_screen_println("Condition:"); reset_text_color(); - int numNewlines = crash_screen_print_with_newlines( - LEFT_MARGIN + 32, - sCrashScreenPrintRow_Pixels, + numNewlines = crash_screen_print_with_newlines( + LEFT_MARGIN + (GLYPH_WIDTH * 8), + sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, LEFT_MARGIN, "(%s)", __n64Assert_Condition ); - sCrashScreenPrintRow_Pixels += ((numNewlines + 1) * GLYPH_HEIGHT); + sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); // Print the message, if assertf/aggressf/errorf were used. if (__n64Assert_MessageBuf[0] != 0) { set_text_color(241, 196, 15); crash_screen_println("Message:"); reset_text_color(); - numNewlines += + numNewlines = crash_screen_print_with_newlines( - LEFT_MARGIN + 32, - sCrashScreenPrintRow_Pixels, + LEFT_MARGIN + (GLYPH_WIDTH * 7), + sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, LEFT_MARGIN, "%s", __n64Assert_MessageBuf ); } - sCrashScreenPrintRow_Pixels += ((numNewlines + 1) * GLYPH_HEIGHT); + sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); #if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) if (stackTraceGenerated) { set_text_color(241, 196, 15); From 9106c47ae623be312d529550455e42004ff55361 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 20:37:41 -0500 Subject: [PATCH 085/109] and the overview screen --- src/debugger/crash_screen.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 32cab6f70d..fa15d7c483 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -359,6 +359,8 @@ void crash_screen_print_fpcsr(u32 fpcsr) { void draw_crash_overview(OSThread *thread, s32 cause) { __OSThreadContext *tc = &thread->context; + int numNewlines = 0; + crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); set_text_color(0xFF, 0, 0); @@ -377,14 +379,19 @@ void draw_crash_overview(OSThread *thread, s32 cause) { set_text_color(241, 196, 15); #ifdef DEBUG_EXPORT_ALL_LINES sprintf(file_line, "%s:%d", info.file, info.line); - crash_screen_println("File/Line: "); #else // DEBUG_EXPORT_ALL_LINES sprintf(file_line, "%s", info.file); - crash_screen_println("File: "); #endif // DEBUG_EXPORT_ALL_LINES - + crash_screen_println("File: "); reset_text_color(); - crash_screen_println("%s", crash_screen_ellide_string(file_line, ELLISION_LENGTH)); + + numNewlines = crash_screen_print_with_newlines( + LEFT_MARGIN + (GLYPH_WIDTH * 5), + sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, + LEFT_MARGIN, + file_line + ); + sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); } #endif // DEBUG_EXPORT_SYMBOLS From 021f812f199da2431c2beed72beb3b18965556c1 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 21:04:50 -0500 Subject: [PATCH 086/109] manual edit to overview page --- src/debugger/crash_screen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index fa15d7c483..2a90d4f1be 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -382,6 +382,7 @@ void draw_crash_overview(OSThread *thread, s32 cause) { #else // DEBUG_EXPORT_ALL_LINES sprintf(file_line, "%s", info.file); #endif // DEBUG_EXPORT_ALL_LINES + sCrashScreenPrintRow_Pixels += 5; crash_screen_println("File: "); reset_text_color(); From d9282e7de9b9d26ecc00683178522081918d5458 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 21:34:41 -0500 Subject: [PATCH 087/109] allow DEBUG_EXPORT_ALL_LINES to work again --- tools/n64sym.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/n64sym.c b/tools/n64sym.c index 75d63b8488..1d01256cc5 100644 --- a/tools/n64sym.c +++ b/tools/n64sym.c @@ -130,7 +130,7 @@ void symbol_add(const char *elf, uint32_t addr, bool is_func) cmd_addr[i++] = elf; if (subprocess_create(cmd_addr, subprocess_option_search_user_path | subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { - fprintf(stderr, "Error: cannot run: %s\n", addrbin); + fprintf(stderr, "[symbol_add] Error: cannot run: %s\n", addrbin); exit(1); } addr2line_w = subprocess_stdin(&subp); @@ -235,8 +235,8 @@ void address_add(const char *elf, uint32_t addr) { cmd_addr[i++] = "--exe"; cmd_addr[i++] = elf; - if (subprocess_create(cmd_addr, subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { - fprintf(stderr, "Error: cannot run: %s\n", addrbin); + if (subprocess_create(cmd_addr, subprocess_option_search_user_path | subprocess_option_inherit_environment | subprocess_option_no_window, &subp) != 0) { + fprintf(stderr, "[address_add] Error: cannot run: %s\n", addrbin); exit(1); } addr2line_w = subprocess_stdin(&subp); @@ -324,7 +324,7 @@ bool elf_find_callsites(const char *elf) verbose("Running: %s\n", cmd); FILE *disasm = popen(cmd, "r"); if (!disasm) { - fprintf(stderr, "Error: cannot run: %s\n", cmd); + fprintf(stderr, "[elf_find_callsites] Error: cannot run: %s\n", cmd); exit(1); } From 70eacc02081f5de58a5d9d5c968d213a4632b3a1 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 21:37:01 -0500 Subject: [PATCH 088/109] color overview --- src/debugger/crash_screen.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 2a90d4f1be..3879a9eda7 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -367,8 +367,13 @@ void draw_crash_overview(OSThread *thread, s32 cause) { crash_screen_println("Game crashed!"); reset_text_color(); - crash_screen_println("Thread: %d", thread->id); - crash_screen_println("Cause: %s", gCauseDesc[cause]); + set_text_color(241, 196, 15); + crash_screen_println("Thread:"); + crash_screen_println("Cause:"); + reset_text_color(); + sCrashScreenPrintRow_Pixels -= (2 * GLYPH_HEIGHT); + crash_screen_println(" %d", thread->id); + crash_screen_println(" %s", gCauseDesc[cause]); #ifdef DEBUG_EXPORT_SYMBOLS symtable_info_t info = get_symbol_info(tc->pc); From dd6c2f789eca17e348aa9d76d38e4e451d17ef26 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Thu, 6 Nov 2025 21:39:01 -0500 Subject: [PATCH 089/109] we dont use this define anymore --- src/debugger/crash_screen.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 3e2c0118f6..4ca155c55d 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -20,9 +20,6 @@ // Where to stop drawing the Header rectangle and where to start drawing the Body rectangle. #define RECT_BOUNDARY_Y TOP_MARGIN + GLYPH_HEIGHT + 5 -// Max length of string before prepending a "(...)" -#define ELLISION_LENGTH 45 - enum CrashPages { PAGE_SIMPLE, PAGE_CONTEXT, From 5b0462dfae9ff1223280b0c1df747c30cd293d07 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 18:00:44 -0500 Subject: [PATCH 090/109] feed1 --- src/debugger/assert.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/debugger/assert.h b/src/debugger/assert.h index 1b9b12f278..fabde00c3c 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -45,9 +45,7 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *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 + * Will cause a crash if `cond` is not true, and DEBUG is defined. */ #ifdef DEBUG_ASSERTIONS #define assertf(cond, ...) do {\ From a0c167f2760cf7f9c8cb34621bc28ad97813388f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 18:01:50 -0500 Subject: [PATCH 091/109] feed2 --- src/debugger/assert.h | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/debugger/assert.h b/src/debugger/assert.h index fabde00c3c..a1019cee82 100644 --- a/src/debugger/assert.h +++ b/src/debugger/assert.h @@ -12,7 +12,7 @@ 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 + * Will always halt with your message of choice */ #define errorf(message, ...) { \ sprintf(__n64Assert_MessageBuf, message ## __VA_ARGS__); \ @@ -29,7 +29,7 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); #define __assert_wrapper(cond) __n64Assert(__FILE__, __LINE__, (cond)) /** - * `aggress` and `aggressf` will always cause a crash if `cond` is not true (handle with care) + * `aggress` and `aggressf` will always halt if `cond` is not true (handle with care) */ #define aggressf(cond, ...) do {\ if ((cond) == FALSE) { \ @@ -45,28 +45,24 @@ extern void __n64Assert(char *fileName, u32 lineNum, char *cond); } while (0); /** - * Will cause a crash if `cond` is not true, and DEBUG is defined. + * Asserts will halt the game if `cond` is not true, and DEBUG is defined. */ #ifdef DEBUG_ASSERTIONS -#define assertf(cond, ...) do {\ +#define assert(cond) do {\ if ((cond) == FALSE) { \ - sprintf(__n64Assert_MessageBuf, __VA_ARGS__); \ + __n64Assert_MessageBuf[0] = 0; \ __assert_wrapper(#cond); \ } \ } while (0); -#else -#define assertf(cond, ...) -#endif - -#ifdef DEBUG_ASSERTIONS -#define assert(cond) do {\ +#define assertf(cond, ...) do {\ if ((cond) == FALSE) { \ - __n64Assert_MessageBuf[0] = 0; \ + sprintf(__n64Assert_MessageBuf, __VA_ARGS__); \ __assert_wrapper(#cond); \ } \ } while (0); -#else +#else // DEBUG_ASSERTIONS #define assert(cond) -#endif +#define assertf(cond, ...) +#endif // DEBUG_ASSERTIONS #endif // ASSEMBLER From d8a133131024fb849e992e6ca9b6ed5df4fdfa90 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 18:09:36 -0500 Subject: [PATCH 092/109] page_ -> CRASH_SCREEN_PAGE_ --- src/debugger/crash_screen.c | 39 +++++++++++++++++++------------------ src/debugger/crash_screen.h | 14 ++++++------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 3879a9eda7..4a3ad727a9 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -31,14 +31,14 @@ extern void read_controller_inputs(s32 threadID); extern char *strstr(char *, char *); static char *crashPageNames[] = { - [PAGE_SIMPLE] = "(Overview)", - [PAGE_CONTEXT] = "(Context)", + [CRASH_SCREEN_PAGE_SIMPLE] = "(Overview)", + [CRASH_SCREEN_PAGE_CONTEXT] = "(Context)", + [CRASH_SCREEN_PAGE_STACKTRACE] = "(Stack Trace)", #ifdef PUPPYPRINT_DEBUG - [PAGE_LOG] = "(Log)", + [CRASH_SCREEN_PAGE_LOG] = "(Log)", #endif - [PAGE_STACKTRACE] = "(Stack Trace)", - [PAGE_DISASM] = "(Disassembly)", - [PAGE_ASSERTS] = "(Assert)", + [CRASH_SCREEN_PAGE_DISASM] = "(Disassembly)", + [CRASH_SCREEN_PAGE_ASSERTS] = "(Assert)", }; static u8 sCrashScreenCharToGlyph[128] = { @@ -681,16 +681,16 @@ void draw_crash_screen(OSThread *thread) { if (gPlayer1Controller->buttonPressed & R_TRIG) { crashPage++; - if (crashPage == PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage++; + if (crashPage == CRASH_SCREEN_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--; + if (crashPage == CRASH_SCREEN_PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; updateBuffer = TRUE; } - if (crashPage == PAGE_DISASM) { + if (crashPage == CRASH_SCREEN_PAGE_DISASM) { u32 sNewProgramPosition = sProgramPosition; if (gPlayer1Controller->buttonDown & D_CBUTTONS) { sNewProgramPosition += 4; @@ -709,26 +709,27 @@ void draw_crash_screen(OSThread *thread) { } } - if ((crashPage >= PAGE_COUNT) && (crashPage != 255)) { + if ((crashPage >= CRASH_SCREEN_PAGE_COUNT) && (crashPage != 255)) { crashPage = 0; } if (crashPage == 255) { - crashPage = (PAGE_COUNT - 1); - if (crashPage == PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; + crashPage = (CRASH_SCREEN_PAGE_COUNT - 1); + // Do not navigate to the assert page if an assert didn't happen + if (crashPage == CRASH_SCREEN_PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; } if (updateBuffer) { crash_screen_draw_rect(0, 0, SCREEN_WIDTH, RECT_BOUNDARY_Y); crash_screen_set_print_top(TOP_MARGIN); crash_screen_println("Page:%02d %-19s 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; + case CRASH_SCREEN_PAGE_SIMPLE: draw_crash_overview(thread, cause); break; + case CRASH_SCREEN_PAGE_CONTEXT: draw_crash_context(thread, cause); break; #ifdef PUPPYPRINT_DEBUG - case PAGE_LOG: draw_crash_log(); break; + case CRASH_SCREEN_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; + case CRASH_SCREEN_PAGE_STACKTRACE: draw_stacktrace(thread, cause); break; + case CRASH_SCREEN_PAGE_DISASM: draw_disasm(thread); break; + case CRASH_SCREEN_PAGE_ASSERTS: draw_assert(thread); break; } osWritebackDCacheAll(); @@ -777,7 +778,7 @@ void thread2_crash_screen(UNUSED void *arg) { } // If an assert happened, go straight to that page if (thread->context.cause == EXC_SYSCALL) { - crashPage = PAGE_ASSERTS; + crashPage = CRASH_SCREEN_PAGE_ASSERTS; } continue; } diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 4ca155c55d..1001dc1464 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -21,15 +21,15 @@ #define RECT_BOUNDARY_Y TOP_MARGIN + GLYPH_HEIGHT + 5 enum CrashPages { - PAGE_SIMPLE, - PAGE_CONTEXT, + CRASH_SCREEN_PAGE_SIMPLE, + CRASH_SCREEN_PAGE_CONTEXT, + CRASH_SCREEN_PAGE_STACKTRACE, #ifdef PUPPYPRINT_DEBUG - PAGE_LOG, + CRASH_SCREEN_PAGE_LOG, #endif - PAGE_STACKTRACE, - PAGE_DISASM, - PAGE_ASSERTS, - PAGE_COUNT + CRASH_SCREEN_PAGE_DISASM, + CRASH_SCREEN_PAGE_ASSERTS, + CRASH_SCREEN_PAGE_COUNT }; // Exports for the debugger/ system From fe1e595f8a9a4b39f6f030a7b7706fc522119111 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 18:43:15 -0500 Subject: [PATCH 093/109] feedback so far --- src/debugger/crash_screen.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 4a3ad727a9..dcb9ca9aa2 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -167,10 +167,16 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { u32 bit; u32 rowMask; s32 i, j; + u32 startOfGlyphData = 0; if (glyph > 0x7F) return; - data = &sCrashScreenFont[((glyph&0xF)*GLYPH_HEIGHT * 2) + (glyph >= 64)]; + startOfGlyphData = (glyph & 0xF) * GLYPH_HEIGHT * 2; + if (glyph >= 64) { + startOfGlyphData++; + } + + data = &sCrashScreenFont[startOfGlyphData]; ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; @@ -179,7 +185,7 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { for (i = 0; i < GLYPH_HEIGHT; i++) { bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); rowMask = *data++; - data ++; + data++; for (j = 0; j < (GLYPH_WIDTH); j++) { if (bit & rowMask) { From 4f38e4992687f254973a1cd05af637445c148b99 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 18:44:07 -0500 Subject: [PATCH 094/109] indent --- src/debugger/crash_screen.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index dcb9ca9aa2..83c9349c8f 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -660,12 +660,12 @@ void draw_assert(OSThread *thread) { } } #else // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) - // Print address of last func (we know the current func is __n64Assert) - u32 ret_addr = tc->ra; - set_text_color(241, 196, 15); - crash_screen_println("Called From:"); - reset_text_color(); - crash_screen_println("\t0x%08X", ret_addr); + // Print address of last func (we know the current func is __n64Assert) + u32 ret_addr = tc->ra; + set_text_color(241, 196, 15); + crash_screen_println("Called From:"); + reset_text_color(); + crash_screen_println("\t0x%08X", ret_addr); #endif // defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) } else { crash_screen_println("No failed assert to report."); From 1c9cd99168c78da9a36396d063b2700d8bd3f9db Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 19:03:37 -0500 Subject: [PATCH 095/109] rename crashscreen.h defs --- src/debugger/crash_screen.c | 128 ++++++++++++++++++------------------ src/debugger/crash_screen.h | 18 ++--- src/debugger/disasm.c | 2 +- 3 files changed, 74 insertions(+), 74 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 83c9349c8f..4e9e1a2bd6 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -52,7 +52,7 @@ static u8 sCrashScreenCharToGlyph[128] = { 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] = { +static u32 sCrashScreenFont[CRASH_SCREEN_GLYPH_HEIGHT * CRASH_SCREEN_FONT_ROWS * 2 + 1] = { #include "textures/crash_custom/crash_screen_font.ia1.inc.c" }; @@ -92,7 +92,7 @@ static u32 sCrashScreenStackTraceCount = 0; static u16 gCrashScreenTextColor = 0xFFFF; static u32 sCrashScreenPrintRow_Pixels = 0; -static u32 sCrashScreenPrintLnHeight_Pixels = GLYPH_HEIGHT; +static u32 sCrashScreenPrintLnHeight_Pixels = CRASH_SCREEN_GLYPH_HEIGHT; static struct { OSThread thread; u64 stack[THREAD2_STACK / sizeof(u64)]; @@ -144,7 +144,7 @@ static void crash_screen_set_println_height(u32 height_pixels) { } static void crash_screen_reset_println_height(void) { - sCrashScreenPrintLnHeight_Pixels = GLYPH_HEIGHT; + sCrashScreenPrintLnHeight_Pixels = CRASH_SCREEN_GLYPH_HEIGHT; } void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { @@ -171,7 +171,7 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { if (glyph > 0x7F) return; - startOfGlyphData = (glyph & 0xF) * GLYPH_HEIGHT * 2; + startOfGlyphData = (glyph & 0xF) * CRASH_SCREEN_GLYPH_HEIGHT * 2; if (glyph >= 64) { startOfGlyphData++; } @@ -182,19 +182,19 @@ void crash_screen_draw_glyph(s32 x, s32 y, s32 glyph) { u16 color = gCrashScreenTextColor; - for (i = 0; i < GLYPH_HEIGHT; i++) { - bit = 0x80000000U >> ((glyph >> 4) * GLYPH_WIDTH); + for (i = 0; i < CRASH_SCREEN_GLYPH_HEIGHT; i++) { + bit = 0x80000000U >> ((glyph >> 4) * CRASH_SCREEN_GLYPH_WIDTH); rowMask = *data++; data++; - for (j = 0; j < (GLYPH_WIDTH); j++) { + for (j = 0; j < (CRASH_SCREEN_GLYPH_WIDTH); j++) { if (bit & rowMask) { *ptr = color; } ptr++; bit >>= 1; } - ptr += gCrashScreen.width - (GLYPH_WIDTH); + ptr += gCrashScreen.width - (CRASH_SCREEN_GLYPH_WIDTH); } osWritebackDCacheAll(); } @@ -226,7 +226,7 @@ int crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const cha ptr = crashScreenBuf; while (*ptr && size-- > 0) { - if (xOffset >= SCREEN_WIDTH - (xNewline + X_KERNING)) { + if (xOffset >= SCREEN_WIDTH - (xNewline + CRASH_SCREEN_X_KERNING)) { y += 10; xOffset = xNewline; numNewlines += 1; @@ -242,7 +242,7 @@ int crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const cha continue; } else if (*ptr == '\t') { osSyncPrintf("TAB %d ->", xOffset); - xOffset += GLYPH_WIDTH * (TAB_WIDTH_CHARS - 1); + xOffset += CRASH_SCREEN_GLYPH_WIDTH * (CRASH_SCREEN_TAB_WIDTH_CHARS - 1); osSyncPrintf("%d\n", xOffset); ptr++; @@ -252,7 +252,7 @@ int crash_screen_print_with_newlines(s32 x, s32 y, const s32 xNewline, const cha } ptr++; - xOffset += X_KERNING; + xOffset += CRASH_SCREEN_X_KERNING; } } @@ -277,7 +277,7 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { while (*ptr && size-- > 0) { if (*ptr == '\t') { ptr++; - x += GLYPH_WIDTH * (TAB_WIDTH_CHARS - 1); + x += CRASH_SCREEN_GLYPH_WIDTH * (CRASH_SCREEN_TAB_WIDTH_CHARS - 1); continue; } glyph = sCrashScreenCharToGlyph[*ptr & 0x7f]; @@ -287,7 +287,7 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { } ptr++; - x += X_KERNING; + x += CRASH_SCREEN_X_KERNING; } } @@ -298,7 +298,7 @@ void crash_screen_println(const char *fmt, ...) { char *ptr; u32 glyph; s32 size; - s32 x = LEFT_MARGIN; + s32 x = CRASH_SCREEN_LEFT_MARGIN; s32 y = sCrashScreenPrintRow_Pixels; va_list args; @@ -312,7 +312,7 @@ void crash_screen_println(const char *fmt, ...) { while (*ptr && size-- > 0) { if (*ptr == '\t') { ptr++; - x += GLYPH_WIDTH * (TAB_WIDTH_CHARS - 1); + x += CRASH_SCREEN_GLYPH_WIDTH * (CRASH_SCREEN_TAB_WIDTH_CHARS - 1); continue; } @@ -323,7 +323,7 @@ void crash_screen_println(const char *fmt, ...) { } ptr++; - x += X_KERNING; + x += CRASH_SCREEN_X_KERNING; } } @@ -352,7 +352,7 @@ void crash_screen_print_fpcsr(u32 fpcsr) { s32 i; u32 bit = BIT(17); - crash_screen_print(LEFT_MARGIN + 90, 220, "FPCSR:%08XH", fpcsr); + crash_screen_print(CRASH_SCREEN_LEFT_MARGIN + 90, 220, "FPCSR:%08XH", fpcsr); for (i = 0; i < 6; i++) { if (fpcsr & bit) { crash_screen_print(222, 220, "(%s)", gFpcsrDesc[i]); @@ -367,7 +367,7 @@ void draw_crash_overview(OSThread *thread, s32 cause) { int numNewlines = 0; - crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); set_text_color(0xFF, 0, 0); crash_screen_println("Game crashed!"); @@ -377,7 +377,7 @@ void draw_crash_overview(OSThread *thread, s32 cause) { crash_screen_println("Thread:"); crash_screen_println("Cause:"); reset_text_color(); - sCrashScreenPrintRow_Pixels -= (2 * GLYPH_HEIGHT); + sCrashScreenPrintRow_Pixels -= (2 * CRASH_SCREEN_GLYPH_HEIGHT); crash_screen_println(" %d", thread->id); crash_screen_println(" %s", gCauseDesc[cause]); @@ -385,7 +385,7 @@ void draw_crash_overview(OSThread *thread, s32 cause) { symtable_info_t info = get_symbol_info(tc->pc); if (info.line != -1) { - char file_line[MAX_PATH]; + char file_line[CRASH_SCREEN_MAX_PATH]; set_text_color(241, 196, 15); #ifdef DEBUG_EXPORT_ALL_LINES @@ -398,12 +398,12 @@ void draw_crash_overview(OSThread *thread, s32 cause) { reset_text_color(); numNewlines = crash_screen_print_with_newlines( - LEFT_MARGIN + (GLYPH_WIDTH * 5), - sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, - LEFT_MARGIN, + CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 5), + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, file_line ); - sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); } #endif // DEBUG_EXPORT_SYMBOLS @@ -431,7 +431,7 @@ void draw_crash_overview(OSThread *thread, s32 cause) { void draw_crash_context(OSThread *thread, s32 cause) { __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); crash_screen_println("Thread:%d (%s)", thread->id, gCauseDesc[cause]); crash_screen_set_println_height(10); @@ -459,22 +459,22 @@ void draw_crash_context(OSThread *thread, s32 cause) { crash_screen_print_fpcsr(tc->fpcsr); osWritebackDCacheAll(); - crash_screen_print_float_reg(LEFT_MARGIN + 0, 170, 0, &tc->fp0.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 90, 170, 2, &tc->fp2.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 180, 170, 4, &tc->fp4.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 0, 180, 6, &tc->fp6.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 90, 180, 8, &tc->fp8.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 180, 180, 10, &tc->fp10.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 0, 190, 12, &tc->fp12.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 90, 190, 14, &tc->fp14.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 180, 190, 16, &tc->fp16.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 0, 200, 18, &tc->fp18.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 90, 200, 20, &tc->fp20.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 180, 200, 22, &tc->fp22.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 0, 210, 24, &tc->fp24.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 90, 210, 26, &tc->fp26.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 180, 210, 28, &tc->fp28.f.f_even); - crash_screen_print_float_reg(LEFT_MARGIN + 0, 220, 30, &tc->fp30.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 170, 0, &tc->fp0.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 170, 2, &tc->fp2.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 170, 4, &tc->fp4.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 180, 6, &tc->fp6.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 180, 8, &tc->fp8.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 180, 10, &tc->fp10.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 190, 12, &tc->fp12.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 190, 14, &tc->fp14.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 190, 16, &tc->fp16.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 200, 18, &tc->fp18.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 200, 20, &tc->fp20.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 200, 22, &tc->fp22.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 210, 24, &tc->fp24.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 90, 210, 26, &tc->fp26.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 180, 210, 28, &tc->fp28.f.f_even); + crash_screen_print_float_reg(CRASH_SCREEN_LEFT_MARGIN + 0, 220, 30, &tc->fp30.f.f_even); crash_screen_reset_println_height(); } @@ -496,7 +496,7 @@ void draw_crash_log(void) { void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); crash_screen_println("Stack Trace from %08X:", (u32) tc->sp); #if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) @@ -519,7 +519,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { 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(CRASH_SCREEN_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) @@ -528,7 +528,7 @@ void draw_stacktrace(OSThread *thread, UNUSED s32 cause) { void draw_disasm(OSThread *thread) { __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); if (sProgramPosition == 0) { sProgramPosition = (tc->pc - 36); } @@ -549,7 +549,7 @@ void draw_disasm(OSThread *thread) { if (disasm[0] == 0) { - crash_screen_print(LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%08X", addr); + crash_screen_print(CRASH_SCREEN_LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%08X", addr); } else { #ifdef DEBUG_EXPORT_SYMBOLS symtable_info_t info = get_symbol_info(addr); @@ -557,7 +557,7 @@ void draw_disasm(OSThread *thread) { 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, basePositionY + (skiplines * 10) + (i * 10), "<%s:>", info.func); + crash_screen_print(CRASH_SCREEN_LEFT_MARGIN, basePositionY + (skiplines * 10) + (i * 10), "<%s:>", info.func); reset_text_color(); skiplines++; } @@ -567,7 +567,7 @@ void draw_disasm(OSThread *thread) { #endif // DEBUG_EXPORT_ALL_LINES if (info.line != -1) { set_text_color(200, 200, 200); - crash_screen_print(LEFT_MARGIN, basePositionY + (skiplines * 10) + (i * 10), "%d:", info.line); + crash_screen_print(CRASH_SCREEN_LEFT_MARGIN, basePositionY + (skiplines * 10) + (i * 10), "%d:", info.line); reset_text_color(); } #ifndef DEBUG_EXPORT_ALL_LINES @@ -580,7 +580,7 @@ void draw_disasm(OSThread *thread) { } else { reset_text_color(); } - crash_screen_print(LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%s", disasm); + crash_screen_print(CRASH_SCREEN_LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%s", disasm); } } @@ -591,7 +591,7 @@ void draw_disasm(OSThread *thread) { void draw_assert(OSThread *thread) { __OSThreadContext *tc = &thread->context; - crash_screen_draw_rect(0, RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); crash_screen_set_print_top(35); set_text_color(0xFF, 0, 0); @@ -601,33 +601,33 @@ void draw_assert(OSThread *thread) { if (__n64Assert_Filename != NULL) { // print this on the same line as `File: ` but to its right - char file_line[MAX_PATH]; + char file_line[CRASH_SCREEN_MAX_PATH]; sprintf(file_line, "%s:%d", __n64Assert_Filename, __n64Assert_LineNum); set_text_color(241, 196, 15); crash_screen_println("File/Line:"); reset_text_color(); int numNewlines = crash_screen_print_with_newlines( - LEFT_MARGIN + (GLYPH_WIDTH * 8), - sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, - LEFT_MARGIN, + CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 8), + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, file_line ); - sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); // Print the assert condition that failed. set_text_color(241, 196, 15); crash_screen_println("Condition:"); reset_text_color(); numNewlines = crash_screen_print_with_newlines( - LEFT_MARGIN + (GLYPH_WIDTH * 8), - sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, - LEFT_MARGIN, + CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 8), + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, "(%s)", __n64Assert_Condition ); - sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); // Print the message, if assertf/aggressf/errorf were used. if (__n64Assert_MessageBuf[0] != 0) { @@ -636,14 +636,14 @@ void draw_assert(OSThread *thread) { reset_text_color(); numNewlines = crash_screen_print_with_newlines( - LEFT_MARGIN + (GLYPH_WIDTH * 7), - sCrashScreenPrintRow_Pixels - GLYPH_HEIGHT, - LEFT_MARGIN, + CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 7), + sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, + CRASH_SCREEN_LEFT_MARGIN, "%s", __n64Assert_MessageBuf ); } - sCrashScreenPrintRow_Pixels += (numNewlines * GLYPH_HEIGHT); + sCrashScreenPrintRow_Pixels += (numNewlines * CRASH_SCREEN_GLYPH_HEIGHT); #if defined(DEBUG_EXPORT_SYMBOLS) && defined(DEBUG_FULL_STACK_TRACE) if (stackTraceGenerated) { set_text_color(241, 196, 15); @@ -724,8 +724,8 @@ void draw_crash_screen(OSThread *thread) { if (crashPage == CRASH_SCREEN_PAGE_ASSERTS && tc->cause != EXC_SYSCALL) crashPage--; } if (updateBuffer) { - crash_screen_draw_rect(0, 0, SCREEN_WIDTH, RECT_BOUNDARY_Y); - crash_screen_set_print_top(TOP_MARGIN); + crash_screen_draw_rect(0, 0, SCREEN_WIDTH, CRASH_SCREEN_RECT_BOUNDARY_Y); + crash_screen_set_print_top(CRASH_SCREEN_TOP_MARGIN); crash_screen_println("Page:%02d %-19s L/Z: Left R: Right", crashPage, crashPageNames[crashPage]); switch (crashPage) { case CRASH_SCREEN_PAGE_SIMPLE: draw_crash_overview(thread, cause); break; diff --git a/src/debugger/crash_screen.h b/src/debugger/crash_screen.h index 1001dc1464..76fef00411 100644 --- a/src/debugger/crash_screen.h +++ b/src/debugger/crash_screen.h @@ -1,24 +1,24 @@ #ifndef CRASH_SCREEN_H #define CRASH_SCREEN_H -#define MAX_PATH 256 +#define CRASH_SCREEN_MAX_PATH 256 // Configurable Defines -#define X_KERNING 6 -#define GLYPH_WIDTH 8 -#define GLYPH_HEIGHT 12 -#define FONT_ROWS 16 +#define CRASH_SCREEN_X_KERNING 6 +#define CRASH_SCREEN_GLYPH_WIDTH 8 +#define CRASH_SCREEN_GLYPH_HEIGHT 12 +#define CRASH_SCREEN_FONT_ROWS 16 // Margins for crash screen prints -#define LEFT_MARGIN 16 -#define TOP_MARGIN 16 +#define CRASH_SCREEN_LEFT_MARGIN 16 +#define CRASH_SCREEN_TOP_MARGIN 16 // Tab width ('\t') for crash screen prints, in characters. // Note that we only treat tabs as a wide space. -#define TAB_WIDTH_CHARS 4 +#define CRASH_SCREEN_TAB_WIDTH_CHARS 4 // Where to stop drawing the Header rectangle and where to start drawing the Body rectangle. -#define RECT_BOUNDARY_Y TOP_MARGIN + GLYPH_HEIGHT + 5 +#define CRASH_SCREEN_RECT_BOUNDARY_Y CRASH_SCREEN_TOP_MARGIN + CRASH_SCREEN_GLYPH_HEIGHT + 5 enum CrashPages { CRASH_SCREEN_PAGE_SIMPLE, diff --git a/src/debugger/disasm.c b/src/debugger/disasm.c index bbcab8ba2e..eb588bd04d 100644 --- a/src/debugger/disasm.c +++ b/src/debugger/disasm.c @@ -296,7 +296,7 @@ char *insn_disasm(InsnData *addr) { case PARAM_JAL: { target = 0x80000000 | ((insn.d & 0x1FFFFFF) * 4); #ifdef DEBUG_EXPORT_SYMBOLS - char symBuffer[MAX_PATH]; + char symBuffer[CRASH_SCREEN_MAX_PATH]; sprintf(symBuffer, "%s(%08X)", parse_map(target, FALSE), target); strp += sprintf(strp, "%-9s %s", insn_db[i].name, crash_screen_ellide_string(symBuffer, 30)); #else From 136593c2061723ef5e572e7d41ee2825b2fbb3ac Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 19:27:02 -0500 Subject: [PATCH 096/109] tab -> --- src/debugger/crash_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 4e9e1a2bd6..e551442b72 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -731,7 +731,7 @@ void draw_crash_screen(OSThread *thread) { case CRASH_SCREEN_PAGE_SIMPLE: draw_crash_overview(thread, cause); break; case CRASH_SCREEN_PAGE_CONTEXT: draw_crash_context(thread, cause); break; #ifdef PUPPYPRINT_DEBUG - case CRASH_SCREEN_PAGE_LOG: draw_crash_log(); break; + case CRASH_SCREEN_PAGE_LOG: draw_crash_log(); break; #endif case CRASH_SCREEN_PAGE_STACKTRACE: draw_stacktrace(thread, cause); break; case CRASH_SCREEN_PAGE_DISASM: draw_disasm(thread); break; From 10ff9045f093e071b2475813ad0240762fddc92e Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 11 Nov 2025 19:27:20 -0500 Subject: [PATCH 097/109] and order --- src/debugger/crash_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index e551442b72..8799a0087e 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -730,10 +730,10 @@ void draw_crash_screen(OSThread *thread) { switch (crashPage) { case CRASH_SCREEN_PAGE_SIMPLE: draw_crash_overview(thread, cause); break; case CRASH_SCREEN_PAGE_CONTEXT: draw_crash_context(thread, cause); break; + case CRASH_SCREEN_PAGE_STACKTRACE: draw_stacktrace(thread, cause); break; #ifdef PUPPYPRINT_DEBUG case CRASH_SCREEN_PAGE_LOG: draw_crash_log(); break; #endif - case CRASH_SCREEN_PAGE_STACKTRACE: draw_stacktrace(thread, cause); break; case CRASH_SCREEN_PAGE_DISASM: draw_disasm(thread); break; case CRASH_SCREEN_PAGE_ASSERTS: draw_assert(thread); break; } From 9b727e100440d942d2b88787d61dc82a084b666f Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 12 Nov 2025 22:23:28 -0500 Subject: [PATCH 098/109] translucent crash --- src/debugger/crash_screen.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 8799a0087e..117880a5ae 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -154,7 +154,8 @@ void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { - *ptr = 0x0001; + // 0xe738 = 0b1110011100111000 + *ptr = ((*ptr & 0xe738) >> 2) | 1; ptr++; } ptr += gCrashScreen.width - w; From 4227828359934f5873ad46a94410cfc05e994dc2 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Wed, 12 Nov 2025 22:34:41 -0500 Subject: [PATCH 099/109] address feedback kinda --- src/debugger/crash_screen.c | 42 +++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 117880a5ae..d7097a3106 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -61,25 +61,31 @@ static u8 updateBuffer = TRUE; static char crashScreenBuf[0x200]; +/** + * Verbose descriptions for exception causes. + */ 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", + [EXC_INT >> 2] = "Interrupt", + [EXC_MOD >> 2] = "TLB modification", + [EXC_RMISS >> 2] = "TLB exception on load", + [EXC_WMISS >> 2] = "TLB exception on store", + [EXC_RADE >> 2] = "Address error on load", + [EXC_WADE >> 2] = "Address error on store", + [EXC_IBE >> 2] = "Bus error on inst.", + [EXC_DBE >> 2] = "Bus error on data", + [EXC_SYSCALL >> 2] = "Failed Assert: See Assert Page", + [EXC_BREAK >> 2] = "Breakpoint exception", + [EXC_II >> 2] = "Reserved instruction", + [EXC_CPU >> 2] = "Coprocessor unusable", + [EXC_OV >> 2] = "Arithmetic overflow", + [EXC_TRAP >> 2] = "Trap exception", + [EXC_VCEI >> 2] = "Virtual coherency on inst.", + [EXC_FPE >> 2] = "Floating point exception", + // These two exceptions are not enumerated like the ones above. + // They take values 23 and 31, respectively, so here they're just placed + // after the ones that do enumerate nicely. + /*EXC_WATCH */ "Watchpoint exception", + /*EXC_VCED */ "Virtual coherency on data", }; char *gFpcsrDesc[6] = { From 03679f5ca6f0c1f72b7b674a33f5c58c29cfc56c Mon Sep 17 00:00:00 2001 From: someone2639 Date: Tue, 18 Nov 2025 22:06:41 -0500 Subject: [PATCH 100/109] add comment --- src/debugger/crash_screen.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index d7097a3106..ecd1c29443 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -160,6 +160,13 @@ void crash_screen_draw_rect(s32 x, s32 y, s32 w, s32 h) { ptr = gCrashScreen.framebuffer + gCrashScreen.width * y + x; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { + /** + * Instead of setting the framebuffer pixels fully dark, + * SM64 "darkens" the RGBA5551 pixel. This is done by + * shifting every RGB component right by 2 in one operation, + * essentially setting the brightness to 1/4. + */ + // 0xe738 = 0b1110011100111000 *ptr = ((*ptr & 0xe738) >> 2) | 1; ptr++; From b2e85067a010af204fa8e92fc2af3a4352c7fdce Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 23 Nov 2025 15:04:14 -0500 Subject: [PATCH 101/109] fix background on puppyprint page --- src/debugger/crash_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index ecd1c29443..27b5af86b9 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -497,7 +497,7 @@ void draw_crash_context(OSThread *thread, s32 cause) { #ifdef PUPPYPRINT_DEBUG void draw_crash_log(void) { s32 i; - crash_screen_draw_rect(0, 20, SCREEN_WIDTH, 210); + crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); osWritebackDCacheAll(); #define LINE_HEIGHT (25 + ((LOG_BUFFER_SIZE - 1) * 10)) for (i = 0; i < LOG_BUFFER_SIZE; i++) { From aa9482e800b528e8f46d75be49627311525821a7 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 23 Nov 2025 15:08:53 -0500 Subject: [PATCH 102/109] fix spacing --- src/debugger/crash_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 27b5af86b9..f042f2b196 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -650,7 +650,7 @@ void draw_assert(OSThread *thread) { reset_text_color(); numNewlines = crash_screen_print_with_newlines( - CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 7), + CRASH_SCREEN_LEFT_MARGIN + 8 + (CRASH_SCREEN_GLYPH_WIDTH * 7), sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, CRASH_SCREEN_LEFT_MARGIN, "%s", From f89440a53eb8de0cb02d34b781451998275de539 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 23 Nov 2025 15:20:04 -0500 Subject: [PATCH 103/109] fix the disasm bug (dont even remember why i added that logic) --- src/debugger/crash_screen.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index f042f2b196..2aea1e0240 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -550,11 +550,6 @@ void draw_disasm(OSThread *thread) { osWritebackDCacheAll(); int skiplines = 0; -#ifdef DEBUG_EXPORT_SYMBOLS - int currline = 0; -#endif // DEBUG_EXPORT_SYMBOLS - - u32 basePositionY = sCrashScreenPrintRow_Pixels; for (int i = 0; i < 19; i++) { u32 addr = (sProgramPosition + (i * 4)); @@ -563,15 +558,14 @@ void draw_disasm(OSThread *thread) { if (disasm[0] == 0) { - crash_screen_print(CRASH_SCREEN_LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%08X", addr); + crash_screen_println(" %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; + if (info.func_offset == 0 && info.distance == 0) { set_text_color(239, 196, 15); - crash_screen_print(CRASH_SCREEN_LEFT_MARGIN, basePositionY + (skiplines * 10) + (i * 10), "<%s:>", info.func); + crash_screen_println("<%s:>", info.func); reset_text_color(); skiplines++; } @@ -581,20 +575,24 @@ void draw_disasm(OSThread *thread) { #endif // DEBUG_EXPORT_ALL_LINES if (info.line != -1) { set_text_color(200, 200, 200); - crash_screen_print(CRASH_SCREEN_LEFT_MARGIN, basePositionY + (skiplines * 10) + (i * 10), "%d:", info.line); + crash_screen_println("%d:", info.line); + reset_text_color(); + sCrashScreenPrintRow_Pixels -= sCrashScreenPrintLnHeight_Pixels; + crash_screen_println(" %s", disasm); } #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(); + else { + if (addr == tc->pc) { + set_text_color(255, 0, 0); + } else { + reset_text_color(); + } + crash_screen_println(" %s", disasm); } - crash_screen_print(CRASH_SCREEN_LEFT_MARGIN + 22, basePositionY + (skiplines * 10) + (i * 10), "%s", disasm); } } From 51b2272c78261a16da4b1e913584a2b1d9547cb0 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Sun, 23 Nov 2025 23:09:51 -0500 Subject: [PATCH 104/109] add dwarf to some audio files too --- Makefile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8be95978c0..2b9c2cd80f 100644 --- a/Makefile +++ b/Makefile @@ -107,7 +107,8 @@ ifeq ($(DEBUG_EXPORT_SYMBOLS),1) DEFINES += DEBUG_EXPORT_ALL_LINES=1 endif DEFINES += DEBUG_EXPORT_SYMBOLS=1 - DEFAULT_OPT_FLAGS += -g1 -gdwarf-4 + GENERATE_DWARF := -g1 -gdwarf-4 + DEFAULT_OPT_FLAGS += $(GENERATE_DWARF) SYMBOL_TABLE = $(BUILD_DIR)/sm64.sym.o endif ifeq ($(DEBUG_FULL_STACK_TRACE),1) @@ -683,13 +684,13 @@ else endif endif -$(BUILD_DIR)/src/usb/usb.o: OPT_FLAGS := -O0 +$(BUILD_DIR)/src/usb/usb.o: OPT_FLAGS := $(GENERATE_DWARF) -O0 $(BUILD_DIR)/src/usb/usb.o: CFLAGS += -Wno-unused-variable -Wno-sign-compare -Wno-unused-function -$(BUILD_DIR)/src/usb/debug.o: OPT_FLAGS := -O0 +$(BUILD_DIR)/src/usb/debug.o: OPT_FLAGS := $(GENERATE_DWARF) -O0 $(BUILD_DIR)/src/usb/debug.o: CFLAGS += -Wno-unused-parameter -Wno-maybe-uninitialized # File specific opt flags -$(BUILD_DIR)/src/audio/heap.o: OPT_FLAGS := -Os -fno-jump-tables -$(BUILD_DIR)/src/audio/synthesis.o: OPT_FLAGS := -Os -fno-jump-tables +$(BUILD_DIR)/src/audio/heap.o: OPT_FLAGS := $(GENERATE_DWARF) -Os -fno-jump-tables +$(BUILD_DIR)/src/audio/synthesis.o: OPT_FLAGS := $(GENERATE_DWARF) -Os -fno-jump-tables $(BUILD_DIR)/src/engine/surface_collision.o: OPT_FLAGS := $(COLLISION_OPT_FLAGS) $(BUILD_DIR)/src/engine/math_util.o: OPT_FLAGS := $(MATH_UTIL_OPT_FLAGS) From 84c900d23d1f8816f84b5040f1e49c14e1870053 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 24 Nov 2025 00:47:46 -0500 Subject: [PATCH 105/109] update heuristic to catch a weird edge case --- src/debugger/stacktrace.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c index d772a3dc1e..f2b1dbfa4a 100644 --- a/src/debugger/stacktrace.c +++ b/src/debugger/stacktrace.c @@ -79,11 +79,23 @@ u32 generate_stack(OSThread *thread) { if (insn_is_jal((Insn *) &jal)) { u32 jalTarget = 0x80000000 | ((jal & 0x03FFFFFF) * 4); + osSyncPrintf("jal target %08X\n", jalTarget); // 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 { + // Just in case we're on a weird boundary, find the _previous_ + // function start and see if we jal'd to there instead. + do { + funcstart = symt_addrtab_entry(&symt, --idx); + } while (!ADDRENTRY_IS_FUNC(funcstart) && !ADDRENTRY_IS_INLINE(funcstart)); + + 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 From 35a76d64203ec5189d695843dd9093954c9ab687 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 24 Nov 2025 00:51:32 -0500 Subject: [PATCH 106/109] remove debug print --- src/debugger/stacktrace.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/debugger/stacktrace.c b/src/debugger/stacktrace.c index f2b1dbfa4a..a35d683f6f 100644 --- a/src/debugger/stacktrace.c +++ b/src/debugger/stacktrace.c @@ -79,7 +79,6 @@ u32 generate_stack(OSThread *thread) { if (insn_is_jal((Insn *) &jal)) { u32 jalTarget = 0x80000000 | ((jal & 0x03FFFFFF) * 4); - osSyncPrintf("jal target %08X\n", jalTarget); // make sure JAL is to the current func if (jalTarget == ADDRENTRY_ADDR(funcstart)) { From 5702a6b24888036633fe3ffb2042810926d39bdb Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 24 Nov 2025 20:03:18 -0500 Subject: [PATCH 107/109] remove all hardcoded x positions and replace them with spaces --- src/debugger/crash_screen.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 2aea1e0240..6be2891c1b 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -403,16 +403,16 @@ void draw_crash_overview(OSThread *thread, s32 cause) { set_text_color(241, 196, 15); #ifdef DEBUG_EXPORT_ALL_LINES - sprintf(file_line, "%s:%d", info.file, info.line); + sprintf(file_line, " %s:%d", info.file, info.line); #else // DEBUG_EXPORT_ALL_LINES - sprintf(file_line, "%s", info.file); + sprintf(file_line, " %s", info.file); #endif // DEBUG_EXPORT_ALL_LINES sCrashScreenPrintRow_Pixels += 5; crash_screen_println("File: "); reset_text_color(); numNewlines = crash_screen_print_with_newlines( - CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 5), + CRASH_SCREEN_LEFT_MARGIN, sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, CRASH_SCREEN_LEFT_MARGIN, file_line @@ -614,12 +614,12 @@ void draw_assert(OSThread *thread) { if (__n64Assert_Filename != NULL) { // print this on the same line as `File: ` but to its right char file_line[CRASH_SCREEN_MAX_PATH]; - sprintf(file_line, "%s:%d", __n64Assert_Filename, __n64Assert_LineNum); + sprintf(file_line, " %s:%d", __n64Assert_Filename, __n64Assert_LineNum); set_text_color(241, 196, 15); crash_screen_println("File/Line:"); reset_text_color(); int numNewlines = crash_screen_print_with_newlines( - CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 8), + CRASH_SCREEN_LEFT_MARGIN, sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, CRASH_SCREEN_LEFT_MARGIN, file_line @@ -632,10 +632,10 @@ void draw_assert(OSThread *thread) { crash_screen_println("Condition:"); reset_text_color(); numNewlines = crash_screen_print_with_newlines( - CRASH_SCREEN_LEFT_MARGIN + (CRASH_SCREEN_GLYPH_WIDTH * 8), + CRASH_SCREEN_LEFT_MARGIN, sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, CRASH_SCREEN_LEFT_MARGIN, - "(%s)", + " (%s)", __n64Assert_Condition ); @@ -648,10 +648,10 @@ void draw_assert(OSThread *thread) { reset_text_color(); numNewlines = crash_screen_print_with_newlines( - CRASH_SCREEN_LEFT_MARGIN + 8 + (CRASH_SCREEN_GLYPH_WIDTH * 7), + CRASH_SCREEN_LEFT_MARGIN, sCrashScreenPrintRow_Pixels - CRASH_SCREEN_GLYPH_HEIGHT, CRASH_SCREEN_LEFT_MARGIN, - "%s", + " %s", __n64Assert_MessageBuf ); } From f57652f00ec3a13d1d24e957a3ddd996ee3b0b73 Mon Sep 17 00:00:00 2001 From: someone2639 Date: Mon, 24 Nov 2025 20:04:06 -0500 Subject: [PATCH 108/109] puppyprint swap --- src/debugger/crash_screen.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/debugger/crash_screen.c b/src/debugger/crash_screen.c index 6be2891c1b..5f4b98fa9b 100644 --- a/src/debugger/crash_screen.c +++ b/src/debugger/crash_screen.c @@ -499,11 +499,9 @@ void draw_crash_log(void) { s32 i; crash_screen_draw_rect(0, CRASH_SCREEN_RECT_BOUNDARY_Y, SCREEN_WIDTH, SCREEN_HEIGHT); osWritebackDCacheAll(); -#define LINE_HEIGHT (25 + ((LOG_BUFFER_SIZE - 1) * 10)) for (i = 0; i < LOG_BUFFER_SIZE; i++) { - crash_screen_println(consoleLogTable[i]); + crash_screen_println(consoleLogTable[LOG_BUFFER_SIZE - i]); } -#undef LINE_HEIGHT } #endif From 0dce81fb0bf01bd4ce2170404f346a07db1b7b5d Mon Sep 17 00:00:00 2001 From: Gregory Heskett Date: Wed, 3 Dec 2025 00:13:49 -0500 Subject: [PATCH 109/109] Add new Makefile flags to vscode c_cpp_properties.json --- .vscode/c_cpp_properties.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 3de36d2bb5..ee7fa23b40 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -27,6 +27,8 @@ "F3DEX_GBI_2=1", "F3DZEX_NON_GBI_2=1", "F3DEX_GBI_SHARED=1", + "DEBUG_EXPORT_SYMBOLS=1", + "DEBUG_FULL_STACK_TRACE=1", "LIBPL=1" ], "compilerPath": "/usr/bin/mips-linux-gnu-gcc",