mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-26 15:54:18 +01:00
Complete Exceptional Signal Handler Implementation + Fix More Destruction Behavior
This commit is contained in:
parent
8bf08ed66f
commit
d155e9cd71
15
.idea/codeStyles/Project.xml
generated
15
.idea/codeStyles/Project.xml
generated
@ -9,9 +9,18 @@
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="java.util" withSubpackages="false" static="false" />
|
||||
<package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
|
||||
<package name="io.ktor" withSubpackages="true" static="false" />
|
||||
<package name="java.util" alias="false" withSubpackages="false" />
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
<package name="io.ktor" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||
<value>
|
||||
<package name="" alias="false" withSubpackages="true" />
|
||||
<package name="java" alias="false" withSubpackages="true" />
|
||||
<package name="javax" alias="false" withSubpackages="true" />
|
||||
<package name="kotlin" alias="false" withSubpackages="true" />
|
||||
<package name="" alias="true" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="SPACE_BEFORE_TYPE_COLON" value="true" />
|
||||
|
7
.idea/discord.xml
generated
7
.idea/discord.xml
generated
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordIntegrationProjectSettings" description="Lightswitch is an experimental Nintendo Switch emulator for Android phones." />
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
</component>
|
||||
</project>
|
@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
|
||||
set(source_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -flto=full -Wno-unused-command-line-argument")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -flto=full -fno-stack-protector -Wno-unused-command-line-argument")
|
||||
if (uppercase_CMAKE_BUILD_TYPE STREQUAL "RELEASE")
|
||||
add_compile_definitions(NDEBUG)
|
||||
endif ()
|
||||
|
@ -25,7 +25,6 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
|
||||
FrameTime = 0;
|
||||
|
||||
pthread_setname_np(pthread_self(), "EmuMain");
|
||||
setpriority(PRIO_PGRP, static_cast<id_t>(gettid()), -8); // Set the priority of this process to the highest value
|
||||
|
||||
auto jvmManager{std::make_shared<skyline::JvmManager>(env, instance)};
|
||||
auto settings{std::make_shared<skyline::Settings>(preferenceFd)};
|
||||
@ -72,15 +71,10 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_stopEmulation(JNIEn
|
||||
os = OsWeak.lock();
|
||||
auto process{os->state.process};
|
||||
while (!process) {
|
||||
__sync_synchronize();
|
||||
process = os->state.process;
|
||||
__sync_synchronize();
|
||||
}
|
||||
while (!process->mainThread)
|
||||
__sync_synchronize();
|
||||
auto thread{process->mainThread}; // We just need to kill the main thread, it'll kill the rest itself
|
||||
while (!thread->running)
|
||||
__sync_synchronize();
|
||||
thread->Kill(true);
|
||||
process->Kill(true, false, true);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setSurface(JNIEnv *, jobject, jobject surface) {
|
||||
|
@ -61,12 +61,9 @@ namespace skyline {
|
||||
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger)
|
||||
: os(os), jvm(std::move(jvmManager)), settings(std::move(settings)), logger(std::move(logger)) {
|
||||
// We assign these later as they use the state in their constructor and we don't want null pointers
|
||||
nce = std::make_shared<nce::NCE>(*this);
|
||||
gpu = std::make_shared<gpu::GPU>(*this);
|
||||
audio = std::make_shared<audio::Audio>(*this);
|
||||
nce = std::make_shared<nce::NCE>(*this);
|
||||
input = std::make_shared<input::Input>(*this);
|
||||
}
|
||||
|
||||
thread_local std::shared_ptr<kernel::type::KThread> DeviceState::thread = nullptr;
|
||||
thread_local nce::ThreadContext *DeviceState::ctx = nullptr;
|
||||
}
|
||||
|
@ -479,10 +479,10 @@ namespace skyline {
|
||||
std::shared_ptr<loader::Loader> loader;
|
||||
std::shared_ptr<gpu::GPU> gpu;
|
||||
std::shared_ptr<audio::Audio> audio;
|
||||
std::shared_ptr<input::Input> input;
|
||||
std::shared_ptr<nce::NCE> nce;
|
||||
std::shared_ptr<kernel::type::KProcess> process;
|
||||
thread_local static std::shared_ptr<kernel::type::KThread> thread; //!< The KThread of the thread which accesses this object
|
||||
thread_local static nce::ThreadContext *ctx; //!< The context of the guest thread for the corresponding host thread
|
||||
static thread_local inline std::shared_ptr<kernel::type::KThread> thread{}; //!< The KThread of the thread which accesses this object
|
||||
static thread_local inline nce::ThreadContext *ctx{}; //!< The context of the guest thread for the corresponding host thread
|
||||
std::shared_ptr<input::Input> input;
|
||||
};
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ namespace skyline {
|
||||
std::condition_variable produceCondition;
|
||||
|
||||
public:
|
||||
CircularQueue(size_t size) : vector(size * sizeof(Type)) {}
|
||||
inline CircularQueue(size_t size) : vector(size * sizeof(Type)) {}
|
||||
|
||||
~CircularQueue() {
|
||||
inline ~CircularQueue() {
|
||||
ssize_t size{};
|
||||
if (start < end)
|
||||
size = end - start;
|
||||
|
@ -4,22 +4,59 @@
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unwind.h>
|
||||
#include <android/log.h>
|
||||
#include "signal.h"
|
||||
|
||||
namespace skyline::signal {
|
||||
thread_local SignalException signalException;
|
||||
thread_local std::exception_ptr SignalExceptionPtr;
|
||||
|
||||
void ExceptionThrow() {
|
||||
throw signalException;
|
||||
std::rethrow_exception(SignalExceptionPtr);
|
||||
}
|
||||
|
||||
std::terminate_handler terminateHandler{};
|
||||
|
||||
void TerminateHandler() {
|
||||
auto exception{std::current_exception()};
|
||||
if (terminateHandler && exception && exception == SignalExceptionPtr) {
|
||||
struct StackFrame {
|
||||
StackFrame *next;
|
||||
void *lr;
|
||||
} *frame;
|
||||
|
||||
asm("MOV %0, FP" : "=r"(frame));
|
||||
frame = frame->next->next;
|
||||
|
||||
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;
|
||||
|
||||
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 FP, %x2\n\t" // The stack frame of the calling function should be set
|
||||
"BR %x3"
|
||||
: : "r"(frame + 1), "r"(frame->lr), "r"(frame->next), "r"(&ExceptionThrow));
|
||||
|
||||
__builtin_unreachable();
|
||||
} else {
|
||||
terminateHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void ExceptionalSignalHandler(int signal, siginfo *info, ucontext *context) {
|
||||
SignalException signalException;
|
||||
signalException.signal = signal;
|
||||
signalException.pc = context->uc_mcontext.pc;
|
||||
if (signal == SIGSEGV)
|
||||
signalException.faultAddress = info->si_addr;
|
||||
SignalExceptionPtr = std::make_exception_ptr(signalException);
|
||||
context->uc_mcontext.pc = reinterpret_cast<u64>(&ExceptionThrow);
|
||||
|
||||
auto handler{std::get_terminate()};
|
||||
if (handler != TerminateHandler) {
|
||||
terminateHandler = handler;
|
||||
std::set_terminate(TerminateHandler);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Signature>
|
||||
@ -47,48 +84,42 @@ namespace skyline::signal {
|
||||
TlsRestorer = function;
|
||||
}
|
||||
|
||||
std::array<void (*)(int, struct siginfo *, void *), NSIG> DefaultSignalHandlers;
|
||||
struct DefaultSignalHandler {
|
||||
void (*function)(int, struct siginfo *, void *){};
|
||||
|
||||
struct ThreadSignalHandler {
|
||||
pthread_key_t key;
|
||||
std::atomic<u32> count;
|
||||
|
||||
void Decrement();
|
||||
|
||||
static void DecrementStatic(ThreadSignalHandler *thiz) {
|
||||
thiz->Decrement();
|
||||
}
|
||||
~DefaultSignalHandler();
|
||||
};
|
||||
|
||||
std::array<ThreadSignalHandler, NSIG> ThreadSignalHandlers;
|
||||
std::array<DefaultSignalHandler, NSIG> DefaultSignalHandlers;
|
||||
|
||||
void ThreadSignalHandler::Decrement() {
|
||||
u32 current;
|
||||
while ((current = count.load()) && !count.compare_exchange_strong(current, --current));
|
||||
if (current == 0) {
|
||||
int signal{static_cast<int>(this - ThreadSignalHandlers.data())};
|
||||
DefaultSignalHandler::~DefaultSignalHandler() {
|
||||
if (function) {
|
||||
int signal{static_cast<int>(this - DefaultSignalHandlers.data())};
|
||||
|
||||
struct sigaction oldAction;
|
||||
Sigaction(signal, nullptr, &oldAction);
|
||||
|
||||
struct sigaction action{
|
||||
.sa_sigaction = DefaultSignalHandlers.at(signal),
|
||||
.sa_sigaction = function,
|
||||
.sa_flags = oldAction.sa_flags,
|
||||
};
|
||||
Sigaction(signal, &action);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
void ThreadSignalHandler(int signal, siginfo *info, ucontext *context) {
|
||||
void *tls{}; // The TLS value prior to being restored if it is
|
||||
if (TlsRestorer)
|
||||
tls = TlsRestorer();
|
||||
|
||||
auto handler{reinterpret_cast<void (*)(int, struct siginfo *, ucontext *, void *)>(pthread_getspecific(ThreadSignalHandlers.at(signal).key))};
|
||||
auto handler{ThreadSignalHandlers.at(signal)};
|
||||
if (handler) {
|
||||
handler(signal, info, context, tls);
|
||||
handler(signal, info, context, &tls);
|
||||
} else {
|
||||
auto defaultHandler{DefaultSignalHandlers.at(signal)};
|
||||
auto defaultHandler{DefaultSignalHandlers.at(signal).function};
|
||||
if (defaultHandler)
|
||||
defaultHandler(signal, info, context);
|
||||
}
|
||||
@ -97,7 +128,7 @@ namespace skyline::signal {
|
||||
asm volatile("MSR TPIDR_EL0, %x0"::"r"(tls));
|
||||
}
|
||||
|
||||
void SetSignalHandler(std::initializer_list<int> signals, void (*function)(int, struct siginfo *, ucontext *, void *)) {
|
||||
void SetSignalHandler(std::initializer_list<int> signals, SignalHandler function) {
|
||||
static std::array<std::once_flag, NSIG> signalHandlerOnce{};
|
||||
|
||||
stack_t stack;
|
||||
@ -108,21 +139,15 @@ namespace skyline::signal {
|
||||
};
|
||||
|
||||
for (int signal : signals) {
|
||||
auto &threadHandler{ThreadSignalHandlers.at(signal)};
|
||||
std::call_once(signalHandlerOnce[signal], [signal, action, &threadHandler]() {
|
||||
if (int result = pthread_key_create(&threadHandler.key, reinterpret_cast<void (*)(void *)>(&ThreadSignalHandler::DecrementStatic)))
|
||||
throw exception("Failed to create per-thread signal handler pthread key: {}", strerror(result));
|
||||
|
||||
std::call_once(signalHandlerOnce[signal], [signal, action]() {
|
||||
struct sigaction oldAction;
|
||||
Sigaction(signal, &action, &oldAction);
|
||||
if (oldAction.sa_flags && oldAction.sa_flags != action.sa_flags)
|
||||
throw exception("Old sigaction flags aren't equivalent to the replaced signal: {:#b} | {:#b}", oldAction.sa_flags, action.sa_flags);
|
||||
|
||||
DefaultSignalHandlers.at(signal) = oldAction.sa_sigaction;
|
||||
DefaultSignalHandlers.at(signal).function = (oldAction.sa_flags & SA_SIGINFO) ? oldAction.sa_sigaction : reinterpret_cast<void (*)(int, struct siginfo *, void *)>(oldAction.sa_handler);
|
||||
});
|
||||
if (!pthread_getspecific(ThreadSignalHandlers.at(signal).key))
|
||||
threadHandler.count++;
|
||||
pthread_setspecific(ThreadSignalHandlers.at(signal).key, reinterpret_cast<void *>(function));
|
||||
ThreadSignalHandlers.at(signal) = function;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ namespace skyline::signal {
|
||||
|
||||
/**
|
||||
* @brief A signal handler which automatically throws an exception with the corresponding signal metadata in a SignalException
|
||||
* @note A termination handler is set in this which prevents any termination from going through as to break out of 'noexcept', do not use std::terminate in a catch clause for this exception
|
||||
*/
|
||||
void ExceptionalSignalHandler(int signal, siginfo *, ucontext *context);
|
||||
|
||||
@ -42,14 +43,17 @@ namespace skyline::signal {
|
||||
*/
|
||||
void SetTlsRestorer(void *(*function)());
|
||||
|
||||
using SignalHandler = void (*)(int, struct siginfo *, ucontext *, void **);
|
||||
|
||||
/**
|
||||
* @brief A wrapper around Sigaction to make it easy to set a sigaction signal handler for multiple signals and also allow for thread-local signal handlers
|
||||
* @param function A sa_action callback with the old TLS (If present) as the 4th argument
|
||||
* @param function A sa_action callback with a pointer to the old TLS (If present) as the 4th argument
|
||||
* @note If 'nullptr' is written into the 4th argument then the old TLS won't be restored or it'll be set to any non-null value written into it
|
||||
*/
|
||||
void SetSignalHandler(std::initializer_list<int> signals, void (*function)(int, struct siginfo *, ucontext *, void *));
|
||||
void SetSignalHandler(std::initializer_list<int> signals, SignalHandler function);
|
||||
|
||||
inline void SetSignalHandler(std::initializer_list<int> signals, void (*function)(int, struct siginfo *, ucontext *)) {
|
||||
SetSignalHandler(signals, reinterpret_cast<void (*)(int, struct siginfo *, ucontext *, void *)>(function));
|
||||
SetSignalHandler(signals, reinterpret_cast<SignalHandler>(function));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,11 +101,11 @@ namespace skyline::gpu::gpfifo {
|
||||
if (e.signal != SIGINT) {
|
||||
state.logger->Write(Logger::LogLevel::Error, e.what());
|
||||
signal::BlockSignal({SIGINT});
|
||||
state.process->mainThread->Kill(false);
|
||||
state.process->Kill(false);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
state.logger->Write(Logger::LogLevel::Error, e.what());
|
||||
state.process->mainThread->Kill(false);
|
||||
state.process->Kill(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ namespace skyline::kernel::svc {
|
||||
void ExitProcess(const DeviceState &state) {
|
||||
state.logger->Debug("svcExitProcess: Exiting process");
|
||||
if (state.thread->id)
|
||||
state.process->mainThread->Kill(false);
|
||||
state.process->Kill(false);
|
||||
std::longjmp(state.thread->originalCtx, true);
|
||||
}
|
||||
|
||||
@ -250,10 +250,15 @@ namespace skyline::kernel::svc {
|
||||
throw exception("svcCreateThread: Cannot find memory object in handle table for thread stack: 0x{:X}", stackTop);
|
||||
|
||||
auto thread{state.process->CreateThread(entry, entryArgument, stackTop, priority, idealCore)};
|
||||
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, ID: {})", thread->handle, entry, entryArgument, stackTop, priority, thread->id);
|
||||
if (thread) {
|
||||
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, ID: {})", thread->handle, entry, entryArgument, stackTop, priority, thread->id);
|
||||
|
||||
state.ctx->gpr.w1 = thread->handle;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
state.ctx->gpr.w1 = thread->handle;
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
} else {
|
||||
state.ctx->gpr.w1 = 0;
|
||||
state.ctx->gpr.w0 = result::OutOfResource;
|
||||
}
|
||||
}
|
||||
|
||||
void StartThread(const DeviceState &state) {
|
||||
@ -509,7 +514,8 @@ namespace skyline::kernel::svc {
|
||||
span waitHandles(reinterpret_cast<KHandle *>(state.ctx->gpr.x1), numHandles);
|
||||
|
||||
for (const auto &handle : waitHandles) {
|
||||
handleStr += fmt::format("* 0x{:X}\n", handle);
|
||||
if (Logger::LogLevel::Debug <= state.logger->configLevel)
|
||||
handleStr += fmt::format("* 0x{:X}\n", handle);
|
||||
|
||||
auto object{state.process->GetHandle(handle)};
|
||||
switch (object->objectType) {
|
||||
|
@ -30,6 +30,31 @@ namespace skyline::kernel::type {
|
||||
|
||||
KProcess::KProcess(const DeviceState &state) : memory(state), KSyncObject(state, KType::KProcess) {}
|
||||
|
||||
KProcess::~KProcess() {
|
||||
std::lock_guard guard(threadMutex);
|
||||
disableThreadCreation = true;
|
||||
for (const auto &thread : threads)
|
||||
thread->Kill(true);
|
||||
}
|
||||
|
||||
void KProcess::Kill(bool join, bool all, bool disableCreation) {
|
||||
std::lock_guard guard(threadMutex);
|
||||
if (disableCreation)
|
||||
disableThreadCreation = true;
|
||||
if (all) {
|
||||
for (const auto &thread : threads)
|
||||
thread->Kill(join);
|
||||
} else {
|
||||
std::shared_ptr<KThread> thread;
|
||||
try {
|
||||
thread = threads.at(0);
|
||||
} catch (const std::out_of_range &) {
|
||||
return;
|
||||
}
|
||||
thread->Kill(join);
|
||||
}
|
||||
}
|
||||
|
||||
void KProcess::InitializeHeap() {
|
||||
constexpr size_t DefaultHeapSize{0x200000};
|
||||
heap = heap.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.heap.address), DefaultHeapSize, memory::Permission{true, true, false}, memory::states::Heap);
|
||||
@ -50,15 +75,17 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
std::shared_ptr<KThread> KProcess::CreateThread(void *entry, u64 argument, void *stackTop, i8 priority, i8 idealCore) {
|
||||
if (!stackTop && !mainThread) { //!< Main thread stack is created by the kernel and owned by the process
|
||||
std::lock_guard guard(threadMutex);
|
||||
if (disableThreadCreation)
|
||||
return nullptr;
|
||||
if (!stackTop && threads.empty()) { //!< Main thread stack is created by the kernel and owned by the process
|
||||
mainThreadStack = mainThreadStack.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.stack.address), state.process->npdm.meta.mainThreadStackSize, memory::Permission{true, true, false}, memory::states::Stack);
|
||||
if (mprotect(mainThreadStack->ptr, PAGE_SIZE, PROT_NONE))
|
||||
throw exception("Failed to create guard page for thread stack at 0x{:X}", mainThreadStack->ptr);
|
||||
stackTop = mainThreadStack->ptr + mainThreadStack->size;
|
||||
}
|
||||
auto thread{NewHandle<KThread>(this, threadIndex++, entry, argument, stackTop, (priority == -1) ? state.process->npdm.meta.mainThreadPriority : priority, (idealCore == -1) ? state.process->npdm.meta.idealCore : idealCore).item};
|
||||
if (!mainThread)
|
||||
mainThread = thread;
|
||||
auto thread{NewHandle<KThread>(this, threads.size(), entry, argument, stackTop, (priority == -1) ? state.process->npdm.meta.mainThreadPriority : priority, (idealCore == -1) ? state.process->npdm.meta.idealCore : idealCore).item};
|
||||
threads.push_back(thread);
|
||||
return thread;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,9 @@ namespace skyline {
|
||||
std::mutex mutexLock;
|
||||
std::mutex conditionalLock;
|
||||
|
||||
size_t threadIndex{}; //!< The ID assigned to the next created thread
|
||||
std::mutex threadMutex; //!< Synchronizes thread creation to prevent a race between thread creation and thread killing
|
||||
bool disableThreadCreation{}; //!< If to disable thread creation, we use this to prevent thread creation after all threads have been killed
|
||||
std::vector<std::shared_ptr<KThread>> threads;
|
||||
|
||||
/**
|
||||
* @brief The status of a single TLS page (A page is 4096 bytes on ARMv8)
|
||||
@ -69,7 +71,6 @@ namespace skyline {
|
||||
std::shared_ptr<KPrivateMemory> mainThreadStack;
|
||||
std::shared_ptr<KPrivateMemory> heap;
|
||||
std::vector<std::shared_ptr<TlsPage>> tlsPages;
|
||||
std::shared_ptr<KThread> mainThread;
|
||||
vfs::NPDM npdm;
|
||||
|
||||
private:
|
||||
@ -79,6 +80,18 @@ namespace skyline {
|
||||
public:
|
||||
KProcess(const DeviceState &state);
|
||||
|
||||
~KProcess();
|
||||
|
||||
/**
|
||||
* @brief Kill the main thread/all running threads in the process in a graceful manner
|
||||
* @param join Return after the main thread has joined rather than instantly
|
||||
* @param all If to kill all running threads or just the main thread
|
||||
* @param disableCreation If to disable further thread creation
|
||||
* @note If there are no threads then this will silently return
|
||||
* @note The main thread should eventually kill the rest of the threads itself
|
||||
*/
|
||||
void Kill(bool join, bool all = false, bool disableCreation = false);
|
||||
|
||||
/**
|
||||
* @note This requires VMM regions to be initialized, it will map heap at an arbitrary location otherwise
|
||||
*/
|
||||
@ -90,6 +103,7 @@ namespace skyline {
|
||||
u8 *AllocateTlsSlot();
|
||||
|
||||
/**
|
||||
* @return A shared pointer to a KThread initialized with the specified values or nullptr, if thread creation has been disabled
|
||||
* @note The default values are for the main thread and will use values from the NPDM
|
||||
*/
|
||||
std::shared_ptr<KThread> CreateThread(void *entry, u64 argument = 0, void *stackTop = nullptr, i8 priority = -1, i8 idealCore = -1);
|
||||
@ -144,20 +158,24 @@ namespace skyline {
|
||||
if (handle == threadSelf)
|
||||
return state.thread;
|
||||
objectType = KType::KThread;
|
||||
} else if constexpr(std::is_same<objectClass, KProcess>())
|
||||
} else if constexpr(std::is_same<objectClass, KProcess>()) {
|
||||
constexpr KHandle processSelf{0xFFFF8001}; // The handle used by threads in a process to refer to the process
|
||||
if (handle == processSelf)
|
||||
return state.process;
|
||||
objectType = KType::KProcess;
|
||||
else if constexpr(std::is_same<objectClass, KSharedMemory>())
|
||||
} else if constexpr(std::is_same<objectClass, KSharedMemory>()) {
|
||||
objectType = KType::KSharedMemory;
|
||||
else if constexpr(std::is_same<objectClass, KTransferMemory>())
|
||||
} else if constexpr(std::is_same<objectClass, KTransferMemory>()) {
|
||||
objectType = KType::KTransferMemory;
|
||||
else if constexpr(std::is_same<objectClass, KPrivateMemory>())
|
||||
} else if constexpr(std::is_same<objectClass, KPrivateMemory>()) {
|
||||
objectType = KType::KPrivateMemory;
|
||||
else if constexpr(std::is_same<objectClass, KSession>())
|
||||
} else if constexpr(std::is_same<objectClass, KSession>()) {
|
||||
objectType = KType::KSession;
|
||||
else if constexpr(std::is_same<objectClass, KEvent>())
|
||||
} else if constexpr(std::is_same<objectClass, KEvent>()) {
|
||||
objectType = KType::KEvent;
|
||||
else
|
||||
} else {
|
||||
throw exception("KProcess::GetHandle couldn't determine object type");
|
||||
}
|
||||
try {
|
||||
auto &item{handles.at(handle - constant::BaseHandleIndex)};
|
||||
if (item != nullptr && item->objectType == objectType)
|
||||
@ -166,7 +184,7 @@ namespace skyline {
|
||||
throw exception("GetHandle was called with a deleted handle: 0x{:X}", handle);
|
||||
else
|
||||
throw exception("Tried to get kernel object (0x{:X}) with different type: {} when object is {}", handle, objectType, item->objectType);
|
||||
} catch (std::out_of_range) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range(fmt::format("GetHandle was called with an invalid handle: 0x{:X}", handle));
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,14 @@ namespace skyline::kernel::type {
|
||||
}
|
||||
|
||||
KThread::~KThread() {
|
||||
std::unique_lock lock(mutex);
|
||||
if (running && pthread != pthread_self()) {
|
||||
pthread_kill(pthread, SIGINT);
|
||||
if (thread)
|
||||
thread->join();
|
||||
else
|
||||
if (!thread.joinable())
|
||||
pthread_join(pthread, nullptr);
|
||||
}
|
||||
if (thread.joinable())
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void KThread::StartThread() {
|
||||
@ -41,8 +42,10 @@ namespace skyline::kernel::type {
|
||||
running = false;
|
||||
Signal();
|
||||
|
||||
pthread_setname_np(pthread, threadName.data());
|
||||
state.logger->UpdateTag();
|
||||
if (threadName[0] != 'H' || threadName[1] != 'O' || threadName[2] != 'S' || threadName[3] != '-') {
|
||||
pthread_setname_np(pthread, threadName.data());
|
||||
state.logger->UpdateTag();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -140,8 +143,8 @@ namespace skyline::kernel::type {
|
||||
lock.unlock();
|
||||
StartThread();
|
||||
} else {
|
||||
thread.emplace(&KThread::StartThread, this);
|
||||
pthread = thread->native_handle();
|
||||
thread = std::thread(&KThread::StartThread, this);
|
||||
pthread = thread.native_handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,8 +153,12 @@ namespace skyline::kernel::type {
|
||||
std::lock_guard lock(mutex);
|
||||
if (running) {
|
||||
pthread_kill(pthread, SIGINT);
|
||||
if (join)
|
||||
pthread_join(pthread, nullptr);
|
||||
if (join) {
|
||||
if (thread.joinable())
|
||||
thread.join();
|
||||
else
|
||||
pthread_join(pthread, nullptr);
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ namespace skyline {
|
||||
class KThread : public KSyncObject, public std::enable_shared_from_this<KThread> {
|
||||
private:
|
||||
KProcess *parent;
|
||||
std::optional<std::thread> thread; //!< If this KThread is backed by a host thread then this'll hold it
|
||||
std::thread thread; //!< If this KThread is backed by a host thread then this'll hold it
|
||||
pthread_t pthread{}; //!< The pthread_t for the host thread running this guest thread
|
||||
|
||||
void StartThread();
|
||||
@ -83,7 +83,7 @@ namespace skyline {
|
||||
void Start(bool self = false);
|
||||
|
||||
/**
|
||||
* @param join Returns after the guest thread has joined rather than instantly
|
||||
* @param join Return after the thread has joined rather than instantly
|
||||
*/
|
||||
void Kill(bool join);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <unistd.h>
|
||||
#include "common/signal.h"
|
||||
#include "os.h"
|
||||
@ -28,62 +29,72 @@ namespace skyline::nce {
|
||||
state.logger->Error("{} (SVC: 0x{:X})", e.what(), svc);
|
||||
if (state.thread->id) {
|
||||
signal::BlockSignal({SIGINT});
|
||||
state.process->mainThread->Kill(false);
|
||||
state.process->Kill(false);
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (state.thread->id) {
|
||||
signal::BlockSignal({SIGINT});
|
||||
state.process->mainThread->Kill(false);
|
||||
state.process->Kill(false);
|
||||
}
|
||||
abi::__cxa_end_catch();
|
||||
std::longjmp(state.thread->originalCtx, true);
|
||||
}
|
||||
}
|
||||
|
||||
void NCE::SignalHandler(int signal, siginfo *info, ucontext *context, void *oldTls) {
|
||||
if (oldTls) {
|
||||
const auto &state{*reinterpret_cast<ThreadContext *>(oldTls)->state};
|
||||
void NCE::SignalHandler(int signal, siginfo *info, ucontext *context, void **tls) {
|
||||
if (*tls) {
|
||||
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));
|
||||
|
||||
state.logger->Warn("Thread #{} has crashed due to signal: {}", state.thread->id, strsignal(signal));
|
||||
std::string raw;
|
||||
std::string trace;
|
||||
std::string cpuContext;
|
||||
|
||||
std::string raw;
|
||||
std::string trace;
|
||||
std::string cpuContext;
|
||||
const auto &ctx{reinterpret_cast<ucontext *>(context)->uc_mcontext};
|
||||
constexpr u16 instructionCount{20}; // The amount of previous instructions to print
|
||||
auto offset{ctx.pc - (instructionCount * sizeof(u32)) + (2 * sizeof(u32))};
|
||||
span instructions(reinterpret_cast<u32 *>(offset), instructionCount);
|
||||
if (mprotect(instructions.data(), instructions.size_bytes(), PROT_READ | PROT_WRITE | PROT_EXEC) == 0) {
|
||||
for (auto &instruction : instructions) {
|
||||
instruction = __builtin_bswap32(instruction);
|
||||
|
||||
const auto &ctx{reinterpret_cast<ucontext *>(context)->uc_mcontext};
|
||||
constexpr u16 instructionCount{20}; // The amount of previous instructions to print
|
||||
auto offset{ctx.pc - (instructionCount * sizeof(u32)) + (2 * sizeof(u32))};
|
||||
span instructions(reinterpret_cast<u32 *>(offset), instructionCount);
|
||||
for (auto &instruction : instructions) {
|
||||
instruction = __builtin_bswap32(instruction);
|
||||
if (offset == ctx.pc)
|
||||
trace += fmt::format("\n-> 0x{:X} : 0x{:08X}", offset, instruction);
|
||||
else
|
||||
trace += fmt::format("\n 0x{:X} : 0x{:08X}", offset, instruction);
|
||||
|
||||
if (offset == ctx.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);
|
||||
}
|
||||
|
||||
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}", ctx.pc);
|
||||
}
|
||||
|
||||
if (ctx.fault_address)
|
||||
cpuContext += fmt::format("\nFault Address: 0x{:X}", ctx.fault_address);
|
||||
|
||||
if (ctx.sp)
|
||||
cpuContext += fmt::format("\nStack Pointer: 0x{:X}", ctx.sp);
|
||||
|
||||
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, ctx.regs[index], index < 10 ? ' ' : '\0', index + 1, ctx.regs[index]);
|
||||
|
||||
state.logger->Debug("CPU Context:{}", cpuContext);
|
||||
}
|
||||
|
||||
if (ctx.fault_address)
|
||||
cpuContext += fmt::format("\nFault Address: 0x{:X}", ctx.fault_address);
|
||||
|
||||
if (ctx.sp)
|
||||
cpuContext += fmt::format("\nStack Pointer: 0x{:X}", ctx.sp);
|
||||
|
||||
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, ctx.regs[index], index < 10 ? ' ' : '\0', index + 1, ctx.regs[index]);
|
||||
|
||||
state.logger->Debug("Process Trace:{}", trace);
|
||||
state.logger->Debug("Raw Instructions: 0x{}", raw);
|
||||
state.logger->Debug("CPU Context:{}", cpuContext);
|
||||
|
||||
context->uc_mcontext.pc = reinterpret_cast<skyline::u64>(&std::longjmp);
|
||||
context->uc_mcontext.regs[0] = reinterpret_cast<u64>(state.thread->originalCtx);
|
||||
context->uc_mcontext.regs[1] = true;
|
||||
|
||||
*tls = nullptr;
|
||||
} else {
|
||||
signal::ExceptionalSignalHandler(signal, info, context); //!< Delegate throwing a host exception to the exceptional signal handler
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace skyline::nce {
|
||||
static void SvcHandler(u16 svc, ThreadContext *ctx);
|
||||
|
||||
public:
|
||||
static void SignalHandler(int signal, siginfo *info, ucontext *context, void *oldTls);
|
||||
static void SignalHandler(int signal, siginfo *info, ucontext *context, void **tls);
|
||||
|
||||
NCE(const DeviceState &state);
|
||||
|
||||
|
@ -33,6 +33,10 @@ namespace skyline::kernel {
|
||||
process = std::make_shared<kernel::type::KProcess>(state);
|
||||
auto entry{state.loader->LoadProcessData(process, state)};
|
||||
process->InitializeHeap();
|
||||
process->CreateThread(entry)->Start(true);
|
||||
auto thread{process->CreateThread(entry)};
|
||||
if (thread) {
|
||||
thread->Start(true);
|
||||
process->Kill(true, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ namespace skyline::service::audio {
|
||||
|
||||
Result IAudioOut::GetReleasedAudioOutBuffer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto maxCount{static_cast<u32>(request.outputBuf.at(0).size() >> 3)};
|
||||
std::vector<u64> releasedBuffers{track->GetReleasedBuffers(maxCount)};
|
||||
auto releasedBuffers{track->GetReleasedBuffers(maxCount)};
|
||||
auto count{static_cast<u32>(releasedBuffers.size())};
|
||||
|
||||
// Fill rest of output buffer with zeros
|
||||
|
@ -67,6 +67,7 @@ namespace skyline::service::fssrv {
|
||||
/**
|
||||
* @brief Returns a handle to an instance of #IFileSystem
|
||||
* @url https://switchbrew.org/wiki/Filesystem_services#IFileSystem for the requested save data area
|
||||
* @url https://switchbrew.org/wiki/Filesystem_services#OpenSaveDataFileSystem
|
||||
*/
|
||||
Result OpenSaveDataFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
|
||||
|
||||
|
@ -114,7 +114,7 @@ namespace skyline::service::nvdrv::device {
|
||||
|
||||
auto &event{*events.at(userEventId)};
|
||||
if (event.state == NvHostEvent::State::Cancelled || event.state == NvHostEvent::State::Available || event.state == NvHostEvent::State::Signaled) {
|
||||
state.logger->Debug("Now waiting on nvhost event: {} with fence: {}", userEventId, data.fence.id);
|
||||
state.logger->Debug("Waiting on nvhost event: {} with fence: {}", userEventId, data.fence.id);
|
||||
event.Wait(state.gpu, data.fence);
|
||||
|
||||
data.value.val = 0;
|
||||
@ -139,7 +139,7 @@ namespace skyline::service::nvdrv::device {
|
||||
}
|
||||
|
||||
NvStatus NvHostCtrl::EventSignal(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
auto userEventId{buffer.as<u32>()};
|
||||
auto userEventId{buffer.as<u16>()};
|
||||
state.logger->Debug("Signalling nvhost event: {}", userEventId);
|
||||
|
||||
if (userEventId >= constant::NvHostEventCount || !events.at(userEventId))
|
||||
|
Loading…
Reference in New Issue
Block a user