Merge pull request #760 from merwie/feat/kernel-raise-exception-windows
feat(libkernel): deliver sceKernelRaiseException on Windows
This commit is contained in:
commit
e674a6384d
|
|
@ -284,6 +284,15 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_exception_handler PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test_executable(guest_raise_exception_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseException.cpp)
|
||||
target_include_directories(guest_raise_exception_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_raise_exception_tests PRIVATE libkernel libc)
|
||||
configure_windows_unwind(guest_raise_exception_tests)
|
||||
add_test(NAME guest_raise_exception COMMAND guest_raise_exception_tests)
|
||||
set_tests_properties(guest_raise_exception PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_sync_on_address_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyncOnAddress.cpp)
|
||||
target_include_directories(guest_sync_on_address_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_sync_on_address_tests PRIVATE libkernel libc)
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ struct PthreadPrivate {
|
|||
std::atomic<unsigned> references{2};
|
||||
void* stackAddress = nullptr;
|
||||
std::size_t stackSize = 0;
|
||||
std::atomic<int> waitCount{0};
|
||||
std::atomic<KernelCpumask> affinity{DEFAULT_THREAD_AFFINITY};
|
||||
std::atomic<int> priority{DEFAULT_THREAD_PRIORITY};
|
||||
std::mutex nameLock;
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
|
|||
const auto entry = args->entry;
|
||||
void* arg = args->arg;
|
||||
PthreadPrivate* self = args->self;
|
||||
TimedWait::BindThreadWaitState(&self->waitCount);
|
||||
if (!self->stackAddress) SetStackFromHost(self);
|
||||
currentThread = self;
|
||||
RegisterStack(self);
|
||||
|
|
@ -407,6 +408,7 @@ Pthread APS5_VABI scePthreadSelf() {
|
|||
adopted->threadId = std::this_thread::get_id();
|
||||
adopted->_detached = true;
|
||||
adopted->references.store(1, std::memory_order_relaxed);
|
||||
TimedWait::BindThreadWaitState(&adopted->waitCount);
|
||||
SetStackFromHost(adopted.get());
|
||||
currentThread = adopted.release();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,85 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/KernelErrors.hpp"
|
||||
#include "prx/libkernel/Pthread/include/Pthread.hpp"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
extern "C" Pthread APS5_VABI scePthreadSelf();
|
||||
#ifdef _WIN32
|
||||
extern "C" void Aps5RedirectedEntryStub();
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct GuestMcontext {
|
||||
std::uint64_t onstack;
|
||||
std::uint64_t rdi;
|
||||
std::uint64_t rsi;
|
||||
std::uint64_t rdx;
|
||||
std::uint64_t rcx;
|
||||
std::uint64_t r8;
|
||||
std::uint64_t r9;
|
||||
std::uint64_t rax;
|
||||
std::uint64_t rbx;
|
||||
std::uint64_t rbp;
|
||||
std::uint64_t r10;
|
||||
std::uint64_t r11;
|
||||
std::uint64_t r12;
|
||||
std::uint64_t r13;
|
||||
std::uint64_t r14;
|
||||
std::uint64_t r15;
|
||||
std::uint32_t trapno;
|
||||
std::uint16_t fs;
|
||||
std::uint16_t gs;
|
||||
std::uint64_t addr;
|
||||
std::uint32_t flags;
|
||||
std::uint16_t es;
|
||||
std::uint16_t ds;
|
||||
std::uint64_t err;
|
||||
std::uint64_t rip;
|
||||
std::uint64_t cs;
|
||||
std::uint64_t rflags;
|
||||
std::uint64_t rsp;
|
||||
std::uint64_t ss;
|
||||
std::uint64_t len;
|
||||
std::uint64_t fpformat;
|
||||
std::uint64_t ownedfp;
|
||||
std::uint64_t lbrfrom;
|
||||
std::uint64_t lbrto;
|
||||
std::uint64_t aux1;
|
||||
std::uint64_t aux2;
|
||||
std::uint64_t fpstate[104];
|
||||
std::uint64_t fsbase;
|
||||
std::uint64_t gsbase;
|
||||
std::uint64_t spare[6];
|
||||
};
|
||||
|
||||
struct GuestUcontext {
|
||||
std::uint32_t sigmask[4];
|
||||
std::int32_t reserved[12];
|
||||
GuestMcontext mcontext;
|
||||
GuestUcontext* link;
|
||||
void* stackPointer;
|
||||
std::uint64_t stackSize;
|
||||
std::int32_t stackFlags;
|
||||
std::int32_t stackAlign;
|
||||
std::int32_t flags;
|
||||
std::int32_t spare[4];
|
||||
std::int32_t tail[3];
|
||||
};
|
||||
|
||||
static_assert(offsetof(GuestUcontext, mcontext) == 0x40);
|
||||
static_assert(offsetof(GuestUcontext, mcontext) + offsetof(GuestMcontext, rsp) == 0xf8);
|
||||
|
||||
using GuestExceptionHandler = void (APS5_VABI *)(int, void*);
|
||||
|
||||
constexpr std::array<int, 6> AllowedSignals{1, 4, 8, 10, 11, 30};
|
||||
|
||||
std::mutex handlersLock;
|
||||
|
|
@ -19,8 +93,179 @@ bool Allowed(int signum) {
|
|||
return false;
|
||||
}
|
||||
|
||||
GuestExceptionHandler Handler(int signum) {
|
||||
std::lock_guard lock(handlersLock);
|
||||
return reinterpret_cast<GuestExceptionHandler>(handlers[signum]);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
constexpr std::size_t RedZone = 128;
|
||||
constexpr std::size_t HomeArea = 32;
|
||||
|
||||
struct Delivery {
|
||||
GuestExceptionHandler handler;
|
||||
int signum;
|
||||
CONTEXT context;
|
||||
};
|
||||
|
||||
void Deliver(GuestExceptionHandler handler, int signum, CONTEXT& context) {
|
||||
GuestUcontext ucontext{};
|
||||
auto& m = ucontext.mcontext;
|
||||
m.rdi = context.Rdi;
|
||||
m.rsi = context.Rsi;
|
||||
m.rdx = context.Rdx;
|
||||
m.rcx = context.Rcx;
|
||||
m.r8 = context.R8;
|
||||
m.r9 = context.R9;
|
||||
m.rax = context.Rax;
|
||||
m.rbx = context.Rbx;
|
||||
m.rbp = context.Rbp;
|
||||
m.r10 = context.R10;
|
||||
m.r11 = context.R11;
|
||||
m.r12 = context.R12;
|
||||
m.r13 = context.R13;
|
||||
m.r14 = context.R14;
|
||||
m.r15 = context.R15;
|
||||
m.rip = context.Rip;
|
||||
m.rsp = context.Rsp;
|
||||
m.rflags = context.EFlags;
|
||||
m.cs = context.SegCs;
|
||||
m.ss = context.SegSs;
|
||||
m.len = sizeof(GuestMcontext);
|
||||
static_assert(sizeof(context.FltSave) <= sizeof(m.fpstate));
|
||||
std::memcpy(m.fpstate, &context.FltSave, sizeof(context.FltSave));
|
||||
handler(signum, &ucontext);
|
||||
context.Rdi = m.rdi;
|
||||
context.Rsi = m.rsi;
|
||||
context.Rdx = m.rdx;
|
||||
context.Rcx = m.rcx;
|
||||
context.R8 = m.r8;
|
||||
context.R9 = m.r9;
|
||||
context.Rax = m.rax;
|
||||
context.Rbx = m.rbx;
|
||||
context.Rbp = m.rbp;
|
||||
context.R10 = m.r10;
|
||||
context.R11 = m.r11;
|
||||
context.R12 = m.r12;
|
||||
context.R13 = m.r13;
|
||||
context.R14 = m.r14;
|
||||
context.R15 = m.r15;
|
||||
context.Rip = m.rip;
|
||||
context.Rsp = m.rsp;
|
||||
context.EFlags = static_cast<DWORD>(m.rflags);
|
||||
std::memcpy(&context.FltSave, m.fpstate, sizeof(context.FltSave));
|
||||
}
|
||||
|
||||
[[noreturn]] void RedirectedEntry(Delivery* delivery) {
|
||||
CONTEXT context = delivery->context;
|
||||
Deliver(delivery->handler, delivery->signum, context);
|
||||
RtlRestoreContext(&context, nullptr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool StackWritable(DWORD64 low, DWORD64 high) {
|
||||
for (DWORD64 address = low; address < high;) {
|
||||
MEMORY_BASIC_INFORMATION info{};
|
||||
if (VirtualQuery(reinterpret_cast<void*>(address), &info, sizeof(info)) != sizeof(info)) return false;
|
||||
if (info.State != MEM_COMMIT || (info.Protect & PAGE_GUARD) != 0 || (info.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) == 0) return false;
|
||||
address = reinterpret_cast<DWORD64>(info.BaseAddress) + info.RegionSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CALLBACK WaitingEntry(ULONG_PTR parameter) {
|
||||
auto* delivery = reinterpret_cast<Delivery*>(parameter);
|
||||
const auto handler = delivery->handler;
|
||||
const int signum = delivery->signum;
|
||||
delete delivery;
|
||||
CONTEXT context{};
|
||||
RtlCaptureContext(&context);
|
||||
Deliver(handler, signum, context);
|
||||
}
|
||||
|
||||
static_assert(HomeArea + 8 == 40, "Aps5RedirectedEntryStub finds the delivery 40 bytes above its stack pointer");
|
||||
static_assert(offsetof(Delivery, context) == 16 && offsetof(CONTEXT, Rax) == 0x78 && offsetof(CONTEXT, Rbp) == 0xa0 && offsetof(CONTEXT, R15) == 0xf0, "Aps5RedirectedEntryStub stores the live registers into the delivery's context");
|
||||
|
||||
bool Exited(HANDLE native) {
|
||||
return WaitForSingleObject(native, 0) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
|
||||
if (thread == scePthreadSelf()) {
|
||||
CONTEXT context{};
|
||||
RtlCaptureContext(&context);
|
||||
Deliver(handler, signum, context);
|
||||
return true;
|
||||
}
|
||||
const auto native = static_cast<HANDLE>(thread->nativeHandle);
|
||||
auto queued = std::make_unique<Delivery>(Delivery{handler, signum, {}});
|
||||
if (SuspendThread(native) == static_cast<DWORD>(-1)) {
|
||||
if (Exited(native)) return false;
|
||||
throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread");
|
||||
}
|
||||
if (Exited(native)) {
|
||||
ResumeThread(native);
|
||||
return false;
|
||||
}
|
||||
if (thread->waitCount.load(std::memory_order_seq_cst) > 0) {
|
||||
const bool accepted = QueueUserAPC(WaitingEntry, native, reinterpret_cast<ULONG_PTR>(queued.get())) != 0;
|
||||
ResumeThread(native);
|
||||
if (!accepted) throw std::runtime_error("sceKernelRaiseException: cannot queue delivery to the waiting thread");
|
||||
queued.release();
|
||||
return true;
|
||||
}
|
||||
alignas(16) Delivery delivery{handler, signum, {}};
|
||||
delivery.context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
|
||||
if (!GetThreadContext(native, &delivery.context)) {
|
||||
ResumeThread(native);
|
||||
throw std::runtime_error("sceKernelRaiseException: cannot read the target thread context");
|
||||
}
|
||||
const DWORD64 slot = (delivery.context.Rsp - RedZone - sizeof(Delivery)) & ~static_cast<DWORD64>(15);
|
||||
if (!StackWritable(slot - HomeArea - 8, delivery.context.Rsp - RedZone)) {
|
||||
ResumeThread(native);
|
||||
throw std::runtime_error("sceKernelRaiseException: the target thread stack below its red zone is not committed");
|
||||
}
|
||||
std::memcpy(reinterpret_cast<void*>(slot), &delivery, sizeof(Delivery));
|
||||
CONTEXT redirected = delivery.context;
|
||||
redirected.Rsp = slot - HomeArea - 8;
|
||||
redirected.Rip = reinterpret_cast<DWORD64>(&Aps5RedirectedEntryStub);
|
||||
if (!SetThreadContext(native, &redirected)) {
|
||||
ResumeThread(native);
|
||||
throw std::runtime_error("sceKernelRaiseException: cannot redirect the target thread");
|
||||
}
|
||||
ResumeThread(native);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
extern "C" [[noreturn]] void Aps5RedirectedEntry(void* delivery) {
|
||||
RedirectedEntry(static_cast<Delivery*>(delivery));
|
||||
}
|
||||
asm(".text\n"
|
||||
".globl Aps5RedirectedEntryStub\n"
|
||||
"Aps5RedirectedEntryStub:\n"
|
||||
" movq %rax, 176(%rsp)\n"
|
||||
" movq %rcx, 184(%rsp)\n"
|
||||
" movq %rdx, 192(%rsp)\n"
|
||||
" movq %rbx, 200(%rsp)\n"
|
||||
" movq %rbp, 216(%rsp)\n"
|
||||
" movq %rsi, 224(%rsp)\n"
|
||||
" movq %rdi, 232(%rsp)\n"
|
||||
" movq %r8, 240(%rsp)\n"
|
||||
" movq %r9, 248(%rsp)\n"
|
||||
" movq %r10, 256(%rsp)\n"
|
||||
" movq %r11, 264(%rsp)\n"
|
||||
" movq %r12, 272(%rsp)\n"
|
||||
" movq %r13, 280(%rsp)\n"
|
||||
" movq %r14, 288(%rsp)\n"
|
||||
" movq %r15, 296(%rsp)\n"
|
||||
" leaq 40(%rsp), %rcx\n"
|
||||
" jmp Aps5RedirectedEntry\n");
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler) {
|
||||
|
|
@ -39,10 +284,16 @@ int APS5_VABI sceKernelRemoveExceptionHandler(int signum) {
|
|||
}
|
||||
|
||||
int APS5_VABI sceKernelRaiseException(Pthread thread, int signum) {
|
||||
(void)thread;
|
||||
(void)signum;
|
||||
if (signum != 30) return SCE_KERNEL_ERROR_EINVAL;
|
||||
if (thread == nullptr || thread->_finished.load(std::memory_order_acquire)) return SCE_KERNEL_ERROR_ESRCH;
|
||||
const auto handler = Handler(signum);
|
||||
if (handler == nullptr) throw std::runtime_error("sceKernelRaiseException: no handler installed for the signal");
|
||||
#ifdef _WIN32
|
||||
return RaiseOn(thread, handler, signum) ? 0 : SCE_KERNEL_ERROR_ESRCH;
|
||||
#else
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void APS5_VABI sceKernelDebugRaiseException(int c1, int c2) {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,37 @@ namespace {
|
|||
constexpr std::uint64_t YIELD_BELOW_NANOS = 50000ULL;
|
||||
constexpr std::uint64_t LEAD_NANOS = 500000ULL;
|
||||
|
||||
thread_local std::atomic<int> ownWaitState{0};
|
||||
thread_local std::atomic<int>* waitState = &ownWaitState;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
using NtTestAlertFunction = LONG(NTAPI*)();
|
||||
|
||||
void DrainApcs() {
|
||||
static const auto testAlert = reinterpret_cast<NtTestAlertFunction>(reinterpret_cast<void*>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtTestAlert")));
|
||||
if (testAlert) testAlert();
|
||||
else SleepEx(0, TRUE);
|
||||
}
|
||||
|
||||
DWORD AlertableWait(DWORD count, const HANDLE* handles, DWORD milliseconds) {
|
||||
waitState->fetch_add(1, std::memory_order_seq_cst);
|
||||
DWORD result;
|
||||
do {
|
||||
result = WaitForMultipleObjectsEx(count, handles, FALSE, milliseconds, TRUE);
|
||||
} while (result == WAIT_IO_COMPLETION);
|
||||
waitState->fetch_sub(1, std::memory_order_seq_cst);
|
||||
DrainApcs();
|
||||
return result;
|
||||
}
|
||||
|
||||
void AlertableSleep(DWORD milliseconds) {
|
||||
waitState->fetch_add(1, std::memory_order_seq_cst);
|
||||
SleepEx(milliseconds, TRUE);
|
||||
waitState->fetch_sub(1, std::memory_order_seq_cst);
|
||||
DrainApcs();
|
||||
}
|
||||
|
||||
void WINAPI DestroyWaiter(void* value);
|
||||
|
||||
DWORD WaiterSlotIndex() {
|
||||
|
|
@ -36,7 +65,7 @@ bool TimerWait(HANDLE timer, std::uint64_t nanos) {
|
|||
due.QuadPart = -static_cast<LONGLONG>(nanos / 100ULL);
|
||||
if (due.QuadPart == 0) due.QuadPart = -1;
|
||||
if (!SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE)) return false;
|
||||
return WaitForSingleObject(timer, INFINITE) == WAIT_OBJECT_0;
|
||||
return AlertableWait(1, &timer, INFINITE) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
DWORD WholeMilliseconds(std::uint64_t nanos) {
|
||||
|
|
@ -51,7 +80,7 @@ void SleepNanosCoarse(std::uint64_t nanos) {
|
|||
}
|
||||
thread_local HANDLE timer = CreateHighResolutionTimer();
|
||||
if (TimerWait(timer, nanos)) return;
|
||||
Sleep(static_cast<DWORD>((nanos + 999999ULL) / 1000000ULL));
|
||||
AlertableSleep(static_cast<DWORD>((nanos + 999999ULL) / 1000000ULL));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -90,7 +119,7 @@ Waiter* ThisThreadWaiter() {
|
|||
}
|
||||
|
||||
bool EventSet(HANDLE event, DWORD milliseconds) {
|
||||
return WaitForSingleObject(event, milliseconds) == WAIT_OBJECT_0;
|
||||
return AlertableWait(1, &event, milliseconds) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
bool WaitEventUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
|
||||
|
|
@ -104,7 +133,7 @@ bool WaitEventUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
|
|||
due.QuadPart = -static_cast<LONGLONG>(bulk / 100ULL);
|
||||
if (waiter->timer && SetWaitableTimer(waiter->timer, &due, 0, nullptr, nullptr, FALSE)) {
|
||||
HANDLE handles[2] = {waiter->event, waiter->timer};
|
||||
if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) == WAIT_OBJECT_0) return true;
|
||||
if (AlertableWait(2, handles, INFINITE) == WAIT_OBJECT_0) return true;
|
||||
continue;
|
||||
}
|
||||
if (EventSet(waiter->event, WholeMilliseconds(bulk))) return true;
|
||||
|
|
@ -140,7 +169,7 @@ void Condition::unlink(Waiter* waiter) {
|
|||
}
|
||||
|
||||
void Condition::waitSignal(Waiter* waiter) {
|
||||
WaitForSingleObject(waiter->event, INFINITE);
|
||||
AlertableWait(1, &waiter->event, INFINITE);
|
||||
}
|
||||
|
||||
bool Condition::waitSignalUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
|
||||
|
|
@ -150,7 +179,7 @@ bool Condition::waitSignalUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
|
|||
unlink(waiter);
|
||||
return false;
|
||||
}
|
||||
EventSet(waiter->event, 0);
|
||||
WaitForSingleObject(waiter->event, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -190,6 +219,10 @@ void Condition::NotifyAll() {
|
|||
|
||||
#endif
|
||||
|
||||
void BindThreadWaitState(std::atomic<int>* state) {
|
||||
waitState = state != nullptr ? state : &ownWaitState;
|
||||
}
|
||||
|
||||
bool Coarse() {
|
||||
#ifdef _WIN32
|
||||
static const bool coarse = std::getenv("APS5_COARSE_TIMED_WAITS") != nullptr;
|
||||
|
|
@ -233,7 +266,7 @@ void SleepUntil(std::uint64_t deadlineNanos) {
|
|||
std::uint64_t now = NowNanos();
|
||||
if (deadlineNanos > now + LEAD_NANOS) {
|
||||
thread_local HANDLE timer = CreateHighResolutionTimer();
|
||||
if (!TimerWait(timer, deadlineNanos - now - LEAD_NANOS)) Sleep(WholeMilliseconds(deadlineNanos - now - LEAD_NANOS));
|
||||
if (!TimerWait(timer, deadlineNanos - now - LEAD_NANOS)) AlertableSleep(WholeMilliseconds(deadlineNanos - now - LEAD_NANOS));
|
||||
now = NowNanos();
|
||||
}
|
||||
while (now < deadlineNanos) {
|
||||
|
|
@ -251,7 +284,7 @@ void PollSleepUntil(std::uint64_t deadlineNanos) {
|
|||
const std::uint64_t now = NowNanos();
|
||||
if (deadlineNanos <= now) return;
|
||||
thread_local HANDLE timer = CreateHighResolutionTimer();
|
||||
if (!TimerWait(timer, deadlineNanos - now)) Sleep(WholeMilliseconds(deadlineNanos - now));
|
||||
if (!TimerWait(timer, deadlineNanos - now)) AlertableSleep(WholeMilliseconds(deadlineNanos - now));
|
||||
#else
|
||||
SleepUntil(deadlineNanos);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP
|
||||
#define CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
namespace TimedWait {
|
||||
|
||||
bool Coarse();
|
||||
void BindThreadWaitState(std::atomic<int>* state);
|
||||
std::uint64_t NowNanos();
|
||||
std::uint64_t DeadlineNanos(std::uint64_t microseconds);
|
||||
std::uint64_t RemainingMicros(std::uint64_t deadlineNanos);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,199 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler);
|
||||
int APS5_VABI sceKernelRemoveExceptionHandler(int signum);
|
||||
int APS5_VABI sceKernelRaiseException(Pthread thread, int signum);
|
||||
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
|
||||
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
|
||||
Pthread APS5_VABI scePthreadSelf();
|
||||
int APS5_VABI sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t attr, int init, int max, void* opt);
|
||||
int APS5_VABI sceKernelDeleteSema(KernelSema sem);
|
||||
int APS5_VABI sceKernelSignalSema(KernelSema sem, int count);
|
||||
int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time);
|
||||
}
|
||||
|
||||
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016);
|
||||
static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast<int>(0x80020003);
|
||||
static constexpr int SIGUSR1 = 30;
|
||||
static constexpr int Repeats = 100;
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
static std::atomic<int> calls{0};
|
||||
static std::atomic<std::thread::id> handlerThread;
|
||||
static std::atomic<std::uint64_t> handlerRsp{0};
|
||||
static std::atomic<std::uintptr_t> handlerFrame{0};
|
||||
|
||||
static void APS5_VABI Handler(int signum, void* context) {
|
||||
Require(signum == SIGUSR1);
|
||||
std::uint64_t rsp = 0;
|
||||
std::memcpy(&rsp, static_cast<unsigned char*>(context) + 0xf8, sizeof(rsp));
|
||||
int local = 0;
|
||||
handlerFrame.store(reinterpret_cast<std::uintptr_t>(&local));
|
||||
handlerRsp.store(rsp);
|
||||
handlerThread.store(std::this_thread::get_id());
|
||||
calls.fetch_add(1);
|
||||
}
|
||||
|
||||
struct Worker {
|
||||
std::atomic<bool> started{false};
|
||||
std::atomic<bool> stop{false};
|
||||
std::thread::id id;
|
||||
KernelSema sem = nullptr;
|
||||
int waitResult = -1;
|
||||
};
|
||||
|
||||
static void* APS5_VABI Busy(void* arg) {
|
||||
auto& worker = *static_cast<Worker*>(arg);
|
||||
worker.id = std::this_thread::get_id();
|
||||
worker.started.store(true);
|
||||
volatile std::uint64_t spins = 0;
|
||||
while (!worker.stop.load()) spins = spins + 1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void* APS5_VABI Waiting(void* arg) {
|
||||
auto& worker = *static_cast<Worker*>(arg);
|
||||
worker.id = std::this_thread::get_id();
|
||||
worker.started.store(true);
|
||||
worker.waitResult = sceKernelWaitSema(worker.sem, 1, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::mutex hostLock;
|
||||
|
||||
static constexpr int HostRounds = 20;
|
||||
static std::atomic<int> hostRound{0};
|
||||
|
||||
static void* APS5_VABI HostBlocked(void* arg) {
|
||||
auto& worker = *static_cast<Worker*>(arg);
|
||||
worker.id = std::this_thread::get_id();
|
||||
for (int round = 0; round < HostRounds; ++round) {
|
||||
while (hostRound.load() != round) std::this_thread::yield();
|
||||
worker.started.store(true);
|
||||
std::lock_guard lock(hostLock);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static constexpr int LeavingRounds = 200;
|
||||
static std::atomic<int> leavingRound{0};
|
||||
|
||||
static void* APS5_VABI Leaving(void* arg) {
|
||||
auto& worker = *static_cast<Worker*>(arg);
|
||||
worker.id = std::this_thread::get_id();
|
||||
for (int round = 0; round < LeavingRounds; ++round) {
|
||||
worker.started.store(true);
|
||||
Require(sceKernelWaitSema(worker.sem, 1, nullptr) == 0);
|
||||
volatile std::uint64_t spins = 0;
|
||||
while (leavingRound.load() == round) spins = spins + 1;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::atomic<bool> finishedReturned{false};
|
||||
|
||||
static void* APS5_VABI Finished(void*) {
|
||||
finishedReturned.store(true);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void ExpectDelivery(int before, std::thread::id thread) {
|
||||
for (int attempt = 0; attempt < 5000 && calls.load() == before; ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
Require(calls.load() == before + 1);
|
||||
Require(handlerThread.load() == thread);
|
||||
Require(handlerRsp.load() != 0 && handlerFrame.load() < handlerRsp.load());
|
||||
}
|
||||
|
||||
int main() {
|
||||
Require(sceKernelRaiseException(scePthreadSelf(), 11) == SCE_KERNEL_ERROR_EINVAL);
|
||||
bool rejected = false;
|
||||
try {
|
||||
sceKernelRaiseException(scePthreadSelf(), SIGUSR1);
|
||||
} catch (const std::runtime_error&) {
|
||||
rejected = true;
|
||||
}
|
||||
Require(rejected);
|
||||
Require(sceKernelInstallExceptionHandler(SIGUSR1, reinterpret_cast<void*>(&Handler)) == 0);
|
||||
|
||||
Require(sceKernelRaiseException(scePthreadSelf(), SIGUSR1) == 0);
|
||||
Require(calls.load() == 1 && handlerThread.load() == std::this_thread::get_id());
|
||||
|
||||
Worker busy;
|
||||
Pthread busyThread = nullptr;
|
||||
Require(scePthreadCreate(&busyThread, nullptr, Busy, &busy, "busy") == 0);
|
||||
while (!busy.started.load()) std::this_thread::yield();
|
||||
for (int raised = 0; raised < Repeats; ++raised) {
|
||||
Require(sceKernelRaiseException(busyThread, SIGUSR1) == 0);
|
||||
ExpectDelivery(1 + raised, busy.id);
|
||||
}
|
||||
busy.stop.store(true);
|
||||
Require(scePthreadJoin(busyThread, nullptr) == 0);
|
||||
|
||||
Worker waiting;
|
||||
Require(sceKernelCreateSema(&waiting.sem, "raise", 0, 0, 1, nullptr) == 0);
|
||||
Pthread waitingThread = nullptr;
|
||||
Require(scePthreadCreate(&waitingThread, nullptr, Waiting, &waiting, "waiting") == 0);
|
||||
while (!waiting.started.load()) std::this_thread::yield();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
for (int raised = 0; raised < Repeats; ++raised) {
|
||||
Require(sceKernelRaiseException(waitingThread, SIGUSR1) == 0);
|
||||
ExpectDelivery(1 + Repeats + raised, waiting.id);
|
||||
}
|
||||
Require(sceKernelSignalSema(waiting.sem, 1) == 0);
|
||||
Require(scePthreadJoin(waitingThread, nullptr) == 0);
|
||||
Require(waiting.waitResult == 0);
|
||||
Require(sceKernelDeleteSema(waiting.sem) == 0);
|
||||
|
||||
Worker blocked;
|
||||
Pthread blockedThread = nullptr;
|
||||
hostLock.lock();
|
||||
Require(scePthreadCreate(&blockedThread, nullptr, HostBlocked, &blocked, "host blocked") == 0);
|
||||
for (int round = 0; round < HostRounds; ++round) {
|
||||
while (!blocked.started.load()) std::this_thread::yield();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
Require(sceKernelRaiseException(blockedThread, SIGUSR1) == 0);
|
||||
hostLock.unlock();
|
||||
ExpectDelivery(1 + 2 * Repeats + round, blocked.id);
|
||||
hostLock.lock();
|
||||
blocked.started.store(false);
|
||||
hostRound.store(round + 1);
|
||||
}
|
||||
hostLock.unlock();
|
||||
Require(scePthreadJoin(blockedThread, nullptr) == 0);
|
||||
|
||||
Worker leaving;
|
||||
Require(sceKernelCreateSema(&leaving.sem, "leaving", 0, 0, 1, nullptr) == 0);
|
||||
Pthread leavingThread = nullptr;
|
||||
Require(scePthreadCreate(&leavingThread, nullptr, Leaving, &leaving, "leaving") == 0);
|
||||
for (int round = 0; round < LeavingRounds; ++round) {
|
||||
while (!leaving.started.load()) std::this_thread::yield();
|
||||
leaving.started.store(false);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(200));
|
||||
Require(sceKernelSignalSema(leaving.sem, 1) == 0);
|
||||
for (int spin = 0; spin < round % 16 * 64; ++spin) std::this_thread::yield();
|
||||
Require(sceKernelRaiseException(leavingThread, SIGUSR1) == 0);
|
||||
ExpectDelivery(1 + 2 * Repeats + HostRounds + round, leaving.id);
|
||||
leavingRound.store(round + 1);
|
||||
}
|
||||
Require(scePthreadJoin(leavingThread, nullptr) == 0);
|
||||
Require(sceKernelDeleteSema(leaving.sem) == 0);
|
||||
|
||||
Pthread finishedThread = nullptr;
|
||||
Require(scePthreadCreate(&finishedThread, nullptr, Finished, nullptr, "finished") == 0);
|
||||
while (!finishedReturned.load()) std::this_thread::yield();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
Require(sceKernelRaiseException(finishedThread, SIGUSR1) == SCE_KERNEL_ERROR_ESRCH);
|
||||
Require(scePthreadJoin(finishedThread, nullptr) == 0);
|
||||
|
||||
Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0);
|
||||
}
|
||||
|
|
@ -211,7 +211,12 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
|
||||
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
|
||||
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
|
||||
- [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler but nothing delivers to it: a host fault still ends the process, and `sceKernelRaiseException` is not implemented. PPSA12544's PS5Util module (Unity) installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection, so delivery must run the handler on the target thread with a FreeBSD `ucontext`
|
||||
- [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler, but a host fault still ends the process: only signals raised with `sceKernelRaiseException` are delivered. Unity's PS5Util module installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) delivers on Windows only and throws on Linux
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) builds the handler's `ucontext` with FreeBSD's amd64 layout (16-byte sigmask, `mcontext` at 0x40, `mc_rsp` at 0xf8); the console's layout is not verified beyond the fields PS5Util reads
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) does not consult the target thread's signal mask
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) saves and restores only `CONTEXT_FULL | CONTEXT_FLOATING_POINT` on Windows, so the upper halves of the YMM registers are not preserved if the handler or the entry uses AVX
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) reaches a thread that waits in an alertable `TimedWait` wait through an APC. A thread blocked anywhere else, including all waits under `APS5_COARSE_TIMED_WAITS` and host waits inside the emulator, is redirected and runs the handler only when that wait returns
|
||||
- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`.
|
||||
- [Thread cancellation](../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) is not modelled: `scePthreadCancel` throws, so no cancel request is ever pending and `scePthreadTestcancel` and `pthread_testcancel` return without acting; the cancel state and type set by `scePthreadSetcancelstate` and `scePthreadSetcanceltype` are stored and not used
|
||||
- [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws.
|
||||
|
|
|
|||
Loading…
Reference in New Issue