1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 12:12:02 +00:00

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.
This commit is contained in:
Pablo Curiel 2021-08-25 16:48:01 -04:00
parent 4bea23b758
commit 3f9cde8185
7 changed files with 332 additions and 44 deletions

View file

@ -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. /// 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); 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. /// Returns the current application updated state.
bool utilsGetApplicationUpdatedState(void); bool utilsGetApplicationUpdatedState(void);

View file

@ -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_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_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_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!" "line_11": "\uE016 And, at last but not least, you! Thank you for using my work!"
}, },

View file

@ -9,5 +9,8 @@
"__time_format_comment__": "Use 12 for a 12-hour clock, or 24 for a 24-hour clock", "__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": "{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})."
} }

View file

@ -65,7 +65,7 @@ static AppletHookCookie g_systemOverclockCookie = {0};
static bool g_longRunningProcess = false; 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 char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB" };
static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes); 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 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); 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; ret = g_resourcesInit;
if (ret) break; if (ret) break;
/* Lock applet exit. */
appletLockExit();
/* Retrieve pointer to the application launch path. */ /* Retrieve pointer to the application launch path. */
_utilsGetLaunchPath(program_argc, program_argv); _utilsGetLaunchPath(program_argc, program_argv);
@ -132,7 +136,7 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
/* Create logfile. */ /* Create logfile. */
logWriteStringToLogFile("________________________________________________________________\r\n"); 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. */ /* Log Horizon OS version. */
u32 hos_version = hosversionGet(); u32 hos_version = hosversionGet();
@ -237,14 +241,33 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording(); 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); g_nxLinkSocketFd = nxlinkConnectToHost(true, true);
/* Update flags. */ /* Update flags. */
ret = g_resourcesInit = true; 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; return ret;
} }
@ -253,12 +276,8 @@ void utilsCloseResources(void)
{ {
SCOPED_LOCK(&g_resourcesMutex) SCOPED_LOCK(&g_resourcesMutex)
{ {
/* Close nxlink socket. */ /* Restore console output. */
if (g_nxLinkSocketFd >= 0) utilsRestoreConsoleOutput();
{
close(g_nxLinkSocketFd);
g_nxLinkSocketFd = -1;
}
/* Unset long running process state. */ /* Unset long running process state. */
utilsSetLongRunningProcessState(false); utilsSetLongRunningProcessState(false);
@ -316,6 +335,9 @@ void utilsCloseResources(void)
/* Close logfile. */ /* Close logfile. */
logCloseLogFile(); logCloseLogFile();
/* Unlock applet exit. */
appletUnlockExit();
g_resourcesInit = false; g_resourcesInit = false;
} }
} }
@ -836,6 +858,48 @@ end:
return path; 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 utilsGetApplicationUpdatedState(void)
{ {
bool ret = false; 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}; if (!fd || *fd < 0) return;
char msg[0x100] = {0}; close(*fd);
*fd = -1;
}
/* Don't consider stick movement as button inputs. */ static void utilsRestoreConsoleOutput(void)
u64 flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \ {
HidNpadButton_StickRUp | HidNpadButton_StickRDown); SCOPED_LOCK(&g_resourcesMutex)
/* 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())
{ {
padUpdate(&pad); if (g_nxLinkSocketFd >= 0) utilsCloseFileDescriptor(&g_nxLinkSocketFd);
if (padGetButtonsDown(&pad) & flag) break;
}
consoleExit(NULL); if (g_stdoutFd >= 0)
{
dup2(g_stdoutFd, STDOUT_FILENO);
utilsCloseFileDescriptor(&g_stdoutFd);
}
if (g_stderrFd >= 0)
{
dup2(g_stderrFd, STDERR_FILENO);
utilsCloseFileDescriptor(&g_stderrFd);
}
}
} }
static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos) static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos)

View file

@ -846,7 +846,7 @@ void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data)
bool titleAreOrphanTitlesAvailable(void) bool titleAreOrphanTitlesAvailable(void)
{ {
bool ret = false; 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; return ret;
} }

View file

@ -0,0 +1,223 @@
/*
* exception_handler.cpp
*
* Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
* 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 <https://www.gnu.org/licenses/>.
*/
#include <nxdt_utils.h>
#include <borealis.hpp>
#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<u64>(reinterpret_cast<uintptr_t>(&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<StackFrame*>(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);
}
}

View file

@ -25,6 +25,8 @@
using namespace brls::i18n::literals; /* For _i18n. */ using namespace brls::i18n::literals; /* For _i18n. */
bool g_borealisInitialized = false;
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
/* Set scope guard to clean up resources at exit. */ /* Set scope guard to clean up resources at exit. */
@ -45,6 +47,7 @@ int main(int argc, char *argv[])
/* Initialize Borealis. */ /* Initialize Borealis. */
if (!brls::Application::init(APP_TITLE)) return EXIT_FAILURE; if (!brls::Application::init(APP_TITLE)) return EXIT_FAILURE;
g_borealisInitialized = true;
/* Check if we're running under applet mode. */ /* Check if we're running under applet mode. */
if (utilsAppletModeCheck()) if (utilsAppletModeCheck())