From 3f9cde8185b8d585e904d345d9c0889800946b30 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 25 Aug 2021 16:48:01 -0400 Subject: [PATCH] Implemented exception handler. The exception handler is capable of logging CPU registers and a stack trace using the current log implementation. Furthermore, if borealis has been initialized, it'll also display the PC register value using a CrashFrame. Otherwise, console output is used to display the same message. Other changes include: * utils: made utilsPrintConsoleError non-static. * utils: implemented a workaround to restore console output after initializing nxlink. --- include/core/nxdt_utils.h | 3 + romfs/i18n/en-US/about_tab.json | 2 +- romfs/i18n/en-US/generic.json | 5 +- source/core/nxdt_utils.c | 138 ++++++++++++++------ source/core/title.c | 2 +- source/exception_handler.cpp | 223 ++++++++++++++++++++++++++++++++ source/main.cpp | 3 + 7 files changed, 332 insertions(+), 44 deletions(-) create mode 100644 source/exception_handler.cpp diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index b9cbc20..d1a387b 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -151,6 +151,9 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element); /// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned. char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension); +/// Prints an error message using the standard console output and waits for the user to press a button. +void utilsPrintConsoleError(const char *msg); + /// Returns the current application updated state. bool utilsGetApplicationUpdatedState(void); diff --git a/romfs/i18n/en-US/about_tab.json b/romfs/i18n/en-US/about_tab.json index b7fc6ff..be32294 100644 --- a/romfs/i18n/en-US/about_tab.json +++ b/romfs/i18n/en-US/about_tab.json @@ -24,7 +24,7 @@ "line_07": "\uE016 The folks from NSWDB.COM and No-Intro.org, for being kind enough to put up public APIs to perform online checksum lookups.", "line_08": "\uE016 The folks at the nxdumptool Discord server.", "line_09": "\uE016 The Comfy Boyes, for always being awesome and supportive. You know who you are.", - "line_10": "\uE016 My girlfriend, for always being by my side and motivating me to keep working on all my projects. I love you. \uE87D", + "line_10": "\uE016 AndreĆ­na, for always being by my side and motivating me to keep working on all my projects.", "line_11": "\uE016 And, at last but not least, you! Thank you for using my work!" }, diff --git a/romfs/i18n/en-US/generic.json b/romfs/i18n/en-US/generic.json index 965b8e5..e043e84 100644 --- a/romfs/i18n/en-US/generic.json +++ b/romfs/i18n/en-US/generic.json @@ -9,5 +9,8 @@ "__time_format_comment__": "Use 12 for a 12-hour clock, or 24 for a 24-hour clock", "date": "{1:02d}/{2:02d}/{0} {3:02d}:{4:02d}:{5:02d} {6}", - "__date_comment__": "{0} = Year, {1} = Month, {2} = Day, {3} = Hour, {4} = Minute, {5} = Second, {6} = AM/PM (if time_format is set to 12)" + "__date_comment__": "{0} = Year, {1} = Month, {2} = Day, {3} = Hour, {4} = Minute, {5} = Second, {6} = AM/PM (if time_format is set to 12)", + + "libnx_abort": "Fatal error triggered in libnx!\nError code: 0x{:08X}.", + "exception_triggered": "Fatal exception triggered!\nReason: {} (0x{:X})." } diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 3646f21..a94b257 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -65,7 +65,7 @@ static AppletHookCookie g_systemOverclockCookie = {0}; static bool g_longRunningProcess = false; -static int g_nxLinkSocketFd = -1; +static int g_stdoutFd = -1, g_stderrFd = -1, g_nxLinkSocketFd = -1; static const char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB" }; static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes); @@ -106,7 +106,8 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param); static void utilsChangeHomeButtonBlockStatus(bool block); -static void utilsPrintConsoleError(void); +NX_INLINE void utilsCloseFileDescriptor(int *fd); +static void utilsRestoreConsoleOutput(void); static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos); @@ -120,6 +121,9 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) ret = g_resourcesInit; if (ret) break; + /* Lock applet exit. */ + appletLockExit(); + /* Retrieve pointer to the application launch path. */ _utilsGetLaunchPath(program_argc, program_argv); @@ -132,7 +136,7 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) /* Create logfile. */ logWriteStringToLogFile("________________________________________________________________\r\n"); - LOG_MSG(APP_TITLE " v%u.%u.%u starting (" GIT_REV "). Built on " __DATE__ " - " __TIME__ ".", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); + LOG_MSG(APP_TITLE " v" APP_VERSION " starting (" GIT_REV "). Built on " __DATE__ " - " __TIME__ "."); /* Log Horizon OS version. */ u32 hos_version = hosversionGet(); @@ -237,14 +241,33 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording(); } - /* Redirect stdout and stderr over network to nxlink. */ + /* Duplicate the stdout and stderr file handles, then redirect stdout and stderr over network to nxlink. */ + g_stdoutFd = dup(STDOUT_FILENO); + g_stderrFd = dup(STDERR_FILENO); g_nxLinkSocketFd = nxlinkConnectToHost(true, true); /* Update flags. */ ret = g_resourcesInit = true; } - if (!ret) utilsPrintConsoleError(); + if (!ret) + { + size_t msg_size = 0; + char log_msg[0x100] = {0}, *msg = NULL; + + /* Get last log message. */ + logGetLastMessage(log_msg, sizeof(log_msg)); + + /* Generate error message. */ + utilsAppendFormattedStringToBuffer(&msg, &msg_size, "An error occurred while initializing resources."); + if (*log_msg) utilsAppendFormattedStringToBuffer(&msg, &msg_size, "\n\n%s", log_msg); + + /* Print error message. */ + utilsPrintConsoleError(msg); + + /* Free error message. */ + if (msg) free(msg); + } return ret; } @@ -253,12 +276,8 @@ void utilsCloseResources(void) { SCOPED_LOCK(&g_resourcesMutex) { - /* Close nxlink socket. */ - if (g_nxLinkSocketFd >= 0) - { - close(g_nxLinkSocketFd); - g_nxLinkSocketFd = -1; - } + /* Restore console output. */ + utilsRestoreConsoleOutput(); /* Unset long running process state. */ utilsSetLongRunningProcessState(false); @@ -316,6 +335,9 @@ void utilsCloseResources(void) /* Close logfile. */ logCloseLogFile(); + /* Unlock applet exit. */ + appletUnlockExit(); + g_resourcesInit = false; } } @@ -836,6 +858,48 @@ end: return path; } +void utilsPrintConsoleError(const char *msg) +{ + PadState pad = {0}; + + /* Don't consider stick movement as button inputs. */ + u64 flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \ + HidNpadButton_StickRUp | HidNpadButton_StickRDown); + + /* Configure input. */ + /* Up to 8 different, full controller inputs. */ + /* Individual Joy-Cons not supported. */ + padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl); + padInitializeWithMask(&pad, 0x1000000FFUL); + + /* Restore console output. */ + utilsRestoreConsoleOutput(); + + /* Initialize console output. */ + consoleInit(NULL); + + /* Print message. */ + if (msg && *msg) + { + printf("%s", msg); + } else { + printf("An error occurred."); + } + + printf("\n\nFor more information, please check the logfile. Press any button to exit."); + consoleUpdate(NULL); + + /* Wait until the user presses a button. */ + while(appletMainLoop()) + { + padUpdate(&pad); + if (padGetButtonsDown(&pad) & flag) break; + } + + /* Deinitialize console output. */ + consoleExit(NULL); +} + bool utilsGetApplicationUpdatedState(void) { bool ret = false; @@ -1078,39 +1142,31 @@ static void utilsChangeHomeButtonBlockStatus(bool block) } } -static void utilsPrintConsoleError(void) +NX_INLINE void utilsCloseFileDescriptor(int *fd) { - PadState pad = {0}; - char msg[0x100] = {0}; - - /* Don't consider stick movement as button inputs. */ - u64 flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \ - HidNpadButton_StickRUp | HidNpadButton_StickRDown); - - /* Configure input. */ - /* Up to 8 different, full controller inputs. */ - /* Individual Joy-Cons not supported. */ - padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl); - padInitializeWithMask(&pad, 0x1000000FFUL); - - /* Get last log message. */ - logGetLastMessage(msg, sizeof(msg)); - - consoleInit(NULL); - - printf("An error occurred while initializing resources.\n\n"); - if (*msg) printf("%s\n\n", msg); - printf("For more information, please check the logfile. Press any button to exit."); - - consoleUpdate(NULL); - - while(appletMainLoop()) + if (!fd || *fd < 0) return; + close(*fd); + *fd = -1; +} + +static void utilsRestoreConsoleOutput(void) +{ + SCOPED_LOCK(&g_resourcesMutex) { - padUpdate(&pad); - if (padGetButtonsDown(&pad) & flag) break; + if (g_nxLinkSocketFd >= 0) utilsCloseFileDescriptor(&g_nxLinkSocketFd); + + if (g_stdoutFd >= 0) + { + dup2(g_stdoutFd, STDOUT_FILENO); + utilsCloseFileDescriptor(&g_stdoutFd); + } + + if (g_stderrFd >= 0) + { + dup2(g_stderrFd, STDERR_FILENO); + utilsCloseFileDescriptor(&g_stderrFd); + } } - - consoleExit(NULL); } static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos) diff --git a/source/core/title.c b/source/core/title.c index ffcd534..124c674 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -846,7 +846,7 @@ void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data) bool titleAreOrphanTitlesAvailable(void) { bool ret = false; - SCOPED_LOCK(&g_titleMutex) ret = (g_titleInterfaceInit && g_orphanTitleInfo && *g_orphanTitleInfo && g_orphanTitleInfoCount > 0); + SCOPED_TRY_LOCK(&g_titleMutex) ret = (g_titleInterfaceInit && g_orphanTitleInfo && *g_orphanTitleInfo && g_orphanTitleInfoCount > 0); return ret; } diff --git a/source/exception_handler.cpp b/source/exception_handler.cpp new file mode 100644 index 0000000..23f1658 --- /dev/null +++ b/source/exception_handler.cpp @@ -0,0 +1,223 @@ +/* + * exception_handler.cpp + * + * Copyright (c) 2020-2021, DarkMatterCore . + * Copyright (c) 2019-2020, WerWolv. + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * Loosely based on debug_helpers.cpp from EdiZon-Rewrite. + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#define FP_MASK 0xFFFFFFFFFF000000UL +#define STACK_TRACE_SIZE 0x20 +#define IS_HB_ADDR(x) (info.addr && info.size && (x) >= info.addr && (x) < (info.addr + info.size)) + +namespace i18n = brls::i18n; /* For getStr(). */ +using namespace i18n::literals; /* For _i18n. */ + +extern bool g_borealisInitialized; + +extern "C" { + /* Overrides for libnx weak symbols. */ + u32 __nx_applet_exit_mode = 0; + alignas(16) u8 __nx_exception_stack[0x8000]; + u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); +} + +namespace nxdt::utils { + static void GetHomebrewMemoryInfo(MemoryInfo *out) + { + if (!out) + { + LOG_MSG("Invalid parameters!"); + return; + } + + u32 p = 0; + Result rc = 0; + + /* Find the memory region in which this function is stored. */ + /* The start of it will be the base address the homebrew was mapped to. */ + rc = svcQueryMemory(out, &p, static_cast(reinterpret_cast(&GetHomebrewMemoryInfo))); + if (R_FAILED(rc)) LOG_MSG("svcQueryMemory failed! (0x%08X).", rc); + } + + static bool UnwindStack(u64 *out_stack_trace, u32 *out_stack_trace_size, size_t max_stack_trace_size, u64 cur_fp) + { + if (!out_stack_trace || !out_stack_trace_size || !max_stack_trace_size || !cur_fp) + { + LOG_MSG("Invalid parameters!"); + return false; + } + + struct StackFrame { + u64 fp; ///< Frame Pointer (pointer to previous stack frame). + u64 lr; ///< Link Register (return address). + }; + + *out_stack_trace_size = 0; + u64 fp_base = (cur_fp & FP_MASK); + + for(size_t i = 0; i < max_stack_trace_size; i++) + { + /* Last check fixes a crash while dealing with certain stack frame addresses. */ + if (!cur_fp || (cur_fp % sizeof(u64)) || (cur_fp & FP_MASK) != fp_base) break; + + auto cur_trace = reinterpret_cast(cur_fp); + out_stack_trace[(*out_stack_trace_size)++] = cur_trace->lr; + cur_fp = cur_trace->fp; + } + + return (*out_stack_trace_size > 0); + } + + static void NORETURN AbortProgramExecution(std::string str) + { + if (g_borealisInitialized) + { + /* Display crash frame and exit Borealis. */ + brls::Application::crash(str); + while(brls::Application::mainLoop()); + } else { + /* Print error message using console output. */ + utilsPrintConsoleError(str.c_str()); + } + + /* Clean up resources. */ + utilsCloseResources(); + + /* Exit application. */ + __nx_applet_exit_mode = 1; + exit(EXIT_FAILURE); + + __builtin_unreachable(); + } +} + +extern "C" { + /* libnx abort function override. */ + void diagAbortWithResult(Result res) + { + /* Log error. */ + LOG_MSG("*** libnx aborted with error code: 0x%08X ***", res); + + /* Abort program execution. */ + std::string crash_str = (g_borealisInitialized ? i18n::getStr("generic/libnx_abort"_i18n, res) : fmt::format("Fatal error triggered in libnx!\nError code: 0x{:08X}.", res)); + nxdt::utils::AbortProgramExecution(crash_str); + } + + /* libnx exception handler override. */ + void __libnx_exception_handler(ThreadExceptionDump *ctx) + { + MemoryInfo info = {0}; + u32 stack_trace_size = 0; + u64 stack_trace[STACK_TRACE_SIZE] = {0}; + + char *exception_str = NULL; + size_t exception_str_size = 0; + + std::string error_desc_str, crash_str; + + /* Get homebrew memory info. */ + nxdt::utils::GetHomebrewMemoryInfo(&info); + + /* Log exception type. */ + LOG_MSG("*** Exception Triggered ***"); + + switch(ctx->error_desc) + { + case ThreadExceptionDesc_InstructionAbort: + error_desc_str = "Instruction Abort"; + break; + case ThreadExceptionDesc_MisalignedPC: + error_desc_str = "Misaligned Program Counter"; + break; + case ThreadExceptionDesc_MisalignedSP: + error_desc_str = "Misaligned Stack Pointer"; + break; + case ThreadExceptionDesc_SError: + error_desc_str = "SError"; + break; + case ThreadExceptionDesc_BadSVC: + error_desc_str = "Bad SVC"; + break; + case ThreadExceptionDesc_Trap: + error_desc_str = "SIGTRAP"; + break; + case ThreadExceptionDesc_Other: + error_desc_str = "Segmentation Fault"; + break; + default: + error_desc_str = "Unknown"; + break; + } + + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "Type: %s (0x%X)\r\n", error_desc_str.c_str(), ctx->error_desc); + + /* Log CPU registers. */ + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "Registers:"); + + for(size_t i = 0; i < MAX_ELEMENTS(ctx->cpu_gprs); i++) + { + u64 reg = ctx->cpu_gprs[i].x; + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n X%02lu: 0x%lX", i, reg); + if (IS_HB_ADDR(reg)) utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, " (BASE + 0x%lX)", reg - info.addr); + } + + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n FP: 0x%lX", ctx->fp.x); + if (IS_HB_ADDR(ctx->fp.x)) utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, " (BASE + 0x%lX)", ctx->fp.x - info.addr); + + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n LR: 0x%lX", ctx->lr.x); + if (IS_HB_ADDR(ctx->lr.x)) utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, " (BASE + 0x%lX)", ctx->lr.x - info.addr); + + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n SP: 0x%lX", ctx->sp.x); + if (IS_HB_ADDR(ctx->sp.x)) utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, " (BASE + 0x%lX)", ctx->sp.x - info.addr); + + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n PC: 0x%lX", ctx->pc.x); + if (IS_HB_ADDR(ctx->pc.x)) utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, " (BASE + 0x%lX)", ctx->pc.x - info.addr); + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n"); + + /* Unwind stack. */ + if (nxdt::utils::UnwindStack(stack_trace, &stack_trace_size, STACK_TRACE_SIZE, ctx->fp.x)) + { + /* Log stack trace. */ + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "Stack Trace:"); + + for(u32 i = 0; i < stack_trace_size; i++) + { + u64 addr = stack_trace[i]; + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n [%02u]: 0x%lX", stack_trace_size - i - 1, addr); + if (IS_HB_ADDR(addr)) utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, " (BASE + 0x%lX)", addr - info.addr); + } + + utilsAppendFormattedStringToBuffer(&exception_str, &exception_str_size, "\r\n"); + } + + /* Write log string. */ + logWriteStringToLogFile(exception_str); + + /* Free exception info string. */ + if (exception_str) free(exception_str); + + /* Abort program execution. */ + crash_str = (g_borealisInitialized ? i18n::getStr("generic/exception_triggered"_i18n, error_desc_str, ctx->error_desc) : fmt::format("Fatal exception triggered!\nReason: {} (0x{:X}).", error_desc_str, ctx->error_desc)); + crash_str += (fmt::format("\nPC: 0x{:X}", ctx->pc.x) + (IS_HB_ADDR(ctx->pc.x) ? fmt::format(" (BASE + 0x{:X}).", ctx->pc.x - info.addr) : ".")); + nxdt::utils::AbortProgramExecution(crash_str); + } +} diff --git a/source/main.cpp b/source/main.cpp index d222bae..644b252 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -25,6 +25,8 @@ using namespace brls::i18n::literals; /* For _i18n. */ +bool g_borealisInitialized = false; + int main(int argc, char *argv[]) { /* Set scope guard to clean up resources at exit. */ @@ -45,6 +47,7 @@ int main(int argc, char *argv[]) /* Initialize Borealis. */ if (!brls::Application::init(APP_TITLE)) return EXIT_FAILURE; + g_borealisInitialized = true; /* Check if we're running under applet mode. */ if (utilsAppletModeCheck())