mm/src/boot/fault.c

1130 lines
37 KiB
C

/**
* @file fault.c
*
* This file implements the screen that may be viewed when the game crashes.
* This is the second known version of the crash screen, an evolved version from OoT's.
*
* 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:
*
* (DPad-Left & L & R & C-Right) & Start
*
* When entering this button combination, buttons that are &'d together must all be pressed together.
*
* "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 "fault_internal.h"
#include "fault.h"
#include "prevent_bss_reordering2.h"
#include "global.h"
#include "vt.h"
#include "PR/osint.h"
#include "stackcheck.h"
#include "z64thread.h"
FaultMgr* sFaultInstance;
f32 sFaultTimeTotal; // read but not set anywhere
// data
const char* sCpuExceptions[] = {
"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* sFpuExceptions[] = {
"Unimplemented operation", "Invalid operation", "Division by zero", "Overflow", "Underflow", "Inexact operation",
};
void Fault_SleepImpl(u32 duration) {
OSTime value = (duration * OS_CPU_COUNTER) / 1000ULL;
Sleep_Cycles(value);
}
/**
* 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`.
*/
void Fault_AddClient(FaultClient* client, FaultClientCallback callback, void* arg0, void* arg1) {
OSIntMask mask;
u32 alreadyExists = false;
mask = osSetIntMask(1);
// Ensure the client is not already registered
{
FaultClient* iter = sFaultInstance->clients;
while (iter != NULL) {
if (iter == client) {
alreadyExists = true;
goto end;
}
iter = iter->next;
}
}
client->callback = callback;
client->arg0 = arg0;
client->arg1 = arg1;
client->next = sFaultInstance->clients;
sFaultInstance->clients = client;
end:
osSetIntMask(mask);
if (alreadyExists) {
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* iter = sFaultInstance->clients;
FaultClient* lastIter = NULL;
OSIntMask mask;
u32 listIsEmpty = false;
mask = osSetIntMask(1);
while (iter) {
if (iter == client) {
if (lastIter != NULL) {
lastIter->next = client->next;
} else {
sFaultInstance->clients = client;
if (sFaultInstance->clients) {
sFaultInstance->clients = client->next;
} else {
listIsEmpty = 1;
}
}
break;
}
lastIter = iter;
iter = iter->next;
}
osSetIntMask(mask);
if (listIsEmpty) {
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
*/
void Fault_AddAddrConvClient(FaultAddrConvClient* client, FaultAddrConvClientCallback callback, void* arg) {
OSIntMask mask;
s32 alreadyExists = false;
mask = osSetIntMask(1);
{
FaultAddrConvClient* iter = sFaultInstance->addrConvClients;
while (iter != NULL) {
if (iter == client) {
alreadyExists = true;
goto end;
}
iter = iter->next;
}
}
client->callback = callback;
client->arg = arg;
client->next = sFaultInstance->addrConvClients;
sFaultInstance->addrConvClients = client;
end:
osSetIntMask(mask);
if (alreadyExists) {
osSyncPrintf(VT_COL(RED, WHITE) "fault_AddressConverterAddClient: %08x は既にリスト中にある\n" VT_RST, client);
}
}
void Fault_RemoveAddrConvClient(FaultAddrConvClient* client) {
FaultAddrConvClient* iter = sFaultInstance->addrConvClients;
FaultAddrConvClient* lastIter = NULL;
OSIntMask mask;
bool listIsEmpty = false;
mask = osSetIntMask(1);
while (iter) {
if (iter == client) {
if (lastIter != NULL) {
lastIter->next = client->next;
} else {
sFaultInstance->addrConvClients = client;
if (sFaultInstance->addrConvClients) {
sFaultInstance->addrConvClients = client->next;
} else {
listIsEmpty = true;
}
}
break;
}
lastIter = iter;
iter = iter->next;
}
osSetIntMask(mask);
if (listIsEmpty) {
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) {
uintptr_t ret;
FaultAddrConvClient* iter = sFaultInstance->addrConvClients;
while (iter != NULL) {
if (iter->callback != NULL) {
ret = iter->callback(addr, iter->arg);
if (ret != 0) {
return ret;
}
}
iter = iter->next;
}
return 0;
}
void Fault_Sleep(u32 msec) {
Fault_SleepImpl(msec);
}
void Fault_PadCallback(Input* input) {
PadMgr_GetInput2(input, 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 == BTN_L) {
sFaultInstance->autoScroll = !sFaultInstance->autoScroll;
}
if (sFaultInstance->autoScroll) {
if (count-- < 1) {
return false;
}
} else {
if ((pressedBtn == BTN_A) || (pressedBtn == BTN_DRIGHT)) {
return false;
}
if (pressedBtn == BTN_DLEFT) {
return true;
}
if (pressedBtn == BTN_DUP) {
FaultDrawer_SetOsSyncPrintfEnabled(true);
}
if (pressedBtn == BTN_DDOWN) {
FaultDrawer_SetOsSyncPrintfEnabled(false);
}
}
}
}
void Fault_WaitForInput(void) {
Fault_WaitForInputImpl();
}
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 index, f32* value) {
u32 raw = *(u32*)value;
s32 v0 = ((raw & 0x7F800000) >> 0x17) - 0x7F;
if ((v0 >= -0x7E && v0 < 0x80) || raw == 0) {
FaultDrawer_Printf("F%02d:%14.7e ", index, *value);
} else {
// Print subnormal floats as their IEEE-754 hex representation
FaultDrawer_Printf("F%02d: %08x(16) ", index, raw);
}
}
void Fault_LogFReg(s32 idx, f32* value) {
u32 raw = *(u32*)value;
s32 v0 = ((raw & 0x7F800000) >> 0x17) - 0x7F;
if ((v0 >= -0x7E && v0 < 0x80) || raw == 0) {
osSyncPrintf("F%02d:%14.7e ", idx, *value);
} else {
osSyncPrintf("F%02d: %08x(16) ", idx, *(u32*)value);
}
}
void Fault_PrintFPCR(u32 value) {
s32 i;
u32 flag = 0x20000;
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(sFpuExceptions); i++) {
if (value & flag) {
FaultDrawer_Printf("(%s)", sFpuExceptions[i]);
break;
}
flag >>= 1;
}
FaultDrawer_Printf("\n");
}
void Fault_LogFPCSR(u32 value) {
s32 i;
u32 flag = 0x20000;
osSyncPrintf("FPCSR:%08xH ", value);
for (i = 0; i < ARRAY_COUNT(sFpuExceptions); i++) {
if (value & flag) {
osSyncPrintf("(%s)\n", sFpuExceptions[i]);
break;
}
flag >>= 1;
}
}
void Fault_PrintThreadContext(OSThread* thread) {
__OSThreadContext* threadCtx;
s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5);
if (causeStrIdx == 23) { // Watchpoint
causeStrIdx = 16;
}
if (causeStrIdx == 31) { // Virtual coherency on data
causeStrIdx = 17;
}
FaultDrawer_FillScreen();
FaultDrawer_SetCharPad(-2, 4);
FaultDrawer_SetCursor(22, 20);
threadCtx = &thread->context;
FaultDrawer_Printf("THREAD:%d (%d:%s)\n", thread->id, causeStrIdx, sCpuExceptions[causeStrIdx]);
FaultDrawer_SetCharPad(-1, 0);
FaultDrawer_Printf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)threadCtx->pc, (u32)threadCtx->sr,
(u32)threadCtx->badvaddr);
FaultDrawer_Printf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)threadCtx->at, (u32)threadCtx->v0, (u32)threadCtx->v1);
FaultDrawer_Printf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)threadCtx->a0, (u32)threadCtx->a1, (u32)threadCtx->a2);
FaultDrawer_Printf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)threadCtx->a3, (u32)threadCtx->t0, (u32)threadCtx->t1);
FaultDrawer_Printf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)threadCtx->t2, (u32)threadCtx->t3, (u32)threadCtx->t4);
FaultDrawer_Printf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)threadCtx->t5, (u32)threadCtx->t6, (u32)threadCtx->t7);
FaultDrawer_Printf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)threadCtx->s0, (u32)threadCtx->s1, (u32)threadCtx->s2);
FaultDrawer_Printf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)threadCtx->s3, (u32)threadCtx->s4, (u32)threadCtx->s5);
FaultDrawer_Printf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)threadCtx->s6, (u32)threadCtx->s7, (u32)threadCtx->t8);
FaultDrawer_Printf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)threadCtx->t9, (u32)threadCtx->gp, (u32)threadCtx->sp);
FaultDrawer_Printf("S8:%08xH RA:%08xH LO:%08xH\n\n", (u32)threadCtx->s8, (u32)threadCtx->ra, (u32)threadCtx->lo);
Fault_PrintFPCR(threadCtx->fpcsr);
FaultDrawer_Printf("\n");
Fault_PrintFReg(0, &threadCtx->fp0.f.f_even);
Fault_PrintFReg(2, &threadCtx->fp2.f.f_even);
FaultDrawer_Printf("\n");
Fault_PrintFReg(4, &threadCtx->fp4.f.f_even);
Fault_PrintFReg(6, &threadCtx->fp6.f.f_even);
FaultDrawer_Printf("\n");
Fault_PrintFReg(8, &threadCtx->fp8.f.f_even);
Fault_PrintFReg(0xA, &threadCtx->fp10.f.f_even);
FaultDrawer_Printf("\n");
Fault_PrintFReg(0xC, &threadCtx->fp12.f.f_even);
Fault_PrintFReg(0xE, &threadCtx->fp14.f.f_even);
FaultDrawer_Printf("\n");
Fault_PrintFReg(0x10, &threadCtx->fp16.f.f_even);
Fault_PrintFReg(0x12, &threadCtx->fp18.f.f_even);
FaultDrawer_Printf("\n");
Fault_PrintFReg(0x14, &threadCtx->fp20.f.f_even);
Fault_PrintFReg(0x16, &threadCtx->fp22.f.f_even);
FaultDrawer_Printf("\n");
Fault_PrintFReg(0x18, &threadCtx->fp24.f.f_even);
Fault_PrintFReg(0x1A, &threadCtx->fp26.f.f_even);
FaultDrawer_Printf("\n");
Fault_PrintFReg(0x1C, &threadCtx->fp28.f.f_even);
Fault_PrintFReg(0x1E, &threadCtx->fp30.f.f_even);
FaultDrawer_Printf("\n");
FaultDrawer_SetCharPad(0, 0);
if (sFaultTimeTotal != 0.0f) {
FaultDrawer_DrawText(160, 216, "%5.2f sec\n", sFaultTimeTotal);
}
}
void osSyncPrintfThreadContext(OSThread* thread) {
__OSThreadContext* threadCtx;
s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5);
if (causeStrIdx == 23) { // Watchpoint
causeStrIdx = 16;
}
if (causeStrIdx == 31) { // Virtual coherency on data
causeStrIdx = 17;
}
threadCtx = &thread->context;
osSyncPrintf("\n");
osSyncPrintf("THREAD ID:%d (%d:%s)\n", thread->id, causeStrIdx, sCpuExceptions[causeStrIdx]);
osSyncPrintf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)threadCtx->pc, (u32)threadCtx->sr, (u32)threadCtx->badvaddr);
osSyncPrintf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)threadCtx->at, (u32)threadCtx->v0, (u32)threadCtx->v1);
osSyncPrintf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)threadCtx->a0, (u32)threadCtx->a1, (u32)threadCtx->a2);
osSyncPrintf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)threadCtx->a3, (u32)threadCtx->t0, (u32)threadCtx->t1);
osSyncPrintf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)threadCtx->t2, (u32)threadCtx->t3, (u32)threadCtx->t4);
osSyncPrintf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)threadCtx->t5, (u32)threadCtx->t6, (u32)threadCtx->t7);
osSyncPrintf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)threadCtx->s0, (u32)threadCtx->s1, (u32)threadCtx->s2);
osSyncPrintf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)threadCtx->s3, (u32)threadCtx->s4, (u32)threadCtx->s5);
osSyncPrintf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)threadCtx->s6, (u32)threadCtx->s7, (u32)threadCtx->t8);
osSyncPrintf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)threadCtx->t9, (u32)threadCtx->gp, (u32)threadCtx->sp);
osSyncPrintf("S8:%08xH RA:%08xH LO:%08xH\n", (u32)threadCtx->s8, (u32)threadCtx->ra, (u32)threadCtx->lo);
osSyncPrintf("\n");
Fault_LogFPCSR(threadCtx->fpcsr);
osSyncPrintf("\n");
Fault_LogFReg(0, &threadCtx->fp0.f.f_even);
Fault_LogFReg(2, &threadCtx->fp2.f.f_even);
osSyncPrintf("\n");
Fault_LogFReg(4, &threadCtx->fp4.f.f_even);
Fault_LogFReg(6, &threadCtx->fp6.f.f_even);
osSyncPrintf("\n");
Fault_LogFReg(8, &threadCtx->fp8.f.f_even);
Fault_LogFReg(10, &threadCtx->fp10.f.f_even);
osSyncPrintf("\n");
Fault_LogFReg(12, &threadCtx->fp12.f.f_even);
Fault_LogFReg(14, &threadCtx->fp14.f.f_even);
osSyncPrintf("\n");
Fault_LogFReg(16, &threadCtx->fp16.f.f_even);
Fault_LogFReg(18, &threadCtx->fp18.f.f_even);
osSyncPrintf("\n");
Fault_LogFReg(20, &threadCtx->fp20.f.f_even);
Fault_LogFReg(22, &threadCtx->fp22.f.f_even);
osSyncPrintf("\n");
Fault_LogFReg(24, &threadCtx->fp24.f.f_even);
Fault_LogFReg(26, &threadCtx->fp26.f.f_even);
osSyncPrintf("\n");
Fault_LogFReg(28, &threadCtx->fp28.f.f_even);
Fault_LogFReg(30, &threadCtx->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* Fault_FindFaultedThread(void) {
OSThread* iter = __osGetActiveQueue();
while (iter->priority != OS_PRIORITY_THREADTAIL) {
if ((iter->priority > OS_PRIORITY_IDLE) && (iter->priority < OS_PRIORITY_APPMAX) &&
(iter->flags & (OS_FLAG_CPU_BREAK | OS_FLAG_FAULT))) {
return iter;
}
iter = iter->tlnext;
}
return NULL;
}
void Fault_Wait5Seconds(void) {
s32 pad;
OSTime start = osGetTime();
do {
Fault_Sleep(1000 / 60);
} while ((osGetTime() - start) <= OS_SEC_TO_CYCLES(5));
sFaultInstance->autoScroll = true;
}
/**
* Waits for the following button combination to be entered before returning:
*
* (DPad-Left & L & R & C-Right) & Start
*/
void Fault_WaitForButtonCombo(void) {
Input* input = &sFaultInstance->inputs[0];
FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 1));
do {
do {
Fault_Sleep(1000 / 60);
Fault_UpdatePadImpl();
} while (!CHECK_BTN_ALL(input->press.button, BTN_RESET));
} while (!CHECK_BTN_ALL(input->cur.button, BTN_DLEFT | BTN_L | BTN_R | BTN_CRIGHT));
}
void Fault_DrawMemDumpContents(const char* title, uintptr_t addr, u32 param_3) {
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;
Fault_FillScreenBlack();
FaultDrawer_SetCharPad(-2, 0);
FaultDrawer_DrawText(36, 18, "%s %08x", title ? title : "PrintDump", alignedAddr);
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 A 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) {
s32 scrollCountdown;
s32 off;
Input* input = &sFaultInstance->inputs[0];
uintptr_t addr = pc;
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 down 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, BTN_L)) {
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, BTN_START)) {
return;
}
// Memory dump controls
off = 0x10;
if (CHECK_BTN_ALL(input->cur.button, BTN_A)) {
off *= 0x10;
}
if (CHECK_BTN_ALL(input->cur.button, BTN_B)) {
off *= 0x100;
}
if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) {
addr -= off;
}
if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) {
addr += off;
}
if (CHECK_BTN_ALL(input->press.button, BTN_CUP)) {
addr = pc;
}
if (CHECK_BTN_ALL(input->press.button, BTN_CDOWN)) {
addr = sp;
}
if (CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) {
addr = cLeftJump;
}
if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) {
addr = cRightJump;
}
} while (!CHECK_BTN_ALL(input->press.button, BTN_L));
// 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;
u32 lastInsn;
u16 insnHi;
s16 insnLo;
u32 imm;
if ((sp % 4 != 0) || (sp < K0BASE) || (sp >= K2BASE) || (ra % 4 != 0) || (ra < K0BASE) || (ra >= K2BASE)) {
*spPtr = 0;
*pcPtr = 0;
*raPtr = 0;
return;
}
if ((pc % 4 != 0) || (pc < K0BASE) || (pc >= K2BASE)) {
*pcPtr = ra;
return;
}
lastInsn = 0;
while (true) {
insnHi = *(uintptr_t*)pc >> 16;
insnLo = *(uintptr_t*)pc & 0xFFFF;
imm = insnLo;
if (insnHi == 0x8FBF) {
// lw $ra, <imm>($sp)
// read return address saved on the stack
ra = *(uintptr_t*)(sp + imm);
} else if (insnHi == 0x27BD) {
// addiu $sp, $sp, <imm>
// stack pointer increment or decrement
sp += imm;
} else if (*(uintptr_t*)pc == 0x42000018) {
// eret
// cannot backtrace through an eret, give up
sp = 0;
pc = 0;
ra = 0;
goto done;
}
if (lastInsn == 0x3E00008) {
// 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 = *(uintptr_t*)pc;
pc += sizeof(u32);
}
done:
*spPtr = sp;
*pcPtr = pc;
*raPtr = ra;
}
/**
* Draws the stack trace page contents for the specified thread
*/
void Fault_DrawStackTrace(OSThread* thread, u32 flags) {
s32 line;
uintptr_t sp = thread->context.sp;
uintptr_t ra = thread->context.ra;
uintptr_t pc = thread->context.pc;
s32 pad;
uintptr_t addr;
Fault_FillScreenBlack();
FaultDrawer_DrawText(120, 16, "STACK TRACE");
FaultDrawer_DrawText(36, 24, "SP PC (VPC)");
for (line = 1; (line < 22) && (((ra != 0) || (sp != 0)) && (pc != (uintptr_t)__osCleanupThread)); line++) {
FaultDrawer_DrawText(0x24, line * 8 + 24, "%08x %08x", sp, pc);
if (flags & 1) {
// Try to convert the relocated program counter to the corresponding unrelocated virtual address
addr = Fault_ConvertAddress(pc);
if (addr != 0) {
FaultDrawer_Printf(" -> %08x", addr);
}
} else {
FaultDrawer_Printf(" -> ????????");
}
Fault_WalkStack(&sp, &pc, &ra);
}
}
void Fault_LogStackTrace(OSThread* thread, u32 flags) {
s32 line;
uintptr_t sp = thread->context.sp;
uintptr_t ra = thread->context.ra;
uintptr_t pc = thread->context.pc;
uintptr_t addr;
osSyncPrintf("STACK TRACE");
osSyncPrintf("SP PC (VPC)\n");
for (line = 1; (line < 22) && (((ra != 0) || (sp != 0)) && (pc != (uintptr_t)__osCleanupThread)); line++) {
osSyncPrintf("%08x %08x", sp, pc);
if (flags & 1) {
// Try to convert the relocated program counter to the corresponding unrelocated virtual address
addr = Fault_ConvertAddress(pc);
if (addr != 0) {
osSyncPrintf(" -> %08x", addr);
}
} else {
osSyncPrintf(" -> ????????");
}
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) {
fb = sFaultInstance->fb;
} else {
fb = osViGetNextFramebuffer();
if ((uintptr_t)fb == K0BASE) {
fb = (void*)(PHYS_TO_K0(osMemSize) - SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(u16));
}
}
osViSwapBuffer(fb);
FaultDrawer_SetDrawerFrameBuffer(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);
client->callback(client->arg0, client->arg1);
Fault_WaitForInput();
Fault_DisplayFrameBuffer();
}
client = client->next;
}
}
void Fault_SetOptionsFromController3(void) {
static u32 faultCustomOptions;
Input* input3 = &sFaultInstance->inputs[3];
u32 pad;
uintptr_t pc;
uintptr_t ra;
uintptr_t sp;
// BTN_RESET is the "neutral reset". Corresponds to holding L+R and pressing S
if (CHECK_BTN_ALL(input3->press.button, BTN_RESET)) {
faultCustomOptions = !faultCustomOptions;
}
if (faultCustomOptions) {
pc = gGraphThread.context.pc;
ra = gGraphThread.context.ra;
sp = gGraphThread.context.sp;
if (CHECK_BTN_ALL(input3->cur.button, BTN_R)) {
static u32 faultCopyToLog;
faultCopyToLog = !faultCopyToLog;
FaultDrawer_SetOsSyncPrintfEnabled(faultCopyToLog);
}
if (CHECK_BTN_ALL(input3->cur.button, BTN_A)) {
osSyncPrintf("GRAPH PC=%08x RA=%08x STACK=%08x\n", pc, ra, sp);
}
if (CHECK_BTN_ALL(input3->cur.button, BTN_B)) {
FaultDrawer_SetDrawerFrameBuffer(osViGetNextFramebuffer(), 0x140, 0xF0);
Fault_DrawRec(0, 0xD7, 0x140, 9, 1);
FaultDrawer_SetCharPad(-2, 0);
FaultDrawer_DrawText(0x20, 0xD8, "GRAPH PC %08x RA %08x SP %08x", pc, ra, sp);
}
}
}
void Fault_UpdatePad(void) {
Fault_UpdatePadImpl();
Fault_SetOptionsFromController3();
}
#define FAULT_MSG_CPU_BREAK ((OSMesg)1)
#define FAULT_MSG_FAULT ((OSMesg)2)
#define FAULT_MSG_UNK ((OSMesg)3)
void Fault_ThreadEntry(void* arg) {
OSMesg msg;
u32 pad;
OSThread* faultedThread;
// 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 = Fault_FindFaultedThread();
osSyncPrintf("FindFaultedThread()=%08x\n", faultedThread);
}
} while (faultedThread == NULL);
__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);
osSyncPrintfThreadContext(faultedThread);
Fault_WaitForInput();
// Stack trace page
Fault_DrawStackTrace(faultedThread, 0);
Fault_LogStackTrace(faultedThread, 0);
Fault_WaitForInput();
// Client pages
Fault_ProcessClients();
// Memory dump page
Fault_DrawMemDump((u32)(faultedThread->context.pc - 0x100), (u32)faultedThread->context.sp, 0, 0);
Fault_DrawStackTrace(faultedThread, 1);
Fault_LogStackTrace(faultedThread, 1);
Fault_WaitForInput();
// 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_SetDrawerFrameBuffer(fb, w, h);
}
STACK(sFaultStack, 0x600);
StackEntry sFaultStackInfo;
FaultMgr gFaultMgr;
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(&sFaultStackInfo, sFaultStack, STACK_TOP(sFaultStack), 0, 0x100, "fault");
osCreateThread(&sFaultInstance->thread, Z_THREAD_ID_FAULT, Fault_ThreadEntry, NULL, STACK_TOP(sFaultStack),
Z_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_HangupFaultClient(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;
s32 pad;
Fault_AddClient(&client, (void*)Fault_HangupFaultClient, (void*)exp1, (void*)exp2);
*(u32*)0x11111111 = 0; // trigger an exception via unaligned memory access
}
/**
* Like `Fault_AddHungupAndCrashImpl`, however provides a fixed message containing
* file and line number
*/
void Fault_AddHungupAndCrash(const char* file, s32 line) {
char msg[0x100];
sprintf(msg, "HungUp %s:%d", file, line);
Fault_AddHungupAndCrashImpl(msg, NULL);
}