mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-28 06:55:29 +03:00
Generate Stack Traces + More Robust Terminate Handler + Exit Process on Signal in Guest
This commit is contained in:
parent
14dbb5305a
commit
ebadc1d1e1
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
1487
.idea/inspectionProfiles/Project_Default.xml
generated
1487
.idea/inspectionProfiles/Project_Default.xml
generated
File diff suppressed because it is too large
Load Diff
12
.idea/misc.xml
generated
12
.idea/misc.xml
generated
@ -9,7 +9,7 @@
|
||||
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="12">
|
||||
<list size="15">
|
||||
<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="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="10" class="java.lang.String" itemvalue="android.annotation.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>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="11">
|
||||
<list size="14">
|
||||
<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="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="9" class="java.lang.String" itemvalue="android.annotation.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>
|
||||
</value>
|
||||
</option>
|
||||
</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" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <span>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
|
@ -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<void *>(context->uc_mcontext.pc);
|
||||
if (signal == SIGSEGV)
|
||||
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);
|
||||
context->uc_mcontext.pc = reinterpret_cast<u64>(&ExceptionThrow);
|
||||
|
||||
@ -109,7 +133,7 @@ namespace skyline::signal {
|
||||
|
||||
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 *tls{}; // The TLS value prior to being restored if it is
|
||||
if (TlsRestorer)
|
||||
|
@ -6,6 +6,14 @@
|
||||
#include <common.h>
|
||||
|
||||
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<void*> frames; //!< A vector of all stack frame entries prior to the signal occuring
|
||||
|
||||
inline std::string what() const {
|
||||
if (!fault)
|
||||
|
@ -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;
|
||||
|
@ -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<u8>(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<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')
|
||||
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{};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <cxxabi.h>
|
||||
#include <nce.h>
|
||||
#include <os.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
@ -8,7 +10,7 @@
|
||||
#include "loader.h"
|
||||
|
||||
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)};
|
||||
|
||||
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<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);
|
||||
@ -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<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;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <linux/elf.h>
|
||||
#include <vfs/nacp.h>
|
||||
#include <common/signal.h>
|
||||
#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<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
|
||||
*/
|
||||
@ -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<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::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
|
||||
*/
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
@ -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<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)
|
||||
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<kernel::type::KProcess> 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);
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,10 @@ namespace skyline::loader {
|
||||
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 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);
|
||||
};
|
||||
|
@ -45,21 +45,26 @@ namespace skyline::loader {
|
||||
}
|
||||
|
||||
void *NroLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> 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;
|
||||
|
@ -29,34 +29,39 @@ namespace skyline::loader {
|
||||
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>()};
|
||||
|
||||
if (header.magic != util::MakeMagic<u32>("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<kernel::type::KProcess> 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;
|
||||
}
|
||||
|
@ -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<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
|
||||
* @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<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);
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ namespace skyline::loader {
|
||||
|
||||
void *NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> 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<u8> NspLoader::GetIcon() {
|
||||
|
@ -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<ThreadContext *>(*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<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;
|
||||
|
||||
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)
|
||||
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<skyline::u64>(&std::longjmp);
|
||||
mctx.pc = reinterpret_cast<u64>(&std::longjmp);
|
||||
mctx.regs[0] = reinterpret_cast<u64>(state.thread->originalCtx);
|
||||
mctx.regs[1] = true;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<OsBacking>(fd, true, mode);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user