Generate Stack Traces + More Robust Terminate Handler + Exit Process on Signal in Guest

This commit is contained in:
◱ PixelyIon 2021-01-11 23:20:31 +05:30 committed by ◱ Mark
parent 14dbb5305a
commit ebadc1d1e1
21 changed files with 312 additions and 1573 deletions

2
.idea/compiler.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" /> <bytecodeTargetLevel target="11" />
</component> </component>
</project> </project>

File diff suppressed because it is too large Load Diff

12
.idea/misc.xml generated
View File

@ -9,7 +9,7 @@
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" /> <option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
<option name="myNullables"> <option name="myNullables">
<value> <value>
<list size="12"> <list size="15">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
@ -22,12 +22,15 @@
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" /> <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" /> <item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" />
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" /> <item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
<item index="13" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
<item index="14" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
</list> </list>
</value> </value>
</option> </option>
<option name="myNotNulls"> <option name="myNotNulls">
<value> <value>
<list size="11"> <list size="14">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
@ -39,11 +42,14 @@
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" /> <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" /> <item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" /> <item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
<item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
<item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
<item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
</list> </list>
</value> </value>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@ -6,6 +6,7 @@
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <span> #include <span>
#include <list>
#include <vector> #include <vector>
#include <fstream> #include <fstream>
#include <mutex> #include <mutex>

View File

@ -15,21 +15,37 @@ namespace skyline::signal {
std::terminate_handler terminateHandler{}; 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() { void TerminateHandler() {
auto exception{std::current_exception()}; auto exception{std::current_exception()};
if (terminateHandler && exception && exception == SignalExceptionPtr) { if (terminateHandler && exception && exception == SignalExceptionPtr) {
struct StackFrame { StackFrame *frame;
StackFrame *next;
void *lr;
} *frame;
asm("MOV %0, FP" : "=r"(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 auto lookupFrame{frame};
frame = frame->next->next; while (lookupFrame) {
else if (_Unwind_FindEnclosingFunction(frame->next->next->next->next->next->lr) == &ExceptionThrow) // We're in a deeper loop, just terminate auto function{_Unwind_FindEnclosingFunction(frame->lr)};
frame = frame->next->next->next->next->next->next->next; 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 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" "MOV LR, %x1\n\t"
@ -49,6 +65,14 @@ namespace skyline::signal {
signalException.pc = reinterpret_cast<void *>(context->uc_mcontext.pc); signalException.pc = reinterpret_cast<void *>(context->uc_mcontext.pc);
if (signal == SIGSEGV) if (signal == SIGSEGV)
signalException.fault = info->si_addr; signalException.fault = info->si_addr;
signalException.frames.push_back(reinterpret_cast<void *>(context->uc_mcontext.pc));
StackFrame *frame{reinterpret_cast<StackFrame *>(context->uc_mcontext.regs[29])};
while (frame) {
signalException.frames.push_back(frame->lr);
frame = frame->next;
}
SignalExceptionPtr = std::make_exception_ptr(signalException); SignalExceptionPtr = std::make_exception_ptr(signalException);
context->uc_mcontext.pc = reinterpret_cast<u64>(&ExceptionThrow); context->uc_mcontext.pc = reinterpret_cast<u64>(&ExceptionThrow);
@ -109,7 +133,7 @@ namespace skyline::signal {
thread_local std::array<SignalHandler, NSIG> ThreadSignalHandlers{}; thread_local std::array<SignalHandler, NSIG> 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 ThreadSignalHandler(int signal, siginfo *info, ucontext *context) {
void *tls{}; // The TLS value prior to being restored if it is void *tls{}; // The TLS value prior to being restored if it is
if (TlsRestorer) if (TlsRestorer)

View File

@ -6,6 +6,14 @@
#include <common.h> #include <common.h>
namespace skyline::signal { 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 * @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 * @note This doesn't inherit std::exception as it shouldn't be caught as such
@ -16,6 +24,7 @@ namespace skyline::signal {
int signal{}; int signal{};
void* pc{}; void* pc{};
void *fault{}; void *fault{};
std::vector<void*> frames; //!< A vector of all stack frame entries prior to the signal occuring
inline std::string what() const { inline std::string what() const {
if (!fault) if (!fault)

View File

@ -257,6 +257,7 @@ namespace skyline::kernel::svc {
state.ctx->gpr.w1 = thread->handle; state.ctx->gpr.w1 = thread->handle;
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} else { } 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.w1 = 0;
state.ctx->gpr.w0 = result::OutOfResource; state.ctx->gpr.w0 = result::OutOfResource;
} }
@ -336,7 +337,7 @@ namespace skyline::kernel::svc {
void SetThreadPriority(const DeviceState &state) { void SetThreadPriority(const DeviceState &state) {
KHandle handle{state.ctx->gpr.w0}; KHandle handle{state.ctx->gpr.w0};
u32 priority{state.ctx->gpr.w1}; u8 priority{static_cast<u8>(state.ctx->gpr.w1)};
if (!state.process->npdm.threadInfo.priority.Valid(priority)) { if (!state.process->npdm.threadInfo.priority.Valid(priority)) {
state.logger->Warn("svcSetThreadPriority: 'priority' invalid: 0x{:X}", priority); state.logger->Warn("svcSetThreadPriority: 'priority' invalid: 0x{:X}", priority);
state.ctx->gpr.w0 = result::InvalidPriority; state.ctx->gpr.w0 = result::InvalidPriority;
@ -735,13 +736,26 @@ namespace skyline::kernel::svc {
state.ctx->gpr.w0 = Result{}; 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) { void OutputDebugString(const DeviceState &state) {
auto debug{span(reinterpret_cast<u8 *>(state.ctx->gpr.x0), state.ctx->gpr.x1).as_string()}; auto string{span(reinterpret_cast<u8 *>(state.ctx->gpr.x0), state.ctx->gpr.x1).as_string()};
if (debug.back() == '\n') if (string.back() == '\n')
debug.remove_suffix(1); string.remove_suffix(1);
state.logger->Info("svcOutputDebugString: {}", debug); state.logger->Info("svcOutputDebugString: {}", string);
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }

View File

@ -186,6 +186,12 @@ namespace skyline::kernel::svc {
*/ */
void GetThreadId(const DeviceState &state); 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 * @brief Outputs a debug string
* @url https://switchbrew.org/wiki/SVC#OutputDebugString * @url https://switchbrew.org/wiki/SVC#OutputDebugString
@ -252,7 +258,7 @@ namespace skyline::kernel::svc {
nullptr, // 0x23 nullptr, // 0x23
nullptr, // 0x24 nullptr, // 0x24
GetThreadId, // 0x25 GetThreadId, // 0x25
nullptr, // 0x26 Break, // 0x26
OutputDebugString, // 0x27 OutputDebugString, // 0x27
nullptr, // 0x28 nullptr, // 0x28
GetInfo, // 0x29 GetInfo, // 0x29

View File

@ -21,7 +21,14 @@ namespace skyline::loader {
Segment text; //!< The .text segment container Segment text; //!< The .text segment container
Segment ro; //!< The .rodata segment container Segment ro; //!< The .rodata segment container
Segment data; //!< The .data segment container Segment data; //!< The .data segment container
size_t bssSize; //!< The size of the .bss segment 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
}; };
} }

View File

@ -1,6 +1,8 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <dlfcn.h>
#include <cxxabi.h>
#include <nce.h> #include <nce.h>
#include <os.h> #include <os.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
@ -8,7 +10,7 @@
#include "loader.h" #include "loader.h"
namespace skyline::loader { namespace skyline::loader {
Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset) { Loader::ExecutableLoadInfo Loader::LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset, const std::string &name) {
u8 *base{reinterpret_cast<u8 *>(process->memory.base.address + offset)}; u8 *base{reinterpret_cast<u8 *>(process->memory.base.address + offset)};
u64 textSize{executable.text.contents.size()}; 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); 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 patch{state.nce->GetPatchData(executable.text.contents)};
auto size{patch.size + textSize + roSize + dataSize};
process->NewHandle<kernel::type::KPrivateMemory>(base, patch.size, memory::Permission{false, false, false}, memory::states::Reserved); // --- process->NewHandle<kernel::type::KPrivateMemory>(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); 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.ro.offset, executable.ro.contents.data(), roSize);
std::memcpy(base + patch.size + executable.data.offset, executable.data.contents.data(), dataSize - executable.bssSize); 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<Elf64_Sym *>(rodataOffset + executable.dynsym.offset), executable.dynsym.size / sizeof(Elf64_Sym)),
.symbolStrings = span(reinterpret_cast<char *>(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<u8 *>(ptr) - reinterpret_cast<u8 *>(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<char, decltype(&std::free)> demangled{abi::__cxa_demangle(symbol.name, nullptr, &length, &status), std::free};
return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast<u64>(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<u64>(pointer), symbol.executableName);
} else if (dladdr(pointer, &info)) {
int status{};
size_t length{};
std::unique_ptr<char, decltype(&std::free)> demangled{abi::__cxa_demangle(info.dli_sname, nullptr, &length, &status), std::free};
return fmt::format("\n* 0x{:X} ({} from {})", reinterpret_cast<u64>(pointer), (status == 0) ? std::string(demangled.get()) : info.dli_sname, info.dli_fname);
} else {
return fmt::format("\n* 0x{:X}", reinterpret_cast<u64>(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<void *> frames) {
std::string trace;
for (const auto &frame : frames)
trace += GetFunctionStackTrace(this, frame);
return trace;
} }
} }

View File

@ -3,7 +3,9 @@
#pragma once #pragma once
#include <linux/elf.h>
#include <vfs/nacp.h> #include <vfs/nacp.h>
#include <common/signal.h>
#include "executable.h" #include "executable.h"
namespace skyline::loader { namespace skyline::loader {
@ -46,7 +48,23 @@ namespace skyline::loader {
* @brief The Loader class provides an abstract interface for ROM loaders * @brief The Loader class provides an abstract interface for ROM loaders
*/ */
class Loader { 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<Elf64_Sym> symbols; //!< A span over the .dynsym section of this executable
span<char> symbolStrings; //!< A span over the .dynstr section of this executable
};
std::vector<ExecutableSymbolicInfo> executables;
public:
/** /**
* @brief Information about the placement of an executable in memory * @brief Information about the placement of an executable in memory
*/ */
@ -57,15 +75,13 @@ namespace skyline::loader {
}; };
/** /**
* @brief Loads an executable into memory * @brief Patches an executable and loads it into memory while setting up symbolic information
* @param process The process to load the executable into
* @param executable The executable itself
* @param offset The offset from the base address that the executable should be placed at * @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 * @return An ExecutableLoadInfo struct containing the load base and size
*/ */
static ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset = 0); ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string& name = {});
public:
std::optional<vfs::NACP> nacp; std::optional<vfs::NACP> nacp;
std::shared_ptr<vfs::Backing> romFs; std::shared_ptr<vfs::Backing> romFs;
@ -79,5 +95,27 @@ namespace skyline::loader {
* @return Entry point to the start of the main executable in the ROM * @return Entry point to the start of the main executable in the ROM
*/ */
virtual void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) = 0; virtual void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> 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<void*> frames);
}; };
} }

View File

@ -12,7 +12,7 @@ namespace skyline::loader {
throw exception("Only NCAs with an ExeFS can be loaded directly"); throw exception("Only NCAs with an ExeFS can be loaded directly");
} }
void *NcaLoader::LoadExeFs(const std::shared_ptr<vfs::FileSystem> &exeFs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void *NcaLoader::LoadExeFs(Loader *loader, const std::shared_ptr<vfs::FileSystem> &exeFs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
if (exeFs == nullptr) if (exeFs == nullptr)
throw exception("Cannot load a null ExeFS"); throw exception("Cannot load a null ExeFS");
@ -22,7 +22,7 @@ namespace skyline::loader {
state.process->memory.InitializeVmm(process->npdm.meta.flags.type); 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}; u64 offset{loadInfo.size};
u8 *base{loadInfo.base}; u8 *base{loadInfo.base};
void *entry{loadInfo.entry}; void *entry{loadInfo.entry};
@ -35,7 +35,7 @@ namespace skyline::loader {
else else
continue; 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); state.logger->Info("Loaded '{}.nso' at 0x{:X} (.text @ 0x{:X})", nso, base + offset, loadInfo.entry);
offset += loadInfo.size; offset += loadInfo.size;
} }
@ -47,6 +47,6 @@ namespace skyline::loader {
void *NcaLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void *NcaLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
process->npdm = vfs::NPDM(nca.exeFs->OpenFile("main.npdm"), state); process->npdm = vfs::NPDM(nca.exeFs->OpenFile("main.npdm"), state);
return LoadExeFs(nca.exeFs, process, state); return LoadExeFs(this, nca.exeFs, process, state);
} }
} }

View File

@ -19,11 +19,10 @@ namespace skyline::loader {
NcaLoader(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<crypto::KeyStore> &keyStore); NcaLoader(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<crypto::KeyStore> &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 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<vfs::FileSystem> &exefs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state); static void *LoadExeFs(Loader *loader, const std::shared_ptr<vfs::FileSystem> &exefs, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state); void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
}; };

View File

@ -45,21 +45,26 @@ namespace skyline::loader {
} }
void *NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void *NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
Executable nroExecutable{}; Executable executable{};
nroExecutable.text.contents = GetSegment(header.text); executable.text.contents = GetSegment(header.text);
nroExecutable.text.offset = 0; executable.text.offset = 0;
nroExecutable.ro.contents = GetSegment(header.ro); executable.ro.contents = GetSegment(header.ro);
nroExecutable.ro.offset = header.text.size; executable.ro.offset = header.text.size;
nroExecutable.data.contents = GetSegment(header.data); executable.data.contents = GetSegment(header.data);
nroExecutable.data.offset = header.text.size + header.ro.size; 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); 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); state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size);
return loadInfo.entry; return loadInfo.entry;

View File

@ -29,34 +29,39 @@ namespace skyline::loader {
return outputBuffer; return outputBuffer;
} }
Loader::ExecutableLoadInfo NsoLoader::LoadNso(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset) { Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset, const std::string& name) {
auto header{backing->Read<NsoHeader>()}; auto header{backing->Read<NsoHeader>()};
if (header.magic != util::MakeMagic<u32>("NSO0")) if (header.magic != util::MakeMagic<u32>("NSO0"))
throw exception("Invalid NSO magic! 0x{0:X}", header.magic); 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); executable.text.contents = GetSegment(backing, header.text, header.flags.textCompressed ? header.textCompressedSize : 0);
nsoExecutable.text.contents.resize(util::AlignUp(nsoExecutable.text.contents.size(), PAGE_SIZE)); executable.text.contents.resize(util::AlignUp(executable.text.contents.size(), PAGE_SIZE));
nsoExecutable.text.offset = header.text.memoryOffset; executable.text.offset = header.text.memoryOffset;
nsoExecutable.ro.contents = GetSegment(backing, header.ro, header.flags.roCompressed ? header.roCompressedSize : 0); executable.ro.contents = GetSegment(backing, header.ro, header.flags.roCompressed ? header.roCompressedSize : 0);
nsoExecutable.ro.contents.resize(util::AlignUp(nsoExecutable.ro.contents.size(), PAGE_SIZE)); executable.ro.contents.resize(util::AlignUp(executable.ro.contents.size(), PAGE_SIZE));
nsoExecutable.ro.offset = header.ro.memoryOffset; executable.ro.offset = header.ro.memoryOffset;
nsoExecutable.data.contents = GetSegment(backing, header.data, header.flags.dataCompressed ? header.dataCompressedSize : 0); executable.data.contents = GetSegment(backing, header.data, header.flags.dataCompressed ? header.dataCompressedSize : 0);
nsoExecutable.data.contents.resize(util::AlignUp(nsoExecutable.data.contents.size(), PAGE_SIZE)); executable.data.contents.resize(util::AlignUp(executable.data.contents.size(), PAGE_SIZE));
nsoExecutable.data.offset = header.data.memoryOffset; 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<kernel::type::KProcess> process, const DeviceState &state) { void *NsoLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
state.process->memory.InitializeVmm(memory::AddressSpaceType::AddressSpace39Bit); 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); state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size);
return loadInfo.entry; return loadInfo.entry;
} }

View File

@ -34,6 +34,12 @@ namespace skyline::loader {
}; };
static_assert(sizeof(NsoSegmentHeader) == 0xC); 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 { struct NsoHeader {
u32 magic; //!< The NSO magic "NSO0" u32 magic; //!< The NSO magic "NSO0"
u32 version; //!< The version of the application u32 version; //!< The version of the application
@ -55,9 +61,9 @@ namespace skyline::loader {
u32 _pad1_[7]; u32 _pad1_[7];
u64 apiInfo; //!< The .rodata-relative offset of .apiInfo NsoRelativeSegmentHeader apiInfo; //!< The .rodata-relative segment .apiInfo
u64 dynstr; //!< The .rodata-relative offset of .dynstr NsoRelativeSegmentHeader dynstr; //!< The .rodata-relative segment .dynstr
u64 dynsym; //!< The .rodata-relative offset of .dynsym NsoRelativeSegmentHeader dynsym; //!< The .rodata-relative segment .dynsym
std::array<std::array<u64, 4>, 3> segmentHashes; //!< The SHA256 checksums of the .text, .rodata and .data segments std::array<std::array<u64, 4>, 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 * @brief Loads an NSO into memory, offset by the given amount
* @param backing The backing of the NSO * @param backing The backing that the NSO is contained within
* @param process The process to load the NSO into
* @param offset The offset from the base address to place the NSO * @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 * @return An ExecutableLoadInfo struct containing the load base and size
*/ */
static ExecutableLoadInfo LoadNso(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset = 0); static ExecutableLoadInfo LoadNso(Loader *loader, const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset = 0, const std::string &name = {});
void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state); void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
}; };

View File

@ -37,7 +37,7 @@ namespace skyline::loader {
void *NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void *NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
process->npdm = vfs::NPDM(programNca->exeFs->OpenFile("main.npdm"), 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<u8> NspLoader::GetIcon() { std::vector<u8> NspLoader::GetIcon() {

View File

@ -32,7 +32,7 @@ namespace skyline::nce {
} }
} catch (const signal::SignalException &e) { } catch (const signal::SignalException &e) {
if (e.signal != SIGINT) { 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) { if (state.thread->id) {
signal::BlockSignal({SIGINT}); signal::BlockSignal({SIGINT});
state.process->Kill(false); 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 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 std::exception &e) { } 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) { if (state.thread->id) {
signal::BlockSignal({SIGINT}); signal::BlockSignal({SIGINT});
state.process->Kill(false); state.process->Kill(false);
@ -56,47 +56,26 @@ namespace skyline::nce {
auto &mctx{ctx->uc_mcontext}; auto &mctx{ctx->uc_mcontext};
const auto &state{*reinterpret_cast<ThreadContext *>(*tls)->state}; const auto &state{*reinterpret_cast<ThreadContext *>(*tls)->state};
if (signal != SIGINT) { if (signal != SIGINT) {
state.logger->Warn("Thread #{} has crashed due to signal: {}", state.thread->id, strsignal(signal)); signal::StackFrame topFrame{.lr = reinterpret_cast<void *>(ctx->uc_mcontext.pc), .next = reinterpret_cast<signal::StackFrame *>(ctx->uc_mcontext.regs[29])};
std::string trace{state.loader->GetStackTrace(&topFrame)};
std::string raw;
std::string trace;
std::string cpuContext; 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<u32 *>(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) 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) 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) state.logger->Error("Thread #{} has crashed due to signal: {}\nStack Trace:{}\nCPU Context:{}", state.thread->id, strsignal(signal), trace, cpuContext);
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->Debug("CPU Context:{}", cpuContext); if (state.thread->id) {
signal::BlockSignal({SIGINT});
state.process->Kill(false);
}
} }
mctx.pc = reinterpret_cast<skyline::u64>(&std::longjmp); mctx.pc = reinterpret_cast<u64>(&std::longjmp);
mctx.regs[0] = reinterpret_cast<u64>(state.thread->originalCtx); mctx.regs[0] = reinterpret_cast<u64>(state.thread->originalCtx);
mctx.regs[1] = true; mctx.regs[1] = true;

View File

@ -14,8 +14,8 @@ namespace skyline::vfs {
if (entry.applicationName.front() == '\0') if (entry.applicationName.front() == '\0')
continue; continue;
applicationName = std::string(entry.applicationName.data(), entry.applicationName.size()); applicationName = span(entry.applicationName).as_string(true);
applicationPublisher = std::string(entry.applicationPublisher.data(), entry.applicationPublisher.size()); applicationPublisher = span(entry.applicationPublisher).as_string(true);
} }
} }
} }

View File

@ -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))}; int fd{open((basePath + path).c_str(), (mode.read && mode.write) ? O_RDWR : (mode.write ? O_WRONLY : O_RDONLY))};
if (fd < 0) 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<OsBacking>(fd, true, mode); return std::make_shared<OsBacking>(fd, true, mode);
} }