mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-08 11:51:48 +00:00
3f9cde8185
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.
223 lines
9.1 KiB
C++
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);
|
|
}
|
|
}
|