Files
DariusHQ cf1545d751 feat(libkernel): implement getcontext, setcontext and makecontext
PSXS5, SwanStationPS5 and Nuvio stop at start-up on setcontext on main,
and EVO Player and ProsperoStore do on Windows. The three are imported
by the statically linked OpenSSL's ASYNC code.

The three use FreeBSD 11's amd64 ucontext_t (mcontext at 0x10, mc_len
800, uc_link at 0x330, 0x370 bytes), which the titles are built
against: EVO Player's ASYNC_is_capable captures into a 0x370-byte stack
buffer, and async_fibre_makecontext sets uc_link at 0x330 and uc_stack
at 0x338. getcontext saves every register at its call in assembly and
stores the context as FreeBSD's sys_getcontext does: rax and rdx as 0,
the carry flag clear, the FXSAVE image and the signal mask, and nothing
from uc_link on. setcontext checks mc_len and mc_flags as set_mcontext
does, installs the signal mask, masks MXCSR with the host's MXCSR_MASK
and returns through iretq, so every general register and the flags are
restored and nothing is written below the target stack pointer.
makecontext follows FreeBSD 11's amd64 libc.

Windows checks guard-page growth and exception unwinding against the
stack bounds in the TEB. setcontext and longjmp now set them to the
stack they switch to, which makecontext registers and pins writable as
libSceFiber does for fiber stacks. OpenSSL enters a context with
setcontext and then switches with _setjmp and _longjmp.
2026-10-11 13:00:38 +03:00

64 lines
2.6 KiB
C++

#include "prx/libc/include/GuestStacks.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/System/include/UserContext.hpp"
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
extern "C" int APS5_VABI setcontext_nid_postfix(const UserUcontext* context);
namespace {
using ContextFunction = void (APS5_VABI*)(std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t);
constexpr int MaximumArguments = 6;
constexpr std::uint64_t MinimumStackSize = 2048;
}
extern "C" {
[[noreturn]] void APS5_VABI Aps5MakecontextStart_nid_no_patch(UserUcontext* context, ContextFunction function, const std::uint64_t* arguments) {
function(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
if (context->link == nullptr) LibcExit_nid_no_patch(0);
setcontext_nid_postfix(context->link);
std::abort();
}
void APS5_VABI makecontext_nid_postfix(UserUcontext* context, void (APS5_VABI* start)(), int count, ...) {
if (context == nullptr || context->mcontext.len != sizeof(UserMcontext)) return;
if (count < 0 || count > MaximumArguments || context->stackPointer == nullptr || context->stackSize < MinimumStackSize) {
context->mcontext.len = 0;
return;
}
const auto top = (reinterpret_cast<std::uintptr_t>(context->stackPointer) + context->stackSize) & ~static_cast<std::uintptr_t>(15);
auto* arguments = reinterpret_cast<std::uint64_t*>(top) - MaximumArguments;
auto* stack = arguments - 1;
#ifdef _WIN32
__builtin_sysv_va_list list;
__builtin_sysv_va_start(list, count);
for (int index = 0; index < count; ++index) arguments[index] = __builtin_va_arg(list, std::uint64_t);
__builtin_sysv_va_end(list);
#else
std::va_list list;
va_start(list, count);
for (int index = 0; index < count; ++index) arguments[index] = va_arg(list, std::uint64_t);
va_end(list);
#endif
for (int index = count; index < MaximumArguments; ++index) arguments[index] = 0;
*stack = 0;
auto& machine = context->mcontext;
machine.rdi = reinterpret_cast<std::uint64_t>(context);
machine.rsi = reinterpret_cast<std::uint64_t>(start);
machine.rdx = reinterpret_cast<std::uint64_t>(arguments);
machine.rbp = 0;
machine.rbx = reinterpret_cast<std::uint64_t>(stack);
machine.rsp = reinterpret_cast<std::uint64_t>(stack);
machine.rip = reinterpret_cast<std::uint64_t>(&Aps5MakecontextStart_nid_no_patch);
GuestStacks::GuestStackRegister_nid_no_patch(context->stackPointer, context->stackSize);
}
}