abort/error: print backtrace, abuse templates, overhaul result/diag (macos not done yet)

This commit is contained in:
Michael Scire 2022-03-10 01:15:45 -08:00 committed by SciresM
parent 18168d54c3
commit 646f84bad1
118 changed files with 2843 additions and 369 deletions

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_abort_observer_manager.hpp"
namespace ams::diag::impl {
AbortObserverManager *GetAbortObserverManager() {
AMS_FUNCTION_LOCAL_STATIC(AbortObserverManager, s_manager);
return std::addressof(s_manager);
}
SdkAbortObserverManager *GetSdkAbortObserverManager() {
AMS_FUNCTION_LOCAL_STATIC(SdkAbortObserverManager, s_manager);
return std::addressof(s_manager);
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "diag_observer_manager.hpp"
namespace ams::diag::impl {
using AbortObserverManager = ObserverManager<AbortObserverHolder, const AbortInfo &>;
using SdkAbortObserverManager = ObserverManager<SdkAbortObserverHolder, const SdkAbortInfo &>;
AbortObserverManager *GetAbortObserverManager();
SdkAbortObserverManager *GetSdkAbortObserverManager();
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#if defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS)
#include <fcntl.h>
#include <unistd.h>
#endif
namespace ams::diag::impl {
namespace {
#if defined(ATMOSPHERE_ARCH_X64)
struct StackFrame {
u64 fp; /* rbp */
u64 lr; /* rip */
};
#elif defined(ATMOSPHERE_ARCH_X86)
struct StackFrame {
u32 fp; /* ebp */
u32 lr; /* eip */
}
#elif defined(ATMOSPHERE_ARCH_ARM64)
struct StackFrame {
u64 fp;
u64 lr;
};
#elif defined(ATMOSPHERE_ARCH_ARM)
struct StackFrame {
u32 fp;
u32 lr;
}
#else
#error "Unknown architecture for generic backtrace."
#endif
bool TryRead(os::NativeHandle native_handle, void *dst, size_t size, const void *address) {
#if defined(ATMOSPHERE_OS_WINDOWS)
return ::ReadProcessMemory(native_handle, address, dst, size, nullptr);
#elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS)
s32 ret;
do {
ret = ::write(native_handle, address, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
return false;
}
std::memcpy(dst, address, size);
return true;
#else
#error "Unknown OS for Backtrace native handle"
#endif
}
}
NOINLINE void Backtrace::Initialize() {
/* Clear our size. */
m_index = 0;
m_size = 0;
/* Get the base frame pointer. */
const void *cur_fp = __builtin_frame_address(0);
/* Try to read stack frames, until we run out. */
#if defined(ATMOSPHERE_OS_WINDOWS)
const os::NativeHandle native_handle = ::GetCurrentProcess();
#elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS)
os::NativeHandle pipe_handles[2];
s32 nret;
do { nret = ::pipe(pipe_handles); } while (nret < 0 && errno == EINTR);
if (nret < 0) { return; }
do { nret = ::fcntl(pipe_handles[0], F_SETFL, O_NONBLOCK); } while (nret < 0 && errno == EINTR);
if (nret < 0) { return; }
do { nret = ::fcntl(pipe_handles[1], F_SETFL, O_NONBLOCK); } while (nret < 0 && errno == EINTR);
if (nret < 0) { return; }
ON_SCOPE_EXIT {
do { nret = ::close(pipe_handles[0]); } while (nret < 0 && errno == EINTR);
do { nret = ::close(pipe_handles[1]); } while (nret < 0 && errno == EINTR);
};
const os::NativeHandle native_handle = pipe_handles[1];
if (native_handle < 0) { return; }
#else
#error "Unknown OS for Backtrace native handle"
#endif
StackFrame frame;
while (m_size < BacktraceEntryCountMax) {
/* Clear the frame. */
frame = {};
/* Read the next frame. */
if (!TryRead(native_handle, std::addressof(frame), sizeof(frame), cur_fp)) {
break;
}
/* Add the return address. */
m_backtrace_addresses[m_size++] = reinterpret_cast<void *>(frame.lr);
/* Set the next fp. */
cur_fp = reinterpret_cast<const void *>(frame.fp);
}
}
bool Backtrace::Step() {
return (++m_index) < m_size;
}
uintptr_t Backtrace::GetStackPointer() const {
return 0;
}
uintptr_t Backtrace::GetReturnAddress() const {
return reinterpret_cast<uintptr_t>(m_backtrace_addresses[m_index]);
}
}

View file

@ -0,0 +1,217 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::diag::impl {
namespace {
uintptr_t GetAddressValue(uintptr_t address) {
if (address == 0) {
return 0;
}
if (!util::IsAligned(address, alignof(uintptr_t))) {
return 0;
}
return *reinterpret_cast<uintptr_t *>(address);
}
template<typename T, size_t N>
svc::MemoryInfo *GetMemoryInfoPointer(T (&arr)[N]) {
/* Check that there's enough space. */
static_assert(sizeof(T) * N <= sizeof(svc::MemoryInfo));
static_assert(alignof(T) >= alignof(svc::MemoryInfo));
return reinterpret_cast<svc::MemoryInfo *>(std::addressof(arr[0]));
}
bool IsValidLinkRegisterValue(uintptr_t lr, svc::MemoryInfo *info) {
/* Ensure the memory info is valid. */
Result query_res;
if (!(info->base_address <= lr && lr < info->base_address + info->size) && ({ svc::PageInfo page_info; query_res = svc::QueryMemory(info, std::addressof(page_info), lr); R_FAILED(query_res); })) {
AMS_SDK_LOG("Failed to get backtrace. Query memory failed. (lr: %p, result: %03d-%04d)\n", reinterpret_cast<void *>(lr), query_res.GetModule(), query_res.GetDescription());
return false;
}
/* Check that lr is valid. */
if (lr == 0) {
return false;
}
if (!util::IsAligned(lr, sizeof(u32))) {
AMS_SDK_LOG("Failed to get backtrace. The link register alignment is invalid. (lr: %p)\n", reinterpret_cast<void *>(lr));
return false;
}
/* Check that the lr points to code. */
if (info->permission != svc::MemoryPermission_ReadExecute) {
AMS_SDK_LOG("Failed to get backtrace. The link register points out of the code. (lr: %p)\n", reinterpret_cast<void *>(lr));
return false;
}
return true;
}
void GetNormalStackInfo(Backtrace::StackInfo *out) {
if (void * const fiber = nullptr /* TODO: os::GetCurrentFiber() */; fiber == nullptr) {
/* Get thread. */
auto * const thread = os::GetCurrentThread();
out->stack_top = reinterpret_cast<uintptr_t>(thread->stack);
out->stack_bottom = reinterpret_cast<uintptr_t>(thread->stack) + thread->stack_size;
} else {
/* TODO: Fiber. */
}
}
bool GetExceptionStackInfo(Backtrace::StackInfo *out, uintptr_t sp) {
/* Get the current stack info. */
uintptr_t cur_stack = 0;
size_t cur_stack_size = 0;
os::GetCurrentStackInfo(std::addressof(cur_stack), std::addressof(cur_stack_size));
/* Get the thread's stack info. */
uintptr_t thread_stack = 0;
size_t thread_stack_size = 0;
os::GetThreadStackInfo(std::addressof(thread_stack), std::addressof(thread_stack_size), os::GetCurrentThread());
/* If the current stack is the thread stack, exception stack isn't being used. */
if (cur_stack == thread_stack) {
AMS_ASSERT(cur_stack_size == thread_stack_size);
return false;
}
/* Check if the stack pointer is contained in the current stack. */
if (!(cur_stack <= sp && sp < cur_stack + cur_stack_size)) {
return false;
}
/* Set the output. */
out->stack_top = cur_stack;
out->stack_bottom = cur_stack + cur_stack_size;
return true;
}
}
NOINLINE void Backtrace::Initialize() {
/* Get the stack pointer/frame pointer. */
uintptr_t fp, sp;
__asm__ __volatile__(
#if defined(ATMOSPHERE_ARCH_ARM64)
"mov %[fp], fp\n"
"mov %[sp], sp\n"
#elif defined(ATMOSPHERE_ARCH_ARM)
"mov %[fp], x29\n"
"mov %[sp], sp\n"
#else
#error "Unknown architecture for Horizon fp/sp retrieval."
#endif
: [fp]"=&r"(fp), [sp]"=&r"(sp)
:
: "memory"
);
/* Set our stack info. */
this->SetStackInfo(fp, sp);
/* Step, to get our first lr. */
this->Step();
}
void Backtrace::Initialize(uintptr_t fp, uintptr_t sp, uintptr_t pc) {
/* Set our initial lr. */
m_lr = pc;
/* Set our stack info. */
this->SetStackInfo(fp, sp);
}
bool Backtrace::Step() {
/* We can't step without a frame pointer. */
if (m_fp == 0) {
if (m_current_stack_info != std::addressof(m_normal_stack_info)) {
AMS_SDK_LOG("Failed to get backtrace. The frame pointer is null.\n");
}
return false;
}
/* The frame pointer needs to be aligned. */
if (!util::IsAligned(m_fp, sizeof(uintptr_t))) {
AMS_SDK_LOG("Failed to get backtrace. The frame pointer alignment is invalid. (fp: %p)\n", reinterpret_cast<void *>(m_fp));
return false;
}
/* Ensure our current stack info is good. */
if (!(m_current_stack_info->stack_top <= m_fp && m_fp < m_current_stack_info->stack_bottom)) {
if (m_current_stack_info != std::addressof(m_exception_stack_info) || !(m_normal_stack_info.stack_top <= m_fp && m_fp < m_normal_stack_info.stack_bottom)) {
AMS_SDK_LOG("Failed to get backtrace. The frame pointer points out of the stack. (fp: %p, stack: %p-%p)\n", reinterpret_cast<void *>(m_fp), reinterpret_cast<void *>(m_current_stack_info->stack_top), reinterpret_cast<void *>(m_current_stack_info->stack_bottom));
return false;
}
m_current_stack_info = std::addressof(m_normal_stack_info);
} else if (m_fp <= m_prev_fp) {
AMS_SDK_LOG("Failed to get backtrace. The frame pointer is rewinding. (fp: %p, prev fp: %p, stack: %p-%p)\n", reinterpret_cast<void *>(m_fp), reinterpret_cast<void *>(m_prev_fp), reinterpret_cast<void *>(m_current_stack_info->stack_top), reinterpret_cast<void *>(m_current_stack_info->stack_bottom));
return false;
}
/* Update our previous fp. */
m_prev_fp = m_fp;
/* Read lr/fp. */
m_lr = GetAddressValue(m_fp + sizeof(m_fp));
m_fp = GetAddressValue(m_fp);
/* Check that lr is valid. */
if (IsValidLinkRegisterValue(m_lr, GetMemoryInfoPointer(m_memory_info_buffer))) {
return true;
} else {
m_lr = 0;
return false;
}
}
uintptr_t Backtrace::GetStackPointer() const {
if (m_fp != 0) {
return m_fp - sizeof(m_fp);
} else {
return m_current_stack_info->stack_bottom - sizeof(m_fp);
}
}
uintptr_t Backtrace::GetReturnAddress() const {
return m_lr;
}
void Backtrace::SetStackInfo(uintptr_t fp, uintptr_t sp) {
/* Get the normal stack info. */
GetNormalStackInfo(std::addressof(m_normal_stack_info));
/* Get the exception stack info. */
if (GetExceptionStackInfo(std::addressof(m_exception_stack_info), sp)) {
m_current_stack_info = std::addressof(m_exception_stack_info);
} else {
m_current_stack_info = std::addressof(m_normal_stack_info);
}
/* Set our frame pointer. */
m_fp = fp;
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_dump_stack_trace.hpp"
namespace ams::diag::impl {
#if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_DEBUGGING)
namespace {
constexpr const char *ToString(AbortReason reason) {
switch (reason) {
case AbortReason_Audit:
return "Auditing Assertion Failure";
case AbortReason_Assert:
return "Assertion Failure";
case AbortReason_UnexpectedDefault:
return "Unexpected Default";
case AbortReason_Abort:
default:
return "Abort";
}
}
void DefaultPrinter(const AbortInfo &info) {
/* Get the thread name. */
const char *thread_name;
if (auto *cur_thread = os::GetCurrentThread(); cur_thread != nullptr) {
thread_name = os::GetThreadNamePointer(cur_thread);
} else {
thread_name = "unknown";
}
#if defined(ATMOSPHERE_OS_HORIZON)
{
u64 process_id = 0;
u64 thread_id = 0;
svc::GetProcessId(std::addressof(process_id), svc::PseudoHandle::CurrentProcess);
svc::GetThreadId(std::addressof(thread_id), svc::PseudoHandle::CurrentThread);
AMS_SDK_LOG("%s: '%s' in %s, process=0x%02" PRIX64 ", thread=%" PRIu64 " (%s)\n%s:%d\n", ToString(info.reason), info.expr, info.func, process_id, thread_id, thread_name, info.file, info.line);
}
#elif defined(ATMOSPHERE_OS_WINDOWS)
{
DWORD process_id = ::GetCurrentProcessId();
DWORD thread_id = ::GetCurrentThreadId();
AMS_SDK_LOG("%s: '%s' in %s, process=0x%" PRIX64 ", thread=%" PRIu64 " (%s)\n%s:%d\n", ToString(info.reason), info.expr, info.func, static_cast<u64>(process_id), static_cast<u64>(thread_id), thread_name, info.file, info.line);
}
#else
{
AMS_SDK_LOG("%s: '%s' in %s, thread=%s\n%s:%d\n", ToString(info.reason), info.expr, info.func, thread_name, info.file, info.line);
}
#endif
AMS_SDK_VLOG(info.message->fmt, *(info.message->vl));
AMS_SDK_LOG("\n");
TentativeDumpStackTrace();
}
}
#endif
void DefaultAbortObserver(const AbortInfo &info) {
#if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)
DefaultPrinter(info);
#else
AMS_UNUSED(info);
#endif
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::diag::impl {
void TentativeDumpStackTrace();
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_dump_stack_trace.hpp"
namespace ams::diag::impl {
void TentativeDumpStackTrace() {
AMS_SDK_LOG("----------------Stack Trace----------------\n");
{
/* Get the backtrace. */
constexpr size_t MaxBackTraceSize = 0x40;
uintptr_t backtrace[MaxBackTraceSize];
const size_t num_items = ::ams::diag::GetBacktrace(backtrace, MaxBackTraceSize);
/* Print each item. */
for (size_t i = 0; i < num_items; ++i) {
char symbol_name[0x200];
if (const uintptr_t symbol_base = ::ams::diag::GetSymbolName(symbol_name, sizeof(symbol_name), backtrace[i] - 1); symbol_base != 0) {
AMS_SDK_LOG("0x%016" PRIX64 " [ %s+0x%" PRIX64 " ]\n", static_cast<u64>(backtrace[i]), symbol_name, static_cast<u64>(backtrace[i] - (symbol_base + 1)));
} else {
AMS_SDK_LOG("0x%016" PRIX64 " [ unknown ]\n", static_cast<u64>(backtrace[i]));
}
}
}
AMS_SDK_LOG("-------------------------------------------\n");
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_dump_stack_trace.hpp"
namespace ams::diag::impl {
void TentativeDumpStackTrace() {
/* TODO */
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::diag::impl {
namespace {
constinit uintptr_t g_abort_impl_return_address = std::numeric_limits<uintptr_t>::max();
}
size_t GetAllBacktrace(uintptr_t *out, size_t out_size, ::ams::diag::Backtrace &bt) {
size_t count = 0;
do {
/* Check that we can write another return address. */
if (count >= out_size) {
break;
}
/* Get the current return address. */
const uintptr_t ret_addr = bt.GetReturnAddress();
/* If it's abort impl, reset the trace we're writing. */
if (ret_addr == g_abort_impl_return_address) {
count = 0;
}
/* Set the output pointer. */
out[count++] = ret_addr;
} while (bt.Step());
/* Return the number of addresses written. */
return count;
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::diag::impl {
void SetAbortImplAddress(uintptr_t address);
size_t GetAllBacktrace(uintptr_t *out, size_t out_size, ::ams::diag::Backtrace &bt);
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::diag::impl {
void InvokeAbortObserver(const AbortInfo &info);
void InvokeSdkAbortObserver(const SdkAbortInfo &info);
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_abort_observer_manager.hpp"
#include "diag_invoke_abort.hpp"
namespace ams::diag::impl {
extern bool g_enable_default_abort_observer;
void DefaultAbortObserver(const AbortInfo &info);
void InvokeAbortObserver(const AbortInfo &info) {
if (g_enable_default_abort_observer) {
DefaultAbortObserver(info);
}
GetAbortObserverManager()->InvokeAllObserver(info);
}
void InvokeSdkAbortObserver(const SdkAbortInfo &info) {
GetSdkAbortObserverManager()->InvokeAllObserver(info);
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_abort_observer_manager.hpp"
#include "diag_invoke_abort.hpp"
namespace ams::diag::impl {
extern bool g_enable_default_abort_observer;
void DefaultAbortObserver(const AbortInfo &info);
void InvokeAbortObserver(const AbortInfo &info) {
if (g_enable_default_abort_observer) {
DefaultAbortObserver(info);
}
GetAbortObserverManager()->InvokeAllObserver(info);
}
void InvokeSdkAbortObserver(const SdkAbortInfo &info) {
GetSdkAbortObserverManager()->InvokeAllObserver(info);
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::diag::impl {
uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address);
size_t GetSymbolSizeImpl(uintptr_t address);
}

View file

@ -0,0 +1,289 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_symbol_impl.hpp"
#define PACKAGE "stratosphere"
#define PACKAGE_VERSION STRINGIFY(ATMOSPHERE_RELEASE_VERSION_MAJOR.ATMOSPHERE_RELEASE_VERSION_MINOR.ATMOSPHERE_RELEASE_VERSION_MICRO)
/* msys2 mingw64 puts headers inside binutils/ */
#if defined(ATMOSPHERE_OS_WINDOWS)
#include <binutils/bfd.h>
#include <psapi.h>
#else
#include <bfd.h>
#endif
#if defined(ATMOSPHERE_OS_LINUX)
#include <unistd.h>
#include <dlfcn.h>
#include <link.h>
extern "C" char __init_array_start;
#endif
#define HAVE_DECL_BASENAME 1
#include <libiberty/demangle.h>
namespace ams::diag::impl {
namespace {
class BfdHelper {
private:
bfd *m_handle;
asymbol **m_symbol;
size_t m_num_symbol;
size_t m_num_func_symbol;
const char *m_module_name;
uintptr_t m_module_address;
size_t m_module_size;
private:
BfdHelper() : m_handle(nullptr), m_symbol(nullptr), m_module_name(nullptr) {
/* Get the current executable name. */
char exe_path[4_KB] = {};
GetExecutablePath(exe_path, sizeof(exe_path));
/* Open bfd. */
bfd *b = ::bfd_openr(exe_path, 0);
if (b == nullptr) {
return;
}
auto bfd_guard = SCOPE_GUARD { ::bfd_close(b); };
/* Check the format. */
if (!::bfd_check_format(b, bfd_object)) {
return;
}
/* Verify the file has symbols. */
if ((bfd_get_file_flags(b) & HAS_SYMS) == 0) {
return;
}
/* Read the symbols. */
unsigned int _;
void *symbol_table;
s64 num_symbols = bfd_read_minisymbols(b, false, std::addressof(symbol_table), std::addressof(_));
if (num_symbols == 0) {
num_symbols = bfd_read_minisymbols(b, true, std::addressof(symbol_table), std::addressof(_));
if (num_symbols < 0) {
return;
}
}
/* We successfully got the symbol table. */
bfd_guard.Cancel();
m_handle = b;
m_symbol = reinterpret_cast<asymbol **>(symbol_table);
m_num_symbol = static_cast<size_t>(num_symbols);
/* Sort the symbol table. */
std::sort(m_symbol + 0, m_symbol + m_num_symbol, [] (asymbol *lhs, asymbol *rhs) {
const bool l_func = (lhs->flags & BSF_FUNCTION);
const bool r_func = (rhs->flags & BSF_FUNCTION);
if (l_func == r_func) {
return bfd_asymbol_value(lhs) < bfd_asymbol_value(rhs);
} else {
return l_func;
}
});
/* Determine number of function symbols. */
m_num_func_symbol = 0;
for (size_t i = 0; i < m_num_symbol; ++i) {
if ((m_symbol[i]->flags & BSF_FUNCTION) == 0) {
m_num_func_symbol = i;
break;
}
}
for (int i = std::strlen(exe_path) - 1; i >= 0; --i) {
if (exe_path[i] == '/' || exe_path[i] == '\\') {
m_module_name = strdup(exe_path + i + 1);
break;
}
}
/* Get our module base/size. */
#if defined(ATMOSPHERE_OS_WINDOWS)
{
MODULEINFO module_info;
if (::GetModuleInformation(::GetCurrentProcess(), ::GetModuleHandleA(nullptr), std::addressof(module_info), sizeof(module_info))) {
m_module_address = reinterpret_cast<uintptr_t>(module_info.lpBaseOfDll);
m_module_size = static_cast<size_t>(module_info.SizeOfImage);
}
}
#elif defined(ATMOSPHERE_OS_LINUX)
{
m_module_address = _r_debug.r_map->l_addr;
m_module_size = reinterpret_cast<uintptr_t>(std::addressof(__init_array_start)) - m_module_address;
}
#endif
}
~BfdHelper() {
if (m_symbol != nullptr) {
std::free(m_symbol);
}
if (m_handle != nullptr) {
::bfd_close(m_handle);
}
}
public:
static BfdHelper &GetInstance() {
AMS_FUNCTION_LOCAL_STATIC(BfdHelper, s_bfd_helper_instance);
return s_bfd_helper_instance;
}
private:
size_t GetSymbolSizeImpl(asymbol **symbol) const {
/* Do our best to guess. */
const auto vma = bfd_asymbol_value(*symbol);
if (symbol != m_symbol + m_num_func_symbol - 1) {
return bfd_asymbol_value(*(symbol + 1)) - vma;
} else {
const auto *sec = (*symbol)->section;
return (sec->vma + sec->size) - vma;
}
}
std::ptrdiff_t GetSymbolAddressDisplacement(uintptr_t address) const {
std::ptrdiff_t displacement = 0;
if (m_module_address <= address && address < m_module_address + m_module_size) {
displacement = m_module_address;
#if defined(ATMOSPHERE_OS_WINDOWS)
{
#if defined(__MINGW64__)
displacement -= UINT64_C(0x140000000);
#elif defined(__MINGW32__)
displacement -= UINT64_C(0x400000);
#else
#error "Unknown build context for windows module base!"
#endif
}
#endif
}
return displacement;
}
asymbol **GetBestSymbol(uintptr_t address) const {
/* Adjust the symbol address. */
address -= this->GetSymbolAddressDisplacement(address);
asymbol **best_symbol = std::lower_bound(m_symbol + 0, m_symbol + m_num_func_symbol, address, [](asymbol *lhs, uintptr_t rhs) {
return bfd_asymbol_value(lhs) < rhs;
});
if (best_symbol == m_symbol + m_num_func_symbol) {
return nullptr;
}
if (bfd_asymbol_value(*best_symbol) != address && best_symbol > m_symbol) {
--best_symbol;
}
const auto vma = bfd_asymbol_value(*best_symbol);
const auto end = vma + this->GetSymbolSizeImpl(best_symbol);
if (vma <= address && address < end) {
return best_symbol;
} else {
return nullptr;
}
}
public:
uintptr_t GetSymbolName(char *dst, size_t dst_size, uintptr_t address) const {
/* Get the symbol. */
auto **symbol = this->GetBestSymbol(address);
if (symbol == nullptr) {
return 0;
}
/* Print the symbol. */
const char *name = bfd_asymbol_name(*symbol);
if (auto *demangled = bfd_demangle(m_handle, name, DMGL_ANSI | DMGL_PARAMS); demangled != nullptr) {
util::TSNPrintf(dst, dst_size, "%s", demangled);
std::free(demangled);
} else {
util::TSNPrintf(dst, dst_size, "%s", name);
}
return bfd_asymbol_value(*symbol) + this->GetSymbolAddressDisplacement(address);
}
size_t GetSymbolSize(uintptr_t address) const {
/* Get the symbol. */
auto **symbol = this->GetBestSymbol(address);
if (symbol == nullptr) {
return 0;
}
return this->GetSymbolSizeImpl(symbol);
}
private:
static void GetExecutablePath(char *dst, size_t dst_size) {
#if defined(ATMOSPHERE_OS_WINDOWS)
{
/* Get the module file name. */
wchar_t module_file_name[0x1000];
if (::GetModuleFileNameW(0, module_file_name, util::size(module_file_name)) == 0) {
dst[0] = 0;
return;
}
/* Convert to utf-8. */
const auto res = ::WideCharToMultiByte(CP_UTF8, 0, module_file_name, -1, dst, dst_size, nullptr, nullptr);
if (res == 0) {
dst[0] = 0;
return;
}
}
#elif defined(ATMOSPHERE_OS_LINUX)
{
if (::readlink("/proc/self/exe", dst, dst_size) == -1) {
dst[0] = 0;
return;
}
}
#elif defined(ATMOSPHERE_OS_MACOS)
{
if (_NSGetExecutablePath(dst, dst_size) != 0) {
dst[0] = 0;
return;
}
}
#else
#error "Unknown OS for BfdHelper GetExecutablePath"
#endif
}
};
}
uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address) {
return BfdHelper::GetInstance().GetSymbolName(dst, dst_size, address);
}
size_t GetSymbolSizeImpl(uintptr_t address) {
return BfdHelper::GetInstance().GetSymbolSize(address);
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "diag_symbol_impl.hpp"
namespace ams::diag::impl {
uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address) {
AMS_UNUSED(dst, dst_size, address);
AMS_ABORT("TODO");
}
size_t GetSymbolSizeImpl(uintptr_t address) {
AMS_UNUSED(address);
AMS_ABORT("TODO");
}
}