diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 2c0060756b6c..cbd1ea412613 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp) +add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp UserContext.cpp) target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) configure_windows_unwind(libSceLibcInternal) diff --git a/core/libs/prx/libSceLibcInternal/UserContext.cpp b/core/libs/prx/libSceLibcInternal/UserContext.cpp new file mode 100644 index 000000000000..3bd2bda58aac --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/UserContext.cpp @@ -0,0 +1,63 @@ +#include "prx/libc/include/GuestStacks.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/System/include/GuestContext.hpp" +#include +#include +#include +#include + +extern "C" int APS5_VABI setcontext_nid_postfix(const GuestUcontext* 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(GuestUcontext* 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(GuestUcontext* context, void (APS5_VABI* start)(), int count, ...) { + if (context == nullptr || context->mcontext.len != sizeof(GuestMcontext)) return; + if (count < 0 || count > MaximumArguments || context->stackPointer == nullptr || context->stackSize < MinimumStackSize) { + context->mcontext.len = 0; + return; + } + const auto top = (reinterpret_cast(context->stackPointer) + context->stackSize) & ~static_cast(15); + auto* arguments = reinterpret_cast(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(context); + machine.rsi = reinterpret_cast(start); + machine.rdx = reinterpret_cast(arguments); + machine.rbp = 0; + machine.rbx = reinterpret_cast(stack); + machine.rsp = reinterpret_cast(stack); + machine.rip = reinterpret_cast(&Aps5MakecontextStart_nid_no_patch); + GuestStacks::GuestStackRegister_nid_no_patch(context->stackPointer, context->stackSize); +} + +} diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 30aaf71ba88f..83d9b975ddad 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/StandardStreams.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestAllocations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestArena.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestStacks.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestWriteWatch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HeapDiagnostics.cpp diff --git a/core/libs/prx/libc/include/GuestStacks.hpp b/core/libs/prx/libc/include/GuestStacks.hpp new file mode 100644 index 000000000000..2b729a9bd57a --- /dev/null +++ b/core/libs/prx/libc/include/GuestStacks.hpp @@ -0,0 +1,19 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTSTACKS_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTSTACKS_HPP + +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace GuestStacks { + +extern "C" { + +void GuestStackRegister_nid_no_patch(const void* pointer, std::size_t bytes); +void APS5_VABI GuestStackSwitch_nid_no_patch(std::uintptr_t stackPointer); + +} + +} + +#endif diff --git a/core/libs/prx/libc/include/general/AsmMacros.hpp b/core/libs/prx/libc/include/general/AsmMacros.hpp new file mode 100644 index 000000000000..0962f0c91720 --- /dev/null +++ b/core/libs/prx/libc/include/general/AsmMacros.hpp @@ -0,0 +1,10 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GENERAL_ASMMACROS_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_GENERAL_ASMMACROS_HPP + +#ifdef _WIN32 +#define APS5_ASM_FUNCTION(name) ".globl " name "\n.def " name "; .scl 2; .type 32; .endef\n" name ":\n" +#else +#define APS5_ASM_FUNCTION(name) ".globl " name "\n.type " name ", @function\n" name ":\n" +#endif + +#endif diff --git a/core/libs/prx/libc/src/GuestStacks.cpp b/core/libs/prx/libc/src/GuestStacks.cpp new file mode 100644 index 000000000000..9b24019938c7 --- /dev/null +++ b/core/libs/prx/libc/src/GuestStacks.cpp @@ -0,0 +1,94 @@ +#include "prx/libc/include/GuestStacks.hpp" + +#ifdef _WIN32 +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include +#include +#endif + +namespace GuestStacks { +namespace { + +#ifdef _WIN32 +struct StackBounds { + std::uintptr_t base; + std::uintptr_t limit; + std::uintptr_t deallocation; +}; + +constexpr std::size_t DeallocationStackOffset = 0x1478; + +std::mutex rangesLock; +std::map ranges; + +StackBounds CurrentBounds() { + auto* tib = reinterpret_cast(NtCurrentTeb()); + auto* teb = reinterpret_cast(tib); + return {reinterpret_cast(tib->StackBase), reinterpret_cast(tib->StackLimit), + *reinterpret_cast(teb + DeallocationStackOffset)}; +} + +void ApplyBounds(const StackBounds& bounds) { + auto* tib = reinterpret_cast(NtCurrentTeb()); + auto* teb = reinterpret_cast(tib); + tib->StackBase = reinterpret_cast(bounds.base); + tib->StackLimit = reinterpret_cast(bounds.limit); + *reinterpret_cast(teb + DeallocationStackOffset) = bounds.deallocation; +} + +bool RegisteredBounds(std::uintptr_t stackPointer, StackBounds& bounds) { + std::lock_guard lock(rangesLock); + const auto next = ranges.upper_bound(stackPointer); + if (next == ranges.begin()) return false; + const auto& [low, high] = *std::prev(next); + if (stackPointer >= high) return false; + bounds = {high, low, low}; + return true; +} + +bool HostBounds(std::uintptr_t stackPointer, StackBounds& bounds) { + MEMORY_BASIC_INFORMATION memory{}; + if (VirtualQuery(reinterpret_cast(stackPointer), &memory, sizeof(memory)) != sizeof(memory) || memory.State != MEM_COMMIT) return false; + const auto base = reinterpret_cast(memory.BaseAddress); + bounds = {base + memory.RegionSize, base, reinterpret_cast(memory.AllocationBase)}; + return true; +} +#endif + +} + +void GuestStackRegister_nid_no_patch(const void* pointer, std::size_t bytes) { +#ifdef _WIN32 + const auto low = reinterpret_cast(pointer); + const auto high = low + bytes; + std::lock_guard lock(rangesLock); + const auto same = ranges.find(low); + if (same != ranges.end() && same->second == high) return; + auto overlap = ranges.upper_bound(low); + if (overlap != ranges.begin() && std::prev(overlap)->second > low) --overlap; + while (overlap != ranges.end() && overlap->first < high) { + GuestArena::GuestArenaUnpinWritable_nid_postfix(reinterpret_cast(overlap->first), overlap->second - overlap->first); + overlap = ranges.erase(overlap); + } + GuestArena::GuestArenaPinWritable_nid_postfix(pointer, bytes); + ranges.emplace(low, high); +#else + (void)pointer; + (void)bytes; +#endif +} + +void APS5_VABI GuestStackSwitch_nid_no_patch(std::uintptr_t stackPointer) { +#ifdef _WIN32 + const auto current = CurrentBounds(); + if (stackPointer >= current.deallocation && stackPointer < current.base) return; + StackBounds target{}; + if (RegisteredBounds(stackPointer, target) || HostBounds(stackPointer, target)) ApplyBounds(target); +#else + (void)stackPointer; +#endif +} + +} diff --git a/core/libs/prx/libc/src/JumpBuffer.cpp b/core/libs/prx/libc/src/JumpBuffer.cpp index 5a1d2fa6c700..77dca484e426 100644 --- a/core/libs/prx/libc/src/JumpBuffer.cpp +++ b/core/libs/prx/libc/src/JumpBuffer.cpp @@ -1,13 +1,9 @@ #include "prx/libc/include/General.hpp" +#include "prx/libc/include/general/AsmMacros.hpp" // setjmp/longjmp must capture the guest's own frame, so they are written directly in assembly with // the guest (System V) calling convention. The saved state fits the guest's 96-byte jmp_buf: // return address, rbx, rsp, rbp, r12-r15, MXCSR and the x87 control word. -#ifdef _WIN32 -#define APS5_ASM_FUNCTION(name) ".globl " name "\n.def " name "; .scl 2; .type 32; .endef\n" name ":\n" -#else -#define APS5_ASM_FUNCTION(name) ".globl " name "\n.type " name ", @function\n" name ":\n" -#endif asm(".text\n" APS5_ASM_FUNCTION("setjmp_nid_postfix") @@ -26,6 +22,15 @@ asm(".text\n" " xor %eax, %eax\n" " ret\n" APS5_ASM_FUNCTION("longjmp_nid_postfix") +#ifdef _WIN32 + " mov %rdi, %rbx\n" + " mov %esi, %r12d\n" + " mov 16(%rdi), %rdi\n" + " and $-16, %rsp\n" + " call GuestStackSwitch_nid_no_patch\n" + " mov %rbx, %rdi\n" + " mov %r12d, %esi\n" +#endif " mov %esi, %eax\n" " test %eax, %eax\n" " jnz 1f\n" diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2520ab4772fb..cd21d355f0c5 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -48,6 +48,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SystemConfiguration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Signals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Notification.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/UserContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/TimedWait.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AppMetadata/src/AppMetadata.cpp @@ -280,6 +281,18 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_key PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_user_context_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserContext.cpp) + target_include_directories(guest_user_context_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_user_context_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_user_context_tests) + add_test(NAME guest_user_context COMMAND guest_user_context_tests) + add_test(NAME guest_user_context_exit COMMAND guest_user_context_tests exit-on-return) + set_tests_properties(guest_user_context guest_user_context_exit PROPERTIES TIMEOUT 20) + if(WIN32) + target_compile_definitions(guest_user_context_tests PRIVATE _WIN32_WINNT=0x0602) + set_tests_properties(guest_user_context guest_user_context_exit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_signal_tests ${LIBS_INCLUDE_DIR}/tests/GuestSignals.cpp) target_include_directories(guest_signal_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_signal_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/System/include/GuestContext.hpp b/core/libs/prx/libkernel/System/include/GuestContext.hpp new file mode 100644 index 000000000000..970b65059044 --- /dev/null +++ b/core/libs/prx/libkernel/System/include/GuestContext.hpp @@ -0,0 +1,72 @@ +#ifndef CORE_LIBS_PRX_LIBKERNEL_SYSTEM_INCLUDE_GUESTCONTEXT_HPP +#define CORE_LIBS_PRX_LIBKERNEL_SYSTEM_INCLUDE_GUESTCONTEXT_HPP + +#include +#include + +struct GuestMcontext { + std::uint64_t onstack; + std::uint64_t rdi; + std::uint64_t rsi; + std::uint64_t rdx; + std::uint64_t rcx; + std::uint64_t r8; + std::uint64_t r9; + std::uint64_t rax; + std::uint64_t rbx; + std::uint64_t rbp; + std::uint64_t r10; + std::uint64_t r11; + std::uint64_t r12; + std::uint64_t r13; + std::uint64_t r14; + std::uint64_t r15; + std::uint32_t trapno; + std::uint16_t fs; + std::uint16_t gs; + std::uint64_t addr; + std::uint32_t flags; + std::uint16_t es; + std::uint16_t ds; + std::uint64_t err; + std::uint64_t rip; + std::uint64_t cs; + std::uint64_t rflags; + std::uint64_t rsp; + std::uint64_t ss; + std::uint64_t len; + std::uint64_t fpformat; + std::uint64_t ownedfp; + std::uint64_t lbrfrom; + std::uint64_t lbrto; + std::uint64_t aux1; + std::uint64_t aux2; + std::uint64_t fpstate[104]; + std::uint64_t fsbase; + std::uint64_t gsbase; + std::uint64_t spare[6]; +}; + +struct GuestUcontext { + std::uint32_t sigmask[4]; + std::int32_t reserved[12]; + GuestMcontext mcontext; + GuestUcontext* link; + void* stackPointer; + std::uint64_t stackSize; + std::int32_t stackFlags; + std::int32_t stackAlign; + std::int32_t flags; + std::int32_t spare[4]; + std::int32_t tail[3]; +}; + +static_assert(offsetof(GuestUcontext, mcontext) == 0x40); +static_assert(offsetof(GuestUcontext, mcontext) + offsetof(GuestMcontext, rsp) == 0xf8); +static_assert(sizeof(GuestMcontext) == 0x480); +static_assert(offsetof(GuestUcontext, link) == 0x4c0); +static_assert(offsetof(GuestUcontext, stackPointer) == 0x4c8); +static_assert(offsetof(GuestUcontext, stackSize) == 0x4d0); +static_assert(sizeof(GuestUcontext) == 0x500); + +#endif diff --git a/core/libs/prx/libkernel/System/src/UserContext.cpp b/core/libs/prx/libkernel/System/src/UserContext.cpp new file mode 100644 index 000000000000..eef2840b331b --- /dev/null +++ b/core/libs/prx/libkernel/System/src/UserContext.cpp @@ -0,0 +1,219 @@ +#include "prx/libc/include/GuestStacks.hpp" +#include "prx/libc/include/general/AsmMacros.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/System/include/GuestContext.hpp" +#include +#include +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); +extern "C" int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet); + +namespace { + +struct RegisterFrame { + std::uint64_t rax; + std::uint64_t rbx; + std::uint64_t rcx; + std::uint64_t rdx; + std::uint64_t rsi; + std::uint64_t rdi; + std::uint64_t rbp; + std::uint64_t rsp; + std::uint64_t r8; + std::uint64_t r9; + std::uint64_t r10; + std::uint64_t r11; + std::uint64_t r12; + std::uint64_t r13; + std::uint64_t r14; + std::uint64_t r15; + std::uint64_t rip; + std::uint64_t rflags; + std::uint64_t cs; + std::uint64_t ss; + alignas(16) std::uint8_t fxsave[512]; +}; + +static_assert(offsetof(RegisterFrame, rsp) == 0x38 && offsetof(RegisterFrame, r8) == 0x40 && offsetof(RegisterFrame, r12) == 0x60 && offsetof(RegisterFrame, r15) == 0x78); +static_assert(offsetof(RegisterFrame, rip) == 0x80 && offsetof(RegisterFrame, rflags) == 0x88 && offsetof(RegisterFrame, cs) == 0x90 && offsetof(RegisterFrame, ss) == 0x98); +static_assert(offsetof(RegisterFrame, fxsave) == 0xa0 && sizeof(RegisterFrame) == 0x2a0); + +constexpr int GuestEinval = 22; +constexpr int GuestSigBlock = 1; +constexpr int GuestSigSetmask = 3; +constexpr std::uint64_t CarryFlag = 0x1; +constexpr std::uint32_t McontextFlagMask = 0x7; +constexpr std::uint64_t FpFormatXmm = 0x10002; +constexpr std::uint64_t FpOwnedFpu = 0x20001; +constexpr std::size_t FxsaveMxcsr = 24; +constexpr std::size_t FxsaveMxcsrMask = 28; +constexpr std::uint32_t DefaultMxcsrMask = 0xffbf; + +int Fail(int error) { + *__error_nid_postfix() = error; + return -1; +} + +std::uint32_t HostMxcsrMask() { + static const std::uint32_t mask = [] { + alignas(16) std::uint8_t image[512]{}; + _fxsave64(image); + std::uint32_t value = 0; + std::memcpy(&value, image + FxsaveMxcsrMask, sizeof(value)); + return value != 0 ? value : DefaultMxcsrMask; + }(); + return mask; +} + +} + +extern "C" [[noreturn]] void APS5_VABI Aps5ContextRestore_nid_no_patch(const RegisterFrame* frame); + +asm(".text\n" + APS5_ASM_FUNCTION("getcontext_nid_postfix") + " pushfq\n" + " push %rbp\n" + " mov %rsp, %rbp\n" + " sub $0x2a0, %rsp\n" + " and $-16, %rsp\n" + " mov %rax, 0x00(%rsp)\n" + " mov %rbx, 0x08(%rsp)\n" + " mov %rcx, 0x10(%rsp)\n" + " mov %rdx, 0x18(%rsp)\n" + " mov %rsi, 0x20(%rsp)\n" + " mov %rdi, 0x28(%rsp)\n" + " mov 0(%rbp), %rax\n" + " mov %rax, 0x30(%rsp)\n" + " lea 24(%rbp), %rax\n" + " mov %rax, 0x38(%rsp)\n" + " mov %r8, 0x40(%rsp)\n" + " mov %r9, 0x48(%rsp)\n" + " mov %r10, 0x50(%rsp)\n" + " mov %r11, 0x58(%rsp)\n" + " mov %r12, 0x60(%rsp)\n" + " mov %r13, 0x68(%rsp)\n" + " mov %r14, 0x70(%rsp)\n" + " mov %r15, 0x78(%rsp)\n" + " mov 16(%rbp), %rax\n" + " mov %rax, 0x80(%rsp)\n" + " mov 8(%rbp), %rax\n" + " mov %rax, 0x88(%rsp)\n" + " xor %eax, %eax\n" + " mov %cs, %ax\n" + " mov %rax, 0x90(%rsp)\n" + " mov %ss, %ax\n" + " mov %rax, 0x98(%rsp)\n" + " fxsave64 0xa0(%rsp)\n" + " mov %rsp, %rsi\n" + " call Aps5GetcontextFinish_nid_no_patch\n" + " mov %rbp, %rsp\n" + " pop %rbp\n" + " lea 8(%rsp), %rsp\n" + " ret\n" + APS5_ASM_FUNCTION("Aps5ContextRestore_nid_no_patch") + " mov %rdi, %r12\n" + " xor %eax, %eax\n" + " mov %ss, %ax\n" + " push %rax\n" + " pushq 0x38(%r12)\n" + " pushfq\n" + " pop %rax\n" + " and $-0x254dd6, %rax\n" + " mov 0x88(%r12), %rcx\n" + " and $0x254dd5, %rcx\n" + " or %rcx, %rax\n" + " push %rax\n" + " xor %eax, %eax\n" + " mov %cs, %ax\n" + " push %rax\n" + " pushq 0x80(%r12)\n" +#ifdef _WIN32 + " mov 0x38(%r12), %rdi\n" + " call GuestStackSwitch_nid_no_patch\n" +#endif + " fxrstor64 0xa0(%r12)\n" + " mov 0x00(%r12), %rax\n" + " mov 0x08(%r12), %rbx\n" + " mov 0x10(%r12), %rcx\n" + " mov 0x18(%r12), %rdx\n" + " mov 0x20(%r12), %rsi\n" + " mov 0x28(%r12), %rdi\n" + " mov 0x30(%r12), %rbp\n" + " mov 0x40(%r12), %r8\n" + " mov 0x48(%r12), %r9\n" + " mov 0x50(%r12), %r10\n" + " mov 0x58(%r12), %r11\n" + " mov 0x68(%r12), %r13\n" + " mov 0x70(%r12), %r14\n" + " mov 0x78(%r12), %r15\n" + " mov 0x60(%r12), %r12\n" + " iretq\n"); + +extern "C" { + +int APS5_VABI Aps5GetcontextFinish_nid_no_patch(GuestUcontext* context, const RegisterFrame* frame) { + if (context == nullptr) return Fail(GuestEinval); + GuestUcontext captured{}; + sigprocmask_nid_postfix(GuestSigBlock, nullptr, captured.sigmask); + auto& machine = captured.mcontext; + machine.rdi = frame->rdi; + machine.rsi = frame->rsi; + machine.rcx = frame->rcx; + machine.r8 = frame->r8; + machine.r9 = frame->r9; + machine.rbx = frame->rbx; + machine.rbp = frame->rbp; + machine.r10 = frame->r10; + machine.r11 = frame->r11; + machine.r12 = frame->r12; + machine.r13 = frame->r13; + machine.r14 = frame->r14; + machine.r15 = frame->r15; + machine.rip = frame->rip; + machine.cs = frame->cs; + machine.rflags = frame->rflags & ~CarryFlag; + machine.rsp = frame->rsp; + machine.ss = frame->ss; + machine.len = sizeof(GuestMcontext); + machine.fpformat = FpFormatXmm; + machine.ownedfp = FpOwnedFpu; + std::memcpy(machine.fpstate, frame->fxsave, sizeof(frame->fxsave)); + std::memcpy(context, &captured, offsetof(GuestUcontext, link)); + return 0; +} + +int APS5_VABI setcontext_nid_postfix(const GuestUcontext* context) { + if (context == nullptr) return Fail(GuestEinval); + const auto& machine = context->mcontext; + if (machine.len != sizeof(GuestMcontext) || (machine.flags & ~McontextFlagMask) != 0) return Fail(GuestEinval); + RegisterFrame frame{}; + frame.rax = machine.rax; + frame.rbx = machine.rbx; + frame.rcx = machine.rcx; + frame.rdx = machine.rdx; + frame.rsi = machine.rsi; + frame.rdi = machine.rdi; + frame.rbp = machine.rbp; + frame.rsp = machine.rsp; + frame.r8 = machine.r8; + frame.r9 = machine.r9; + frame.r10 = machine.r10; + frame.r11 = machine.r11; + frame.r12 = machine.r12; + frame.r13 = machine.r13; + frame.r14 = machine.r14; + frame.r15 = machine.r15; + frame.rip = machine.rip; + frame.rflags = machine.rflags; + std::memcpy(frame.fxsave, machine.fpstate, sizeof(frame.fxsave)); + std::uint32_t mxcsr = 0; + std::memcpy(&mxcsr, frame.fxsave + FxsaveMxcsr, sizeof(mxcsr)); + mxcsr &= HostMxcsrMask(); + std::memcpy(frame.fxsave + FxsaveMxcsr, &mxcsr, sizeof(mxcsr)); + sigprocmask_nid_postfix(GuestSigSetmask, context->sigmask, nullptr); + Aps5ContextRestore_nid_no_patch(&frame); +} + +} diff --git a/core/libs/tests/GuestUserContext.cpp b/core/libs/tests/GuestUserContext.cpp new file mode 100644 index 000000000000..f90803a0d3f1 --- /dev/null +++ b/core/libs/tests/GuestUserContext.cpp @@ -0,0 +1,474 @@ +#include "prx/libc/include/general/AsmMacros.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/System/include/GuestContext.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +using ContextEntry = void (APS5_VABI*)(); + +extern "C" { +[[gnu::returns_twice]] int APS5_VABI getcontext_nid_postfix(GuestUcontext* context); +int APS5_VABI setcontext_nid_postfix(const GuestUcontext* context); +void APS5_VABI makecontext_nid_postfix(GuestUcontext* context, ContextEntry entry, int count, ...); +[[gnu::returns_twice]] int APS5_VABI setjmp_nid_postfix(std::uint64_t* buffer); +[[noreturn]] void APS5_VABI longjmp_nid_postfix(std::uint64_t* buffer, int value); +int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet); +int* APS5_VABI __error_nid_postfix(); + +int APS5_VABI CaptureKnownRegisters(GuestUcontext* context); +void CaptureReturnAddress(); +void RecordRegisters(); +void APS5_VABI EntryThunk(); + +std::uint64_t CapturedStackPointer = 0; +std::uint64_t EntryStackPointer = 0; +std::uint64_t RecordedRegisters[18] = {}; +std::uint32_t RecordedMxcsr = 0; +std::uint16_t RecordedControlWord = 0; +alignas(16) std::uint8_t RecordedXmm[16 * 16] = {}; +GuestUcontext ReturnContext = {}; +} + +asm(".text\n" + APS5_ASM_FUNCTION("CaptureKnownRegisters") + " push %rbx\n" + " push %rbp\n" + " push %r12\n" + " push %r13\n" + " push %r14\n" + " push %r15\n" + " sub $8, %rsp\n" + " movabsq $0x1111111111111111, %rbx\n" + " movabsq $0x2222222222222222, %rbp\n" + " movabsq $0x3333333333333333, %r12\n" + " movabsq $0x4444444444444444, %r13\n" + " movabsq $0x5555555555555555, %r14\n" + " movabsq $0x6666666666666666, %r15\n" + " movabsq $0x7777777777777777, %rcx\n" + " movabsq $0x8888888888888888, %rsi\n" + " movabsq $0x9999999999999999, %r8\n" + " movabsq $0xaaaaaaaaaaaaaaaa, %r9\n" + " movabsq $0xbbbbbbbbbbbbbbbb, %r10\n" + " movabsq $0xcccccccccccccccc, %r11\n" + " movabsq $0xdddddddddddddddd, %rdx\n" + " movabsq $0xeeeeeeeeeeeeeeee, %rax\n" + " mov %rsp, CapturedStackPointer(%rip)\n" + " stc\n" + " call getcontext_nid_postfix\n" + APS5_ASM_FUNCTION("CaptureReturnAddress") + " add $8, %rsp\n" + " pop %r15\n" + " pop %r14\n" + " pop %r13\n" + " pop %r12\n" + " pop %rbp\n" + " pop %rbx\n" + " ret\n" + APS5_ASM_FUNCTION("RecordRegisters") + " pushfq\n" + " mov %rax, RecordedRegisters+0x00(%rip)\n" + " pop %rax\n" + " mov %rax, RecordedRegisters+0x88(%rip)\n" + " mov %rbx, RecordedRegisters+0x08(%rip)\n" + " mov %rcx, RecordedRegisters+0x10(%rip)\n" + " mov %rdx, RecordedRegisters+0x18(%rip)\n" + " mov %rsi, RecordedRegisters+0x20(%rip)\n" + " mov %rdi, RecordedRegisters+0x28(%rip)\n" + " mov %rbp, RecordedRegisters+0x30(%rip)\n" + " mov %rsp, RecordedRegisters+0x38(%rip)\n" + " mov %r8, RecordedRegisters+0x40(%rip)\n" + " mov %r9, RecordedRegisters+0x48(%rip)\n" + " mov %r10, RecordedRegisters+0x50(%rip)\n" + " mov %r11, RecordedRegisters+0x58(%rip)\n" + " mov %r12, RecordedRegisters+0x60(%rip)\n" + " mov %r13, RecordedRegisters+0x68(%rip)\n" + " mov %r14, RecordedRegisters+0x70(%rip)\n" + " mov %r15, RecordedRegisters+0x78(%rip)\n" + " stmxcsr RecordedMxcsr(%rip)\n" + " fnstcw RecordedControlWord(%rip)\n" + " movdqu %xmm0, RecordedXmm+0x00(%rip)\n" + " movdqu %xmm1, RecordedXmm+0x10(%rip)\n" + " movdqu %xmm2, RecordedXmm+0x20(%rip)\n" + " movdqu %xmm3, RecordedXmm+0x30(%rip)\n" + " movdqu %xmm4, RecordedXmm+0x40(%rip)\n" + " movdqu %xmm5, RecordedXmm+0x50(%rip)\n" + " movdqu %xmm6, RecordedXmm+0x60(%rip)\n" + " movdqu %xmm7, RecordedXmm+0x70(%rip)\n" + " movdqu %xmm8, RecordedXmm+0x80(%rip)\n" + " movdqu %xmm9, RecordedXmm+0x90(%rip)\n" + " movdqu %xmm10, RecordedXmm+0xa0(%rip)\n" + " movdqu %xmm11, RecordedXmm+0xb0(%rip)\n" + " movdqu %xmm12, RecordedXmm+0xc0(%rip)\n" + " movdqu %xmm13, RecordedXmm+0xd0(%rip)\n" + " movdqu %xmm14, RecordedXmm+0xe0(%rip)\n" + " movdqu %xmm15, RecordedXmm+0xf0(%rip)\n" + " cld\n" + " lea ReturnContext(%rip), %rdi\n" + " and $-16, %rsp\n" + " call setcontext_nid_postfix\n" + " ud2\n" + APS5_ASM_FUNCTION("EntryThunk") + " mov %rsp, EntryStackPointer(%rip)\n" + " jmp ContextBody\n"); + +namespace { + +constexpr int GuestEinval = 22; +constexpr int GuestSigBlock = 1; +constexpr int GuestSigSetmask = 3; +constexpr std::uint64_t UserChangeableFlags = 0x254dd5; +constexpr std::uint64_t InterruptFlag = 0x200; +constexpr std::size_t FxsaveMxcsr = 24; +constexpr std::size_t FxsaveXmm = 160; +constexpr std::size_t StackBytes = 256 * 1024; + +struct alignas(16) Stack { + unsigned char bytes[StackBytes]; +}; + +struct Fibre { + GuestUcontext context; + std::uint64_t jump[12]; + int started; +}; + +Stack g_restoreStack; +Stack g_contextStack; +Stack g_jobStack; +Stack g_exitStack; +std::uint64_t g_arguments[6] = {}; +int g_entries = 0; +Fibre g_dispatcher = {}; +Fibre g_job = {}; +int g_jobRuns = 0; + +void Require(bool value, const char* what) { + if (!value) { + std::fprintf(stderr, "User context check failed: %s\n", what); + std::abort(); + } +} + +std::uint16_t ControlWord() { + std::uint16_t control = 0; + asm volatile("fnstcw %0" : "=m"(control)); + return control; +} + +std::uint64_t Pattern(unsigned index) { + return 0x0101010101010101ull * (index + 1) + 0x0f00000000000000ull; +} + +void CheckStackBounds(const Stack* stack, const char* what) { +#ifdef _WIN32 + ULONG_PTR low = 0; + ULONG_PTR high = 0; + GetCurrentThreadStackLimits(&low, &high); + Require(low == reinterpret_cast(stack->bytes) && high == reinterpret_cast(stack->bytes + StackBytes), what); +#else + (void)stack; + (void)what; +#endif +} + +void ThrowAndCatch() { + bool caught = false; + try { + throw std::runtime_error("host exception on a context stack"); + } catch (const std::runtime_error&) { + caught = true; + } + Require(caught, "a host exception is caught on a context stack"); +} + +[[gnu::noinline]] void GrowStack(int depth) { + volatile unsigned char page[16 * 1024]; + page[0] = static_cast(depth); + page[sizeof(page) - 1] = static_cast(depth); + if (depth > 0) GrowStack(depth - 1); + Require(page[0] == static_cast(depth), "a deep host frame keeps its contents"); +} + +void CheckErrors() { + *__error_nid_postfix() = 0; + Require(getcontext_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "getcontext rejects a null context"); + *__error_nid_postfix() = 0; + Require(setcontext_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "setcontext rejects a null context"); + GuestUcontext context{}; + Require(getcontext_nid_postfix(&context) == 0, "capture a context to corrupt"); + context.mcontext.len = 0; + *__error_nid_postfix() = 0; + Require(setcontext_nid_postfix(&context) == -1 && *__error_nid_postfix() == GuestEinval, "setcontext rejects a wrong mc_len"); + context.mcontext.len = sizeof(GuestMcontext); + context.mcontext.flags = 0x8; + *__error_nid_postfix() = 0; + Require(setcontext_nid_postfix(&context) == -1 && *__error_nid_postfix() == GuestEinval, "setcontext rejects unknown mc_flags"); + context.mcontext.flags = 0; + GuestUcontext invalid = context; + invalid.stackPointer = g_contextStack.bytes; + invalid.stackSize = StackBytes; + makecontext_nid_postfix(&invalid, &EntryThunk, 7); + Require(invalid.mcontext.len == 0, "makecontext invalidates a context with more than six arguments"); + invalid = context; + invalid.stackPointer = g_contextStack.bytes; + invalid.stackSize = 2047; + makecontext_nid_postfix(&invalid, &EntryThunk, 0); + Require(invalid.mcontext.len == 0, "makecontext invalidates a context with a stack below MINSIGSTKSZ"); + invalid = context; + invalid.stackPointer = nullptr; + invalid.stackSize = StackBytes; + makecontext_nid_postfix(&invalid, &EntryThunk, 0); + Require(invalid.mcontext.len == 0, "makecontext invalidates a context without a stack"); + invalid = context; + invalid.stackPointer = g_contextStack.bytes; + invalid.stackSize = StackBytes; + invalid.mcontext.len = 0; + makecontext_nid_postfix(&invalid, &EntryThunk, 0); + Require(invalid.mcontext.len == 0 && invalid.mcontext.rip == context.mcontext.rip, "makecontext leaves a context without a valid mc_len untouched"); + makecontext_nid_postfix(nullptr, &EntryThunk, 0); +} + +void CheckCapture() { + GuestUcontext context; + std::memset(&context, 0xa5, sizeof(context)); + GuestUcontext untouched; + std::memset(&untouched, 0xa5, sizeof(untouched)); + std::uint32_t previous[4] = {}; + const std::uint32_t blocked[4] = {0x24, 0, 0, 0}; + Require(sigprocmask_nid_postfix(GuestSigSetmask, blocked, previous) == 0, "block signals before the capture"); + Require(CaptureKnownRegisters(&context) == 0, "getcontext returns 0"); + Require(sigprocmask_nid_postfix(GuestSigSetmask, previous, nullptr) == 0, "restore the signal mask"); + const auto& machine = context.mcontext; + Require(machine.rbx == 0x1111111111111111ull && machine.rbp == 0x2222222222222222ull, "getcontext saves rbx and rbp"); + Require(machine.r12 == 0x3333333333333333ull && machine.r13 == 0x4444444444444444ull, "getcontext saves r12 and r13"); + Require(machine.r14 == 0x5555555555555555ull && machine.r15 == 0x6666666666666666ull, "getcontext saves r14 and r15"); + Require(machine.rcx == 0x7777777777777777ull && machine.rsi == 0x8888888888888888ull, "getcontext saves rcx and rsi"); + Require(machine.r8 == 0x9999999999999999ull && machine.r9 == 0xaaaaaaaaaaaaaaaaull, "getcontext saves r8 and r9"); + Require(machine.r10 == 0xbbbbbbbbbbbbbbbbull && machine.r11 == 0xccccccccccccccccull, "getcontext saves r10 and r11"); + Require(machine.rdi == reinterpret_cast(&context), "getcontext saves rdi"); + Require(machine.rax == 0 && machine.rdx == 0, "getcontext stores rax and rdx as 0"); + Require((machine.rflags & 1) == 0, "getcontext clears the carry flag"); + Require(machine.rip == reinterpret_cast(&CaptureReturnAddress), "getcontext saves its return address"); + Require(machine.rsp == CapturedStackPointer, "getcontext saves the caller's stack pointer"); + Require(machine.len == sizeof(GuestMcontext) && machine.fpformat == 0x10002 && machine.ownedfp == 0x20001, "getcontext sets mc_len, mc_fpformat and mc_ownedfp"); + Require(machine.onstack == 0 && machine.trapno == 0 && machine.addr == 0 && machine.flags == 0 && machine.err == 0, "getcontext zeroes the trap fields"); + Require(machine.fsbase == 0 && machine.gsbase == 0 && machine.lbrfrom == 0 && machine.lbrto == 0 && machine.aux1 == 0 && machine.aux2 == 0, "getcontext zeroes the bases and branch records"); + Require(context.sigmask[0] == 0x24 && context.sigmask[1] == 0 && context.sigmask[2] == 0 && context.sigmask[3] == 0, "getcontext saves the signal mask"); + for (const auto word : context.reserved) Require(word == 0, "getcontext zeroes the words after the signal mask"); + std::uint32_t mxcsr = 0; + std::memcpy(&mxcsr, reinterpret_cast(machine.fpstate) + FxsaveMxcsr, sizeof(mxcsr)); + std::uint16_t control = 0; + std::memcpy(&control, machine.fpstate, sizeof(control)); + Require(mxcsr == _mm_getcsr() && control == ControlWord(), "getcontext saves MXCSR and the x87 control word"); + for (std::size_t index = 64; index < 104; ++index) Require(machine.fpstate[index] == 0, "getcontext zeroes mc_fpstate after the FXSAVE image"); + Require(std::memcmp(&context.link, &untouched.link, sizeof(GuestUcontext) - offsetof(GuestUcontext, link)) == 0, "getcontext keeps uc_link, uc_stack and uc_flags"); +} + +void CheckRestore() { + const auto mxcsrBefore = _mm_getcsr(); + const auto controlBefore = ControlWord(); + GuestUcontext target{}; + volatile int phase = 0; + Require(getcontext_nid_postfix(&ReturnContext) == 0, "capture the return context"); + if (phase == 0) { + phase = 1; + Require(getcontext_nid_postfix(&target) == 0, "capture the target template"); + auto& machine = target.mcontext; + machine.rax = Pattern(0); + machine.rbx = Pattern(1); + machine.rcx = Pattern(2); + machine.rdx = Pattern(3); + machine.rsi = Pattern(4); + machine.rdi = Pattern(5); + machine.rbp = Pattern(6); + machine.r8 = Pattern(8); + machine.r9 = Pattern(9); + machine.r10 = Pattern(10); + machine.r11 = Pattern(11); + machine.r12 = Pattern(12); + machine.r13 = Pattern(13); + machine.r14 = Pattern(14); + machine.r15 = Pattern(15); + machine.rsp = reinterpret_cast(g_restoreStack.bytes + StackBytes - 256); + machine.rip = reinterpret_cast(&RecordRegisters); + machine.rflags = (machine.rflags & ~UserChangeableFlags) | 0x8c1; + auto* image = reinterpret_cast(machine.fpstate); + const std::uint16_t control = 0x027f; + std::memcpy(image, &control, sizeof(control)); + const std::uint32_t mxcsr = 0x80007f80u; + std::memcpy(image + FxsaveMxcsr, &mxcsr, sizeof(mxcsr)); + for (unsigned index = 0; index < 32; ++index) { + const auto value = Pattern(index + 16); + std::memcpy(image + FxsaveXmm + index * 8, &value, sizeof(value)); + } + setcontext_nid_postfix(&target); + Require(false, "setcontext returned from a valid context"); + } + const auto& machine = target.mcontext; + Require(RecordedRegisters[0] == Pattern(0) && RecordedRegisters[1] == Pattern(1) && RecordedRegisters[2] == Pattern(2) && RecordedRegisters[3] == Pattern(3), "setcontext restores rax, rbx, rcx and rdx"); + Require(RecordedRegisters[4] == Pattern(4) && RecordedRegisters[5] == Pattern(5) && RecordedRegisters[6] == Pattern(6), "setcontext restores rsi, rdi and rbp"); + for (unsigned index = 8; index < 16; ++index) Require(RecordedRegisters[index] == Pattern(index), "setcontext restores r8 to r15"); + Require(RecordedRegisters[7] == machine.rsp, "setcontext restores rsp without touching the target stack"); + Require((RecordedRegisters[17] & UserChangeableFlags) == 0x8c1 && (RecordedRegisters[17] & InterruptFlag) != 0, "setcontext restores the user-changeable flags only"); + Require(RecordedMxcsr == 0x7f80u, "setcontext restores MXCSR without its reserved bits"); + Require(RecordedControlWord == 0x027f, "setcontext restores the x87 control word"); + for (unsigned index = 0; index < 32; ++index) { + std::uint64_t value = 0; + std::memcpy(&value, RecordedXmm + index * 8, sizeof(value)); + Require(value == Pattern(index + 16), "setcontext restores XMM0 to XMM15"); + } + Require(_mm_getcsr() == mxcsrBefore && ControlWord() == controlBefore, "the return context restores MXCSR and the x87 control word"); +} + +} + +extern "C" void APS5_VABI ContextBody(std::uint64_t first, std::uint64_t second, std::uint64_t third, std::uint64_t fourth, std::uint64_t fifth, std::uint64_t sixth) { + g_arguments[0] = first; + g_arguments[1] = second; + g_arguments[2] = third; + g_arguments[3] = fourth; + g_arguments[4] = fifth; + g_arguments[5] = sixth; + CheckStackBounds(&g_contextStack, "the stack bounds follow setcontext onto the context stack"); + ThrowAndCatch(); + ++g_entries; +} + +extern "C" void APS5_VABI ReturnWithoutLink() {} + +namespace { + +[[gnu::noinline]] void Switch(Fibre* from, Fibre* to) { + from->started = 1; + if (setjmp_nid_postfix(from->jump) == 0) { + if (to->started) longjmp_nid_postfix(to->jump, 1); + setcontext_nid_postfix(&to->context); + Require(false, "setcontext into the job returned"); + } +} + +} + +extern "C" void APS5_VABI JobEntry() { + for (;;) { + ++g_jobRuns; + CheckStackBounds(&g_jobStack, "the stack bounds follow longjmp onto the job stack"); + ThrowAndCatch(); + Switch(&g_job, &g_dispatcher); + } +} + +namespace { + +void CheckMakecontext() { +#ifdef _WIN32 + ULONG_PTR threadLow = 0; + ULONG_PTR threadHigh = 0; + GetCurrentThreadStackLimits(&threadLow, &threadHigh); +#endif + GuestUcontext caller{}; + GuestUcontext context{}; + volatile int phase = 0; + Require(getcontext_nid_postfix(&caller) == 0, "capture the caller"); + if (phase == 0) { + phase = 1; + Require(getcontext_nid_postfix(&context) == 0, "capture the context template"); + context.stackPointer = g_contextStack.bytes; + context.stackSize = StackBytes; + context.link = &caller; + makecontext_nid_postfix(&context, &EntryThunk, 6, 0x8000000000000001ull, 0x8000000000000002ull, 0x8000000000000003ull, 0x8000000000000004ull, 0x8000000000000005ull, 0x8000000000000006ull); + Require(context.mcontext.len == sizeof(GuestMcontext), "makecontext keeps the context valid"); + setcontext_nid_postfix(&context); + Require(false, "setcontext into the made context returned"); + } + Require(g_entries == 1, "the made context runs once and resumes uc_link"); + for (unsigned index = 0; index < 6; ++index) Require(g_arguments[index] == 0x8000000000000001ull + index, "makecontext passes six 64-bit arguments"); + const auto stackLow = reinterpret_cast(g_contextStack.bytes); + Require(EntryStackPointer > stackLow && EntryStackPointer < stackLow + StackBytes, "the made context runs on its own stack"); + Require((EntryStackPointer + 8) % 16 == 0, "the made context's function starts with an aligned stack"); +#ifdef _WIN32 + ULONG_PTR low = 0; + ULONG_PTR high = 0; + GetCurrentThreadStackLimits(&low, &high); + Require(low == threadLow && high == threadHigh, "the stack bounds follow setcontext back to the thread stack"); +#endif +} + +void CheckJumpBuffers() { +#ifdef _WIN32 + ULONG_PTR threadLow = 0; + ULONG_PTR threadHigh = 0; + GetCurrentThreadStackLimits(&threadLow, &threadHigh); +#endif + Require(getcontext_nid_postfix(&g_job.context) == 0, "capture the job template"); + g_job.context.stackPointer = g_jobStack.bytes; + g_job.context.stackSize = StackBytes; + g_job.context.link = nullptr; + makecontext_nid_postfix(&g_job.context, &JobEntry, 0); + for (int round = 1; round <= 4; ++round) { + Switch(&g_dispatcher, &g_job); + Require(g_jobRuns == round, "the job runs once per switch"); +#ifdef _WIN32 + ULONG_PTR low = 0; + ULONG_PTR high = 0; + GetCurrentThreadStackLimits(&low, &high); + Require(low == threadLow && high == threadHigh, "the stack bounds follow longjmp back to the thread stack"); +#endif + } +} + +void CheckSignalMask() { + std::uint32_t original[4] = {}; + Require(sigprocmask_nid_postfix(GuestSigBlock, nullptr, original) == 0, "read the signal mask"); + const std::uint32_t first[4] = {0x10, 0, 0, 0}; + const std::uint32_t second[4] = {0x20, 0, 0, 0}; + GuestUcontext context{}; + volatile int phase = 0; + Require(sigprocmask_nid_postfix(GuestSigSetmask, first, nullptr) == 0, "set the first mask"); + Require(getcontext_nid_postfix(&context) == 0, "capture the masked context"); + if (phase == 0) { + phase = 1; + Require(sigprocmask_nid_postfix(GuestSigSetmask, second, nullptr) == 0, "set the second mask"); + setcontext_nid_postfix(&context); + Require(false, "setcontext returned from a valid context"); + } + std::uint32_t current[4] = {}; + Require(sigprocmask_nid_postfix(GuestSigBlock, nullptr, current) == 0 && current[0] == 0x10, "setcontext restores the context's signal mask"); + Require(sigprocmask_nid_postfix(GuestSigSetmask, original, nullptr) == 0, "restore the signal mask"); +} + +int ExitFromContext() { + GuestUcontext context{}; + Require(getcontext_nid_postfix(&context) == 0, "capture the exiting template"); + context.stackPointer = g_exitStack.bytes; + context.stackSize = StackBytes; + context.link = nullptr; + makecontext_nid_postfix(&context, &ReturnWithoutLink, 0); + setcontext_nid_postfix(&context); + return 1; +} + +} + +int main(int argc, char** argv) { + if (argc > 1 && std::strcmp(argv[1], "exit-on-return") == 0) return ExitFromContext(); + CheckErrors(); + CheckCapture(); + CheckRestore(); + CheckSignalMask(); + std::thread([] { + CheckMakecontext(); + CheckJumpBuffers(); + GrowStack(32); + }).join(); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 84e255a60308..0f4cb288920c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -246,6 +246,11 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console. Handles that are not disk files return `ESPIPE`. `readv` on a pipe or console handle stops after the first iovec read, a short read the console is assumed to allow; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done - [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync` - [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way +- [getcontext and setcontext](../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) and [makecontext](../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) use the exception handler's `ucontext` layout (`mcontext` at 0x40, `uc_link` at 0x4c0, `uc_stack` at 0x4c8, 0x500 bytes); `getcontext` writes the 0x4c0 bytes before `uc_link`. Only `mc_rsp` of that layout is checked against a title. Homebrew built against FreeBSD's headers lays `ucontext_t` out in 0x370 bytes with `uc_link` at 0x330: PPSA99039's OpenSSL `ASYNC` (`crypto/async/arch/async_posix.c`) is built that way and would overrun or misread its contexts if the title started an `ASYNC` job +- [makecontext](../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) follows FreeBSD 11's `lib/libc/amd64/gen/makecontext.c`: at most six arguments, a stack of at least `MINSIGSTKSZ` (2048) bytes, and `exit(0)` when the function returns with no `uc_link`. The console's version is not verified +- [getcontext and setcontext](../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) keep the 512-byte FXSAVE image in `mc_fpstate` (x87, MXCSR, XMM0-15) and zero the rest of it, so the upper halves of YMM0-15, which the console's XSAVE area holds, are neither saved nor restored. `getcontext` sets `mc_fpformat` and `mc_ownedfp` to FreeBSD's `_MC_FPFMT_XMM` and `_MC_FPOWNED_FPU`, `mc_cs` and `mc_ss` to the host selectors, and `mc_flags` to 0. `setcontext` loads `mc_fpstate` whatever `mc_fpformat` and `mc_ownedfp` say, and does not restore the segment selectors or `mc_fsbase` and `mc_gsbase`, which FreeBSD restores when `mc_flags` asks for them +- [makecontext](../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) on Windows pins its stack writable until a later `makecontext` stack overlaps it, since nothing tells the library when the guest frees the stack +- [setcontext](../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) and [longjmp](../../core/libs/prx/libc/src/JumpBuffer.cpp) (libc) on Windows set the thread's stack bounds in the TEB to the stack they switch to: a `makecontext` stack, or else the committed region around the target stack pointer. A switch to any other guest stack, such as a context built by hand, gets the bounds of that region only - [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler, but a host fault still ends the process: only signals raised with `sceKernelRaiseException` are delivered. Unity's PS5Util module installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection - [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) delivers on Windows only and throws on Linux - [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) builds the handler's `ucontext` with FreeBSD's amd64 layout (16-byte sigmask, `mcontext` at 0x40, `mc_rsp` at 0xf8); the console's layout is not verified beyond the fields PS5Util reads