Add stack tracing to skyline::exception

Skyline's `exception` class now stores a list of all stack frames during the invocation of the exception. These can later be parsed by the exception handler to generate a human-readable stack trace. To assist with more complete stack traces, `-fno-omit-frame-pointer` is now passed on debug builds which forces the inclusion of frames on function calls.
This commit is contained in:
PixelyIon 2022-04-11 21:37:47 +05:30
parent cd8fa66326
commit 41b98c7daa
11 changed files with 88 additions and 12 deletions

View File

@ -109,7 +109,7 @@ add_subdirectory("libraries/sirit")
add_subdirectory("libraries/adrenotools") add_subdirectory("libraries/adrenotools")
# Build Skyline with full debugging data and -Og for debug builds # Build Skyline with full debugging data and -Og for debug builds
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -glldb -gdwarf-5") set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -glldb -gdwarf-5 -fno-omit-frame-pointer")
# Include headers from libraries as system headers to silence warnings from them # Include headers from libraries as system headers to silence warnings from them
function(target_link_libraries_system target) function(target_link_libraries_system target)
@ -135,6 +135,7 @@ add_library(skyline SHARED
${source_DIR}/emu_jni.cpp ${source_DIR}/emu_jni.cpp
${source_DIR}/loader_jni.cpp ${source_DIR}/loader_jni.cpp
${source_DIR}/skyline/common.cpp ${source_DIR}/skyline/common.cpp
${source_DIR}/skyline/common/exception.cpp
${source_DIR}/skyline/common/logger.cpp ${source_DIR}/skyline/common/logger.cpp
${source_DIR}/skyline/common/settings.cpp ${source_DIR}/skyline/common/settings.cpp
${source_DIR}/skyline/common/signal.cpp ${source_DIR}/skyline/common/signal.cpp

View File

@ -16,6 +16,7 @@
#include <memory> #include <memory>
#include <compare> #include <compare>
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <common/exception.h>
#include <common/span.h> #include <common/span.h>
#include <common/result.h> #include <common/result.h>
#include <common/logger.h> #include <common/logger.h>

View File

@ -70,15 +70,6 @@ namespace skyline {
} }
} }
/**
* @brief A wrapper over std::runtime_error with {fmt} formatting
*/
class exception : public std::runtime_error {
public:
template<typename S, typename... Args>
exception(const S &formatStr, Args &&... args) : runtime_error(util::Format(formatStr, args...)) {}
};
/** /**
* @brief A deduction guide for overloads required for std::visit with std::variant * @brief A deduction guide for overloads required for std::visit with std::variant
*/ */

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "signal.h"
#include "exception.h"
namespace skyline {
std::vector<void *> exception::GetStackFrames() {
std::vector<void*> frames;
signal::StackFrame *frame{};
asm("MOV %0, FP" : "=r"(frame));
if (frame)
frame = frame->next; // We want to skip the first frame as it's going to be the caller of this function
while (frame && frame->lr) {
frames.push_back(frame->lr);
frame = frame->next;
}
return frames;
}
}

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <vector>
#include "base.h"
namespace skyline {
/**
* @brief A wrapper over std::runtime_error with {fmt} formatting and stack tracing support
*/
class exception : public std::runtime_error {
public:
std::vector<void *> frames; //!< All frames from the stack trace during the exception
/**
* @return A vector of all frames on the caller's stack
* @note This cannot be inlined because it depends on having one stack frame itself consistently
*/
static std::vector<void*> GetStackFrames() __attribute__((noinline));
template<typename S, typename... Args>
exception(const S &formatStr, Args &&... args) : runtime_error(util::Format(formatStr, args...)), frames(GetStackFrames()) {}
};
}

View File

@ -10,6 +10,7 @@
#include <frozen/string.h> #include <frozen/string.h>
#include <xxhash.h> #include <xxhash.h>
#include "base.h" #include "base.h"
#include "exception.h"
namespace skyline::util { namespace skyline::util {
/** /**

View File

@ -92,12 +92,20 @@ namespace skyline::loader {
size_t length{}; size_t length{};
std::unique_ptr<char, decltype(&std::free)> demangled{abi::__cxa_demangle(info.dli_sname, nullptr, &length, &status), std::free}; std::unique_ptr<char, decltype(&std::free)> demangled{abi::__cxa_demangle(info.dli_sname, nullptr, &length, &status), std::free};
auto extractFilename{[](const char *path) {
const char *filename{};
for (const char *p{path}; *p; p++)
if (*p == '/')
filename = p + 1;
return filename;
}}; //!< Extracts the filename from a path as we only want the filename of a shared object
if (info.dli_sname && info.dli_fname) if (info.dli_sname && info.dli_fname)
return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast<uintptr_t>(pointer), (status == 0) ? std::string_view(demangled.get()) : info.dli_sname, info.dli_fname); return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast<uintptr_t>(pointer), (status == 0) ? std::string_view(demangled.get()) : info.dli_sname, extractFilename(info.dli_fname));
else if (info.dli_sname) else if (info.dli_sname)
return fmt::format("\n* 0x{:X} ({})", reinterpret_cast<uintptr_t>(pointer), (status == 0) ? std::string_view(demangled.get()) : info.dli_sname); return fmt::format("\n* 0x{:X} ({})", reinterpret_cast<uintptr_t>(pointer), (status == 0) ? std::string_view(demangled.get()) : info.dli_sname);
else if (info.dli_fname) else if (info.dli_fname)
return fmt::format("\n* 0x{:X} (from {})", reinterpret_cast<uintptr_t>(pointer), info.dli_fname); return fmt::format("\n* 0x{:X} (from {})", reinterpret_cast<uintptr_t>(pointer), extractFilename(info.dli_fname));
else else
return fmt::format("\n* 0x{:X}", reinterpret_cast<uintptr_t>(pointer)); return fmt::format("\n* 0x{:X}", reinterpret_cast<uintptr_t>(pointer));
} else { } else {

View File

@ -50,6 +50,7 @@ namespace skyline::nce {
} else { } else {
Logger::EmulationContext.Flush(); Logger::EmulationContext.Flush();
} }
abi::__cxa_end_catch(); // We call this prior to the longjmp to cause the exception object to be destroyed abi::__cxa_end_catch(); // We call this prior to the longjmp to cause the exception object to be destroyed
std::longjmp(state.thread->originalCtx, true); std::longjmp(state.thread->originalCtx, true);
} catch (const ExitException &e) { } catch (const ExitException &e) {
@ -57,6 +58,19 @@ namespace skyline::nce {
signal::BlockSignal({SIGINT}); signal::BlockSignal({SIGINT});
state.process->Kill(false); state.process->Kill(false);
} }
abi::__cxa_end_catch();
std::longjmp(state.thread->originalCtx, true);
} catch (const exception &e) {
Logger::ErrorNoPrefix("{}\nStack Trace:{}", e.what(), state.loader->GetStackTrace(e.frames));
Logger::EmulationContext.Flush();
if (state.thread->id) {
signal::BlockSignal({SIGINT});
state.process->Kill(false);
}
abi::__cxa_end_catch();
std::longjmp(state.thread->originalCtx, true); std::longjmp(state.thread->originalCtx, true);
} catch (const std::exception &e) { } catch (const std::exception &e) {
if (svc) if (svc)
@ -70,6 +84,7 @@ namespace skyline::nce {
signal::BlockSignal({SIGINT}); signal::BlockSignal({SIGINT});
state.process->Kill(false); state.process->Kill(false);
} }
abi::__cxa_end_catch(); abi::__cxa_end_catch();
std::longjmp(state.thread->originalCtx, true); std::longjmp(state.thread->originalCtx, true);
} }

View File

@ -31,6 +31,9 @@ namespace skyline::service {
TRACE_EVENT("service", perfetto::StaticString{function.name}); TRACE_EVENT("service", perfetto::StaticString{function.name});
try { try {
return function(session, request, response); return function(session, request, response);
} catch (exception &e) {
// We need to forward any skyline::exception objects without modification even though they inherit from std::exception
std::rethrow_exception(std::current_exception());
} catch (const std::exception &e) { } catch (const std::exception &e) {
throw exception("{} (Service: {})", e.what(), function.name); throw exception("{} (Service: {})", e.what(), function.name);
} }

View File

@ -350,6 +350,11 @@ namespace skyline::soc::gm20b {
signal::BlockSignal({SIGINT}); signal::BlockSignal({SIGINT});
state.process->Kill(false); state.process->Kill(false);
} }
} catch (const exception &e) {
Logger::ErrorNoPrefix("{}\nStack Trace:{}", e.what(), state.loader->GetStackTrace(e.frames));
Logger::EmulationContext.Flush();
signal::BlockSignal({SIGINT});
state.process->Kill(false);
} catch (const std::exception &e) { } catch (const std::exception &e) {
Logger::Error(e.what()); Logger::Error(e.what());
Logger::EmulationContext.Flush(); Logger::EmulationContext.Flush();

View File

@ -130,6 +130,11 @@ namespace skyline::soc::host1x {
signal::BlockSignal({SIGINT}); signal::BlockSignal({SIGINT});
state.process->Kill(false); state.process->Kill(false);
} }
} catch (const exception &e) {
Logger::ErrorNoPrefix("{}\nStack Trace:{}", e.what(), state.loader->GetStackTrace(e.frames));
Logger::EmulationContext.Flush();
signal::BlockSignal({SIGINT});
state.process->Kill(false);
} catch (const std::exception &e) { } catch (const std::exception &e) {
Logger::Error(e.what()); Logger::Error(e.what());
Logger::EmulationContext.Flush(); Logger::EmulationContext.Flush();