Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/libs/prx/libSceLibcInternal/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
63 changes: 63 additions & 0 deletions core/libs/prx/libSceLibcInternal/UserContext.cpp
Original file line number Diff line number Diff line change
@@ -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 <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdlib>

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<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);
}

}
1 change: 1 addition & 0 deletions core/libs/prx/libc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions core/libs/prx/libc/include/GuestStacks.hpp
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions core/libs/prx/libc/include/general/AsmMacros.hpp
Original file line number Diff line number Diff line change
@@ -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
94 changes: 94 additions & 0 deletions core/libs/prx/libc/src/GuestStacks.cpp
Original file line number Diff line number Diff line change
@@ -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
}

}
15 changes: 10 additions & 5 deletions core/libs/prx/libc/src/JumpBuffer.cpp
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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"
Expand Down
13 changes: 13 additions & 0 deletions core/libs/prx/libkernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -280,6 +281,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)
Expand Down
72 changes: 72 additions & 0 deletions core/libs/prx/libkernel/System/include/GuestContext.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_SYSTEM_INCLUDE_GUESTCONTEXT_HPP
#define CORE_LIBS_PRX_LIBKERNEL_SYSTEM_INCLUDE_GUESTCONTEXT_HPP

#include <cstddef>
#include <cstdint>

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
Loading