From 41b98c7daab80c50b6455734ce73d59b7dfb399b Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Mon, 11 Apr 2022 21:37:47 +0530 Subject: [PATCH] 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. --- app/CMakeLists.txt | 3 ++- app/src/main/cpp/skyline/common.h | 1 + app/src/main/cpp/skyline/common/base.h | 9 ------- app/src/main/cpp/skyline/common/exception.cpp | 20 ++++++++++++++ app/src/main/cpp/skyline/common/exception.h | 26 +++++++++++++++++++ app/src/main/cpp/skyline/common/utils.h | 1 + app/src/main/cpp/skyline/loader/loader.cpp | 12 +++++++-- app/src/main/cpp/skyline/nce.cpp | 15 +++++++++++ .../cpp/skyline/services/base_service.cpp | 3 +++ app/src/main/cpp/skyline/soc/gm20b/gpfifo.cpp | 5 ++++ .../cpp/skyline/soc/host1x/command_fifo.cpp | 5 ++++ 11 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 app/src/main/cpp/skyline/common/exception.cpp create mode 100644 app/src/main/cpp/skyline/common/exception.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 428932d3..633986f3 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -109,7 +109,7 @@ add_subdirectory("libraries/sirit") add_subdirectory("libraries/adrenotools") # 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 function(target_link_libraries_system target) @@ -135,6 +135,7 @@ add_library(skyline SHARED ${source_DIR}/emu_jni.cpp ${source_DIR}/loader_jni.cpp ${source_DIR}/skyline/common.cpp + ${source_DIR}/skyline/common/exception.cpp ${source_DIR}/skyline/common/logger.cpp ${source_DIR}/skyline/common/settings.cpp ${source_DIR}/skyline/common/signal.cpp diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index b88952fe..ab46af0b 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/app/src/main/cpp/skyline/common/base.h b/app/src/main/cpp/skyline/common/base.h index f89f0a80..eb8bfa4e 100644 --- a/app/src/main/cpp/skyline/common/base.h +++ b/app/src/main/cpp/skyline/common/base.h @@ -70,15 +70,6 @@ namespace skyline { } } - /** - * @brief A wrapper over std::runtime_error with {fmt} formatting - */ - class exception : public std::runtime_error { - public: - template - 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 */ diff --git a/app/src/main/cpp/skyline/common/exception.cpp b/app/src/main/cpp/skyline/common/exception.cpp new file mode 100644 index 00000000..1b57d789 --- /dev/null +++ b/app/src/main/cpp/skyline/common/exception.cpp @@ -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 exception::GetStackFrames() { + std::vector 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; + } +} diff --git a/app/src/main/cpp/skyline/common/exception.h b/app/src/main/cpp/skyline/common/exception.h new file mode 100644 index 00000000..3bb27ba8 --- /dev/null +++ b/app/src/main/cpp/skyline/common/exception.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#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 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 GetStackFrames() __attribute__((noinline)); + + template + exception(const S &formatStr, Args &&... args) : runtime_error(util::Format(formatStr, args...)), frames(GetStackFrames()) {} + }; +} diff --git a/app/src/main/cpp/skyline/common/utils.h b/app/src/main/cpp/skyline/common/utils.h index ddd04576..f8c6b390 100644 --- a/app/src/main/cpp/skyline/common/utils.h +++ b/app/src/main/cpp/skyline/common/utils.h @@ -10,6 +10,7 @@ #include #include #include "base.h" +#include "exception.h" namespace skyline::util { /** diff --git a/app/src/main/cpp/skyline/loader/loader.cpp b/app/src/main/cpp/skyline/loader/loader.cpp index bd45d2bf..885cbcba 100644 --- a/app/src/main/cpp/skyline/loader/loader.cpp +++ b/app/src/main/cpp/skyline/loader/loader.cpp @@ -92,12 +92,20 @@ namespace skyline::loader { size_t length{}; std::unique_ptr 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) - return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast(pointer), (status == 0) ? std::string_view(demangled.get()) : info.dli_sname, info.dli_fname); + return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast(pointer), (status == 0) ? std::string_view(demangled.get()) : info.dli_sname, extractFilename(info.dli_fname)); else if (info.dli_sname) return fmt::format("\n* 0x{:X} ({})", reinterpret_cast(pointer), (status == 0) ? std::string_view(demangled.get()) : info.dli_sname); else if (info.dli_fname) - return fmt::format("\n* 0x{:X} (from {})", reinterpret_cast(pointer), info.dli_fname); + return fmt::format("\n* 0x{:X} (from {})", reinterpret_cast(pointer), extractFilename(info.dli_fname)); else return fmt::format("\n* 0x{:X}", reinterpret_cast(pointer)); } else { diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index dda73a53..af41004a 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -50,6 +50,7 @@ namespace skyline::nce { } else { Logger::EmulationContext.Flush(); } + 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); } catch (const ExitException &e) { @@ -57,6 +58,19 @@ namespace skyline::nce { signal::BlockSignal({SIGINT}); 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); } catch (const std::exception &e) { if (svc) @@ -70,6 +84,7 @@ namespace skyline::nce { signal::BlockSignal({SIGINT}); state.process->Kill(false); } + abi::__cxa_end_catch(); std::longjmp(state.thread->originalCtx, true); } diff --git a/app/src/main/cpp/skyline/services/base_service.cpp b/app/src/main/cpp/skyline/services/base_service.cpp index 514571fb..1efd0079 100644 --- a/app/src/main/cpp/skyline/services/base_service.cpp +++ b/app/src/main/cpp/skyline/services/base_service.cpp @@ -31,6 +31,9 @@ namespace skyline::service { TRACE_EVENT("service", perfetto::StaticString{function.name}); try { 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) { throw exception("{} (Service: {})", e.what(), function.name); } diff --git a/app/src/main/cpp/skyline/soc/gm20b/gpfifo.cpp b/app/src/main/cpp/skyline/soc/gm20b/gpfifo.cpp index 07bdc876..b7a63f00 100644 --- a/app/src/main/cpp/skyline/soc/gm20b/gpfifo.cpp +++ b/app/src/main/cpp/skyline/soc/gm20b/gpfifo.cpp @@ -350,6 +350,11 @@ namespace skyline::soc::gm20b { signal::BlockSignal({SIGINT}); 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) { Logger::Error(e.what()); Logger::EmulationContext.Flush(); diff --git a/app/src/main/cpp/skyline/soc/host1x/command_fifo.cpp b/app/src/main/cpp/skyline/soc/host1x/command_fifo.cpp index 92eccb31..1f7046e1 100644 --- a/app/src/main/cpp/skyline/soc/host1x/command_fifo.cpp +++ b/app/src/main/cpp/skyline/soc/host1x/command_fifo.cpp @@ -130,6 +130,11 @@ namespace skyline::soc::host1x { signal::BlockSignal({SIGINT}); 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) { Logger::Error(e.what()); Logger::EmulationContext.Flush();