From 2c0f986c5274a34902a1e3e10c6fc578d0b85989 Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:33:00 +0100 Subject: [PATCH] core/libraries: HLE fiber reimplementation (#1836) --- CMakeLists.txt | 5 +- src/core/libraries/fiber/fiber.cpp | 509 +++++++++++++++------- src/core/libraries/fiber/fiber.h | 116 +++-- src/core/libraries/fiber/fiber_context.s | 121 +++++ src/core/libraries/kernel/threads/tcb.cpp | 1 + src/core/tls.h | 5 + 6 files changed, 564 insertions(+), 193 deletions(-) create mode 100644 src/core/libraries/fiber/fiber_context.s diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f5832d2..dac3b19a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() -project(shadPS4) +project(shadPS4 CXX C ASM) # Forcing PIE makes sure that the base address is high enough so that it doesn't clash with the PS4 memory. if(UNIX AND NOT APPLE) @@ -392,7 +392,8 @@ set(USBD_LIB src/core/libraries/usbd/usbd.cpp src/core/libraries/usbd/usbd.h ) -set(FIBER_LIB src/core/libraries/fiber/fiber.cpp +set(FIBER_LIB src/core/libraries/fiber/fiber_context.s + src/core/libraries/fiber/fiber.cpp src/core/libraries/fiber/fiber.h src/core/libraries/fiber/fiber_error.h ) diff --git a/src/core/libraries/fiber/fiber.cpp b/src/core/libraries/fiber/fiber.cpp index c28d8d73..27809984 100644 --- a/src/core/libraries/fiber/fiber.cpp +++ b/src/core/libraries/fiber/fiber.cpp @@ -8,253 +8,455 @@ #include "core/libraries/libs.h" #include "core/tls.h" -#ifdef _WIN64 -#include -#endif - namespace Libraries::Fiber { -static constexpr u64 kFiberSignature = 0x054ad954; +static constexpr u32 kFiberSignature0 = 0xdef1649c; +static constexpr u32 kFiberSignature1 = 0xb37592a0; +static constexpr u32 kFiberOptSignature = 0xbb40e64d; +static constexpr u64 kFiberStackSignature = 0x7149f2ca7149f2ca; +static constexpr u64 kFiberStackSizeCheck = 0xdeadbeefdeadbeef; -thread_local SceFiber* gCurrentFiber = nullptr; -thread_local void* gFiberThread = nullptr; +static std::atomic context_size_check = false; -void FiberEntry(void* param) { - SceFiber* fiber = static_cast(param); - u64 argRun = 0; - u64 argRet = 0; - - gCurrentFiber = fiber; - - if (fiber->pArgRun != nullptr) { - argRun = *fiber->pArgRun; - } - - Core::ExecuteGuest(fiber->entry, fiber->argOnInitialize, argRun); - UNREACHABLE(); +OrbisFiberContext* GetFiberContext() { + return Core::GetTcbBase()->tcb_fiber; } -s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry, - u64 argOnInitialize, void* addrContext, u64 sizeContext, - const SceFiberOptParam* optParam) { - LOG_INFO(Lib_Fiber, "called: name = {}", name); +extern "C" s32 PS4_SYSV_ABI _sceFiberSetJmp(OrbisFiberContext* ctx) asm("_sceFiberSetJmp"); +extern "C" s32 PS4_SYSV_ABI _sceFiberLongJmp(OrbisFiberContext* ctx) asm("_sceFiberLongJmp"); +extern "C" void PS4_SYSV_ABI _sceFiberSwitchEntry(OrbisFiberData* data, + bool set_fpu) asm("_sceFiberSwitchEntry"); +extern "C" void PS4_SYSV_ABI _sceFiberForceQuit(u64 ret) asm("_sceFiberForceQuit"); +extern "C" void PS4_SYSV_ABI _sceFiberForceQuit(u64 ret) { + OrbisFiberContext* g_ctx = GetFiberContext(); + g_ctx->return_val = ret; + _sceFiberLongJmp(g_ctx); +} + +void PS4_SYSV_ABI _sceFiberCheckStackOverflow(OrbisFiberContext* ctx) { + u64* stack_base = reinterpret_cast(ctx->current_fiber->addr_context); + if (stack_base && *stack_base != kFiberStackSignature) { + UNREACHABLE_MSG("Stack overflow detected in fiber."); + } +} + +void PS4_SYSV_ABI _sceFiberSwitchToFiber(OrbisFiber* fiber, u64 arg_on_run_to, + OrbisFiberContext* ctx) { + OrbisFiberContext* fiber_ctx = fiber->context; + if (fiber_ctx) { + ctx->arg_on_run_to = arg_on_run_to; + _sceFiberLongJmp(fiber_ctx); + __builtin_trap(); + } + + OrbisFiberData data{}; + if (ctx->prev_fiber) { + OrbisFiber* prev_fiber = ctx->prev_fiber; + ctx->prev_fiber = nullptr; + data.state = reinterpret_cast(&prev_fiber->state); + } else { + data.state = nullptr; + } + + data.entry = fiber->entry; + data.arg_on_initialize = fiber->arg_on_initialize; + data.arg_on_run_to = arg_on_run_to; + data.stack_addr = + reinterpret_cast(reinterpret_cast(fiber->addr_context) + fiber->size_context); + if (fiber->flags & FiberFlags::SetFpuRegs) { + data.fpucw = 0x037f; + data.mxcsr = 0x9fc0; + _sceFiberSwitchEntry(&data, true); + } else { + _sceFiberSwitchEntry(&data, false); + } + + __builtin_trap(); +} + +void PS4_SYSV_ABI _sceFiberSwitch(OrbisFiber* cur_fiber, OrbisFiber* fiber, u64 arg_on_run_to, + OrbisFiberContext* ctx) { + ctx->prev_fiber = cur_fiber; + ctx->current_fiber = fiber; + + if (fiber->addr_context == nullptr) { + ctx->prev_fiber = nullptr; + + OrbisFiberData data{}; + data.entry = fiber->entry; + data.arg_on_initialize = fiber->arg_on_initialize; + data.arg_on_run_to = arg_on_run_to; + data.stack_addr = reinterpret_cast(ctx->rsp & ~15); + data.state = reinterpret_cast(&cur_fiber->state); + + if (fiber->flags & FiberFlags::SetFpuRegs) { + data.fpucw = 0x037f; + data.mxcsr = 0x9fc0; + _sceFiberSwitchEntry(&data, true); + } else { + _sceFiberSwitchEntry(&data, false); + } + + __builtin_trap(); + } + + _sceFiberSwitchToFiber(fiber, arg_on_run_to, ctx); + __builtin_trap(); +} + +void PS4_SYSV_ABI _sceFiberTerminate(OrbisFiber* fiber, u64 arg_on_return, OrbisFiberContext* ctx) { + ctx->arg_on_return = arg_on_return; + _sceFiberLongJmp(ctx); + __builtin_trap(); +} + +s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFiberEntry entry, + u64 arg_on_initialize, void* addr_context, u64 size_context, + const OrbisFiberOptParam* opt_param, u32 build_ver) { if (!fiber || !name || !entry) { return ORBIS_FIBER_ERROR_NULL; } + if ((u64)fiber & 7 || (u64)addr_context & 15) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (opt_param && (u64)opt_param & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (size_context && size_context < ORBIS_FIBER_CONTEXT_MINIMUM_SIZE) { + return ORBIS_FIBER_ERROR_RANGE; + } + if (size_context & 15) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (!addr_context && size_context) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (addr_context && !size_context) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (opt_param && opt_param->magic != kFiberOptSignature) { + return ORBIS_FIBER_ERROR_INVALID; + } - fiber->signature = kFiberSignature; + u32 flags = FiberFlags::None; + if (build_ver >= 0x3500000) { + flags |= FiberFlags::SetFpuRegs; + } + if (context_size_check) { + flags |= FiberFlags::ContextSizeCheck; + } - fiber->entry = entry; - fiber->argOnInitialize = argOnInitialize; - - fiber->argRun = 0; - fiber->pArgRun = &fiber->argRun; - fiber->argReturn = 0; - fiber->pArgReturn = &fiber->argReturn; - - fiber->sizeContext = sizeContext; - - fiber->state = FiberState::Init; -#ifdef _WIN64 - fiber->handle = CreateFiber(sizeContext, FiberEntry, fiber); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); - return ORBIS_OK; -} + fiber->entry = entry; + fiber->arg_on_initialize = arg_on_initialize; + fiber->addr_context = addr_context; + fiber->size_context = size_context; + fiber->context = nullptr; + fiber->flags = flags; -s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam) { - LOG_ERROR(Lib_Fiber, "called"); - - if (!optParam) { - return ORBIS_FIBER_ERROR_NULL; + /* + A low stack area is problematic, as we can easily + cause a stack overflow with our HLE. + */ + if (size_context && size_context <= 4096) { + LOG_WARNING(Lib_Fiber, "Fiber initialized with small stack area."); } + fiber->magic_start = kFiberSignature0; + fiber->magic_end = kFiberSignature1; + + if (addr_context != nullptr) { + fiber->context_start = addr_context; + fiber->context_end = + reinterpret_cast(reinterpret_cast(addr_context) + size_context); + + /* Apply signature to start of stack */ + *(u64*)addr_context = kFiberStackSignature; + + if (flags & FiberFlags::ContextSizeCheck) { + u64* stack_start = reinterpret_cast(fiber->context_start); + u64* stack_end = reinterpret_cast(fiber->context_end); + + u64* stack_ptr = stack_start + 1; + while (stack_ptr < stack_end) { + *stack_ptr++ = kFiberStackSizeCheck; + } + } + } + + fiber->state = FiberState::Idle; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber) { - LOG_TRACE(Lib_Fiber, "called"); +s32 PS4_SYSV_ABI sceFiberOptParamInitialize(OrbisFiberOptParam* opt_param) { + if (!opt_param) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)opt_param & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + opt_param->magic = kFiberOptSignature; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberFinalize(OrbisFiber* fiber) { if (!fiber) { return ORBIS_FIBER_ERROR_NULL; } - if ((u64)fiber % 8 != 0) { + if ((u64)fiber & 7) { return ORBIS_FIBER_ERROR_ALIGNMENT; } - if (fiber->signature != kFiberSignature) { + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { return ORBIS_FIBER_ERROR_INVALID; } - if (fiber->state != FiberState::Run) { + + FiberState expected = FiberState::Idle; + if (!fiber->state.compare_exchange_strong(expected, FiberState::Terminated)) { return ORBIS_FIBER_ERROR_STATE; } - fiber->signature = 0; - fiber->state = FiberState::None; - -#ifdef _WIN64 - DeleteFiber(fiber->handle); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn) { - LOG_TRACE(Lib_Fiber, "called"); - +s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_return) { if (!fiber) { return ORBIS_FIBER_ERROR_NULL; } - if ((u64)fiber % 8 != 0) { + if ((u64)fiber & 7) { return ORBIS_FIBER_ERROR_ALIGNMENT; } - if (fiber->signature != kFiberSignature) { + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { return ORBIS_FIBER_ERROR_INVALID; } - if (fiber->state == FiberState::Run) { + + Core::Tcb* tcb = Core::GetTcbBase(); + if (tcb->tcb_fiber) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + FiberState expected = FiberState::Idle; + if (!fiber->state.compare_exchange_strong(expected, FiberState::Run)) { return ORBIS_FIBER_ERROR_STATE; } - if (gFiberThread == nullptr) { -#ifdef _WIN64 - gFiberThread = ConvertThreadToFiber(nullptr); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif + OrbisFiberContext ctx{}; + ctx.current_fiber = fiber; + ctx.prev_fiber = nullptr; + ctx.return_val = 0; + + tcb->tcb_fiber = &ctx; + + s32 jmp = _sceFiberSetJmp(&ctx); + if (!jmp) { + if (fiber->addr_context) { + _sceFiberSwitchToFiber(fiber, arg_on_run_to, &ctx); + __builtin_trap(); + } + + OrbisFiberData data{}; + data.entry = fiber->entry; + data.arg_on_initialize = fiber->arg_on_initialize; + data.arg_on_run_to = arg_on_run_to; + data.stack_addr = reinterpret_cast(ctx.rsp & ~15); + data.state = nullptr; + if (fiber->flags & FiberFlags::SetFpuRegs) { + data.fpucw = 0x037f; + data.mxcsr = 0x9fc0; + _sceFiberSwitchEntry(&data, true); + } else { + _sceFiberSwitchEntry(&data, false); + } } - gCurrentFiber = fiber; + OrbisFiber* cur_fiber = ctx.current_fiber; + ctx.current_fiber = nullptr; + cur_fiber->state = FiberState::Idle; - if (fiber->pArgRun != nullptr) { - *fiber->pArgRun = argOnRunTo; + if (ctx.return_val != 0) { + /* Fiber entry returned! This should never happen. */ + UNREACHABLE_MSG("Fiber entry function returned."); } - fiber->pArgReturn = argOnReturn; - fiber->state = FiberState::Run; -#ifdef _WIN64 - SwitchToFiber(fiber->handle); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif + if (arg_on_return) { + *arg_on_return = ctx.arg_on_return; + } + + tcb->tcb_fiber = nullptr; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun) { - LOG_TRACE(Lib_Fiber, "called"); - +s32 PS4_SYSV_ABI sceFiberSwitch(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_run) { if (!fiber) { return ORBIS_FIBER_ERROR_NULL; } - if ((u64)fiber % 8 != 0) { + if ((u64)fiber & 7) { return ORBIS_FIBER_ERROR_ALIGNMENT; } - if (fiber->signature != kFiberSignature) { + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { return ORBIS_FIBER_ERROR_INVALID; } - if (gCurrentFiber == nullptr) { + + OrbisFiberContext* g_ctx = GetFiberContext(); + if (!g_ctx) { return ORBIS_FIBER_ERROR_PERMISSION; } - if (fiber->state == FiberState::Run) { + + FiberState expected = FiberState::Idle; + if (!fiber->state.compare_exchange_strong(expected, FiberState::Run)) { return ORBIS_FIBER_ERROR_STATE; } - gCurrentFiber->state = FiberState::Suspend; + OrbisFiber* cur_fiber = g_ctx->current_fiber; + if (cur_fiber->addr_context == nullptr) { + _sceFiberSwitch(cur_fiber, fiber, arg_on_run_to, g_ctx); + __builtin_trap(); + } - // TODO: argOnRun + OrbisFiberContext ctx{}; + s32 jmp = _sceFiberSetJmp(&ctx); + if (!jmp) { + cur_fiber->context = &ctx; + _sceFiberCheckStackOverflow(g_ctx); + _sceFiberSwitch(cur_fiber, fiber, arg_on_run_to, g_ctx); + __builtin_trap(); + } - *fiber->pArgRun = argOnRunTo; - fiber->state = FiberState::Run; + g_ctx = GetFiberContext(); + if (g_ctx->prev_fiber) { + g_ctx->prev_fiber->state = FiberState::Idle; + g_ctx->prev_fiber = nullptr; + } + + if (arg_on_run) { + *arg_on_run = g_ctx->arg_on_run_to; + } - gCurrentFiber = fiber; -#ifdef _WIN64 - SwitchToFiber(fiber->handle); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber) { - LOG_TRACE(Lib_Fiber, "called"); - - if (!fiber || !gCurrentFiber) { - return ORBIS_FIBER_ERROR_NULL; - } - if (gCurrentFiber->signature != kFiberSignature) { - return ORBIS_FIBER_ERROR_PERMISSION; - } - - *fiber = gCurrentFiber; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun) { - LOG_TRACE(Lib_Fiber, "called"); - - if (gCurrentFiber->signature != kFiberSignature) { - return ORBIS_FIBER_ERROR_PERMISSION; - } - - if (gCurrentFiber->pArgReturn != nullptr) { - *gCurrentFiber->pArgReturn = argOnReturn; - } - - // TODO: argOnRun - gCurrentFiber->state = FiberState::Suspend; - gCurrentFiber = nullptr; -#ifdef _WIN64 - SwitchToFiber(gFiberThread); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo) { - LOG_INFO(Lib_Fiber, "called"); - - if (!fiber || !fiberInfo) { +s32 PS4_SYSV_ABI sceFiberGetSelf(OrbisFiber** fiber) { + if (!fiber) { return ORBIS_FIBER_ERROR_NULL; } - fiberInfo->entry = fiber->entry; - fiberInfo->argOnInitialize = fiber->argOnInitialize; - fiberInfo->addrContext = nullptr; - fiberInfo->sizeContext = fiber->sizeContext; - fiberInfo->sizeContextMargin = 0; + OrbisFiberContext* g_ctx = GetFiberContext(); + if (!g_ctx) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + *fiber = g_ctx->current_fiber; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 arg_on_return, u64* arg_on_run) { + OrbisFiberContext* g_ctx = GetFiberContext(); + if (!g_ctx) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + OrbisFiber* cur_fiber = g_ctx->current_fiber; + if (cur_fiber->addr_context) { + OrbisFiberContext ctx{}; + s32 jmp = _sceFiberSetJmp(&ctx); + if (jmp) { + g_ctx = GetFiberContext(); + if (g_ctx->prev_fiber) { + g_ctx->prev_fiber->state = FiberState::Idle; + g_ctx->prev_fiber = nullptr; + } + if (arg_on_run) { + *arg_on_run = g_ctx->arg_on_run_to; + } + return ORBIS_OK; + } + + cur_fiber->context = &ctx; + _sceFiberCheckStackOverflow(g_ctx); + } + + _sceFiberTerminate(cur_fiber, arg_on_return, g_ctx); + __builtin_trap(); +} + +s32 PS4_SYSV_ABI sceFiberGetInfo(OrbisFiber* fiber, OrbisFiberInfo* fiber_info) { + if (!fiber || !fiber_info) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber & 7 || (u64)fiber_info & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber_info->size != sizeof(OrbisFiberInfo)) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { + return ORBIS_FIBER_ERROR_INVALID; + } + + fiber_info->entry = fiber->entry; + fiber_info->arg_on_initialize = fiber->arg_on_initialize; + fiber_info->addr_context = fiber->addr_context; + fiber_info->size_context = fiber->size_context; + strncpy(fiber_info->name, fiber->name, ORBIS_FIBER_MAX_NAME_LENGTH); + + fiber_info->size_context_margin = -1; + if (fiber->flags & FiberFlags::ContextSizeCheck && fiber->addr_context != nullptr) { + u64 stack_margin = 0; + u64* stack_start = reinterpret_cast(fiber->context_start); + u64* stack_end = reinterpret_cast(fiber->context_end); + + if (*stack_start == kFiberStackSignature) { + u64* stack_ptr = stack_start + 1; + while (stack_ptr < stack_end) { + if (*stack_ptr == kFiberStackSizeCheck) { + stack_ptr++; + } + } + + stack_margin = + reinterpret_cast(stack_ptr) - reinterpret_cast(stack_start + 1); + } + + fiber_info->size_context_margin = stack_margin; + } - strncpy(fiberInfo->name, fiber->name, ORBIS_FIBER_MAX_NAME_LENGTH); return ORBIS_OK; } s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags) { - LOG_ERROR(Lib_Fiber, "called"); - if (flags != 0) { return ORBIS_FIBER_ERROR_INVALID; } + u32 expected = 0; + if (!context_size_check.compare_exchange_strong(expected, 1u)) { + return ORBIS_FIBER_ERROR_STATE; + } + return ORBIS_OK; } s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck() { - LOG_ERROR(Lib_Fiber, "called"); + u32 expected = 1; + if (!context_size_check.compare_exchange_strong(expected, 0u)) { + return ORBIS_FIBER_ERROR_STATE; + } + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name) { - LOG_INFO(Lib_Fiber, "called, name = {}", name); - +s32 PS4_SYSV_ABI sceFiberRename(OrbisFiber* fiber, const char* name) { if (!fiber || !name) { return ORBIS_FIBER_ERROR_NULL; } - if ((u64)fiber % 8 != 0) { + if ((u64)fiber & 7) { return ORBIS_FIBER_ERROR_ALIGNMENT; } + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { + return ORBIS_FIBER_ERROR_INVALID; + } strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); return ORBIS_OK; @@ -262,6 +464,7 @@ s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name) { void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("hVYD7Ou2pCQ", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize); + LIB_FUNCTION("7+OJIpko9RY", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize); LIB_FUNCTION("asjUJJ+aa8s", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberOptParamInitialize); LIB_FUNCTION("JeNX5F-NzQU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberFinalize); diff --git a/src/core/libraries/fiber/fiber.h b/src/core/libraries/fiber/fiber.h index 00099f93..325522fc 100644 --- a/src/core/libraries/fiber/fiber.h +++ b/src/core/libraries/fiber/fiber.h @@ -6,73 +6,113 @@ #include "common/assert.h" #include "common/types.h" +#include + namespace Core::Loader { class SymbolsResolver; } namespace Libraries::Fiber { #define ORBIS_FIBER_MAX_NAME_LENGTH (31) +#define ORBIS_FIBER_CONTEXT_MINIMUM_SIZE (512) -typedef void PS4_SYSV_ABI (*SceFiberEntry)(u64 argOnInitialize, u64 argOnRun); +typedef void PS4_SYSV_ABI (*OrbisFiberEntry)(u64 arg_on_initialize, u64 arg_on_run); enum FiberState : u32 { - None = 0u, - Init = 1u, - Run = 2u, - Suspend = 3u, + Run = 1u, + Idle = 2u, + Terminated = 3u, }; -struct SceFiber { - u64 signature; - FiberState state; - SceFiberEntry entry; - u64 argOnInitialize; - u64 argRun; - u64* pArgRun; - u64 argReturn; - u64* pArgReturn; - u64 sizeContext; - char name[ORBIS_FIBER_MAX_NAME_LENGTH]; - void* handle; +enum FiberFlags : u32 { + None = 0x0, + NoUlobjmgr = 0x1, + ContextSizeCheck = 0x10, + SetFpuRegs = 0x100, }; -static_assert(sizeof(SceFiber) <= 256); -struct SceFiberInfo { - u64 size; - SceFiberEntry entry; - u64 argOnInitialize; - void* addrContext; - u64 sizeContext; +struct OrbisFiber; + +struct OrbisFiberContext { + struct { + u64 rax, rcx, rdx, rbx, rsp, rbp, r8, r9, r10, r11, r12, r13, r14, r15; + u16 fpucw; + u32 mxcsr; + }; + OrbisFiber* current_fiber; + OrbisFiber* prev_fiber; + u64 arg_on_run_to; + u64 arg_on_return; + u64 return_val; +}; + +struct OrbisFiberData { + OrbisFiberEntry entry; + u64 arg_on_initialize; + u64 arg_on_run_to; + void* stack_addr; + u32* state; + u16 fpucw; + s8 pad[2]; + u32 mxcsr; +}; + +struct OrbisFiber { + u32 magic_start; + std::atomic state; + OrbisFiberEntry entry; + u64 arg_on_initialize; + void* addr_context; + u64 size_context; char name[ORBIS_FIBER_MAX_NAME_LENGTH + 1]; - u64 sizeContextMargin; + OrbisFiberContext* context; + u32 flags; + void* context_start; + void* context_end; + u32 magic_end; }; -static_assert(sizeof(SceFiberInfo) <= 128); +static_assert(sizeof(OrbisFiber) <= 256); -using SceFiberOptParam = void*; +struct OrbisFiberInfo { + u64 size; + OrbisFiberEntry entry; + u64 arg_on_initialize; + void* addr_context; + u64 size_context; + char name[ORBIS_FIBER_MAX_NAME_LENGTH + 1]; + u64 size_context_margin; + u8 pad[48]; +}; +static_assert(sizeof(OrbisFiberInfo) == 128); -s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry, - u64 argOnInitialize, void* addrContext, u64 sizeContext, - const SceFiberOptParam* optParam); +struct OrbisFiberOptParam { + u32 magic; +}; +static_assert(sizeof(OrbisFiberOptParam) <= 128); -s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam); +s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFiberEntry entry, + u64 arg_on_initialize, void* addr_context, u64 size_context, + const OrbisFiberOptParam* opt_param, u32 build_version); -s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber); +s32 PS4_SYSV_ABI sceFiberOptParamInitialize(OrbisFiberOptParam* opt_param); -s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn); +s32 PS4_SYSV_ABI sceFiberFinalize(OrbisFiber* fiber); -s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun); +s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_return); -s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber); +s32 PS4_SYSV_ABI sceFiberSwitch(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_run); -s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun); +s32 PS4_SYSV_ABI sceFiberGetSelf(OrbisFiber** fiber); -s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo); +s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 arg_on_return, u64* arg_on_run); + +s32 PS4_SYSV_ABI sceFiberGetInfo(OrbisFiber* fiber, OrbisFiberInfo* fiber_info); s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags); s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck(void); -s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name); +s32 PS4_SYSV_ABI sceFiberRename(OrbisFiber* fiber, const char* name); void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Fiber \ No newline at end of file diff --git a/src/core/libraries/fiber/fiber_context.s b/src/core/libraries/fiber/fiber_context.s new file mode 100644 index 00000000..44e51d38 --- /dev/null +++ b/src/core/libraries/fiber/fiber_context.s @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +.global _sceFiberSetJmp +_sceFiberSetJmp: + movq %rax, 0x0(%rdi) + + movq (%rsp), %rdx + movq %rdx, 0x10(%rdi) + + movq %rcx, 0x08(%rdi) + movq %rbx, 0x18(%rdi) + movq %rsp, 0x20(%rdi) + movq %rbp, 0x28(%rdi) + + movq %r8, 0x30(%rdi) + movq %r9, 0x38(%rdi) + movq %r10, 0x40(%rdi) + movq %r11, 0x48(%rdi) + movq %r12, 0x50(%rdi) + movq %r13, 0x58(%rdi) + movq %r14, 0x60(%rdi) + movq %r15, 0x68(%rdi) + + fnstcw 0x70(%rdi) + stmxcsr 0x72(%rdi) + + xor %eax, %eax + ret + +.global _sceFiberLongJmp +_sceFiberLongJmp: + # MXCSR = (MXCSR & 0x3f) ^ (ctx->mxcsr & ~0x3f) + stmxcsr -0x4(%rsp) + movl 0x72(%rdi), %eax + andl $0xffffffc0, %eax + movl -0x4(%rsp), %ecx + andl $0x3f, %ecx + xorl %eax, %ecx + movl %ecx, -0x4(%rsp) + ldmxcsr -0x4(%rsp) + + movq 0x00(%rdi), %rax + movq 0x08(%rdi), %rcx + movq 0x10(%rdi), %rdx + movq 0x18(%rdi), %rbx + movq 0x20(%rdi), %rsp + movq 0x28(%rdi), %rbp + + movq 0x30(%rdi), %r8 + movq 0x38(%rdi), %r9 + movq 0x40(%rdi), %r10 + movq 0x48(%rdi), %r11 + movq 0x50(%rdi), %r12 + movq 0x58(%rdi), %r13 + movq 0x60(%rdi), %r14 + movq 0x68(%rdi), %r15 + + fldcw 0x70(%rdi) + + # Make the jump and return 1 + movq %rdx, 0x00(%rsp) + movl $0x1, %eax + ret + +.global _sceFiberSwitchEntry +_sceFiberSwitchEntry: + mov %rdi, %r11 + + # Set stack address to provided stack + movq 0x18(%r11), %rsp + xorl %ebp, %ebp + + movq 0x20(%r11), %r10 # data->state + + # Set previous fiber state to Idle + test %r10, %r10 + jz .clear_regs + movl $2, (%r10) + +.clear_regs: + test %esi, %esi + jz .skip_fpu_regs + + ldmxcsr 0x2c(%r11) + fldcw 0x28(%r11) + +.skip_fpu_regs: + movq 0x08(%r11), %rdi # data->arg_on_initialize + movq 0x10(%r11), %rsi # data->arg_on_run_to + movq 0x00(%r11), %r11 # data->entry + + xorl %eax, %eax + xorl %ebx, %ebx + xorl %ecx, %ecx + xorl %edx, %edx + xorq %r8, %r8 + xorq %r9, %r9 + xorq %r10, %r10 + xorq %r12, %r12 + xorq %r13, %r13 + xorq %r14, %r14 + xorq %r15, %r15 + pxor %mm0, %mm0 + pxor %mm1, %mm1 + pxor %mm2, %mm2 + pxor %mm3, %mm3 + pxor %mm4, %mm4 + pxor %mm5, %mm5 + pxor %mm6, %mm6 + pxor %mm7, %mm7 + emms + vzeroall + + # Call the fiber's entry function: entry(arg_on_initialize, arg_on_run_to) + call *%r11 + + # Fiber returned, not good + movl $1, %edi + call _sceFiberForceQuit + ret \ No newline at end of file diff --git a/src/core/libraries/kernel/threads/tcb.cpp b/src/core/libraries/kernel/threads/tcb.cpp index e5a15821..e3e8b90c 100644 --- a/src/core/libraries/kernel/threads/tcb.cpp +++ b/src/core/libraries/kernel/threads/tcb.cpp @@ -53,6 +53,7 @@ Core::Tcb* TcbCtor(Pthread* thread, int initial) { if (tcb) { tcb->tcb_thread = thread; + tcb->tcb_fiber = nullptr; } return tcb; } diff --git a/src/core/tls.h b/src/core/tls.h index 4df9e4ac..6edd6a29 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -9,6 +9,10 @@ namespace Xbyak { class CodeGenerator; } +namespace Libraries::Fiber { +struct OrbisFiberContext; +} + namespace Core { union DtvEntry { @@ -20,6 +24,7 @@ struct Tcb { Tcb* tcb_self; DtvEntry* tcb_dtv; void* tcb_thread; + ::Libraries::Fiber::OrbisFiberContext* tcb_fiber; }; #ifdef _WIN32