diff --git a/core/libs/prx/libSceFiber/Export.cpp b/core/libs/prx/libSceFiber/Export.cpp index 81562fa18..1c6ced8b7 100644 --- a/core/libs/prx/libSceFiber/Export.cpp +++ b/core/libs/prx/libSceFiber/Export.cpp @@ -2,6 +2,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include #include #include #include @@ -26,6 +27,7 @@ static constexpr int32_t SCE_FIBER_ERROR_STATE = static_cast(0x80590006 static constexpr std::size_t FIBER_OBJECT_SIZE = 0x100; static constexpr std::size_t FIBER_OPT_PARAM_SIZE = 0x80; static constexpr std::size_t FIBER_MIN_CONTEXT_SIZE = 512; +static constexpr std::uint64_t FIBER_CONTEXT_FILL = 0xdeadbeefdeadbeefull; using GuestFiberEntry = void (APS5_VABI*)(std::uint64_t argOnInitialize, std::uint64_t argOnRun); @@ -49,11 +51,14 @@ struct Fiber { std::uint64_t contextSize; void* savedStack; char name[FIBER_MAX_NAME_LENGTH + 1]; + bool contextSizeCheck; }; static_assert(sizeof(Fiber) <= FIBER_OBJECT_SIZE, "guest reserves 0x100 bytes for SceFiber"); static constexpr std::uint64_t FIBER_MAGIC = 0x5245424946355041ull; +static std::atomic g_contextSizeCheck{false}; + struct StackBounds { void* base; void* limit; @@ -63,6 +68,7 @@ struct StackBounds { struct ThreadFiberState { Fiber* current = nullptr; void* threadStack = nullptr; + std::uint64_t threadFramePointer = 0; StackBounds threadBounds{}; std::uint64_t transfer = 0; Fiber* pendingSuspend = nullptr; @@ -319,6 +325,11 @@ int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject* object, const fiber->context = static_cast(addr_context); fiber->contextSize = size_context; std::strncpy(fiber->name, name, FIBER_MAX_NAME_LENGTH); + fiber->contextSizeCheck = g_contextSizeCheck.load(std::memory_order_relaxed); + if (fiber->contextSizeCheck) { + auto* words = static_cast(addr_context); + std::fill(words, words + size_context / sizeof(std::uint64_t), FIBER_CONTEXT_FILL); + } if (TraceFibers()) std::fprintf(stderr, "[fiber] init %s object=%p context=%p+0x%llx entry=%p\n", fiber->name, static_cast(object), addr_context, static_cast(size_context), reinterpret_cast(entry)); return SCE_OK; } @@ -337,6 +348,7 @@ int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject* object, uint64_t arg_on_r if (!fiber) return object ? SCE_FIBER_ERROR_INVALID : SCE_FIBER_ERROR_NULL; if (ThreadState().current) return SCE_FIBER_ERROR_PERMISSION; if (!AcquireForResume(fiber)) return SCE_FIBER_ERROR_STATE; + ThreadState().threadFramePointer = reinterpret_cast(static_cast(__builtin_frame_address(0))[0]); ThreadState().threadBounds = CurrentBounds(); Resume(fiber, &ThreadState().threadStack, arg_on_run); CompletePendingSuspend(); @@ -401,6 +413,11 @@ int32_t APS5_VABI sceFiberGetInfo(FiberObject* object, FiberInfo* fiber_info) { fiber_info->size_context = fiber->contextSize; std::memcpy(fiber_info->name, fiber->name, sizeof(fiber_info->name)); fiber_info->size_context_margin = static_cast(-1); + if (fiber->contextSizeCheck) { + const auto* words = reinterpret_cast(fiber->context); + const auto* end = words + fiber->contextSize / sizeof(std::uint64_t); + fiber_info->size_context_margin = static_cast(std::find_if(words, end, [](std::uint64_t word) { return word != FIBER_CONTEXT_FILL; }) - words) * sizeof(std::uint64_t); + } return SCE_OK; } @@ -419,20 +436,21 @@ int32_t APS5_VABI sceFiberOptParamInitialize(FiberOptParam* opt_param) { } int32_t APS5_VABI sceFiberGetThreadFramePointerAddress(uint64_t* addr_frame_pointer) { - (void)addr_frame_pointer; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!addr_frame_pointer) return SCE_FIBER_ERROR_NULL; + if (!ThreadState().current) return SCE_FIBER_ERROR_PERMISSION; + *addr_frame_pointer = ThreadState().threadFramePointer; + return SCE_OK; } int32_t APS5_VABI sceFiberStartContextSizeCheck(uint32_t flags) { - (void)flags; - NotImplemented_nid_no_patch(__func__); - return 0; + if (flags != 0) return SCE_FIBER_ERROR_INVALID; + bool expected = false; + return g_contextSizeCheck.compare_exchange_strong(expected, true) ? SCE_OK : SCE_FIBER_ERROR_STATE; } int32_t APS5_VABI sceFiberStopContextSizeCheck(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + bool expected = true; + return g_contextSizeCheck.compare_exchange_strong(expected, false) ? SCE_OK : SCE_FIBER_ERROR_STATE; } } diff --git a/core/libs/tests/Fiber.cpp b/core/libs/tests/Fiber.cpp index 2f0109084..707a59a80 100644 --- a/core/libs/tests/Fiber.cpp +++ b/core/libs/tests/Fiber.cpp @@ -16,6 +16,10 @@ std::int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject*, std::uint64_t, std: 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 sceFiberStartContextSizeCheck(std::uint32_t); +std::int32_t APS5_VABI sceFiberStopContextSizeCheck(void); } static void Require(bool value, const char* what) { @@ -24,6 +28,8 @@ static void Require(bool value, const char* what) { 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); @@ -34,6 +40,9 @@ struct alignas(8) FiberStorage { }; 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); @@ -42,6 +51,9 @@ 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"); volatile double carried = 1.5; std::uint64_t received = 0; Require(sceFiberSwitch(g_second, 200, &received) == 0, "switch to second fiber"); @@ -62,6 +74,20 @@ void APS5_VABI SecondEntry(std::uint64_t argOnInitialize, std::uint64_t argOnRun 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); +} + +[[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() { @@ -71,11 +97,31 @@ int main() { 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; - Require(sceFiberRun_nid_postfix(g_first, 100, &returned) == 0 && returned == 250, "run first until second returns"); + std::uint64_t framePointer = 0; + Require(sceFiberGetThreadFramePointerAddress(&framePointer) == FiberErrorPermission, "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"); }