#include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #ifdef _WIN32 #include #endif using Entry = void (APS5_VABI*)(std::uint64_t, std::uint64_t); extern "C" { std::int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject*, const char*, Entry, std::uint64_t, void*, std::uint64_t, const void*, std::uint32_t); std::int32_t APS5_VABI sceFiberFinalize(FiberObject*); std::int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject*, std::uint64_t, std::uint64_t*); std::int32_t APS5_VABI sceFiberSwitch(FiberObject*, std::uint64_t, std::uint64_t*); std::int32_t APS5_VABI sceFiberReturnToThread(std::uint64_t, std::uint64_t*); std::int32_t APS5_VABI sceFiberGetSelf(FiberObject**); std::int32_t APS5_VABI sceFiberGetInfo(FiberObject*, FiberInfo*); std::int32_t APS5_VABI sceFiberGetThreadFramePointerAddress(std::uint64_t*); std::int32_t APS5_VABI _sceFiberGetThreadFramePointerAddress(std::uint64_t*); std::int32_t APS5_VABI sceFiberStartContextSizeCheck(std::uint32_t); std::int32_t APS5_VABI sceFiberStopContextSizeCheck(void); } static void Require(bool value, const char* what) { if (!value) { std::fprintf(stderr, "Fiber check failed: %s\n", what); std::abort(); } } namespace { constexpr std::int32_t FiberErrorNull = static_cast(0x80590001); constexpr std::int32_t FiberErrorInvalid = static_cast(0x80590004); constexpr std::int32_t FiberErrorPermission = static_cast(0x80590005); constexpr std::int32_t FiberErrorState = static_cast(0x80590006); alignas(16) std::array g_firstContext; alignas(16) std::array g_secondContext; struct alignas(8) FiberStorage { unsigned char bytes[0x100]; }; FiberStorage g_firstStorage; FiberStorage g_checkedStorage; alignas(16) std::array g_checkedContext; std::uint64_t g_threadFramePointer = 0; FiberStorage g_secondStorage; auto* const g_first = reinterpret_cast(&g_firstStorage); auto* const g_second = reinterpret_cast(&g_secondStorage); void APS5_VABI FirstEntry(std::uint64_t argOnInitialize, std::uint64_t argOnRun) { Require(argOnInitialize == 11 && argOnRun == 100, "first fiber arguments"); FiberObject* self = nullptr; Require(sceFiberGetSelf(&self) == 0 && self == g_first, "first fiber self"); std::uint64_t framePointer = 0; Require(sceFiberGetThreadFramePointerAddress(nullptr) == FiberErrorNull, "frame pointer null"); Require(sceFiberGetThreadFramePointerAddress(&framePointer) == 0 && framePointer == g_threadFramePointer, "thread frame pointer"); framePointer = 0; Require(_sceFiberGetThreadFramePointerAddress(nullptr) == FiberErrorNull, "underscore frame pointer null"); Require(_sceFiberGetThreadFramePointerAddress(&framePointer) == 0 && framePointer == g_threadFramePointer, "underscore thread frame pointer"); volatile double carried = 1.5; std::uint64_t received = 0; Require(sceFiberSwitch(g_second, 200, &received) == 0, "switch to second fiber"); Require(received == 300 && carried == 1.5, "switch back to first fiber"); for (;;) { Require(sceFiberReturnToThread(received + 1, &received) == 0, "first fiber return"); } } void APS5_VABI SecondEntry(std::uint64_t argOnInitialize, std::uint64_t argOnRun) { Require(argOnInitialize == 22 && argOnRun == 200, "second fiber arguments"); Require(sceFiberSwitch(g_second, 0, nullptr) == FiberErrorState, "switch to self"); _mm_setcsr(_mm_getcsr() | 0x8000u); std::uint64_t received = 0; Require(sceFiberReturnToThread(250, &received) == 0 && received == 260, "second fiber resumed on another thread"); Require((_mm_getcsr() & 0x8000u) != 0, "second fiber keeps its MXCSR"); Require(sceFiberSwitch(g_first, 300, nullptr) == 0, "switch to first fiber"); Require(false, "second fiber resumed after its last switch"); } void APS5_VABI CheckedEntry(std::uint64_t, std::uint64_t) { volatile unsigned char used[4096]; for (auto& byte : used) byte = 1; std::uint64_t received = 0; for (;;) sceFiberReturnToThread(0, &received); } constexpr std::size_t SmallContextSize = 2048; constexpr unsigned char SmallGuardFill = 0xa5; alignas(16) std::array g_smallArea; FiberStorage g_smallStorage; void APS5_VABI SmallEntry(std::uint64_t, std::uint64_t) { std::uint64_t received = 0; for (;;) sceFiberReturnToThread(0, &received); } void RunSmallContextFirst() { auto* small = reinterpret_cast(&g_smallStorage); auto* context = g_smallArea.data() + g_smallArea.size() - SmallContextSize; g_smallArea.fill(SmallGuardFill); Require(sceFiberReturnToThread(0, nullptr) == FiberErrorPermission, "return to thread outside a fiber"); Require(_sceFiberInitializeImpl_nid_postfix(small, "small", SmallEntry, 0, context, SmallContextSize, nullptr, 0) == 0, "initialize small"); Require(sceFiberRun_nid_postfix(small, 0, nullptr) == 0, "first fiber run of the process on a 2 KiB context"); Require(sceFiberRun_nid_postfix(small, 0, nullptr) == 0, "resume on a 2 KiB context"); Require(sceFiberFinalize(small) == 0, "finalize small"); for (auto* byte = g_smallArea.data(); byte != context; ++byte) { Require(*byte == SmallGuardFill, "nothing is written below a 2 KiB context"); } } constexpr std::size_t HostCallContextSize = 16 * 1024; constexpr std::size_t HostCallStack = 256 * 1024; constexpr std::size_t HostCallFrameSize = 4096; constexpr unsigned char HostCallGuardFill = 0x5c; alignas(16) std::array g_hostCallArea; FiberStorage g_hostCallStorage; std::size_t DescendHostStack(std::size_t remaining); std::size_t (*volatile g_descendHostStack)(std::size_t) = DescendHostStack; std::size_t DescendHostStack(std::size_t remaining) { volatile unsigned char frame[HostCallFrameSize]; frame[0] = 0x11; frame[HostCallFrameSize - 1] = 0x22; const std::size_t frames = remaining > HostCallFrameSize ? g_descendHostStack(remaining - HostCallFrameSize) : 0; Require(frame[0] == 0x11 && frame[HostCallFrameSize - 1] == 0x22, "host call frame intact"); return frames + 1; } #ifdef _WIN32 void CheckWindowsHostStack() { const auto* tib = reinterpret_cast(NtCurrentTeb()); const auto low = reinterpret_cast(tib->StackLimit); const auto high = reinterpret_cast(tib->StackBase); const volatile unsigned char marker = 0; const auto frame = reinterpret_cast(&marker); Require(frame > low && frame < high && high - low >= HostCallContextSize + HostCallStack, "the thread's stack bounds are the fiber's host stack"); } #endif void APS5_VABI HostCallEntry(std::uint64_t, std::uint64_t) { #ifdef _WIN32 CheckWindowsHostStack(); #endif Require(g_descendHostStack(HostCallStack) == HostCallStack / HostCallFrameSize, "host call depth"); std::uint64_t received = 0; for (;;) sceFiberReturnToThread(0, &received); } void RunHostCallOnSmallContext() { auto* fiber = reinterpret_cast(&g_hostCallStorage); auto* context = g_hostCallArea.data() + g_hostCallArea.size() - HostCallContextSize; g_hostCallArea.fill(HostCallGuardFill); Require(_sceFiberInitializeImpl_nid_postfix(fiber, "host call", HostCallEntry, 0, context, HostCallContextSize, nullptr, 0) == 0, "initialize host call fiber"); Require(sceFiberRun_nid_postfix(fiber, 0, nullptr) == 0, "run a 256 KiB host call from a 16 KiB fiber"); Require(sceFiberFinalize(fiber) == 0, "finalize host call fiber"); for (auto* byte = g_hostCallArea.data(); byte != context; ++byte) { Require(*byte == HostCallGuardFill, "a host call from a 16 KiB fiber writes nothing below its context"); } } [[gnu::noinline]] std::int32_t RunFirst(std::uint64_t* returned) { g_threadFramePointer = reinterpret_cast(__builtin_frame_address(0)); const std::int32_t result = sceFiberRun_nid_postfix(g_first, 100, returned); asm volatile("" ::: "memory"); return result; } } int main() { RunSmallContextFirst(); FiberObject* self = nullptr; Require(sceFiberGetSelf(&self) == FiberErrorPermission, "no fiber on the thread"); Require(_sceFiberInitializeImpl_nid_postfix(g_first, "first", FirstEntry, 11, g_firstContext.data(), g_firstContext.size(), nullptr, 0) == 0, "initialize first"); Require(_sceFiberInitializeImpl_nid_postfix(g_second, "second", SecondEntry, 22, g_secondContext.data(), g_secondContext.size(), nullptr, 0) == 0, "initialize second"); const auto threadCsr = _mm_getcsr(); std::uint64_t returned = 0; std::uint64_t framePointer = 0; Require(sceFiberGetThreadFramePointerAddress(&framePointer) == FiberErrorPermission, "frame pointer outside a fiber"); Require(_sceFiberGetThreadFramePointerAddress(&framePointer) == FiberErrorPermission, "underscore frame pointer outside a fiber"); Require(RunFirst(&returned) == 0 && returned == 250, "run first until second returns"); Require(_mm_getcsr() == threadCsr, "thread MXCSR restored after the fibers"); std::thread([&] { Require(sceFiberRun_nid_postfix(g_second, 260, &returned) == 0 && returned == 301, "resume second on another thread"); }).join(); Require(sceFiberFinalize(g_first) == 0, "finalize first"); Require(sceFiberFinalize(g_second) == 0, "finalize second"); auto* checked = reinterpret_cast(&g_checkedStorage); FiberInfo info{}; info.size = sizeof(info); Require(sceFiberStopContextSizeCheck() == FiberErrorState, "stop before start"); Require(sceFiberStartContextSizeCheck(1) == FiberErrorInvalid, "start with flags"); Require(_sceFiberInitializeImpl_nid_postfix(checked, "unchecked", CheckedEntry, 0, g_checkedContext.data(), g_checkedContext.size(), nullptr, 0) == 0, "initialize unchecked"); Require(sceFiberGetInfo(checked, &info) == 0 && info.size_context_margin == ~0ull, "no margin without size check"); Require(sceFiberFinalize(checked) == 0, "finalize unchecked"); Require(sceFiberStartContextSizeCheck(0) == 0, "start size check"); Require(sceFiberStartContextSizeCheck(0) == FiberErrorState, "start twice"); Require(_sceFiberInitializeImpl_nid_postfix(checked, "checked", CheckedEntry, 0, g_checkedContext.data(), g_checkedContext.size(), nullptr, 0) == 0, "initialize checked"); Require(sceFiberGetInfo(checked, &info) == 0 && info.size_context_margin == g_checkedContext.size(), "untouched context is all margin"); Require(sceFiberRun_nid_postfix(checked, 0, nullptr) == 0, "run checked"); Require(sceFiberGetInfo(checked, &info) == 0, "checked info"); Require(info.size_context_margin > 0 && info.size_context_margin < g_checkedContext.size() - 4096, "margin below the used stack"); Require(info.size_context_margin % 8 == 0, "margin in whole words"); Require(sceFiberStopContextSizeCheck() == 0, "stop size check"); RunHostCallOnSmallContext(); }