feat(libSceFiber): implement context size checking and the thread frame pointer
sceFiberStartContextSizeCheck and sceFiberStopContextSizeCheck toggle a mode in which new fibers get their context filled with a pattern, so sceFiberGetInfo can report the unused stack margin. sceFiberGetThreadFramePointerAddress returns the frame pointer of the thread that called sceFiberRun. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bc51b337ad
commit
62530c9d5c
|
|
@ -2,6 +2,7 @@
|
|||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
|
@ -26,6 +27,7 @@ static constexpr int32_t SCE_FIBER_ERROR_STATE = static_cast<int32_t>(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<bool> 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<std::uint8_t*>(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<std::uint64_t*>(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<void*>(object), addr_context, static_cast<unsigned long long>(size_context), reinterpret_cast<void*>(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<std::uint64_t>(static_cast<void**>(__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<uint64_t>(-1);
|
||||
if (fiber->contextSizeCheck) {
|
||||
const auto* words = reinterpret_cast<const std::uint64_t*>(fiber->context);
|
||||
const auto* end = words + fiber->contextSize / sizeof(std::uint64_t);
|
||||
fiber_info->size_context_margin = static_cast<uint64_t>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<std::int32_t>(0x80590001);
|
||||
constexpr std::int32_t FiberErrorInvalid = static_cast<std::int32_t>(0x80590004);
|
||||
constexpr std::int32_t FiberErrorPermission = static_cast<std::int32_t>(0x80590005);
|
||||
constexpr std::int32_t FiberErrorState = static_cast<std::int32_t>(0x80590006);
|
||||
|
||||
|
|
@ -34,6 +40,9 @@ struct alignas(8) FiberStorage {
|
|||
};
|
||||
|
||||
FiberStorage g_firstStorage;
|
||||
FiberStorage g_checkedStorage;
|
||||
alignas(16) std::array<unsigned char, 16 * 1024> g_checkedContext;
|
||||
std::uint64_t g_threadFramePointer = 0;
|
||||
FiberStorage g_secondStorage;
|
||||
auto* const g_first = reinterpret_cast<FiberObject*>(&g_firstStorage);
|
||||
auto* const g_second = reinterpret_cast<FiberObject*>(&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<std::uint64_t>(__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<FiberObject*>(&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");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue