1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-08 11:51:48 +00:00
nxdumptool/source/exception_handler.cpp
Pablo Curiel 3f9cde8185 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.
2021-08-25 16:48:01 -04:00

223 lines
9.1 KiB
C++

/*
* 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);
}
}