summaryrefslogtreecommitdiff
path: root/src/boot
diff options
context:
space:
mode:
authorDerek Hensley <hensley.derek58@gmail.com>2023-08-28 09:59:31 -0700
committerGitHub <noreply@github.com>2023-08-28 09:59:31 -0700
commit1e3511efea9cf4379a04fca77f6a00cb170da28c (patch)
tree7650c04105d35d867939d19e3421102ae511102f /src/boot
parentfcfbb6c52feec4a01b46f0c2bcc037af373d4fe9 (diff)
Fault + Fault_drawer (#77)
* Fault OK * fault_drawer OK * Cleanup * Cleanup pt 2 * Forgot to save * PR
Diffstat (limited to 'src/boot')
-rw-r--r--src/boot/fault.c1333
-rw-r--r--src/boot/fault_drawer.c318
-rw-r--r--src/boot/ovlmgr.c2
-rw-r--r--src/boot/z_std_dma.c2
4 files changed, 1653 insertions, 2 deletions
diff --git a/src/boot/fault.c b/src/boot/fault.c
new file mode 100644
index 0000000..9844630
--- /dev/null
+++ b/src/boot/fault.c
@@ -0,0 +1,1333 @@
+/**
+ * @file fault.c
+ *
+ * This file implements the screen that may be viewed when the game crashes.
+ * This is the second version of the crash screen, originally used in Majora's Mask.
+ *
+ * When the game crashes, a red bar will be drawn to the top-left of the screen, indicating that the
+ * crash screen is available for use. Once this bar appears, it is possible to open the crash screen
+ * with the following button combination:
+ *
+ * (L & R & Z) + DPad-Up + C-Down + C-Up + DPad-Down + DPad-Left + C-Left + C-Right + DPad-Right + (B & A & START)
+ *
+ * When entering this button combination, buttons that are &'d together must all be pressed together.
+ * The L & R presses and B & A presses may be interchanged in the order they are pressed.
+ *
+ * "Clients" may be registered with the crash screen to extend its functionality. There are
+ * two kinds of client, "Client" and "AddressConverterClient". Clients contribute one or
+ * more pages to the crash debugger, while Address Converter Clients allow the crash screen to look up
+ * the virtual addresses of dynamically allocated overlays.
+ *
+ * The crash screen has multiple pages:
+ * - Thread Context
+ * This page shows information about the thread on which the program crashed. It displays
+ * the cause of the crash, state of general-purpose registers, state of floating-point registers
+ * and the floating-point status register. If a floating-point exception caused the crash, it will
+ * be displayed next to the floating-point status register.
+ * - Stack Trace
+ * This page displays a full backtrace from the crashing function back to the start of the thread. It
+ * displays the Program Counter for each function and, if applicable, the Virtual Program Counter
+ * for relocated functions in overlays.
+ * - Client Pages
+ * After the stack trace page, currently registered clients are processed and their pages are displayed.
+ * - Memory Dump
+ * This page implements a scrollable memory dump.
+ * - End Screen
+ * This page informs you that there are no more pages to display.
+ *
+ * To navigate the pages, START and A may be used to advance to the next page, and L toggles whether to
+ * automatically scroll to the next page after some time has passed.
+ * DPad-Up may be pressed to enable sending fault pages over osSyncPrintf as well as displaying them on-screen.
+ * DPad-Down disables sending fault pages over osSyncPrintf.
+ */
+#include "libc/stdbool.h"
+#include "PR/os_internal.h"
+#include "libc/alloca.h"
+#include "libc64/sleep.h"
+#include "m_controller.h"
+#include "m_thread.h"
+#include "macros.h"
+#include "terminal.h"
+#include "padmgr.h"
+#include "stack.h"
+#include "libu64/stackcheck.h"
+
+// Bss put before fault.h as a hacky way to bypass bss reordering
+struct FaultMgr* sFaultInstance;
+u8 sFaultAwaitingInput;
+STACK(sFaultStack, 0x600);
+StackEntry sFaultThreadInfo;
+struct FaultMgr gFaultMgr;
+
+#include "fault.h"
+
+const char* sExceptionNames[] = {
+ "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",
+ "System call exception",
+ "Breakpoint exception",
+ "Reserved instruction",
+ "Coprocessor unusable",
+ "Arithmetic overflow",
+ "Trap exception",
+ "Virtual coherency on inst.",
+ "Floating point exception",
+ "Watchpoint exception",
+ "Virtual coherency on data",
+};
+
+const char* sFpExceptionNames[] = {
+ "Unimplemented operation", "Invalid operation", "Division by zero", "Overflow", "Underflow", "Inexact operation",
+};
+
+// TODO: Should be in a proper header somewhere else?
+extern void __osCleanupThread(void);
+
+typedef struct {
+ /* 0x00 */ s32 (*callback)(void*, void*);
+ /* 0x04 */ void* arg0;
+ /* 0x08 */ void* arg1;
+ /* 0x0C */ s32 ret;
+ /* 0x10 */ OSMesgQueue* queue;
+ /* 0x14 */ OSMesg msg;
+} FaultClientTask; // size = 0x18
+
+void fault_SleepImpl(u32 msec) {
+ u64 cycles = (msec * OS_CPU_COUNTER) / 1000ULL;
+
+ csleep(cycles);
+}
+
+void fault_ClientProcessThread(void* arg) {
+ FaultClientTask* task = (FaultClientTask*)arg;
+
+ // Run the callback
+ if (task->callback != NULL) {
+ task->ret = task->callback(task->arg0, task->arg1);
+ }
+
+ // Send completion notification
+ if (task->queue != NULL) {
+ osSendMesg(task->queue, task->msg, OS_MESG_BLOCK);
+ }
+}
+
+void fault_ClientRunTask(FaultClientTask* task) {
+ OSMesgQueue queue;
+ OSMesg msg[1];
+ OSMesg recMsg;
+ OSThread* thread = NULL;
+ OSTimer timer;
+ u32 timerMsgVal = 666;
+
+ osCreateMesgQueue(&queue, msg, ARRAY_COUNT(msg));
+ task->queue = &queue;
+ task->msg = NULL;
+
+ if (sFaultInstance->clientThreadSp != NULL) {
+ // Run the fault client callback on a separate thread
+ thread = alloca(sizeof(OSThread));
+
+ osCreateThread(thread, M_THREAD_ID_FAULT, fault_ClientProcessThread, task, sFaultInstance->clientThreadSp,
+ M_PRIORITY_FAULT_CLIENT);
+ osStartThread(thread);
+ } else {
+ // Run the fault client callback on this thread
+ fault_ClientProcessThread(task);
+ }
+
+ // Await done
+ while (true) {
+ osSetTimer(&timer, OS_USEC_TO_CYCLES(1000000), 0, &queue, (OSMesg)timerMsgVal);
+ osRecvMesg(&queue, &recMsg, OS_MESG_BLOCK);
+
+ if (recMsg != (OSMesg)666) {
+ break;
+ }
+
+ if (!sFaultAwaitingInput) {
+ task->ret = -1;
+ break;
+ }
+ }
+
+ osStopTimer(&timer);
+
+ // Destroy thread if a thread was used
+ if (thread != NULL) {
+ osStopThread(thread);
+ osDestroyThread(thread);
+ }
+}
+
+s32 fault_ProcessClient(void* callback, void* arg0, void* arg1) {
+ FaultClientTask task;
+
+ task.callback = callback;
+ task.arg0 = arg0;
+ task.arg1 = arg1;
+ task.ret = 0;
+ fault_ClientRunTask(&task);
+ return task.ret;
+}
+
+/**
+ * Registers a fault client.
+ *
+ * Clients contribute at least one page to the crash screen, drawn by `callback`.
+ * Arguments are passed on to the callback through `arg0` and `arg1`.
+ *
+ * The callback is intended to be
+ * `void (*callback)(void* arg0, void* arg1)`
+ */
+void fault_AddClient(FaultClient* client, FaultClientCallback callback, void* arg0, void* arg1) {
+ OSIntMask mask;
+ s32 alreadyExists = false;
+
+ mask = osSetIntMask(OS_IM_NONE);
+
+ // Ensure the client is not already registered
+ {
+ FaultClient* iterClient = sFaultInstance->clients;
+
+ while (iterClient != NULL) {
+ if (iterClient == client) {
+ alreadyExists = true;
+ goto end;
+ }
+ iterClient = iterClient->next;
+ }
+ }
+
+ client->callback = callback;
+ client->arg0 = arg0;
+ client->arg1 = arg1;
+ client->next = sFaultInstance->clients;
+ sFaultInstance->clients = client;
+
+end:
+ osSetIntMask(mask);
+ if (alreadyExists) {
+ // "fault_AddClient: %08x is already in the list"
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_AddClient: %08x は既にリスト中にある\n" VT_RST, client);
+ }
+}
+
+/**
+ * Removes a fault client so that the page is no longer displayed if a crash occurs.
+ */
+void fault_RemoveClient(FaultClient* client) {
+ FaultClient* iterClient = sFaultInstance->clients;
+ FaultClient* lastClient = NULL;
+ OSIntMask mask;
+ s32 listIsEmpty = false;
+
+ mask = osSetIntMask(OS_IM_NONE);
+
+ while (iterClient != NULL) {
+ if (iterClient == client) {
+ if (lastClient != NULL) {
+ lastClient->next = client->next;
+ } else {
+ sFaultInstance->clients = client;
+ if (sFaultInstance->clients) {
+ sFaultInstance->clients = client->next;
+ } else {
+ listIsEmpty = true;
+ }
+ }
+ break;
+ }
+ lastClient = iterClient;
+ iterClient = iterClient->next;
+ }
+
+ osSetIntMask(mask);
+
+ if (listIsEmpty) {
+ // "fault_RemoveClient: %08x list inconsistent"
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_RemoveClient: %08x リスト不整合です\n" VT_RST, client);
+ }
+}
+
+/**
+ * Registers an address converter client. This enables the crash screen to look up virtual
+ * addresses of overlays relocated during runtime. Address conversion is carried out by
+ * `callback`, which either returns a virtual address or NULL if the address could not
+ * be converted.
+ *
+ * The callback is intended to be
+ * `uintptr_t (*callback)(uintptr_t addr, void* arg)`
+ * The callback may return 0 if it could not convert the address
+ * The callback may return -1 to be unregistered
+ */
+void fault_AddressConverterAddClient(FaultAddrConvClient* client, FaultAddrConvClientCallback callback, void* arg) {
+ OSIntMask mask;
+ s32 alreadyExists = false;
+
+ mask = osSetIntMask(OS_IM_NONE);
+
+ // Ensure the client is not already registered
+ {
+ FaultAddrConvClient* iterClient = sFaultInstance->addrConvClients;
+
+ while (iterClient != NULL) {
+ if (iterClient == client) {
+ alreadyExists = true;
+ goto end;
+ }
+ iterClient = iterClient->next;
+ }
+ }
+
+ client->callback = callback;
+ client->arg = arg;
+ client->next = sFaultInstance->addrConvClients;
+ sFaultInstance->addrConvClients = client;
+
+end:
+ osSetIntMask(mask);
+ if (alreadyExists) {
+ // "fault_AddressConverterAddClient: %08x is already in the list"
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_AddressConverterAddClient: %08x は既にリスト中にある\n" VT_RST, client);
+ }
+}
+
+void fault_AddressConverterRemoveClient(FaultAddrConvClient* client) {
+ FaultAddrConvClient* iterClient = sFaultInstance->addrConvClients;
+ FaultAddrConvClient* lastClient = NULL;
+ OSIntMask mask;
+ s32 listIsEmpty = false;
+
+ mask = osSetIntMask(OS_IM_NONE);
+
+ while (iterClient != NULL) {
+ if (iterClient == client) {
+ if (lastClient != NULL) {
+ lastClient->next = client->next;
+ } else {
+ sFaultInstance->addrConvClients = client;
+
+ if (sFaultInstance->addrConvClients != NULL) {
+ sFaultInstance->addrConvClients = client->next;
+ } else {
+ listIsEmpty = true;
+ }
+ }
+ break;
+ }
+ lastClient = iterClient;
+ iterClient = iterClient->next;
+ }
+
+ osSetIntMask(mask);
+
+ if (listIsEmpty) {
+ // "fault_AddressConverterRemoveClient: %08x is already in the list"
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_AddressConverterRemoveClient: %08x は既にリスト中にある\n" VT_RST,
+ client);
+ }
+}
+
+/**
+ * Converts `addr` to a virtual address via the registered
+ * address converter clients
+ */
+uintptr_t fault_ConvertAddress(uintptr_t addr) {
+ s32 ret;
+ FaultAddrConvClient* client = sFaultInstance->addrConvClients;
+
+ while (client != NULL) {
+ if (client->callback != NULL) {
+ ret = fault_ProcessClient(client->callback, (void*)addr, client->arg);
+ if (ret == -1) {
+ fault_AddressConverterRemoveClient(client);
+ } else if (ret != 0) {
+ return (uintptr_t)ret;
+ }
+ }
+ client = client->next;
+ }
+
+ return 0;
+}
+
+void fault_Sleep(u32 msec) {
+ fault_SleepImpl(msec);
+}
+
+void fault_PadCallback(Input* inputs) {
+ padmgr_RequestPadData(inputs, false);
+}
+
+void fault_UpdatePadImpl(void) {
+ sFaultInstance->padCallback(sFaultInstance->inputs);
+}
+
+/**
+ * Awaits user input
+ *
+ * L toggles auto-scroll
+ * DPad-Up enables osSyncPrintf output
+ * DPad-Down disables osSyncPrintf output
+ * A and DPad-Right continues and returns true
+ * DPad-Left continues and returns false
+ */
+u32 fault_WaitForInputImpl(void) {
+ Input* input = &sFaultInstance->inputs[0];
+ s32 count = 600;
+ u32 pressedBtn;
+
+ while (true) {
+ fault_Sleep(1000 / 60);
+ fault_UpdatePadImpl();
+
+ pressedBtn = input->press.button;
+
+ if (pressedBtn == L_TRIG) {
+ sFaultInstance->autoScroll = !sFaultInstance->autoScroll;
+ }
+
+ if (sFaultInstance->autoScroll) {
+ if (count-- < 1) {
+ return false;
+ }
+ } else {
+ if (pressedBtn == A_BUTTON || pressedBtn == R_JPAD) {
+ return false;
+ }
+
+ if (pressedBtn == L_JPAD) {
+ return true;
+ }
+
+ if (pressedBtn == U_JPAD) {
+ FaultDrawer_SetOsSyncPrintfEnabled(true);
+ }
+
+ if (pressedBtn == D_JPAD) {
+ FaultDrawer_SetOsSyncPrintfEnabled(false);
+ }
+ }
+ }
+}
+
+void fault_WaitForInput(void) {
+ sFaultAwaitingInput = true;
+ fault_WaitForInputImpl();
+ sFaultAwaitingInput = false;
+}
+
+void fault_DrawRec(s32 x, s32 y, s32 w, s32 h, u16 color) {
+ FaultDrawer_DrawRecImpl(x, y, x + w - 1, y + h - 1, color);
+}
+
+void fault_FillScreenBlack(void) {
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 1));
+ FaultDrawer_FillScreen();
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0));
+}
+
+void fault_FillScreenRed(void) {
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(240, 0, 0, 1));
+ FaultDrawer_FillScreen();
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0));
+}
+
+void fault_DrawCornerRec(u16 color) {
+ fault_DrawRec(22, 16, 8, 1, color);
+}
+
+void fault_PrintFReg(s32 idx, f32* value) {
+ u32 raw = *(u32*)value;
+ s32 exp = ((raw & 0x7F800000) >> 0x17) - 0x7F;
+
+ if ((exp >= -0x7E && exp < 0x80) || raw == 0) {
+ FaultDrawer_Printf("F%02d:%14.7e ", idx, *value);
+ } else {
+ // Print subnormal floats as their ieee-754 hex representation
+ FaultDrawer_Printf("F%02d: %08x(16) ", idx, raw);
+ }
+}
+
+void fault_LogFReg(s32 idx, f32* value) {
+ u32 raw = *(u32*)value;
+ s32 exp = ((raw & 0x7F800000) >> 0x17) - 0x7F;
+
+ if ((exp >= -0x7E && exp < 0x80) || raw == 0) {
+ osSyncPrintf("F%02d:%14.7e ", idx, *value);
+ } else {
+ osSyncPrintf("F%02d: %08x(16) ", idx, *(u32*)value);
+ }
+}
+
+void fault_PrintFPCSR(u32 value) {
+ s32 i;
+ u32 flag = FPCSR_CE;
+
+ FaultDrawer_Printf("FPCSR:%08xH ", value);
+
+ // Go through each of the six causes and print the name of
+ // the first cause that is set
+ for (i = 0; i < ARRAY_COUNT(sFpExceptionNames); i++) {
+ if (value & flag) {
+ FaultDrawer_Printf("(%s)", sFpExceptionNames[i]);
+ break;
+ }
+ flag >>= 1;
+ }
+ FaultDrawer_Printf("\n");
+}
+
+void fault_LogFPCSR(u32 value) {
+ s32 i;
+ u32 flag = FPCSR_CE;
+
+ osSyncPrintf("FPCSR:%08xH ", value);
+ for (i = 0; i < ARRAY_COUNT(sFpExceptionNames); i++) {
+ if (value & flag) {
+ osSyncPrintf("(%s)\n", sFpExceptionNames[i]);
+ break;
+ }
+ flag >>= 1;
+ }
+}
+
+void fault_PrintThreadContext(OSThread* thread) {
+ __OSThreadContext* ctx;
+ s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5);
+
+ if (causeStrIdx == (EXC_WATCH >> CAUSE_EXCSHIFT)) {
+ causeStrIdx = 16;
+ }
+ if (causeStrIdx == (EXC_VCED >> CAUSE_EXCSHIFT)) {
+ causeStrIdx = 17;
+ }
+
+ FaultDrawer_FillScreen();
+ FaultDrawer_SetCharPad(-2, 4);
+ FaultDrawer_SetCursor(22, 20);
+
+ ctx = &thread->context;
+ FaultDrawer_Printf("THREAD:%d (%d:%s)\n", thread->id, causeStrIdx, sExceptionNames[causeStrIdx]);
+ FaultDrawer_SetCharPad(-1, 0);
+
+ FaultDrawer_Printf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)ctx->pc, (u32)ctx->sr, (u32)ctx->badvaddr);
+ FaultDrawer_Printf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)ctx->at, (u32)ctx->v0, (u32)ctx->v1);
+ FaultDrawer_Printf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)ctx->a0, (u32)ctx->a1, (u32)ctx->a2);
+ FaultDrawer_Printf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)ctx->a3, (u32)ctx->t0, (u32)ctx->t1);
+ FaultDrawer_Printf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)ctx->t2, (u32)ctx->t3, (u32)ctx->t4);
+ FaultDrawer_Printf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)ctx->t5, (u32)ctx->t6, (u32)ctx->t7);
+ FaultDrawer_Printf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)ctx->s0, (u32)ctx->s1, (u32)ctx->s2);
+ FaultDrawer_Printf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)ctx->s3, (u32)ctx->s4, (u32)ctx->s5);
+ FaultDrawer_Printf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)ctx->s6, (u32)ctx->s7, (u32)ctx->t8);
+ FaultDrawer_Printf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)ctx->t9, (u32)ctx->gp, (u32)ctx->sp);
+ FaultDrawer_Printf("S8:%08xH RA:%08xH LO:%08xH\n\n", (u32)ctx->s8, (u32)ctx->ra, (u32)ctx->lo);
+
+ fault_PrintFPCSR(ctx->fpcsr);
+ FaultDrawer_Printf("\n");
+
+ fault_PrintFReg(0, &ctx->fp0.f.f_even);
+ fault_PrintFReg(2, &ctx->fp2.f.f_even);
+ FaultDrawer_Printf("\n");
+ fault_PrintFReg(4, &ctx->fp4.f.f_even);
+ fault_PrintFReg(6, &ctx->fp6.f.f_even);
+ FaultDrawer_Printf("\n");
+ fault_PrintFReg(8, &ctx->fp8.f.f_even);
+ fault_PrintFReg(10, &ctx->fp10.f.f_even);
+ FaultDrawer_Printf("\n");
+ fault_PrintFReg(12, &ctx->fp12.f.f_even);
+ fault_PrintFReg(14, &ctx->fp14.f.f_even);
+ FaultDrawer_Printf("\n");
+ fault_PrintFReg(16, &ctx->fp16.f.f_even);
+ fault_PrintFReg(18, &ctx->fp18.f.f_even);
+ FaultDrawer_Printf("\n");
+ fault_PrintFReg(20, &ctx->fp20.f.f_even);
+ fault_PrintFReg(22, &ctx->fp22.f.f_even);
+ FaultDrawer_Printf("\n");
+ fault_PrintFReg(24, &ctx->fp24.f.f_even);
+ fault_PrintFReg(26, &ctx->fp26.f.f_even);
+ FaultDrawer_Printf("\n");
+ fault_PrintFReg(28, &ctx->fp28.f.f_even);
+ fault_PrintFReg(30, &ctx->fp30.f.f_even);
+ FaultDrawer_Printf("\n");
+
+ FaultDrawer_SetCharPad(0, 0);
+}
+
+void fault_LogThreadContext(OSThread* thread) {
+ __OSThreadContext* ctx;
+ s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5);
+
+ if (causeStrIdx == (EXC_WATCH >> CAUSE_EXCSHIFT)) {
+ causeStrIdx = 16;
+ }
+ if (causeStrIdx == (EXC_VCED >> CAUSE_EXCSHIFT)) {
+ causeStrIdx = 17;
+ }
+
+ ctx = &thread->context;
+ osSyncPrintf("\n");
+ osSyncPrintf("THREAD ID:%d (%d:%s)\n", thread->id, causeStrIdx, sExceptionNames[causeStrIdx]);
+
+ osSyncPrintf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)ctx->pc, (u32)ctx->sr, (u32)ctx->badvaddr);
+ osSyncPrintf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)ctx->at, (u32)ctx->v0, (u32)ctx->v1);
+ osSyncPrintf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)ctx->a0, (u32)ctx->a1, (u32)ctx->a2);
+ osSyncPrintf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)ctx->a3, (u32)ctx->t0, (u32)ctx->t1);
+ osSyncPrintf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)ctx->t2, (u32)ctx->t3, (u32)ctx->t4);
+ osSyncPrintf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)ctx->t5, (u32)ctx->t6, (u32)ctx->t7);
+ osSyncPrintf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)ctx->s0, (u32)ctx->s1, (u32)ctx->s2);
+ osSyncPrintf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)ctx->s3, (u32)ctx->s4, (u32)ctx->s5);
+ osSyncPrintf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)ctx->s6, (u32)ctx->s7, (u32)ctx->t8);
+ osSyncPrintf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)ctx->t9, (u32)ctx->gp, (u32)ctx->sp);
+ osSyncPrintf("S8:%08xH RA:%08xH LO:%08xH\n", (u32)ctx->s8, (u32)ctx->ra, (u32)ctx->lo);
+ osSyncPrintf("\n");
+
+ fault_LogFPCSR(ctx->fpcsr);
+ osSyncPrintf("\n");
+
+ fault_LogFReg(0, &ctx->fp0.f.f_even);
+ fault_LogFReg(2, &ctx->fp2.f.f_even);
+ osSyncPrintf("\n");
+ fault_LogFReg(4, &ctx->fp4.f.f_even);
+ fault_LogFReg(6, &ctx->fp6.f.f_even);
+ osSyncPrintf("\n");
+ fault_LogFReg(8, &ctx->fp8.f.f_even);
+ fault_LogFReg(10, &ctx->fp10.f.f_even);
+ osSyncPrintf("\n");
+ fault_LogFReg(12, &ctx->fp12.f.f_even);
+ fault_LogFReg(14, &ctx->fp14.f.f_even);
+ osSyncPrintf("\n");
+ fault_LogFReg(16, &ctx->fp16.f.f_even);
+ fault_LogFReg(18, &ctx->fp18.f.f_even);
+ osSyncPrintf("\n");
+ fault_LogFReg(20, &ctx->fp20.f.f_even);
+ fault_LogFReg(22, &ctx->fp22.f.f_even);
+ osSyncPrintf("\n");
+ fault_LogFReg(24, &ctx->fp24.f.f_even);
+ fault_LogFReg(26, &ctx->fp26.f.f_even);
+ osSyncPrintf("\n");
+ fault_LogFReg(28, &ctx->fp28.f.f_even);
+ fault_LogFReg(30, &ctx->fp30.f.f_even);
+ osSyncPrintf("\n");
+}
+
+/**
+ * Iterates through the active thread queue for a user thread with either
+ * the CPU break or Fault flag set.
+ */
+OSThread* FindFaultedThread(void) {
+ OSThread* thread = __osGetActiveQueue();
+
+ // -1 indicates the end of the thread queue
+ while (thread->priority != -1) {
+ if (thread->priority > OS_PRIORITY_IDLE && thread->priority < OS_PRIORITY_APPMAX &&
+ (thread->flags & (OS_FLAG_CPU_BREAK | OS_FLAG_FAULT))) {
+ return thread;
+ }
+ thread = thread->tlnext;
+ }
+ return NULL;
+}
+
+void fault_Wait5Seconds(void) {
+ UNUSED s32 pad;
+ OSTime start = osGetTime();
+
+ do {
+ fault_Sleep(1000 / 60);
+ } while ((osGetTime() - start) < OS_USEC_TO_CYCLES(5000000) + 1);
+
+ sFaultInstance->autoScroll = true;
+}
+
+/**
+ * Waits for the following button combination to be entered before returning:
+ *
+ * (L & R & Z) + DPad-Up + C-Down + C-Up + DPad-Down + DPad-Left + C-Left + C-Right + DPad-Right + (B & A & START)
+ */
+void fault_WaitForButtonCombo(void) {
+ Input* input = &sFaultInstance->inputs[0];
+ s32 state;
+ u32 s1;
+ u32 s2;
+ u32 pressedBtn;
+ u32 curBtn;
+
+ //! FAKE:
+ if (1) {}
+ if (1) {}
+
+ osSyncPrintf(
+ VT_FGCOL(WHITE) "KeyWaitB (LRZ " VT_FGCOL(WHITE) "上" VT_FGCOL(YELLOW) "下 " VT_FGCOL(YELLOW) "上" VT_FGCOL(WHITE) "下 " VT_FGCOL(WHITE) "左" VT_FGCOL(
+ YELLOW) "左 " VT_FGCOL(YELLOW) "右" VT_FGCOL(WHITE) "右 " VT_FGCOL(GREEN) "B" VT_FGCOL(BLUE) "A" VT_FGCOL(RED) "START" VT_FGCOL(WHITE) ")" VT_RST
+ "\n");
+ osSyncPrintf(VT_FGCOL(WHITE) "KeyWaitB'(LR左" VT_FGCOL(YELLOW) "右 +" VT_FGCOL(RED) "START" VT_FGCOL(
+ WHITE) ")" VT_RST "\n");
+
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 1));
+
+ state = 0;
+ s1 = 0;
+ s2 = 1;
+
+ while (state != 11) {
+ fault_Sleep(1000 / 60);
+ fault_UpdatePadImpl();
+
+ pressedBtn = input->press.button;
+ curBtn = input->cur.button;
+
+ if (curBtn == 0 && s1 == s2) {
+ s1 = 0;
+ } else if (pressedBtn != 0) {
+ if (s1 == s2) {
+ state = 0;
+ }
+
+ switch (state) {
+ case 0:
+ if (curBtn == (Z_TRIG | L_TRIG | R_TRIG) && pressedBtn == Z_TRIG) {
+ state = s2;
+ s1 = s2;
+ }
+ break;
+
+ case 1:
+ if (pressedBtn == U_JPAD) {
+ state = 2;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 2:
+ if (pressedBtn == D_CBUTTONS) {
+ state = 3;
+ s1 = s2;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 3:
+ if (pressedBtn == U_CBUTTONS) {
+ state = 4;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 4:
+ if (pressedBtn == D_JPAD) {
+ state = 5;
+ s1 = s2;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 5:
+ if (pressedBtn == L_JPAD) {
+ state = 6;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 6:
+ if (pressedBtn == L_CBUTTONS) {
+ state = 7;
+ s1 = s2;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 7:
+ if (pressedBtn == R_CBUTTONS) {
+ state = 8;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 8:
+ if (pressedBtn == R_JPAD) {
+ state = 9;
+ s1 = s2;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 9:
+ if (pressedBtn == (A_BUTTON | B_BUTTON)) {
+ state = 10;
+ } else if (pressedBtn == A_BUTTON) {
+ state = 0x5B;
+ } else if (pressedBtn == B_BUTTON) {
+ state = 0x5C;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 0x5B:
+ if (pressedBtn == B_BUTTON) {
+ state = 10;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 0x5C:
+ if (pressedBtn == A_BUTTON) {
+ state = 10;
+ } else {
+ state = 0;
+ }
+ break;
+
+ case 10:
+ if (pressedBtn == START_BUTTON) {
+ state = 11;
+ } else {
+ state = 0;
+ }
+ break;
+ }
+ }
+
+ osWritebackDCacheAll();
+ }
+}
+
+void fault_DrawMemDumpContents(const char* title, uintptr_t addr, UNUSED u32 arg2) {
+ uintptr_t alignedAddr = addr;
+ u32* writeAddr;
+ s32 y;
+ s32 x;
+
+ // Ensure address is within the bounds of RDRAM (fault_DrawMemDump has already done this)
+ if (alignedAddr < K0BASE) {
+ alignedAddr = K0BASE;
+ }
+ // 8MB RAM, leave room to display 0x100 bytes on the final page
+ //! @bug The loop below draws 22 * 4 * 4 = 0x160 bytes per page. Due to this, by scrolling further than
+ //! 0x807FFEA0 some invalid bytes are read from outside of 8MB RDRAM space. This does not cause a crash,
+ //! however the values it displays are meaningless. On N64 hardware these invalid addresses are read as 0.
+ if (alignedAddr > K0BASE + 0x800000 - 0x100) {
+ alignedAddr = K0BASE + 0x800000 - 0x100;
+ }
+
+ // Ensure address is word-aligned
+ alignedAddr &= ~3;
+ writeAddr = (u32*)alignedAddr;
+
+ // Reset screen
+ fault_FillScreenBlack();
+ FaultDrawer_SetCharPad(-2, 0);
+
+ FaultDrawer_DrawText(36, 18, "%s %08x", title != NULL ? title : "PrintDump", alignedAddr);
+
+ // Draw memory page contents
+ if (alignedAddr >= K0BASE && alignedAddr < K2BASE) {
+ for (y = 0; y < 22; y++) {
+ FaultDrawer_DrawText(24, 28 + y * 9, "%06x", writeAddr);
+ for (x = 0; x < 4; x++) {
+ FaultDrawer_DrawText(82 + x * 52, 28 + y * 9, "%08x", *writeAddr++);
+ }
+ }
+ }
+
+ FaultDrawer_SetCharPad(0, 0);
+}
+
+/**
+ * Draws the memory dump page.
+ *
+ * DPad-Up scrolls up.
+ * DPad-Down scrolls down.
+ * Holding Z while scrolling speeds up scrolling by a factor of 0x10.
+ * Holding B while scrolling speeds up scrolling by a factor of 0x100.
+ *
+ * L toggles auto-scrolling pages.
+ * START and A move on to the next page.
+ *
+ * @param pc Program counter, pressing C-Up jumps to this address
+ * @param sp Stack pointer, pressing C-Down jumps to this address
+ * @param cLeftJump Unused parameter, pressing C-Left jumps to this address
+ * @param cRightJump Unused parameter, pressing C-Right jumps to this address
+ */
+void fault_DrawMemDump(uintptr_t pc, uintptr_t sp, uintptr_t cLeftJump, uintptr_t cRightJump) {
+ Input* input = &sFaultInstance->inputs[0];
+ uintptr_t addr = pc;
+ s32 scrollCountdown;
+ u32 off;
+
+ do {
+ scrollCountdown = 0;
+ // Ensure address is within the bounds of RDRAM
+ if (addr < K0BASE) {
+ addr = K0BASE;
+ }
+ // 8MB RAM, leave room to display 0x100 bytes on the final page
+ if (addr > K0BASE + 0x800000 - 0x100) {
+ addr = K0BASE + 0x800000 - 0x100;
+ }
+
+ // Align the address to 0x10 bytes and draw the page contents
+ addr &= ~0xF;
+ fault_DrawMemDumpContents("Dump", addr, 0);
+ scrollCountdown = 600;
+
+ while (sFaultInstance->autoScroll) {
+ // Count down until it's time to move on to the next page
+ if (scrollCountdown == 0) {
+ return;
+ }
+
+ scrollCountdown--;
+ fault_Sleep(1000 / 60);
+ fault_UpdatePadImpl();
+ if (CHECK_BTN_ALL(input->press.button, L_TRIG)) {
+ // Disable auto-scrolling
+ sFaultInstance->autoScroll = false;
+ }
+ }
+
+ // Wait for input
+ do {
+ fault_Sleep(1000 / 60);
+ fault_UpdatePadImpl();
+ } while (input->press.button == 0);
+
+ // Move to next page
+ if (CHECK_BTN_ALL(input->press.button, START_BUTTON) || CHECK_BTN_ALL(input->cur.button, A_BUTTON)) {
+ return;
+ }
+
+ // Memory dump controls
+ off = 0x10;
+
+ if (CHECK_BTN_ALL(input->cur.button, Z_TRIG)) {
+ off *= 0x10;
+ }
+
+ if (CHECK_BTN_ALL(input->cur.button, B_BUTTON)) {
+ off *= 0x100;
+ }
+
+ if (CHECK_BTN_ALL(input->press.button, U_JPAD)) {
+ addr -= off;
+ }
+
+ if (CHECK_BTN_ALL(input->press.button, D_JPAD)) {
+ addr += off;
+ }
+
+ if (CHECK_BTN_ALL(input->press.button, U_CBUTTONS)) {
+ addr = pc;
+ }
+
+ if (CHECK_BTN_ALL(input->press.button, D_CBUTTONS)) {
+ addr = sp;
+ }
+
+ if (CHECK_BTN_ALL(input->press.button, L_CBUTTONS)) {
+ addr = cLeftJump;
+ }
+
+ if (CHECK_BTN_ALL(input->press.button, R_CBUTTONS)) {
+ addr = cRightJump;
+ }
+ } while (!CHECK_BTN_ALL(input->press.button, L_TRIG));
+
+ // Resume auto-scroll and move to next page
+ sFaultInstance->autoScroll = true;
+}
+
+/**
+ * Searches a single function's stack frame for the function it was called from.
+ * There are two cases that must be covered: Leaf and non-leaf functions.
+ *
+ * A leaf function is one that does not call any other function, in this case the
+ * return address need not be saved to the stack. Since a leaf function does not
+ * call other functions, only the function the stack trace begins in could possibly
+ * be a leaf function, in which case the return address is in the thread context's
+ * $ra already, as it never left.
+ *
+ * The procedure is therefore
+ * - Iterate instructions
+ * - Once jr $ra is found, set pc to $ra
+ * - Done after delay slot
+ *
+ * A non-leaf function calls other functions, it is necessary for the return address
+ * to be saved to the stack. In these functions, it is important to keep track of the
+ * stack frame size of each function.
+ *
+ * The procedure is therefore
+ * - Iterate instructions
+ * - If lw $ra <imm>($sp) is found, fetch the saved $ra from stack memory
+ * - If addiu $sp, $sp, <imm> is found, modify $sp by the immediate value
+ * - If jr $ra is found, set pc to $ra
+ * - Done after delay slot
+ *
+ * Note that searching for one jr $ra is sufficient, as only leaf functions can have
+ * multiple jr $ra in the same function.
+ *
+ * There is also additional handling for eret and j. Neither of these instructions
+ * appear in IDO compiled C, however do show up in the exception handler. It is not
+ * possible to backtrace through an eret as an interrupt can occur at any time, so
+ * there is no choice but to give up here. For j instructions, they can be followed
+ * and the backtrace may continue as normal.
+ */
+void fault_WalkStack(uintptr_t* spPtr, uintptr_t* pcPtr, uintptr_t* raPtr) {
+ uintptr_t sp = *spPtr;
+ uintptr_t pc = *pcPtr;
+ uintptr_t ra = *raPtr;
+ s32 count = 0x10000; // maximum number of instructions to search through
+ u32 lastInsn;
+ u32 insn;
+ u16 insnHi;
+ s16 insnLo;
+ u32 imm;
+
+ // ensure $sp and $ra are aligned and valid pointers, if they aren't a stack
+ // trace cannot be generated
+ if (sp % 4 != 0 || !IS_KSEG0(sp) || ra % 4 != 0 || !IS_KSEG0(ra)) {
+ *raPtr = *pcPtr = *spPtr = 0;
+ return;
+ }
+
+ // ensure pc is aligned and a valid pointer, if not a stack trace cannot
+ // be generated
+ if (pc % 4 != 0 || !IS_KSEG0(pc)) {
+ *pcPtr = ra;
+ return;
+ }
+
+ lastInsn = 0;
+ while (true) {
+ insn = *(u32*)K0_TO_K1(pc);
+ insnHi = insn >> 16;
+ insnLo = insn & 0xFFFF;
+ imm = insnLo;
+
+ if (insnHi == 0x8FBF) {
+ // lw $ra, <imm>($sp)
+ // read return address saved on the stack
+ ra = *(uintptr_t*)K0_TO_K1(sp + imm);
+ } else if (insnHi == 0x27BD) {
+ // addiu $sp, $sp, <imm>
+ // stack pointer increment or decrement
+ sp += imm;
+ } else if (insn == 0x42000018) {
+ // eret
+ // cannot backtrace through an eret, give up
+ ra = pc = sp = 0;
+ goto done;
+ }
+ if (lastInsn == 0x03E00008) {
+ // jr $ra
+ // return to previous function
+ pc = ra;
+ goto done;
+ } else if (lastInsn >> 26 == 2) {
+ // j <target>
+ // extract jump target
+ pc = pc >> 28 << 28 | lastInsn << 6 >> 4;
+ goto done;
+ }
+
+ lastInsn = insn;
+ pc += sizeof(u32);
+ if (count == 0) {
+ break;
+ }
+ count--;
+ }
+ // Hit the maximum number of instructions to search, give up
+ ra = pc = sp = 0;
+
+done:
+ *spPtr = sp;
+ *pcPtr = pc;
+ *raPtr = ra;
+}
+
+/**
+ * Draws the stack trace page contents for the specified thread
+ */
+void fault_DrawStackTrace(OSThread* thread, s32 x, s32 y, s32 height) {
+ s32 line;
+ uintptr_t sp = thread->context.sp;
+ uintptr_t ra = thread->context.ra;
+ uintptr_t pc = thread->context.pc;
+ uintptr_t addr;
+
+ FaultDrawer_DrawText(x, y, "SP PC (VPC)");
+
+ // Backtrace from the current function to the start of the thread
+ for (line = 1; line < height && (ra != 0 || sp != 0) && pc != (uintptr_t)__osCleanupThread; line++) {
+ FaultDrawer_DrawText(x, y + line * 8, "%08x %08x", sp, pc);
+ // Convert relocated address to virtual address if applicable
+ addr = fault_ConvertAddress(pc);
+ if (addr != 0) {
+ FaultDrawer_Printf(" -> %08x", addr);
+ }
+ // Search one function for the previous function
+ fault_WalkStack(&sp, &pc, &ra);
+ }
+}
+
+void fault_LogStackTrace(OSThread* thread, s32 height) {
+ s32 line;
+ uintptr_t sp = thread->context.sp;
+ uintptr_t ra = thread->context.ra;
+ uintptr_t pc = thread->context.pc;
+ uintptr_t addr;
+ UNUSED s32 pad;
+
+ osSyncPrintf("STACK TRACE\nSP PC (VPC)\n");
+ for (line = 1; line < height && (ra != 0 || sp != 0) && pc != (uintptr_t)__osCleanupThread; line++) {
+ osSyncPrintf("%08x %08x", sp, pc);
+ addr = fault_ConvertAddress(pc);
+ if (addr != 0) {
+ osSyncPrintf(" -> %08x", addr);
+ }
+ osSyncPrintf("\n");
+ fault_WalkStack(&sp, &pc, &ra);
+ }
+}
+
+void fault_ResumeThread(OSThread* thread) {
+ thread->context.cause = 0;
+ thread->context.fpcsr = 0;
+ thread->context.pc += sizeof(u32);
+ *((u32*)thread->context.pc) = 0x0000000D; // write in a break instruction
+ osWritebackDCache((void*)thread->context.pc, 4);
+ osInvalICache((void*)thread->context.pc, 4);
+ osStartThread(thread);
+}
+
+void fault_DisplayFrameBuffer(void) {
+ void* fb;
+
+ osViSetYScale(1.0f);
+ osViSetMode(&osViModeNtscLan1);
+ osViSetSpecialFeatures(OS_VI_GAMMA_OFF | OS_VI_DITHER_FILTER_ON);
+ osViBlack(false);
+
+ if (sFaultInstance->fb != NULL) {
+ fb = sFaultInstance->fb;
+ } else {
+ fb = osViGetNextFramebuffer();
+ if ((uintptr_t)fb == K0BASE) {
+ fb = (void*)(PHYS_TO_K0(osMemSize) - sizeof(u16[SCREEN_HEIGHT][SCREEN_WIDTH]));
+ }
+ }
+
+ osViSwapBuffer(fb);
+ FaultDrawer_SetDrawerFB(fb, SCREEN_WIDTH, SCREEN_HEIGHT);
+}
+
+/**
+ * Runs all registered fault clients. Each fault client displays a page
+ * on the crash screen.
+ */
+void fault_ProcessClients(void) {
+ FaultClient* client = sFaultInstance->clients;
+ s32 idx = 0;
+
+ while (client != NULL) {
+ if (client->callback != NULL) {
+ fault_FillScreenBlack();
+ FaultDrawer_SetCharPad(-2, 0);
+ FaultDrawer_Printf(FAULT_COLOR(DARK_GRAY) "CallBack (%d) %08x %08x %08x\n" FAULT_COLOR(WHITE), idx++,
+ client, client->arg0, client->arg1);
+ FaultDrawer_SetCharPad(0, 0);
+ fault_ProcessClient(client->callback, client->arg0, client->arg1);
+ fault_WaitForInput();
+ fault_DisplayFrameBuffer();
+ }
+ client = client->next;
+ }
+}
+
+void fault_UpdatePad(void) {
+ fault_UpdatePadImpl();
+}
+
+#define FAULT_MSG_CPU_BREAK ((OSMesg)1)
+#define FAULT_MSG_FAULT ((OSMesg)2)
+#define FAULT_MSG_UNK ((OSMesg)3)
+
+void fault_ThreadEntry(UNUSED void* arg) {
+ OSMesg msg;
+ OSThread* faultedThread;
+ UNUSED s32 pad;
+
+ // Direct OS event messages to the fault event queue
+ osSetEventMesg(OS_EVENT_CPU_BREAK, &sFaultInstance->queue, FAULT_MSG_CPU_BREAK);
+ osSetEventMesg(OS_EVENT_FAULT, &sFaultInstance->queue, FAULT_MSG_FAULT);
+
+ while (true) {
+ do {
+ // Wait for a thread to hit a fault
+ osRecvMesg(&sFaultInstance->queue, &msg, OS_MESG_BLOCK);
+
+ if (msg == FAULT_MSG_CPU_BREAK) {
+ sFaultInstance->msgId = (u32)FAULT_MSG_CPU_BREAK;
+ // "Fault manager: OS_EVENT_CPU_BREAK received"
+ osSyncPrintf("フォルトマネージャ:OS_EVENT_CPU_BREAKを受信しました\n");
+ } else if (msg == FAULT_MSG_FAULT) {
+ sFaultInstance->msgId = (u32)FAULT_MSG_FAULT;
+ // "Fault manager: OS_EVENT_FAULT received"
+ osSyncPrintf("フォルトマネージャ:OS_EVENT_FAULTを受信しました\n");
+ } else if (msg == FAULT_MSG_UNK) {
+ fault_UpdatePad();
+ faultedThread = NULL;
+ continue;
+ } else {
+ sFaultInstance->msgId = (u32)FAULT_MSG_UNK;
+ // "Fault manager: received an unknown message"
+ osSyncPrintf("フォルトマネージャ:不明なメッセージを受信しました\n");
+ }
+
+ faultedThread = __osGetCurrFaultedThread();
+ osSyncPrintf("__osGetCurrFaultedThread()=%08x\n", faultedThread);
+
+ if (faultedThread == NULL) {
+ faultedThread = FindFaultedThread();
+ osSyncPrintf("FindFaultedThread()=%08x\n", faultedThread);
+ }
+ } while (faultedThread == NULL);
+
+ // Disable floating-point related exceptions
+ __osSetFpcCsr(__osGetFpcCsr() & ~(FPCSR_EV | FPCSR_EZ | FPCSR_EO | FPCSR_EU | FPCSR_EI));
+ sFaultInstance->faultedThread = faultedThread;
+
+ while (!sFaultInstance->faultHandlerEnabled) {
+ fault_Sleep(1000);
+ }
+ fault_Sleep(1000 / 2);
+
+ // Show fault framebuffer
+ fault_DisplayFrameBuffer();
+
+ if (sFaultInstance->autoScroll) {
+ fault_Wait5Seconds();
+ } else {
+ // Draw error bar signifying the crash screen is available
+ fault_DrawCornerRec(GPACK_RGBA5551(255, 0, 0, 1));
+ fault_WaitForButtonCombo();
+ }
+
+ // Set auto-scrolling and default colors
+ sFaultInstance->autoScroll = true;
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0));
+
+ // Draw pages
+ do {
+ // Thread context page
+ fault_PrintThreadContext(faultedThread);
+ fault_LogThreadContext(faultedThread);
+ fault_WaitForInput();
+ // Stack trace page
+ fault_FillScreenBlack();
+ FaultDrawer_DrawText(120, 16, "STACK TRACE");
+ fault_DrawStackTrace(faultedThread, 36, 24, 22);
+ fault_LogStackTrace(faultedThread, 50);
+ fault_WaitForInput();
+ // Client pages
+ fault_ProcessClients();
+ // Memory dump page
+ fault_DrawMemDump(faultedThread->context.pc - 0x100, (uintptr_t)faultedThread->context.sp, 0, 0);
+ // End page
+ fault_FillScreenRed();
+ FaultDrawer_DrawText(64, 80, " CONGRATURATIONS! ");
+ FaultDrawer_DrawText(64, 90, "All Pages are displayed.");
+ FaultDrawer_DrawText(64, 100, " THANK YOU! ");
+ FaultDrawer_DrawText(64, 110, " You are great debugger!");
+ fault_WaitForInput();
+ } while (!sFaultInstance->exit);
+
+ while (!sFaultInstance->exit) {}
+
+ fault_ResumeThread(faultedThread);
+ }
+}
+
+void fault_SetFrameBuffer(void* fb, u16 w, u16 h) {
+ sFaultInstance->fb = fb;
+ FaultDrawer_SetDrawerFB(fb, w, h);
+}
+
+void fault_Init(void) {
+ sFaultInstance = &gFaultMgr;
+ bzero(sFaultInstance, sizeof(FaultMgr));
+ FaultDrawer_Init();
+ FaultDrawer_SetInputCallback(fault_WaitForInput);
+ sFaultInstance->exit = false;
+ sFaultInstance->msgId = 0;
+ sFaultInstance->faultHandlerEnabled = false;
+ sFaultInstance->faultedThread = NULL;
+ sFaultInstance->padCallback = fault_PadCallback;
+ sFaultInstance->clients = NULL;
+ sFaultInstance->autoScroll = false;
+ gFaultMgr.faultHandlerEnabled = true;
+ osCreateMesgQueue(&sFaultInstance->queue, sFaultInstance->msg, ARRAY_COUNT(sFaultInstance->msg));
+ StackCheck_Init(&sFaultThreadInfo, sFaultStack, STACK_TOP(sFaultStack), 0, 0x100, "fault");
+ osCreateThread(&sFaultInstance->thread, M_THREAD_ID_FAULT, fault_ThreadEntry, NULL, STACK_TOP(sFaultStack),
+ M_PRIORITY_FAULT);
+ osStartThread(&sFaultInstance->thread);
+}
+
+/**
+ * Fault page for Hungup crashes. Displays the thread id and two messages
+ * specified in arguments to `fault_AddHungupAndCrashImpl`.
+ */
+void fault_HungupFaultClient(const char* exp1, const char* exp2) {
+ osSyncPrintf("HungUp on Thread %d\n", osGetThreadId(NULL));
+ osSyncPrintf("%s\n", exp1 != NULL ? exp1 : "(NULL)");
+ osSyncPrintf("%s\n", exp2 != NULL ? exp2 : "(NULL)");
+ FaultDrawer_Printf("HungUp on Thread %d\n", osGetThreadId(NULL));
+ FaultDrawer_Printf("%s\n", exp1 != NULL ? exp1 : "(NULL)");
+ FaultDrawer_Printf("%s\n", exp2 != NULL ? exp2 : "(NULL)");
+}
+
+/**
+ * Immediately crashes the current thread, for cases where an irrecoverable
+ * error occurs. The parameters specify two messages detailing the error, one
+ * or both may be NULL.
+ */
+void fault_AddHungupAndCrashImpl(const char* exp1, const char* exp2) {
+ FaultClient client;
+ UNUSED s32 pad;
+
+ fault_AddClient(&client, (void*)fault_HungupFaultClient, (void*)exp1, (void*)exp2);
+ *(u32*)0x11111111 = 0; // trigger an exception via unaligned memory access
+}
+
+/**
+ * Like `fault_AddHungupAndCrashImpl`, however provides a fixed message containing
+ * filename and line number
+ */
+void fault_AddHungupAndCrash(const char* file, s32 line) {
+ char msg[256];
+
+ sprintf(msg, "HungUp %s:%d", file, line);
+ fault_AddHungupAndCrashImpl(msg, NULL);
+}
diff --git a/src/boot/fault_drawer.c b/src/boot/fault_drawer.c
new file mode 100644
index 0000000..1df4080
--- /dev/null
+++ b/src/boot/fault_drawer.c
@@ -0,0 +1,318 @@
+/**
+ * @file fault_drawer.c
+ *
+ * Implements routines for drawing text with a fixed font directly to a framebuffer, used in displaying
+ * the crash screen implemented by fault.c
+ */
+#include "fault.h"
+#include "macros.h"
+#include "libc64/aprintf.h"
+#include "libc/stdarg.h"
+#include "libc/stdbool.h"
+#include "terminal.h"
+
+typedef struct {
+ /* 0x00 */ u16* fb;
+ /* 0x04 */ u16 w;
+ /* 0x06 */ u16 h;
+ /* 0x08 */ u16 yStart;
+ /* 0x0A */ u16 yEnd;
+ /* 0x0C */ u16 xStart;
+ /* 0x0E */ u16 xEnd;
+ /* 0x10 */ u16 foreColor;
+ /* 0x12 */ u16 backColor;
+ /* 0x14 */ u16 cursorX;
+ /* 0x16 */ u16 cursorY;
+ /* 0x18 */ const u32* fontData;
+ /* 0x1C */ u8 charW;
+ /* 0x1D */ u8 charH;
+ /* 0x1E */ s8 charWPad;
+ /* 0x1F */ s8 charHPad;
+ /* 0x20 */ u16 printColors[10];
+ /* 0x34 */ u8 escCode; // bool
+ /* 0x35 */ u8 osSyncPrintfEnabled;
+ /* 0x38 */ void (*inputCallback)(void);
+} FaultDrawer; // size = 0x3C
+
+// TODO: Extract sFaultDrawerFont
+extern const u32 sFaultDrawerFont[];
+#pragma GLOBAL_ASM("asm/jp/nonmatchings/boot/fault_drawer/sFaultDrawerFont.s")
+
+#define FAULT_DRAWER_CURSOR_X 22
+#define FAULT_DRAWER_CURSOR_Y 16
+
+FaultDrawer sFaultDrawerDefault = {
+ (u16*)(PHYS_TO_K0(0x400000) - sizeof(u16[SCREEN_HEIGHT][SCREEN_WIDTH])),
+ SCREEN_WIDTH,
+ SCREEN_HEIGHT,
+ FAULT_DRAWER_CURSOR_Y,
+ SCREEN_HEIGHT - FAULT_DRAWER_CURSOR_Y - 1,
+ FAULT_DRAWER_CURSOR_X,
+ SCREEN_WIDTH - FAULT_DRAWER_CURSOR_X - 1,
+ GPACK_RGBA5551(255, 255, 255, 1),
+ GPACK_RGBA5551(0, 0, 0, 0),
+ FAULT_DRAWER_CURSOR_X,
+ FAULT_DRAWER_CURSOR_Y,
+ sFaultDrawerFont,
+ 8,
+ 8,
+ 0,
+ 0,
+ {
+ GPACK_RGBA5551(0, 0, 0, 1),
+ GPACK_RGBA5551(255, 0, 0, 1),
+ GPACK_RGBA5551(0, 255, 0, 1),
+ GPACK_RGBA5551(255, 255, 0, 1),
+ GPACK_RGBA5551(0, 0, 255, 1),
+ GPACK_RGBA5551(255, 0, 255, 1),
+ GPACK_RGBA5551(0, 255, 255, 1),
+ GPACK_RGBA5551(255, 255, 255, 1),
+ GPACK_RGBA5551(120, 120, 120, 1),
+ GPACK_RGBA5551(176, 176, 176, 1),
+ },
+ false,
+ false,
+ NULL,
+};
+
+FaultDrawer sFaultDrawer;
+
+void FaultDrawer_SetOsSyncPrintfEnabled(u32 enabled) {
+ sFaultDrawer.osSyncPrintfEnabled = enabled;
+}
+
+void FaultDrawer_DrawRecImpl(s32 xStart, s32 yStart, s32 xEnd, s32 yEnd, u16 color) {
+ u16* fb;
+ s32 x;
+ s32 y;
+ s32 xDiff = sFaultDrawer.w - xStart;
+ s32 yDiff = sFaultDrawer.h - yStart;
+ s32 xSize = xEnd - xStart + 1;
+ s32 ySize = yEnd - yStart + 1;
+
+ if ((xDiff > 0) && (yDiff > 0)) {
+ if (xDiff < xSize) {
+ xSize = xDiff;
+ }
+
+ if (yDiff < ySize) {
+ ySize = yDiff;
+ }
+
+ fb = sFaultDrawer.fb + sFaultDrawer.w * yStart + xStart;
+ for (y = 0; y < ySize; y++) {
+ for (x = 0; x < xSize; x++) {
+ *fb++ = color;
+ }
+ fb += sFaultDrawer.w - xSize;
+ }
+
+ osWritebackDCacheAll();
+ }
+}
+
+void FaultDrawer_DrawChar(char c) {
+ FaultDrawer* faultDrawer = &sFaultDrawer;
+ s32 x;
+ s32 y;
+ u32 data;
+ s32 cursorX = sFaultDrawer.cursorX;
+ s32 cursorY = sFaultDrawer.cursorY;
+ s32 shift = c % 4;
+ const u32* dataPtr = &faultDrawer->fontData[((c / 8) * 16) + ((c & 4) >> 2)];
+ u16* fb = sFaultDrawer.fb + (sFaultDrawer.w * cursorY) + cursorX;
+
+ if ((sFaultDrawer.xStart <= cursorX) && ((sFaultDrawer.charW + cursorX - 1) <= sFaultDrawer.xEnd) &&
+ (sFaultDrawer.yStart <= cursorY) && ((sFaultDrawer.charH + cursorY - 1) <= sFaultDrawer.yEnd)) {
+ for (y = 0; y < sFaultDrawer.charH; y++) {
+ u32 mask = 0x10000000 << shift;
+
+ data = *dataPtr;
+ for (x = 0; x < sFaultDrawer.charW; x++) {
+ if (mask & data) {
+ fb[x] = sFaultDrawer.foreColor;
+ } else if (sFaultDrawer.backColor & 1) {
+ fb[x] = sFaultDrawer.backColor;
+ }
+ mask >>= 4;
+ }
+ fb += sFaultDrawer.w;
+ dataPtr += 2;
+ }
+ }
+}
+
+s32 FaultDrawer_ColorToPrintColor(u16 color) {
+ s32 i;
+
+ for (i = 0; i < ARRAY_COUNT(sFaultDrawer.printColors); i++) {
+ if (color == sFaultDrawer.printColors[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void FaultDrawer_UpdatePrintColor(void) {
+ s32 idx;
+
+ if (sFaultDrawer.osSyncPrintfEnabled) {
+ osSyncPrintf(VT_RST);
+
+ idx = FaultDrawer_ColorToPrintColor(sFaultDrawer.foreColor);
+ if (idx >= 0 && idx < ARRAY_COUNT(sFaultDrawer.printColors) - 2) {
+ osSyncPrintf(VT_SGR("3%d"), idx);
+ }
+
+ idx = FaultDrawer_ColorToPrintColor(sFaultDrawer.backColor);
+ if (idx >= 0 && idx < ARRAY_COUNT(sFaultDrawer.printColors) - 2) {
+ osSyncPrintf(VT_SGR("4%d"), idx);
+ }
+ }
+}
+
+void FaultDrawer_SetForeColor(u16 color) {
+ sFaultDrawer.foreColor = color;
+ FaultDrawer_UpdatePrintColor();
+}
+
+void FaultDrawer_SetBackColor(u16 color) {
+ sFaultDrawer.backColor = color;
+ FaultDrawer_UpdatePrintColor();
+}
+
+void FaultDrawer_SetFontColor(u16 color) {
+ FaultDrawer_SetForeColor(color | 1); // force alpha to be set
+}
+
+void FaultDrawer_SetCharPad(s8 padW, s8 padH) {
+ sFaultDrawer.charWPad = padW;
+ sFaultDrawer.charHPad = padH;
+}
+
+void FaultDrawer_SetCursor(s32 x, s32 y) {
+ if (sFaultDrawer.osSyncPrintfEnabled) {
+ osSyncPrintf(VT_CUP("%d", "%d"), (y - sFaultDrawer.yStart) / (sFaultDrawer.charH + sFaultDrawer.charHPad),
+ (x - sFaultDrawer.xStart) / (sFaultDrawer.charW + sFaultDrawer.charWPad));
+ }
+ sFaultDrawer.cursorX = x;
+ sFaultDrawer.cursorY = y;
+}
+
+void FaultDrawer_FillScreen(void) {
+ if (sFaultDrawer.osSyncPrintfEnabled) {
+ osSyncPrintf(VT_CLS);
+ }
+
+ FaultDrawer_DrawRecImpl(sFaultDrawer.xStart, sFaultDrawer.yStart, sFaultDrawer.xEnd, sFaultDrawer.yEnd,
+ sFaultDrawer.backColor | 1);
+ FaultDrawer_SetCursor(sFaultDrawer.xStart, sFaultDrawer.yStart);
+}
+
+char* FaultDrawer_PrintCallback(char* arg, const char* str, size_t count) {
+ for (; count != 0; count--, str++) {
+ s32 curXStart;
+ s32 curXEnd;
+
+ if (sFaultDrawer.escCode) {
+ sFaultDrawer.escCode = false;
+ if (*str > '0' && *str <= '9') {
+ FaultDrawer_SetForeColor(sFaultDrawer.printColors[*str - '0']);
+ }
+
+ curXStart = sFaultDrawer.cursorX;
+ curXEnd = sFaultDrawer.xEnd - sFaultDrawer.charW;
+ } else {
+ switch (*str) {
+ case '\n':
+ if (sFaultDrawer.osSyncPrintfEnabled) {
+ osSyncPrintf("\n");
+ }
+
+ sFaultDrawer.cursorX = sFaultDrawer.w;
+ curXStart = sFaultDrawer.cursorX;
+ curXEnd = sFaultDrawer.xEnd - sFaultDrawer.charW;
+ break;
+
+ case FAULT_ESC:
+ sFaultDrawer.escCode = true;
+ curXStart = sFaultDrawer.cursorX;
+ curXEnd = sFaultDrawer.xEnd - sFaultDrawer.charW;
+ break;
+
+ default:
+ if (sFaultDrawer.osSyncPrintfEnabled) {
+ osSyncPrintf("%c", *str);
+ }
+
+ FaultDrawer_DrawChar(*str);
+ sFaultDrawer.cursorX += sFaultDrawer.charW + sFaultDrawer.charWPad;
+
+ curXStart = sFaultDrawer.cursorX;
+ curXEnd = sFaultDrawer.xEnd - sFaultDrawer.charW;
+ break;
+ }
+ }
+
+ if (curXEnd <= curXStart) {
+ sFaultDrawer.cursorX = sFaultDrawer.xStart;
+ sFaultDrawer.cursorY += sFaultDrawer.charH + sFaultDrawer.charHPad;
+ if (sFaultDrawer.yEnd - sFaultDrawer.charH <= sFaultDrawer.cursorY) {
+ if (sFaultDrawer.inputCallback) {
+ sFaultDrawer.inputCallback();
+ FaultDrawer_FillScreen();
+ }
+ sFaultDrawer.cursorY = sFaultDrawer.yStart;
+ }
+ }
+ }
+ osWritebackDCacheAll();
+
+ return arg;
+}
+
+const char D_8003E280[] = "(null)";
+
+s32 FaultDrawer_VPrintf(const char* fmt, va_list args) {
+ return _Printf(FaultDrawer_PrintCallback, (void*)&sFaultDrawer, fmt, args);
+}
+
+s32 FaultDrawer_Printf(const char* fmt, ...) {
+ s32 ret;
+ va_list args;
+ va_start(args, fmt);
+
+ ret = FaultDrawer_VPrintf(fmt, args);
+
+ va_end(args);
+ return ret;
+}
+
+void FaultDrawer_DrawText(s32 x, s32 y, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ FaultDrawer_SetCursor(x, y);
+ FaultDrawer_VPrintf(fmt, args);
+
+ va_end(args);
+}
+
+void FaultDrawer_SetDrawerFB(void* fb, u16 w, u16 h) {
+ sFaultDrawer.fb = fb;
+ sFaultDrawer.w = w;
+ sFaultDrawer.h = h;
+}
+
+void FaultDrawer_SetInputCallback(void (*callback)(void)) {
+ sFaultDrawer.inputCallback = callback;
+}
+
+void FaultDrawer_WritebackFBDCache(void) {
+ osWritebackDCache(sFaultDrawer.fb, sFaultDrawer.w * sFaultDrawer.h * sizeof(u16));
+}
+
+void FaultDrawer_Init(void) {
+ bcopy(&sFaultDrawerDefault, &sFaultDrawer, sizeof(FaultDrawer));
+ sFaultDrawer.fb = (u16*)(PHYS_TO_K0(osMemSize) - sizeof(u16[SCREEN_HEIGHT][SCREEN_WIDTH]));
+}
diff --git a/src/boot/ovlmgr.c b/src/boot/ovlmgr.c
index 9e20d96..d0abf57 100644
--- a/src/boot/ovlmgr.c
+++ b/src/boot/ovlmgr.c
@@ -49,7 +49,7 @@ void ovlmgr_LoadImpl(RomOffset vromStart, UNUSED RomOffset vromEndUnused, void*
if (ovlRelocs == NULL) {
sprintf(ovlSizeStr, "%ld", ovlSize);
- Fault_AddHungupAndCrashImpl("ovlmgr: Out of Memory", ovlSizeStr);
+ fault_AddHungupAndCrashImpl("ovlmgr: Out of Memory", ovlSizeStr);
}
Overlay_Load(vromStart, vromEnd, ovlStart, ovlEnd, vramStart, vramEnd, allocatedRamAddr, ovlRelocs);
diff --git a/src/boot/z_std_dma.c b/src/boot/z_std_dma.c
index 04184d9..05acca8 100644
--- a/src/boot/z_std_dma.c
+++ b/src/boot/z_std_dma.c
@@ -59,7 +59,7 @@ NORETURN void DmaMgr_Error(DmaRequest* req, const char* file, const char* errorN
}
sprintf(buff2, "%07X %08X %X %.50s", vrom, vram, size, (file != NULL) ? file : "???");
- Fault_AddHungupAndCrashImpl(buff1, buff2);
+ fault_AddHungupAndCrashImpl(buff1, buff2);
}
/**