From caf253ede8533dde369e7961ba145ae0e6c1f09a Mon Sep 17 00:00:00 2001 From: Arceveti Date: Fri, 17 May 2024 14:49:22 -0700 Subject: [PATCH] Stack trace rewrite (now actually works properly) --- src/crash_screen/pages/page_stack.c | 173 ++++++++++++++++++++++------ 1 file changed, 139 insertions(+), 34 deletions(-) diff --git a/src/crash_screen/pages/page_stack.c b/src/crash_screen/pages/page_stack.c index ad7394d57..a444e40cd 100644 --- a/src/crash_screen/pages/page_stack.c +++ b/src/crash_screen/pages/page_stack.c @@ -3,6 +3,7 @@ #include "types.h" #include "sm64.h" +#include "crash_screen/util/insn_disasm.h" #include "crash_screen/util/map_parser.h" #include "crash_screen/cs_controls.h" #include "crash_screen/cs_draw.h" @@ -25,7 +26,7 @@ struct CSSetting cs_settings_group_page_stack[] = { [CS_OPT_HEADER_PAGE_STACK ] = { .type = CS_OPT_TYPE_HEADER, .name = "STACK TRACE", .valNames = &gValNames_bool, .val = SECTION_EXPANDED_DEFAULT, .defaultVal = SECTION_EXPANDED_DEFAULT, .lowerBound = FALSE, .upperBound = TRUE, }, - [CS_OPT_STACK_SHOW_ADDRESSES ] = { .type = CS_OPT_TYPE_SETTING, .name = "Show stack addresses", .valNames = &gValNames_bool, .val = TRUE, .defaultVal = TRUE, .lowerBound = FALSE, .upperBound = TRUE, }, + [CS_OPT_STACK_SHOW_ADDRESSES ] = { .type = CS_OPT_TYPE_SETTING, .name = "Show stack addresses", .valNames = &gValNames_bool, .val = FALSE, .defaultVal = FALSE, .lowerBound = FALSE, .upperBound = TRUE, }, [CS_OPT_STACK_SHOW_OFFSETS ] = { .type = CS_OPT_TYPE_SETTING, .name = "Show function offsets", .valNames = &gValNames_bool, .val = TRUE, .defaultVal = TRUE, .lowerBound = FALSE, .upperBound = TRUE, }, [CS_OPT_END_STACK ] = { .type = CS_OPT_TYPE_END, }, }; @@ -53,10 +54,22 @@ static u32 sStackTraceBufferEnd = 0; static u32 sStackTraceSelectedIndex = 0; static u32 sStackTraceViewportIndex = 0; +static u32 sStackTraceNumShownRows = STACK_TRACE_NUM_ROWS; +static const char* sStackTraceError = NULL; + + +// Jumps with links store the address of the instruction after the delay slot to $ra ($ra = $pc + 2). +#define LINK_SIZE (2 * sizeof(InsnData)) extern void __osCleanupThread(void); +static void set_stack_trace_error_mesg(const char* errorStr) { + if (sStackTraceError == NULL) { + sStackTraceError = errorStr; + } +} + static void append_stack_entry_to_buffer(Address stackAddr, Address currAddr) { sStackTraceBuffer[sStackTraceBufferEnd++] = (FunctionInStack){ .stackAddr = stackAddr, @@ -64,46 +77,138 @@ static void append_stack_entry_to_buffer(Address stackAddr, Address currAddr) { }; } -//! TODO: Use libdragon's method of walking the stack. -void fill_function_stack_trace(void) { +#define STACK_TRACE_MAX_INSN_SCAN (size_t)1024 + +//! TODO: Return should determine whether leaf or null symbol. +_Bool stacktrace_step(Address** sp, Address** ra, Address addr) { + const MapSymbol* symbol = get_map_symbol(addr, SYMBOL_SEARCH_BACKWARD); + if (symbol == NULL) { + set_stack_trace_error_mesg("null symbol"); + return FALSE; + } + size_t stack_size = 0; + size_t ra_offset = 0; + + // Get stack_size and ra_offset. + InsnData* insnPtr = (InsnData*)symbol->addr; // Starting addr. + + const size_t numInsnsInFunc = MIN(STACK_TRACE_MAX_INSN_SCAN, (symbol->size / sizeof(InsnData))); + for (size_t i = 0; i < numInsnsInFunc; i++) { + InsnData insn = *insnPtr; + u16 insnHi = (insn.raw >> 16); + s16 imm = insn.immediate; + + //! TODO: Use defines/enums for insn check magic values: + if ( + (stack_size == 0) && + ((insnHi == 0x27BD) || (insnHi == 0x67BD)) // [addiu/daddiu] $sp, $sp, 0xXXXX + ) { + if (imm < 0) { + stack_size = -imm; + } + } else if ( + ((insnHi == 0xFFBF) || (insnHi == 0xAFBF)) // [sw/sd] $ra, 0xXXXX($sp) + ) { + ra_offset = imm; + } + + // Exit the loop if both have been found. + if ((stack_size != 0) && (ra_offset != 0)) { + *ra = (Address*)((Address)*sp + ra_offset); + *sp = (Address*)((Address)*sp + stack_size); + return TRUE; + } + + insnPtr++; + } + + // No stack allocation found, so thit is a leaf function. + return FALSE; +} + +static const char* stack_error_leaf_not_at_top = "leaf func found not at top"; + +//! TODO: Failsafe in case the address of __osCleanupThread doesn't appear in the stack. +_Bool fill_function_stack_trace(void) { bzero(&sStackTraceBuffer, sizeof(sStackTraceBuffer)); sStackTraceBufferEnd = 0; + _Bool success = FALSE; + // Include the current function at the top: __OSThreadContext* tc = &gInspectThread->context; - append_stack_entry_to_buffer(tc->sp, tc->pc); // Set the first entry in the stack buffer to pc. + const Address epc = GET_EPC(tc); // thread $pc + const Address t_sp = tc->sp; // thread $sp + append_stack_entry_to_buffer((Address)t_sp, epc); // Set the first entry in the stack buffer to the function $EPC is in. - Doubleword* sp = (Doubleword*)(Address)tc->sp; // Stack pointer is already aligned, so get the lower bits. + Address* sp = (Address*)((Address)t_sp); - // Loop through the stack buffer and find all the addresses that point to a function. - while (sStackTraceBufferEnd < STACK_TRACE_BUFFER_SIZE) { - Address addr = LO_OF_64(*sp); // Function address are in the lower bits. + if (IS_DEBUG_MAP_ENABLED()) { + const Address t_ra = tc->ra; // thread $ra + Address* ra = (Address*)((Address)t_ra - LINK_SIZE); + _Bool err = FALSE; - if ( - addr_is_in_text_segment(addr) && - ( - IS_DEBUG_MAP_ENABLED() || - symbol_is_function(get_map_symbol(addr, SYMBOL_SEARCH_BACKWARD)) - ) - ) { - //! TODO: If JAL command uses a different function than the previous entry's funcAddr, replace it with the one in the JAL command? - //! TODO: handle duplicate entries caused by JALR RA, V0 - append_stack_entry_to_buffer((Address)sp + sizeof(Address), addr); // Address in stack uses lower bits of the doubleword. + if (!stacktrace_step(&sp, &ra, epc)) { + // $EPC is in a leaf function, so get the next entry from $ra because it's not in the stack. + if (stacktrace_step(&sp, &ra, ((Address)t_ra - LINK_SIZE))) { + append_stack_entry_to_buffer((Address)t_sp, (t_ra - LINK_SIZE)); + } else { + set_stack_trace_error_mesg(stack_error_leaf_not_at_top); + err = TRUE; + } } - if (addr == (Address)__osCleanupThread) { - break; + if (!err) { + // Loop through the stack buffer and find all the addresses that point to a function. + while (sStackTraceBufferEnd < STACK_TRACE_BUFFER_SIZE) { + if ((Address)*ra == (Address)__osCleanupThread) { + success = TRUE; + break; + } + append_stack_entry_to_buffer((Address)ra, (*ra - LINK_SIZE)); + if (!stacktrace_step(&sp, &ra, ((Address)*ra - LINK_SIZE))) { + set_stack_trace_error_mesg(stack_error_leaf_not_at_top); + break; + } + } } - - sp++; } + + // Fallback simpler stack trace functionality in case walking the stack fails or the debug map is disabled. + // Just uses all aligned .text addresses in the stack. + if (!success) { + // Align sp to lower 32 of 64: + sp = (Address*)(ALIGNFLOOR((Address)sp, sizeof(Doubleword)) + sizeof(Address)); + + // Loop through the stack buffer and find all the addresses that point to a function. + while (sStackTraceBufferEnd < STACK_TRACE_BUFFER_SIZE) { + if (*sp == (Address)__osCleanupThread) { + success = TRUE; + break; + } else if (addr_is_in_text_segment(*sp)) { + append_stack_entry_to_buffer((Address)sp, (*sp - LINK_SIZE)); + } + + sp += 2; + } + } + + return success; } void page_stack_init(void) { sStackTraceSelectedIndex = 0; sStackTraceViewportIndex = 0; + sStackTraceNumShownRows = STACK_TRACE_NUM_ROWS; - fill_function_stack_trace(); + sStackTraceError = NULL; + + if (!cs_get_current_page()->flags.crashed) { + fill_function_stack_trace(); + if (sStackTraceError != NULL) { + sStackTraceNumShownRows--; + } + } } void stack_trace_print_entries(CSTextCoord_u32 line, CSTextCoord_u32 numLines) { @@ -210,16 +315,16 @@ void page_stack_draw(void) { line++; - stack_trace_print_entries(line, STACK_TRACE_NUM_ROWS); + stack_trace_print_entries(line, sStackTraceNumShownRows); // Draw the top line after the entries so the selection rectangle is behind it. cs_draw_divider(DIVIDER_Y(line)); // Scroll Bar: - if (sStackTraceBufferEnd > STACK_TRACE_NUM_ROWS) { + if (sStackTraceBufferEnd > sStackTraceNumShownRows) { cs_draw_scroll_bar( (DIVIDER_Y(line) + 1), DIVIDER_Y(CRASH_SCREEN_NUM_CHARS_Y), - STACK_TRACE_NUM_ROWS, sStackTraceBufferEnd, + sStackTraceNumShownRows, sStackTraceBufferEnd, sStackTraceViewportIndex, COLOR_RGBA32_CRASH_SCROLL_BAR, TRUE ); @@ -227,6 +332,13 @@ void page_stack_draw(void) { cs_draw_divider(DIVIDER_Y(CRASH_SCREEN_NUM_CHARS_Y)); } + if (sStackTraceError != NULL) { + cs_print(TEXT_X(0), TEXT_Y(CRASH_SCREEN_NUM_CHARS_Y - 1), + STR_COLOR_PREFIX"stack error: %s", + COLOR_RGBA32_CRASH_DESCRIPTION_MAIN, sStackTraceError + ); + } + osWritebackDCacheAll(); } @@ -237,19 +349,12 @@ void page_stack_input(void) { open_address_select(sStackTraceBuffer[sStackTraceSelectedIndex].currAddr); } -// #ifdef INCLUDE_DEBUG_MAP -// if (IS_DEBUG_MAP_ENABLED() && (buttonPressed & B_BUTTON)) { -// // Toggle whether to display function names. -// cs_inc_setting(CS_OPT_GROUP_GLOBAL, CS_OPT_GLOBAL_SYMBOL_NAMES, TRUE); -// } -// #endif // INCLUDE_DEBUG_MAP - s32 change = 0; if (gCSDirectionFlags.pressed.up ) change = -1; // Scroll up. if (gCSDirectionFlags.pressed.down) change = +1; // Scroll down. sStackTraceSelectedIndex = WRAP(((s32)sStackTraceSelectedIndex + change), 0, (s32)(sStackTraceBufferEnd - 1)); - sStackTraceViewportIndex = cs_clamp_view_to_selection(sStackTraceViewportIndex, sStackTraceSelectedIndex, STACK_TRACE_NUM_ROWS, 1); + sStackTraceViewportIndex = cs_clamp_view_to_selection(sStackTraceViewportIndex, sStackTraceSelectedIndex, sStackTraceNumShownRows, 1); } void page_stack_print(void) {