Merge pull request #3421 from DariusHQ/feat/libkernel-ucontext-freebsd
feat(libkernel): implement getcontext, setcontext and makecontext
This commit is contained in:
commit
0809bcc8ae
|
|
@ -1,4 +1,4 @@
|
|||
add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp FileTime.cpp)
|
||||
add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp FileTime.cpp UserContext.cpp)
|
||||
target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS})
|
||||
configure_windows_unwind(libSceLibcInternal)
|
||||
|
|
|
|||
|
|
@ -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/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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,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/Interfaces.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.cpp
|
||||
|
|
|
|||
|
|
@ -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 <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
#include "prx/libc/include/GuestStacks.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <windows.h>
|
||||
#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<std::uintptr_t, std::uintptr_t> ranges;
|
||||
|
||||
StackBounds CurrentBounds() {
|
||||
auto* tib = reinterpret_cast<NT_TIB*>(NtCurrentTeb());
|
||||
auto* teb = reinterpret_cast<std::uint8_t*>(tib);
|
||||
return {reinterpret_cast<std::uintptr_t>(tib->StackBase), reinterpret_cast<std::uintptr_t>(tib->StackLimit),
|
||||
*reinterpret_cast<std::uintptr_t*>(teb + DeallocationStackOffset)};
|
||||
}
|
||||
|
||||
void ApplyBounds(const StackBounds& bounds) {
|
||||
auto* tib = reinterpret_cast<NT_TIB*>(NtCurrentTeb());
|
||||
auto* teb = reinterpret_cast<std::uint8_t*>(tib);
|
||||
tib->StackBase = reinterpret_cast<void*>(bounds.base);
|
||||
tib->StackLimit = reinterpret_cast<void*>(bounds.limit);
|
||||
*reinterpret_cast<std::uintptr_t*>(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<const void*>(stackPointer), &memory, sizeof(memory)) != sizeof(memory) || memory.State != MEM_COMMIT) return false;
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(memory.BaseAddress);
|
||||
bounds = {base + memory.RegionSize, base, reinterpret_cast<std::uintptr_t>(memory.AllocationBase)};
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void GuestStackRegister_nid_no_patch(const void* pointer, std::size_t bytes) {
|
||||
#ifdef _WIN32
|
||||
const auto low = reinterpret_cast<std::uintptr_t>(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<const void*>(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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Signals.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/System/src/SignalSets.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
|
||||
|
|
@ -485,6 +486,18 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_pthread_key PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
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:$<TARGET_FILE_DIR:libkernel>")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBKERNEL_SYSTEM_INCLUDE_USERCONTEXT_HPP
|
||||
#define CORE_LIBS_PRX_LIBKERNEL_SYSTEM_INCLUDE_USERCONTEXT_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
struct UserMcontext {
|
||||
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;
|
||||
alignas(16) std::uint64_t fpstate[64];
|
||||
std::uint64_t fsbase;
|
||||
std::uint64_t gsbase;
|
||||
std::uint64_t xfpustate;
|
||||
std::uint64_t xfpustateLength;
|
||||
std::uint64_t spare[4];
|
||||
};
|
||||
|
||||
struct UserUcontext {
|
||||
std::uint32_t sigmask[4];
|
||||
UserMcontext mcontext;
|
||||
UserUcontext* link;
|
||||
void* stackPointer;
|
||||
std::uint64_t stackSize;
|
||||
std::int32_t stackFlags;
|
||||
std::int32_t stackAlign;
|
||||
std::int32_t flags;
|
||||
std::int32_t spare[4];
|
||||
};
|
||||
|
||||
static_assert(offsetof(UserMcontext, rsp) == 0xb8 && offsetof(UserMcontext, len) == 0xc8);
|
||||
static_assert(offsetof(UserMcontext, fpstate) == 0xe0 && offsetof(UserMcontext, fsbase) == 0x2e0);
|
||||
static_assert(sizeof(UserMcontext) == 0x320);
|
||||
static_assert(offsetof(UserUcontext, mcontext) == 0x10);
|
||||
static_assert(offsetof(UserUcontext, link) == 0x330);
|
||||
static_assert(offsetof(UserUcontext, stackPointer) == 0x338);
|
||||
static_assert(offsetof(UserUcontext, stackSize) == 0x340);
|
||||
static_assert(offsetof(UserUcontext, flags) == 0x350);
|
||||
static_assert(sizeof(UserUcontext) == 0x370);
|
||||
|
||||
#endif
|
||||
|
|
@ -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/UserContext.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <immintrin.h>
|
||||
|
||||
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(UserUcontext* context, const RegisterFrame* frame) {
|
||||
if (context == nullptr) return Fail(GuestEinval);
|
||||
UserUcontext 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(UserMcontext);
|
||||
machine.fpformat = FpFormatXmm;
|
||||
machine.ownedfp = FpOwnedFpu;
|
||||
std::memcpy(machine.fpstate, frame->fxsave, sizeof(frame->fxsave));
|
||||
std::memcpy(context, &captured, offsetof(UserUcontext, link));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI setcontext_nid_postfix(const UserUcontext* context) {
|
||||
if (context == nullptr) return Fail(GuestEinval);
|
||||
const auto& machine = context->mcontext;
|
||||
if (machine.len != sizeof(UserMcontext) || (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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,473 @@
|
|||
#include "prx/libc/include/general/AsmMacros.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/System/include/UserContext.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <xmmintrin.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using ContextEntry = void (APS5_VABI*)();
|
||||
|
||||
extern "C" {
|
||||
[[gnu::returns_twice]] int APS5_VABI getcontext_nid_postfix(UserUcontext* context);
|
||||
int APS5_VABI setcontext_nid_postfix(const UserUcontext* context);
|
||||
void APS5_VABI makecontext_nid_postfix(UserUcontext* 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(UserUcontext* 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] = {};
|
||||
UserUcontext 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 {
|
||||
UserUcontext 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<ULONG_PTR>(stack->bytes) && high == reinterpret_cast<ULONG_PTR>(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<unsigned char>(depth);
|
||||
page[sizeof(page) - 1] = static_cast<unsigned char>(depth);
|
||||
if (depth > 0) GrowStack(depth - 1);
|
||||
Require(page[0] == static_cast<unsigned char>(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");
|
||||
UserUcontext 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(UserMcontext);
|
||||
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;
|
||||
UserUcontext 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() {
|
||||
UserUcontext context;
|
||||
std::memset(&context, 0xa5, sizeof(context));
|
||||
UserUcontext 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<std::uint64_t>(&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<std::uint64_t>(&CaptureReturnAddress), "getcontext saves its return address");
|
||||
Require(machine.rsp == CapturedStackPointer, "getcontext saves the caller's stack pointer");
|
||||
Require(machine.len == sizeof(UserMcontext) && 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.xfpustate == 0 && machine.xfpustateLength == 0, "getcontext zeroes the bases and the extended FPU state");
|
||||
for (const auto word : machine.spare) Require(word == 0, "getcontext zeroes mc_spare");
|
||||
Require(context.sigmask[0] == 0x24 && context.sigmask[1] == 0 && context.sigmask[2] == 0 && context.sigmask[3] == 0, "getcontext saves the signal mask");
|
||||
std::uint32_t mxcsr = 0;
|
||||
std::memcpy(&mxcsr, reinterpret_cast<const std::uint8_t*>(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");
|
||||
Require(std::memcmp(&context.link, &untouched.link, sizeof(UserUcontext) - offsetof(UserUcontext, link)) == 0, "getcontext keeps uc_link, uc_stack and uc_flags");
|
||||
}
|
||||
|
||||
void CheckRestore() {
|
||||
const auto mxcsrBefore = _mm_getcsr();
|
||||
const auto controlBefore = ControlWord();
|
||||
UserUcontext 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<std::uint64_t>(g_restoreStack.bytes + StackBytes - 256);
|
||||
machine.rip = reinterpret_cast<std::uint64_t>(&RecordRegisters);
|
||||
machine.rflags = (machine.rflags & ~UserChangeableFlags) | 0x8c1;
|
||||
auto* image = reinterpret_cast<std::uint8_t*>(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
|
||||
UserUcontext caller{};
|
||||
UserUcontext 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(UserMcontext), "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<std::uint64_t>(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};
|
||||
UserUcontext 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() {
|
||||
UserUcontext 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;
|
||||
}
|
||||
|
|
@ -385,6 +385,11 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceKernelGetFsSandboxRandomWord](../../core/libs/prx/libkernel/System/src/Sandbox.cpp) (libkernel) returns a fixed directory name, `gkbHtXc3Wq`. On the console the name is the process's randomized path (syscall 602, `randomized_path`), and the sandbox exposes system files under it, such as `common/lib` and `common/cert`. Nothing is mounted under the name here, so `/gkbHtXc3Wq/common/...` resolves under the guest root like any other path and does not exist
|
||||
- [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) 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. On Linux, it skips a recorded file it cannot open for reading. 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 FreeBSD 11's amd64 `ucontext_t` (`mcontext` at 0x10, `mc_len` 800, `uc_link` at 0x330, `uc_stack` at 0x338, 0x370 bytes), the layout homebrew built against FreeBSD's headers passes: PPSA99039's OpenSSL `ASYNC` (`crypto/async/arch/async_posix.c`) captures into a 0x370-byte stack buffer. `getcontext` writes the 0x330 bytes before `uc_link`, as FreeBSD's `UC_COPY_SIZE`. The console's own layout for these functions is not verified; the exception handler's context is a different one (see `sceKernelRaiseException`)
|
||||
- [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), so the upper halves of YMM0-15 are neither saved nor restored, and `getcontext` leaves `mc_xfpustate` and `mc_xfpustate_len` 0. `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`, `mc_fsbase` and `mc_gsbase` to 0. `setcontext` loads `mc_fpstate` whatever `mc_fpformat` and `mc_ownedfp` say, and restores neither the segment selectors, `mc_fsbase` and `mc_gsbase` nor the extended state, 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) on Linux sends the host SIGUSR1, which the library takes over, to the target thread, so the handler runs wherever the thread was interrupted, emulator code included. On the console kernel objects are system calls and libthr defers signals while a thread holds its internal locks; here a handler that needs a host lock the interrupted code holds deadlocks, and a thread that the handler keeps waiting keeps its host locks. Host waits resume after the handler. The host calls Linux does not restart end early: sleeps that do not retry return before their time, and the poll of `sceNetEpollWait` and socket calls with a send or receive timeout return `EINTR` to the guest
|
||||
- [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
|
||||
|
|
|
|||
Loading…
Reference in New Issue