From ebadc1d1e1215b2b7f5f3ed7a59d55e5d72d7d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=97=B1=20PixelyIon?= Date: Mon, 11 Jan 2021 23:20:31 +0530 Subject: [PATCH] Generate Stack Traces + More Robust Terminate Handler + Exit Process on Signal in Guest --- .idea/compiler.xml | 2 +- .idea/inspectionProfiles/Project_Default.xml | 1487 +---------------- .idea/misc.xml | 12 +- app/src/main/cpp/skyline/common.h | 1 + app/src/main/cpp/skyline/common/signal.cpp | 46 +- app/src/main/cpp/skyline/common/signal.h | 9 + app/src/main/cpp/skyline/kernel/ipc.h | 16 +- app/src/main/cpp/skyline/kernel/svc.cpp | 24 +- app/src/main/cpp/skyline/kernel/svc.h | 8 +- app/src/main/cpp/skyline/loader/executable.h | 9 +- app/src/main/cpp/skyline/loader/loader.cpp | 77 +- app/src/main/cpp/skyline/loader/loader.h | 50 +- app/src/main/cpp/skyline/loader/nca.cpp | 8 +- app/src/main/cpp/skyline/loader/nca.h | 5 +- app/src/main/cpp/skyline/loader/nro.cpp | 23 +- app/src/main/cpp/skyline/loader/nso.cpp | 33 +- app/src/main/cpp/skyline/loader/nso.h | 18 +- app/src/main/cpp/skyline/loader/nsp.cpp | 2 +- app/src/main/cpp/skyline/nce.cpp | 49 +- app/src/main/cpp/skyline/vfs/nacp.cpp | 4 +- .../main/cpp/skyline/vfs/os_filesystem.cpp | 2 +- 21 files changed, 312 insertions(+), 1573 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 61a9130c..fb7f4a8a 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 98e42156..7d92a375 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,5 +1,5 @@ - + - - - - - - - - + diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 18c47835..4144db5c 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/app/src/main/cpp/skyline/common/signal.cpp b/app/src/main/cpp/skyline/common/signal.cpp index e35095f6..1a369147 100644 --- a/app/src/main/cpp/skyline/common/signal.cpp +++ b/app/src/main/cpp/skyline/common/signal.cpp @@ -15,21 +15,37 @@ namespace skyline::signal { std::terminate_handler terminateHandler{}; + inline StackFrame *SafeFrameRecurse(size_t depth, StackFrame *frame) { + if (frame) { + for (size_t it{}; it < depth; it++) { + if (frame->next) + frame = frame->next; + else + terminateHandler(); + } + } else { + terminateHandler(); + } + return frame; + } + void TerminateHandler() { auto exception{std::current_exception()}; if (terminateHandler && exception && exception == SignalExceptionPtr) { - struct StackFrame { - StackFrame *next; - void *lr; - } *frame; - + StackFrame *frame; asm("MOV %0, FP" : "=r"(frame)); - frame = frame->next->next; + frame = SafeFrameRecurse(2, frame); // We unroll past 'std::terminate' - if (_Unwind_FindEnclosingFunction(frame->lr) == &ExceptionThrow) // We're in a loop, skip past a frame - frame = frame->next->next; - else if (_Unwind_FindEnclosingFunction(frame->next->next->next->next->next->lr) == &ExceptionThrow) // We're in a deeper loop, just terminate - frame = frame->next->next->next->next->next->next->next; + auto lookupFrame{frame}; + while (lookupFrame) { + auto function{_Unwind_FindEnclosingFunction(frame->lr)}; + if (function == &ExceptionThrow) + terminateHandler(); // We have no handler to consume the exception, it's time to quit + lookupFrame = lookupFrame->next; + } + + if (!frame->next) + terminateHandler(); // We don't know the frame's stack boundaries, the only option is to quit asm("MOV SP, %x0\n\t" // Stack frame is the first item on a function's stack, it's used to calculate calling function's stack pointer "MOV LR, %x1\n\t" @@ -49,6 +65,14 @@ namespace skyline::signal { signalException.pc = reinterpret_cast(context->uc_mcontext.pc); if (signal == SIGSEGV) signalException.fault = info->si_addr; + + signalException.frames.push_back(reinterpret_cast(context->uc_mcontext.pc)); + StackFrame *frame{reinterpret_cast(context->uc_mcontext.regs[29])}; + while (frame) { + signalException.frames.push_back(frame->lr); + frame = frame->next; + } + SignalExceptionPtr = std::make_exception_ptr(signalException); context->uc_mcontext.pc = reinterpret_cast(&ExceptionThrow); @@ -109,7 +133,7 @@ namespace skyline::signal { thread_local std::array ThreadSignalHandlers{}; - __attribute__((no_stack_protector)) // Stack protector stores data in TLS at the function epilog and verifies it at the prolog, we cannot allow writes to guest TLS and may switch to an alternative TLS during the signal handler and have disabled the stack protector as a result + __attribute__((no_stack_protector)) // Stack protector stores data in TLS at the function epilogue and verifies it at the prolog, we cannot allow writes to guest TLS and may switch to an alternative TLS during the signal handler and have disabled the stack protector as a result void ThreadSignalHandler(int signal, siginfo *info, ucontext *context) { void *tls{}; // The TLS value prior to being restored if it is if (TlsRestorer) diff --git a/app/src/main/cpp/skyline/common/signal.h b/app/src/main/cpp/skyline/common/signal.h index c8e3ae20..0d6e1551 100644 --- a/app/src/main/cpp/skyline/common/signal.h +++ b/app/src/main/cpp/skyline/common/signal.h @@ -6,6 +6,14 @@ #include namespace skyline::signal { + /** + * @brief The structure of a stack frame entry in the ARMv8 ABI + */ + struct StackFrame { + StackFrame *next; + void *lr; + }; + /** * @brief An exception object that is designed specifically to hold Linux signals * @note This doesn't inherit std::exception as it shouldn't be caught as such @@ -16,6 +24,7 @@ namespace skyline::signal { int signal{}; void* pc{}; void *fault{}; + std::vector frames; //!< A vector of all stack frame entries prior to the signal occuring inline std::string what() const { if (!fault) diff --git a/app/src/main/cpp/skyline/kernel/ipc.h b/app/src/main/cpp/skyline/kernel/ipc.h index 17c1b3d2..1fb6c9af 100644 --- a/app/src/main/cpp/skyline/kernel/ipc.h +++ b/app/src/main/cpp/skyline/kernel/ipc.h @@ -40,15 +40,15 @@ namespace skyline { * @url https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure */ struct CommandHeader { - CommandType type : 16; - u8 xNo : 4; - u8 aNo : 4; - u8 bNo : 4; - u8 wNo : 4; - u32 rawSize : 10; + CommandType type : 16; + u8 xNo : 4; + u8 aNo : 4; + u8 bNo : 4; + u8 wNo : 4; + u32 rawSize : 10; BufferCFlag cFlag : 4; u32 : 17; - bool handleDesc : 1; + bool handleDesc : 1; }; static_assert(sizeof(CommandHeader) == 8); @@ -56,7 +56,7 @@ namespace skyline { * @url https://switchbrew.org/wiki/IPC_Marshalling#Handle_descriptor */ struct HandleDescriptor { - bool sendPid : 1; + bool sendPid : 1; u32 copyCount : 4; u32 moveCount : 4; u32 : 23; diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index 399572f7..6a34abb0 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -257,6 +257,7 @@ namespace skyline::kernel::svc { state.ctx->gpr.w1 = thread->handle; state.ctx->gpr.w0 = Result{}; } else { + state.logger->Debug("svcCreateThread: Cannot create thread (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, Ideal Core: {})", entry, entryArgument, stackTop, priority, idealCore); state.ctx->gpr.w1 = 0; state.ctx->gpr.w0 = result::OutOfResource; } @@ -336,7 +337,7 @@ namespace skyline::kernel::svc { void SetThreadPriority(const DeviceState &state) { KHandle handle{state.ctx->gpr.w0}; - u32 priority{state.ctx->gpr.w1}; + u8 priority{static_cast(state.ctx->gpr.w1)}; if (!state.process->npdm.threadInfo.priority.Valid(priority)) { state.logger->Warn("svcSetThreadPriority: 'priority' invalid: 0x{:X}", priority); state.ctx->gpr.w0 = result::InvalidPriority; @@ -735,13 +736,26 @@ namespace skyline::kernel::svc { state.ctx->gpr.w0 = Result{}; } + void Break(const DeviceState &state) { + auto reason{state.ctx->gpr.x0}; + if (reason & (1ULL << 31)) { + state.logger->Error("svcBreak: Debugger is being engaged ({})", reason); + __builtin_trap(); + } else { + state.logger->Error("svcBreak: Stack Trace ({}){}", reason, state.loader->GetStackTrace()); + if (state.thread->id) + state.process->Kill(false); + std::longjmp(state.thread->originalCtx, true); + } + } + void OutputDebugString(const DeviceState &state) { - auto debug{span(reinterpret_cast(state.ctx->gpr.x0), state.ctx->gpr.x1).as_string()}; + auto string{span(reinterpret_cast(state.ctx->gpr.x0), state.ctx->gpr.x1).as_string()}; - if (debug.back() == '\n') - debug.remove_suffix(1); + if (string.back() == '\n') + string.remove_suffix(1); - state.logger->Info("svcOutputDebugString: {}", debug); + state.logger->Info("svcOutputDebugString: {}", string); state.ctx->gpr.w0 = Result{}; } diff --git a/app/src/main/cpp/skyline/kernel/svc.h b/app/src/main/cpp/skyline/kernel/svc.h index 669ec241..d342ea70 100644 --- a/app/src/main/cpp/skyline/kernel/svc.h +++ b/app/src/main/cpp/skyline/kernel/svc.h @@ -186,6 +186,12 @@ namespace skyline::kernel::svc { */ void GetThreadId(const DeviceState &state); + /** + * @brief Causes the debugger to be engaged or the program to end if it isn't being debugged + * @url https://switchbrew.org/wiki/SVC#Break + */ + void Break(const DeviceState &state); + /** * @brief Outputs a debug string * @url https://switchbrew.org/wiki/SVC#OutputDebugString @@ -252,7 +258,7 @@ namespace skyline::kernel::svc { nullptr, // 0x23 nullptr, // 0x24 GetThreadId, // 0x25 - nullptr, // 0x26 + Break, // 0x26 OutputDebugString, // 0x27 nullptr, // 0x28 GetInfo, // 0x29 diff --git a/app/src/main/cpp/skyline/loader/executable.h b/app/src/main/cpp/skyline/loader/executable.h index aec9e796..825d8cbf 100644 --- a/app/src/main/cpp/skyline/loader/executable.h +++ b/app/src/main/cpp/skyline/loader/executable.h @@ -21,7 +21,14 @@ namespace skyline::loader { Segment text; //!< The .text segment container Segment ro; //!< The .rodata segment container Segment data; //!< The .data segment container - size_t bssSize; //!< The size of the .bss segment + + struct RelativeSegment { + size_t offset; //!< The offset from the base address of the related segment that this is segment is located at + size_t size; //!< The size of the segment + }; + + RelativeSegment dynsym; //!< The .dynsym segment relative to .rodata + RelativeSegment dynstr; //!< The .dynstr segment relative to .rodata }; } diff --git a/app/src/main/cpp/skyline/loader/loader.cpp b/app/src/main/cpp/skyline/loader/loader.cpp index d670e66a..e020fca3 100644 --- a/app/src/main/cpp/skyline/loader/loader.cpp +++ b/app/src/main/cpp/skyline/loader/loader.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include #include #include #include @@ -8,7 +10,7 @@ #include "loader.h" namespace skyline::loader { - Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr process, const DeviceState &state, Executable &executable, size_t offset) { + Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name) { u8 *base{reinterpret_cast(process->memory.base.address + offset)}; u64 textSize{executable.text.contents.size()}; @@ -22,6 +24,7 @@ namespace skyline::loader { throw exception("LoadProcessData: Section offsets are not aligned with page size: 0x{:X}, 0x{:X}, 0x{:X}", executable.text.offset, executable.ro.offset, executable.data.offset); auto patch{state.nce->GetPatchData(executable.text.contents)}; + auto size{patch.size + textSize + roSize + dataSize}; process->NewHandle(base, patch.size, memory::Permission{false, false, false}, memory::states::Reserved); // --- state.logger->Debug("Successfully mapped section .patch @ 0x{:X}, Size = 0x{:X}", base, patch.size); @@ -40,6 +43,76 @@ namespace skyline::loader { std::memcpy(base + patch.size + executable.ro.offset, executable.ro.contents.data(), roSize); std::memcpy(base + patch.size + executable.data.offset, executable.data.contents.data(), dataSize - executable.bssSize); - return {base, patch.size + textSize + roSize + dataSize, base + patch.size}; + auto rodataOffset{base + patch.size + executable.ro.offset}; + ExecutableSymbolicInfo symbolicInfo{ + .patchStart = base, + .programStart = base + patch.size, + .programEnd = base + size, + .name = name, + .patchName = name + ".patch", + .symbols = span(reinterpret_cast(rodataOffset + executable.dynsym.offset), executable.dynsym.size / sizeof(Elf64_Sym)), + .symbolStrings = span(reinterpret_cast(rodataOffset + executable.dynstr.offset), executable.dynstr.size), + }; + executables.insert(std::upper_bound(executables.begin(), executables.end(), base, [](void *ptr, const ExecutableSymbolicInfo &it) { return ptr < it.patchStart; }), symbolicInfo); + + return {base, size, base + patch.size}; + } + + Loader::SymbolInfo Loader::ResolveSymbol(void *ptr) { + auto executable{std::lower_bound(executables.begin(), executables.end(), ptr, [](const ExecutableSymbolicInfo &it, void *ptr) { return it.programEnd < ptr; })}; + if (executable != executables.end() && ptr >= executable->patchStart && ptr <= executable->programEnd) { + if (ptr >= executable->programStart) { + auto offset{reinterpret_cast(ptr) - reinterpret_cast(executable->programStart)}; + auto symbol{std::find_if(executable->symbols.begin(), executable->symbols.end(), [&offset](const Elf64_Sym &sym) { return sym.st_value <= offset && sym.st_value + sym.st_size > offset; })}; + if (symbol != executable->symbols.end() && symbol->st_name && symbol->st_name < executable->symbolStrings.size()) { + return {executable->symbolStrings.data() + symbol->st_name, executable->name}; + } else { + return {.executableName = executable->name}; + } + } else { + return {.executableName = executable->patchName}; + } + } + return {}; + } + + inline std::string GetFunctionStackTrace(Loader *loader, void *pointer) { + Dl_info info; + auto symbol{loader->ResolveSymbol(pointer)}; + if (symbol.name) { + int status{}; + size_t length{}; + std::unique_ptr demangled{abi::__cxa_demangle(symbol.name, nullptr, &length, &status), std::free}; + + return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast(pointer), (status == 0) ? std::string(demangled.get()) : symbol.name, symbol.executableName); + } else if (!symbol.executableName.empty()) { + return fmt::format("\n* 0x{:X} (from {})", reinterpret_cast(pointer), symbol.executableName); + } else if (dladdr(pointer, &info)) { + int status{}; + size_t length{}; + std::unique_ptr demangled{abi::__cxa_demangle(info.dli_sname, nullptr, &length, &status), std::free}; + + return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast(pointer), (status == 0) ? std::string(demangled.get()) : info.dli_sname, info.dli_fname); + } else { + return fmt::format("\n* 0x{:X}", reinterpret_cast(pointer)); + } + } + + std::string Loader::GetStackTrace(signal::StackFrame *frame) { + std::string trace; + if (!frame) + asm("MOV %0, FP" : "=r"(frame)); + while (frame) { + trace += GetFunctionStackTrace(this, frame->lr); + frame = frame->next; + } + return trace; + } + + std::string Loader::GetStackTrace(const std::vector frames) { + std::string trace; + for (const auto &frame : frames) + trace += GetFunctionStackTrace(this, frame); + return trace; } } diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h index 6c0bca65..8feb4207 100644 --- a/app/src/main/cpp/skyline/loader/loader.h +++ b/app/src/main/cpp/skyline/loader/loader.h @@ -3,7 +3,9 @@ #pragma once +#include #include +#include #include "executable.h" namespace skyline::loader { @@ -46,7 +48,23 @@ namespace skyline::loader { * @brief The Loader class provides an abstract interface for ROM loaders */ class Loader { - protected: + private: + /** + * @brief All data used to determine the corresponding symbol for an address + */ + struct ExecutableSymbolicInfo { + void* patchStart; //!< A pointer to the start of this executable's patch section + void* programStart; //!< A pointer to the start of this executable + void* programEnd; //!< A pointer to the end of this executable + std::string name; //!< The name of the executable this belongs to + std::string patchName; //!< The name of the executable this belongs to's patch section + span symbols; //!< A span over the .dynsym section of this executable + span symbolStrings; //!< A span over the .dynstr section of this executable + }; + + std::vector executables; + + public: /** * @brief Information about the placement of an executable in memory */ @@ -57,15 +75,13 @@ namespace skyline::loader { }; /** - * @brief Loads an executable into memory - * @param process The process to load the executable into - * @param executable The executable itself + * @brief Patches an executable and loads it into memory while setting up symbolic information * @param offset The offset from the base address that the executable should be placed at + * @param name An optional name for the executable, used for symbol resolution * @return An ExecutableLoadInfo struct containing the load base and size */ - static ExecutableLoadInfo LoadExecutable(const std::shared_ptr process, const DeviceState &state, Executable &executable, size_t offset = 0); + ExecutableLoadInfo LoadExecutable(const std::shared_ptr process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string& name = {}); - public: std::optional nacp; std::shared_ptr romFs; @@ -79,5 +95,27 @@ namespace skyline::loader { * @return Entry point to the start of the main executable in the ROM */ virtual void *LoadProcessData(const std::shared_ptr process, const DeviceState &state) = 0; + + struct SymbolInfo { + char* name; //!< The name of the symbol that was found + std::string_view executableName; //!< The executable that contained the symbol + }; + + /** + * @return All symbolic information about the symbol for the specified address + * @note If a symbol isn't found then SymbolInfo::name will be nullptr + */ + SymbolInfo ResolveSymbol(void *ptr); + + /** + * @param frame The initial stack frame or the calling function's stack frame by default + * @return A string with the stack trace based on the supplied context + */ + std::string GetStackTrace(signal::StackFrame* frame = nullptr); + + /** + * @return A string with the stack trace based on the stack frames in the supplied vector + */ + std::string GetStackTrace(const std::vector frames); }; } diff --git a/app/src/main/cpp/skyline/loader/nca.cpp b/app/src/main/cpp/skyline/loader/nca.cpp index 10e756df..292f1ef0 100644 --- a/app/src/main/cpp/skyline/loader/nca.cpp +++ b/app/src/main/cpp/skyline/loader/nca.cpp @@ -12,7 +12,7 @@ namespace skyline::loader { throw exception("Only NCAs with an ExeFS can be loaded directly"); } - void *NcaLoader::LoadExeFs(const std::shared_ptr &exeFs, const std::shared_ptr process, const DeviceState &state) { + void *NcaLoader::LoadExeFs(Loader *loader, const std::shared_ptr &exeFs, const std::shared_ptr process, const DeviceState &state) { if (exeFs == nullptr) throw exception("Cannot load a null ExeFS"); @@ -22,7 +22,7 @@ namespace skyline::loader { state.process->memory.InitializeVmm(process->npdm.meta.flags.type); - auto loadInfo{NsoLoader::LoadNso(nsoFile, process, state)}; + auto loadInfo{NsoLoader::LoadNso(loader, nsoFile, process, state, 0, "rtld.nso")}; u64 offset{loadInfo.size}; u8 *base{loadInfo.base}; void *entry{loadInfo.entry}; @@ -35,7 +35,7 @@ namespace skyline::loader { else continue; - loadInfo = NsoLoader::LoadNso(nsoFile, process, state, offset); + loadInfo = NsoLoader::LoadNso(loader, nsoFile, process, state, offset, nso + std::string(".nso")); state.logger->Info("Loaded '{}.nso' at 0x{:X} (.text @ 0x{:X})", nso, base + offset, loadInfo.entry); offset += loadInfo.size; } @@ -47,6 +47,6 @@ namespace skyline::loader { void *NcaLoader::LoadProcessData(const std::shared_ptr process, const DeviceState &state) { process->npdm = vfs::NPDM(nca.exeFs->OpenFile("main.npdm"), state); - return LoadExeFs(nca.exeFs, process, state); + return LoadExeFs(this, nca.exeFs, process, state); } } diff --git a/app/src/main/cpp/skyline/loader/nca.h b/app/src/main/cpp/skyline/loader/nca.h index f3ffae20..8bc9a5bf 100644 --- a/app/src/main/cpp/skyline/loader/nca.h +++ b/app/src/main/cpp/skyline/loader/nca.h @@ -19,11 +19,10 @@ namespace skyline::loader { NcaLoader(const std::shared_ptr &backing, const std::shared_ptr &keyStore); /** - * @brief Loads an ExeFS into memory + * @brief Loads an ExeFS into memory and processes it accordingly for execution * @param exefs A filesystem object containing the ExeFS filesystem to load into memory - * @param process The process to load the ExeFS into */ - static void *LoadExeFs(const std::shared_ptr &exefs, const std::shared_ptr process, const DeviceState &state); + static void *LoadExeFs(Loader *loader, const std::shared_ptr &exefs, const std::shared_ptr process, const DeviceState &state); void *LoadProcessData(const std::shared_ptr process, const DeviceState &state); }; diff --git a/app/src/main/cpp/skyline/loader/nro.cpp b/app/src/main/cpp/skyline/loader/nro.cpp index 2d1784fd..f9628f53 100644 --- a/app/src/main/cpp/skyline/loader/nro.cpp +++ b/app/src/main/cpp/skyline/loader/nro.cpp @@ -45,21 +45,26 @@ namespace skyline::loader { } void *NroLoader::LoadProcessData(const std::shared_ptr process, const DeviceState &state) { - Executable nroExecutable{}; + Executable executable{}; - nroExecutable.text.contents = GetSegment(header.text); - nroExecutable.text.offset = 0; + executable.text.contents = GetSegment(header.text); + executable.text.offset = 0; - nroExecutable.ro.contents = GetSegment(header.ro); - nroExecutable.ro.offset = header.text.size; + executable.ro.contents = GetSegment(header.ro); + executable.ro.offset = header.text.size; - nroExecutable.data.contents = GetSegment(header.data); - nroExecutable.data.offset = header.text.size + header.ro.size; + executable.data.contents = GetSegment(header.data); + executable.data.offset = header.text.size + header.ro.size; - nroExecutable.bssSize = header.bssSize; + executable.bssSize = header.bssSize; + + if (header.dynsym.offset > header.ro.offset && header.dynsym.offset + header.dynsym.size < header.ro.offset + header.ro.size && header.dynstr.offset > header.ro.offset && header.dynstr.offset + header.dynstr.size < header.ro.offset + header.ro.size) { + executable.dynsym = {header.dynsym.offset, header.dynsym.size}; + executable.dynstr = {header.dynstr.offset, header.dynstr.size}; + } state.process->memory.InitializeVmm(memory::AddressSpaceType::AddressSpace39Bit); - auto loadInfo{LoadExecutable(process, state, nroExecutable)}; + auto loadInfo{LoadExecutable(process, state, executable, 0, nacp->applicationName.empty() ? "main.nro" : nacp->applicationName + ".nro")}; state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size); return loadInfo.entry; diff --git a/app/src/main/cpp/skyline/loader/nso.cpp b/app/src/main/cpp/skyline/loader/nso.cpp index 36da3e22..81e9b911 100644 --- a/app/src/main/cpp/skyline/loader/nso.cpp +++ b/app/src/main/cpp/skyline/loader/nso.cpp @@ -29,34 +29,39 @@ namespace skyline::loader { return outputBuffer; } - Loader::ExecutableLoadInfo NsoLoader::LoadNso(const std::shared_ptr &backing, const std::shared_ptr process, const DeviceState &state, size_t offset) { + Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr &backing, const std::shared_ptr process, const DeviceState &state, size_t offset, const std::string& name) { auto header{backing->Read()}; if (header.magic != util::MakeMagic("NSO0")) throw exception("Invalid NSO magic! 0x{0:X}", header.magic); - Executable nsoExecutable{}; + Executable executable{}; - nsoExecutable.text.contents = GetSegment(backing, header.text, header.flags.textCompressed ? header.textCompressedSize : 0); - nsoExecutable.text.contents.resize(util::AlignUp(nsoExecutable.text.contents.size(), PAGE_SIZE)); - nsoExecutable.text.offset = header.text.memoryOffset; + executable.text.contents = GetSegment(backing, header.text, header.flags.textCompressed ? header.textCompressedSize : 0); + executable.text.contents.resize(util::AlignUp(executable.text.contents.size(), PAGE_SIZE)); + executable.text.offset = header.text.memoryOffset; - nsoExecutable.ro.contents = GetSegment(backing, header.ro, header.flags.roCompressed ? header.roCompressedSize : 0); - nsoExecutable.ro.contents.resize(util::AlignUp(nsoExecutable.ro.contents.size(), PAGE_SIZE)); - nsoExecutable.ro.offset = header.ro.memoryOffset; + executable.ro.contents = GetSegment(backing, header.ro, header.flags.roCompressed ? header.roCompressedSize : 0); + executable.ro.contents.resize(util::AlignUp(executable.ro.contents.size(), PAGE_SIZE)); + executable.ro.offset = header.ro.memoryOffset; - nsoExecutable.data.contents = GetSegment(backing, header.data, header.flags.dataCompressed ? header.dataCompressedSize : 0); - nsoExecutable.data.contents.resize(util::AlignUp(nsoExecutable.data.contents.size(), PAGE_SIZE)); - nsoExecutable.data.offset = header.data.memoryOffset; + executable.data.contents = GetSegment(backing, header.data, header.flags.dataCompressed ? header.dataCompressedSize : 0); + executable.data.contents.resize(util::AlignUp(executable.data.contents.size(), PAGE_SIZE)); + executable.data.offset = header.data.memoryOffset; - nsoExecutable.bssSize = util::AlignUp(header.bssSize, PAGE_SIZE); + executable.bssSize = util::AlignUp(header.bssSize, PAGE_SIZE); - return LoadExecutable(process, state, nsoExecutable, offset); + if (header.dynsym.offset + header.dynsym.size <= header.ro.decompressedSize && header.dynstr.offset + header.dynstr.size <= header.ro.decompressedSize) { + executable.dynsym = {header.dynsym.offset, header.dynsym.size}; + executable.dynstr = {header.dynstr.offset, header.dynstr.size}; + } + + return loader->LoadExecutable(process, state, executable, offset, name); } void *NsoLoader::LoadProcessData(const std::shared_ptr process, const DeviceState &state) { state.process->memory.InitializeVmm(memory::AddressSpaceType::AddressSpace39Bit); - auto loadInfo{LoadNso(backing, process, state)}; + auto loadInfo{LoadNso(this, backing, process, state)}; state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size); return loadInfo.entry; } diff --git a/app/src/main/cpp/skyline/loader/nso.h b/app/src/main/cpp/skyline/loader/nso.h index 214e2169..f172a577 100644 --- a/app/src/main/cpp/skyline/loader/nso.h +++ b/app/src/main/cpp/skyline/loader/nso.h @@ -34,6 +34,12 @@ namespace skyline::loader { }; static_assert(sizeof(NsoSegmentHeader) == 0xC); + struct NsoRelativeSegmentHeader { + u32 offset; //!< The offset of the segment into it's parent segment + u32 size; //!< Size of the segment + }; + static_assert(sizeof(NsoRelativeSegmentHeader) == 0x8); + struct NsoHeader { u32 magic; //!< The NSO magic "NSO0" u32 version; //!< The version of the application @@ -55,9 +61,9 @@ namespace skyline::loader { u32 _pad1_[7]; - u64 apiInfo; //!< The .rodata-relative offset of .apiInfo - u64 dynstr; //!< The .rodata-relative offset of .dynstr - u64 dynsym; //!< The .rodata-relative offset of .dynsym + NsoRelativeSegmentHeader apiInfo; //!< The .rodata-relative segment .apiInfo + NsoRelativeSegmentHeader dynstr; //!< The .rodata-relative segment .dynstr + NsoRelativeSegmentHeader dynsym; //!< The .rodata-relative segment .dynsym std::array, 3> segmentHashes; //!< The SHA256 checksums of the .text, .rodata and .data segments }; @@ -76,12 +82,12 @@ namespace skyline::loader { /** * @brief Loads an NSO into memory, offset by the given amount - * @param backing The backing of the NSO - * @param process The process to load the NSO into + * @param backing The backing that the NSO is contained within * @param offset The offset from the base address to place the NSO + * @param name An optional name for the NSO, used for symbol resolution * @return An ExecutableLoadInfo struct containing the load base and size */ - static ExecutableLoadInfo LoadNso(const std::shared_ptr &backing, const std::shared_ptr process, const DeviceState &state, size_t offset = 0); + static ExecutableLoadInfo LoadNso(Loader *loader, const std::shared_ptr &backing, const std::shared_ptr process, const DeviceState &state, size_t offset = 0, const std::string &name = {}); void *LoadProcessData(const std::shared_ptr process, const DeviceState &state); }; diff --git a/app/src/main/cpp/skyline/loader/nsp.cpp b/app/src/main/cpp/skyline/loader/nsp.cpp index c58f84ac..b8ee49df 100644 --- a/app/src/main/cpp/skyline/loader/nsp.cpp +++ b/app/src/main/cpp/skyline/loader/nsp.cpp @@ -37,7 +37,7 @@ namespace skyline::loader { void *NspLoader::LoadProcessData(const std::shared_ptr process, const DeviceState &state) { process->npdm = vfs::NPDM(programNca->exeFs->OpenFile("main.npdm"), state); - return NcaLoader::LoadExeFs(programNca->exeFs, process, state); + return NcaLoader::LoadExeFs(this, programNca->exeFs, process, state); } std::vector NspLoader::GetIcon() { diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 98cb5376..44f1ee26 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -32,7 +32,7 @@ namespace skyline::nce { } } catch (const signal::SignalException &e) { if (e.signal != SIGINT) { - state.logger->Error("{} (SVC: 0x{:X})", e.what(), svc); + state.logger->Error("{} (SVC: 0x{:X})\nStack Trace:{}", e.what(), svc, state.loader->GetStackTrace(e.frames)); if (state.thread->id) { signal::BlockSignal({SIGINT}); state.process->Kill(false); @@ -41,7 +41,7 @@ namespace skyline::nce { 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 std::exception &e) { - state.logger->Error("{} (SVC: 0x{:X})", e.what(), svc); + state.logger->Error("{} (SVC: 0x{:X})\nStack Trace:{}", e.what(), svc, state.loader->GetStackTrace()); if (state.thread->id) { signal::BlockSignal({SIGINT}); state.process->Kill(false); @@ -56,47 +56,26 @@ namespace skyline::nce { auto &mctx{ctx->uc_mcontext}; const auto &state{*reinterpret_cast(*tls)->state}; if (signal != SIGINT) { - state.logger->Warn("Thread #{} has crashed due to signal: {}", state.thread->id, strsignal(signal)); + signal::StackFrame topFrame{.lr = reinterpret_cast(ctx->uc_mcontext.pc), .next = reinterpret_cast(ctx->uc_mcontext.regs[29])}; + std::string trace{state.loader->GetStackTrace(&topFrame)}; - std::string raw; - std::string trace; std::string cpuContext; - - constexpr u16 instructionCount{20}; // The amount of previous instructions to print - auto offset{mctx.pc - (instructionCount * sizeof(u32)) + (2 * sizeof(u32))}; - span instructions(reinterpret_cast(offset), instructionCount); - if (mprotect(util::AlignDown(instructions.data(), PAGE_SIZE), util::AlignUp(instructions.size_bytes(), PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC) == 0) { - for (auto &instruction : instructions) { - instruction = __builtin_bswap32(instruction); - - if (offset == mctx.pc) - trace += fmt::format("\n-> 0x{:X} : 0x{:08X}", offset, instruction); - else - trace += fmt::format("\n 0x{:X} : 0x{:08X}", offset, instruction); - - raw += fmt::format("{:08X}", instruction); - offset += sizeof(u32); - } - - state.logger->Debug("Process Trace:{}", trace); - state.logger->Debug("Raw Instructions: 0x{}", raw); - } else { - cpuContext += fmt::format("\nPC: 0x{:X} ('mprotect' failed with '{}')", mctx.pc, strerror(errno)); - } - if (mctx.fault_address) - cpuContext += fmt::format("\nFault Address: 0x{:X}", mctx.fault_address); - + cpuContext += fmt::format("\n Fault Address: 0x{:X}", mctx.fault_address); if (mctx.sp) - cpuContext += fmt::format("\nStack Pointer: 0x{:X}", mctx.sp); + cpuContext += fmt::format("\n Stack Pointer: 0x{:X}", mctx.sp); + for (u8 index{}; index < (sizeof(mcontext_t::regs) / sizeof(u64)); index += 2) + cpuContext += fmt::format("\n X{:<2}: 0x{:<16X} X{:<2}: 0x{:X}", index, mctx.regs[index], index + 1, mctx.regs[index + 1]); - for (u8 index{}; index < ((sizeof(mcontext_t::regs) / sizeof(u64)) - 2); index += 2) - cpuContext += fmt::format("\n{}X{}: 0x{:<16X} {}{}: 0x{:X}", index < 10 ? ' ' : '\0', index, mctx.regs[index], index < 10 ? 'X' : '\0', index + 1, mctx.regs[index]); + state.logger->Error("Thread #{} has crashed due to signal: {}\nStack Trace:{}\nCPU Context:{}", state.thread->id, strsignal(signal), trace, cpuContext); - state.logger->Debug("CPU Context:{}", cpuContext); + if (state.thread->id) { + signal::BlockSignal({SIGINT}); + state.process->Kill(false); + } } - mctx.pc = reinterpret_cast(&std::longjmp); + mctx.pc = reinterpret_cast(&std::longjmp); mctx.regs[0] = reinterpret_cast(state.thread->originalCtx); mctx.regs[1] = true; diff --git a/app/src/main/cpp/skyline/vfs/nacp.cpp b/app/src/main/cpp/skyline/vfs/nacp.cpp index 61eacaa2..2b45c34b 100644 --- a/app/src/main/cpp/skyline/vfs/nacp.cpp +++ b/app/src/main/cpp/skyline/vfs/nacp.cpp @@ -14,8 +14,8 @@ namespace skyline::vfs { if (entry.applicationName.front() == '\0') continue; - applicationName = std::string(entry.applicationName.data(), entry.applicationName.size()); - applicationPublisher = std::string(entry.applicationPublisher.data(), entry.applicationPublisher.size()); + applicationName = span(entry.applicationName).as_string(true); + applicationPublisher = span(entry.applicationPublisher).as_string(true); } } } diff --git a/app/src/main/cpp/skyline/vfs/os_filesystem.cpp b/app/src/main/cpp/skyline/vfs/os_filesystem.cpp index d66250fa..d851701e 100644 --- a/app/src/main/cpp/skyline/vfs/os_filesystem.cpp +++ b/app/src/main/cpp/skyline/vfs/os_filesystem.cpp @@ -64,7 +64,7 @@ namespace skyline::vfs { int fd{open((basePath + path).c_str(), (mode.read && mode.write) ? O_RDWR : (mode.write ? O_WRONLY : O_RDONLY))}; if (fd < 0) - throw exception("Failed to open file: {}", strerror(errno)); + throw exception("Failed to open file at '{}': {}", path, strerror(errno)); return std::make_shared(fd, true, mode); }