Merge pull request #10 from boykopovar/feat/sce-module-load

Feat/sce module load
The game can expect its modified prx from the `sce_module`/`sce_modules` folder. The relinker now converts PRX files from sce_module to `dll`/`so` format with `.guest.prx` postfix. During linking, libraries from `sce_module` are given priority. If a library from `sce_module` fails to import a symbol or contains a syscall, an exception will be thrown.

This made it possible to remove the GuestLocale formats that were customized for specific guests. Launched apps now use their own PRX files from the `sce_module` folder.
Locale handling functions now use `std::locale`.

Added a lot of posix functions to `libkernel` (they are thin wrappers and do what the existing non-posix versions do)
This commit is contained in:
boykopovar 2026-09-27 15:47:36 +03:00 committed by GitHub
commit e06dbff7d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 2713 additions and 807 deletions

View File

@ -361,12 +361,6 @@ add_executable(application_heap_tests EXCLUDE_FROM_ALL tests/ApplicationHeap.cpp
target_include_directories(application_heap_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(application_heap_tests PROPERTIES CXX_EXTENSIONS OFF)
add_executable(guest_locale_tests EXCLUDE_FROM_ALL tests/GuestLocale.cpp)
target_include_directories(guest_locale_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(guest_locale_tests PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(guest_locale_tests PRIVATE libc)
configure_windows_unwind(guest_locale_tests)
if(MINGW)
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")

View File

@ -173,9 +173,9 @@ using PthreadCondattr = PthreadCondattrPrivate*;
using PthreadKey = int;
using pthread_entry_func_t = void* (*)(void*);
using pthread_key_destructor_func_t = void (*)(void*);
using thread_dtors_func_t = void (*)();
using get_thread_atexit_count_func_t = int (*)(KernelModule);
using thread_atexit_report_func_t = void (*)(KernelModule);
using thread_dtors_func_t = void (APS5_VABI *)();
using get_thread_atexit_count_func_t = int (APS5_VABI *)(KernelModule);
using thread_atexit_report_func_t = void (APS5_VABI *)(KernelModule);
struct FileStat {
std::uint32_t st_dev;

View File

@ -1,14 +1,9 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
extern "C" {
void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) {
(void)info;
NotImplemented_nid_no_patch(__func__);
LibcHeapTraceInfo_nid_no_patch(info);
}
}

View File

@ -14,6 +14,7 @@ target_sources(libc PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestMemoryBacking.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeapStorage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/HeapDiagnostics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Mspace.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationHeap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationProcessParameters.cpp

View File

@ -2,6 +2,7 @@
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
uint32_t Need_sceLibc = 1;
@ -39,8 +40,7 @@ extern "C" {
}
void APS5_VABI LibcHeapGetTraceInfo_nid_postfix(LibcHeapInfo* info) {
(void)info;
NotImplemented_nid_no_patch(__func__);
LibcHeapTraceInfo_nid_no_patch(info);
}
int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (*destructor)(void*), void* object, void* module_id) {

View File

@ -1,66 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLOCALE_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLOCALE_HPP
#include <cstddef>
#include <cstdint>
#include "general/VabiMacros.hpp"
namespace GuestLocale {
struct LocinfoStorage {
alignas(std::uint64_t) unsigned char bytes[64];
};
struct Facet;
struct FacetVtable {
void (APS5_VABI *destroy)(Facet* self);
void (APS5_VABI *deleteObject)(Facet* self);
void (APS5_VABI *retain)(Facet* self);
Facet* (APS5_VABI *release)(Facet* self);
};
struct Facet {
const FacetVtable* vtable;
std::uint32_t references;
std::uint32_t reserved;
};
struct Implementation {
Facet base;
Facet** facets;
std::uint64_t facetCount;
std::uint32_t category;
bool transparent;
const char* name;
};
struct IosBase {
const void* vtable;
std::int64_t standardStream;
std::int32_t state;
std::int32_t exceptions;
std::int32_t flags;
std::int32_t precision;
std::int32_t width;
std::uint32_t reserved;
void* storage;
void* callbacks;
Implementation** locale;
};
static_assert(sizeof(Facet) == 0x10);
static_assert(offsetof(Implementation, facets) == 0x10);
static_assert(offsetof(Implementation, facetCount) == 0x18);
static_assert(offsetof(Implementation, transparent) == 0x24);
static_assert(offsetof(Implementation, name) == 0x28);
static_assert(offsetof(IosBase, state) == 0x10);
static_assert(offsetof(IosBase, storage) == 0x28);
static_assert(offsetof(IosBase, callbacks) == 0x30);
static_assert(offsetof(IosBase, locale) == 0x38);
static_assert(sizeof(IosBase) == 0x40);
static_assert(alignof(LocinfoStorage) == 8);
}
#endif

View File

@ -0,0 +1,8 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_HEAPDIAGNOSTICS_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_HEAPDIAGNOSTICS_HPP
#include "SceTypes.hpp"
extern "C" void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info);
#endif

View File

@ -3,5 +3,6 @@
extern "C" void LibcRegisterShutdown_nid_postfix(void (*callback)());
extern "C" void LibcRunShutdown_nid_postfix();
extern "C" [[noreturn]] void LibcExit_nid_no_patch(int code);
#endif

View File

@ -0,0 +1,45 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
namespace {
std::uint64_t mspaceAtomicIdMask = 0;
std::array<std::uint64_t, 64> mstateTable{};
static_assert(sizeof(LibcHeapInfo) == 32);
static_assert(offsetof(LibcHeapInfo, mspace_atomic_id_mask) == 16);
static_assert(offsetof(LibcHeapInfo, mstate_table) == 24);
}
extern "C" {
void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info) {
if (info == nullptr)
throw std::invalid_argument("heap trace info: null output");
if (info->size != sizeof(LibcHeapInfo))
throw std::invalid_argument("heap trace info: unsupported structure size");
info->unknown2 = 0;
info->mspace_atomic_id_mask = &mspaceAtomicIdMask;
info->mstate_table = mstateTable.data();
}
// unknown signature
void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName) {
(void)heapName;
NotImplemented_nid_no_patch(__func__);
}
// unknown signature
void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error) {
(void)heap;
(void)block;
(void)error;
NotImplemented_nid_no_patch(__func__);
}
}

View File

@ -1,203 +1,141 @@
#include <array>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cwchar>
#include <cwctype>
#include <ios>
#include <mutex>
#include <atomic>
#include <cstring>
#include <vector>
#include <array>
#include <limits>
#include <locale>
#include <memory>
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/GuestLocale.hpp"
namespace {
std::mutex g_localeInitMutex;
bool g_localeInitialized = false;
std::vector<GuestLocale::Facet*> g_registeredFacets;
void APS5_VABI DestroyClassicLocale(GuestLocale::Facet*) {
throw std::runtime_error("Cannot destroy the classic locale");
void ValidateCharacter(int value) {
if (value != EOF && (value < 0 || value > UCHAR_MAX)) throw std::invalid_argument("Invalid character value");
}
void APS5_VABI RetainLocale(GuestLocale::Facet* self) {
if (self == nullptr) throw std::invalid_argument("locale retain: null facet");
std::atomic_ref<std::uint32_t> references(self->references);
auto count = references.load();
do {
if (count == 0 || count == UINT32_MAX) throw std::runtime_error("locale retain: invalid reference count");
} while (!references.compare_exchange_weak(count, count + 1));
int ClassifyCharacter(int value, std::ctype_base::mask mask) {
ValidateCharacter(value);
if (value == EOF) return 0;
return std::use_facet<std::ctype<char>>(std::locale::classic()).is(mask, static_cast<char>(value));
}
GuestLocale::Facet* APS5_VABI ReleaseLocale(GuestLocale::Facet* self) {
if (self == nullptr) throw std::invalid_argument("locale release: null facet");
std::atomic_ref<std::uint32_t> references(self->references);
auto count = references.load();
do {
if (count <= 1) throw std::runtime_error("classic locale: unbalanced release");
} while (!references.compare_exchange_weak(count, count - 1));
return nullptr;
int ConvertCharacter(int value, bool upper) {
ValidateCharacter(value);
if (value == EOF) return EOF;
const auto& facet = std::use_facet<std::ctype<char>>(std::locale::classic());
const auto character = static_cast<char>(value);
return static_cast<unsigned char>(upper ? facet.toupper(character) : facet.tolower(character));
}
const GuestLocale::FacetVtable g_localeVtable{DestroyClassicLocale, DestroyClassicLocale, RetainLocale, ReleaseLocale};
GuestLocale::Facet* g_classicFacets[1]{};
GuestLocale::Implementation g_classicLocale{{&g_localeVtable, 1, 0}, g_classicFacets, 1, 0, false, "C"};
constexpr std::array<short, 257> MakeClassificationTable() {
std::array<short, 257> table{};
for (unsigned int value = 0; value < 256; ++value) {
short mask = 0;
if (value < 32 || value == 127) mask |= 0x80;
if (value == ' ') mask |= 0x04;
if (value >= '\t' && value <= '\r') mask |= 0x40;
if (value >= 'A' && value <= 'Z') mask |= 0x02;
if (value >= 'a' && value <= 'z') mask |= 0x10;
if (value >= '0' && value <= '9') mask |= 0x20;
if ((value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f')) mask |= 0x01;
if (value >= 33 && value <= 126 && (mask & 0x232) == 0) mask |= 0x08;
table[value + 1] = mask;
}
std::array<int, UCHAR_MAX + 2> MakeCaseTable(bool upper) {
std::array<int, UCHAR_MAX + 2> table{};
table[0] = EOF;
for (int value = 0; value <= UCHAR_MAX; ++value) table[value + 1] = ConvertCharacter(value, upper);
return table;
}
constexpr std::array<short, 257> MakeCaseTable(bool upper) {
std::array<short, 257> table{};
table[0] = -1;
for (unsigned int value = 0; value < 256; ++value) {
auto converted = value;
if (upper && value >= 'a' && value <= 'z') converted -= 'a' - 'A';
if (!upper && value >= 'A' && value <= 'Z') converted += 'a' - 'A';
table[value + 1] = static_cast<short>(converted);
}
return table;
}
constexpr auto g_classificationTable = MakeClassificationTable();
constexpr auto g_lowerTable = MakeCaseTable(false);
constexpr auto g_upperTable = MakeCaseTable(true);
}
extern "C" {
// The runtime currently exposes the classic C locale. Keep byte classification
// independent of any locale selected by host-side libraries.
int APS5_VABI isupper_nid_postfix(int c) { return c >= 'A' && c <= 'Z'; }
int APS5_VABI islower_nid_postfix(int c) { return c >= 'a' && c <= 'z'; }
int APS5_VABI isalpha_nid_postfix(int c) { return isupper_nid_postfix(c) || islower_nid_postfix(c); }
int APS5_VABI isdigit_nid_postfix(int c) { return c >= '0' && c <= '9'; }
int APS5_VABI isalnum_nid_postfix(int c) { return isalpha_nid_postfix(c) || isdigit_nid_postfix(c); }
int APS5_VABI isspace_nid_postfix(int c) { return c == ' ' || (c >= '\t' && c <= '\r'); }
int APS5_VABI isblank_nid_postfix(int c) { return c == ' ' || c == '\t'; }
int APS5_VABI iscntrl_nid_postfix(int c) { return (c >= 0 && c < 32) || c == 127; }
int APS5_VABI isprint_nid_postfix(int c) { return c >= 32 && c <= 126; }
int APS5_VABI isgraph_nid_postfix(int c) { return c >= 33 && c <= 126; }
int APS5_VABI ispunct_nid_postfix(int c) { return isgraph_nid_postfix(c) && !isalnum_nid_postfix(c); }
int APS5_VABI isxdigit_nid_postfix(int c) {
return isdigit_nid_postfix(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
int APS5_VABI toupper_nid_postfix(int c) { return islower_nid_postfix(c) ? c - ('a' - 'A') : c; }
int APS5_VABI tolower_nid_postfix(int c) { return isupper_nid_postfix(c) ? c + ('a' - 'A') : c; }
std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix = 0;
std::uint64_t _ZNSt5ctypeIwE2idE_nid_postfix = 0;
std::uint64_t _ZNSt7collateIwE2idE_nid_postfix = 0;
std::uint64_t _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[12] {};
int APS5_VABI isupper_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::upper); }
int APS5_VABI islower_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::lower); }
int APS5_VABI isalpha_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alpha); }
int APS5_VABI isdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::digit); }
int APS5_VABI isalnum_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alnum); }
int APS5_VABI isspace_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::space); }
int APS5_VABI isblank_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::blank); }
int APS5_VABI iscntrl_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::cntrl); }
int APS5_VABI isprint_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::print); }
int APS5_VABI isgraph_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::graph); }
int APS5_VABI ispunct_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::punct); }
int APS5_VABI isxdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::xdigit); }
int APS5_VABI toupper_nid_postfix(int c) { return ConvertCharacter(c, true); }
int APS5_VABI tolower_nid_postfix(int c) { return ConvertCharacter(c, false); }
std::streamoff _ZSt7_BADOFF_nid_postfix = -1;
std::fpos_t _ZSt4_Fpz_nid_postfix {};
std::int32_t _ZNSt6locale2id7_Id_cntE_nid_postfix = 0;
std::fpos_t _ZSt4_Fpz_nid_postfix{};
GuestLocale::Implementation* _ZSt21_sceLibcClassicLocale_nid_postfix = &g_classicLocale;
const std::locale* _ZSt21_sceLibcClassicLocale_nid_postfix = &std::locale::classic();
void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(GuestLocale::IosBase* self) {
void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(std::ios_base* self) {
if (self == nullptr) throw std::invalid_argument("ios_base destructor: null object");
if (self->standardStream != 0 || self->storage != nullptr || self->callbacks != nullptr) throw std::runtime_error("ios_base destructor: unsupported stream storage or callbacks");
if (self->locale != &_ZSt21_sceLibcClassicLocale_nid_postfix) throw std::runtime_error("ios_base destructor: unsupported locale ownership");
self->locale = nullptr;
self->std::ios_base::~ios_base();
}
GuestLocale::Implementation* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix() {
std::lock_guard<std::mutex> lock(g_localeInitMutex);
if (!g_localeInitialized) {
g_localeInitialized = true;
}
return &g_classicLocale;
const std::locale* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix() {
return &std::locale::classic();
}
void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(GuestLocale::Facet* self) {
if (self == nullptr || self->vtable == nullptr) throw std::invalid_argument("locale register: invalid facet");
std::lock_guard<std::mutex> lock(g_localeInitMutex);
for (const auto* facet : g_registeredFacets) {
if (facet == self) throw std::runtime_error("locale register: duplicate facet");
}
g_registeredFacets.push_back(self);
void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(std::locale::facet*) {
NotImplemented_nid_no_patch("locale::facet::_Register");
}
GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix() {
return &g_classicLocale;
const std::locale* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix() {
thread_local std::locale locale;
locale = std::locale();
return &locale;
}
void APS5_VABI _ZNSt7collateIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet**, const GuestLocale::Implementation*) {
NotImplemented_nid_no_patch(__func__);
void APS5_VABI _ZNSt7collateIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(const std::locale::facet**, const std::locale*) {
NotImplemented_nid_no_patch("collate<wchar_t>::_Getcat");
}
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName) {
if (self == nullptr || localeName == nullptr || std::strcmp(localeName, "C") != 0) throw std::invalid_argument("_Locinfo: only the C locale is supported");
new (self) GuestLocale::LocinfoStorage{};
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(std::locale* self, const char* localeName) {
if (self == nullptr || localeName == nullptr) throw std::invalid_argument("Locale constructor: null argument");
std::construct_at(self, localeName);
}
void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(GuestLocale::LocinfoStorage* self) {
if (self == nullptr) throw std::invalid_argument("_Locinfo destructor: null object");
void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(std::locale* self) {
if (self == nullptr) throw std::invalid_argument("Locale destructor: null object");
std::destroy_at(self);
}
int APS5_VABI _Mbtowcx_nid_postfix(std::uint16_t* dst, const char* src, std::size_t count, mbstate_t* st) {
if (dst == nullptr || src == nullptr || st == nullptr || count == 0) throw std::invalid_argument("_Mbtowcx: invalid conversion arguments");
wchar_t converted{};
const auto result = std::mbrtowc(&converted, src, count, st);
std::size_t APS5_VABI _Mbtowcx_nid_postfix(wchar_t* dst, const char* src, std::size_t count, std::mbstate_t* state) {
if (dst == nullptr || src == nullptr || state == nullptr || count == 0) throw std::invalid_argument("_Mbtowcx: invalid conversion arguments");
const auto result = std::mbrtowc(dst, src, count, state);
if (result == static_cast<std::size_t>(-1)) throw std::runtime_error("_Mbtowcx: invalid multibyte character");
if (result == static_cast<std::size_t>(-2)) throw std::runtime_error("_Mbtowcx: incomplete multibyte character");
if (result > static_cast<std::size_t>(std::numeric_limits<int>::max()) || static_cast<std::uint32_t>(converted) > 0xffff) throw std::runtime_error("_Mbtowcx: conversion exceeds guest character limits");
*dst = static_cast<std::uint16_t>(converted);
return static_cast<int>(result);
return result;
}
int APS5_VABI _Wctombx_nid_postfix(char* dst, std::uint16_t src, mbstate_t* st) {
if (dst == nullptr || st == nullptr) throw std::invalid_argument("_Wctombx: invalid conversion arguments");
const auto result = std::wcrtomb(dst, static_cast<wchar_t>(src), st);
std::size_t APS5_VABI _Wctombx_nid_postfix(char* dst, wchar_t src, std::mbstate_t* state) {
if (dst == nullptr || state == nullptr) throw std::invalid_argument("_Wctombx: invalid conversion arguments");
const auto result = std::wcrtomb(dst, src, state);
if (result == static_cast<std::size_t>(-1)) throw std::runtime_error("_Wctombx: invalid wide character");
if (result > static_cast<std::size_t>(std::numeric_limits<int>::max())) throw std::runtime_error("_Wctombx: conversion size exceeds guest limits");
return static_cast<int>(result);
return result;
}
const short* APS5_VABI _Getpctype_nid_postfix() {
return g_classificationTable.data() + 1;
const std::ctype<char>::mask* APS5_VABI _Getpctype_nid_postfix() {
return std::ctype<char>::classic_table();
}
const short* APS5_VABI _Getptolower_nid_postfix() {
return g_lowerTable.data() + 1;
const int* APS5_VABI _Getptolower_nid_postfix() {
static const auto table = MakeCaseTable(false);
return table.data() + 1;
}
const short* APS5_VABI _Getptoupper_nid_postfix() {
return g_upperTable.data() + 1;
const int* APS5_VABI _Getptoupper_nid_postfix() {
static const auto table = MakeCaseTable(true);
return table.data() + 1;
}
mbstate_t* APS5_VABI _Getpmbstate_nid_postfix() {
thread_local mbstate_t state {};
std::mbstate_t* APS5_VABI _Getpmbstate_nid_postfix() {
thread_local std::mbstate_t state{};
return &state;
}
mbstate_t* APS5_VABI _Getpwcstate_nid_postfix() {
thread_local mbstate_t state {};
std::mbstate_t* APS5_VABI _Getpwcstate_nid_postfix() {
thread_local std::mbstate_t state{};
return &state;
}
wint_t APS5_VABI _Towctrans_nid_postfix(wint_t c, wctrans_t desc) {
std::wint_t APS5_VABI _Towctrans_nid_postfix(std::wint_t c, std::wctrans_t desc) {
if (desc == std::wctrans_t{}) throw std::invalid_argument("_Towctrans: invalid transformation");
return std::towctrans(c, desc);
}

View File

@ -61,11 +61,15 @@ extern "C" {
std::_Exit(code);
}
void APS5_VABI exit_nid_postfix(int code) {
[[noreturn]] void LibcExit_nid_no_patch(int code) {
LibcRunShutdown_nid_postfix();
std::exit(code);
}
void APS5_VABI exit_nid_postfix(int code) {
LibcExit_nid_no_patch(code);
}
[[noreturn]] void abort_nid_postfix(
uint64_t arg0, uint64_t arg1, uint64_t arg2,
uint64_t arg3, uint64_t arg4, uint64_t arg5

View File

@ -202,3 +202,14 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries,
}
}
extern "C" {
int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length) {
(void)address;
(void)length;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -7,6 +7,7 @@
namespace File {
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb);
void FillFileStat(int nativeDescriptor, FileStat* sb);
}

View File

@ -10,21 +10,21 @@ using NativeStat = struct __stat64;
static int DoStat(const std::filesystem::path& p, NativeStat* st) {
return _wstat64(p.wstring().c_str(), st);
}
static int DoFstat(int fd, NativeStat* st) {
return _fstat64(fd, st);
}
#else
#include <sys/stat.h>
using NativeStat = struct stat;
static int DoStat(const std::filesystem::path& p, NativeStat* st) {
return ::stat(p.c_str(), st);
}
static int DoFstat(int fd, NativeStat* st) {
return ::fstat(fd, st);
}
#endif
namespace File {
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
NativeStat st{};
if (DoStat(nativePath, &st) != 0) {
throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string());
}
static void CopyNativeStat(const NativeStat& st, FileStat* sb) {
*sb = FileStat{};
sb->st_mode = static_cast<std::uint16_t>(st.st_mode);
sb->st_size = static_cast<std::int64_t>(st.st_size);
@ -72,4 +72,22 @@ void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
#endif
}
namespace File {
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
NativeStat st{};
if (DoStat(nativePath, &st) != 0) {
throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string());
}
CopyNativeStat(st, sb);
}
void FillFileStat(int nativeDescriptor, FileStat* sb) {
NativeStat st{};
if (DoFstat(nativeDescriptor, &st) != 0) {
throw std::runtime_error(std::string("FillFileStat: fstat failed for fd ") + std::to_string(nativeDescriptor));
}
CopyNativeStat(st, sb);
}
}

View File

@ -1,21 +1,115 @@
#include <cstdint>
#include <cstddef>
#include <limits>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include "prx/libkernel/File/include/NativeStat.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include <cerrno>
#include <cstdarg>
#include <string>
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#include <direct.h>
#include <sys/stat.h>
static int NativeRmdir(const std::filesystem::path& path) {
return ::_wrmdir(path.wstring().c_str());
}
static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) {
(void)mode;
return ::_wmkdir(path.wstring().c_str());
}
static int NativeChmod(const std::filesystem::path& path, int mode) {
return ::_wchmod(path.wstring().c_str(), mode);
}
static int NativeFtruncate(int descriptor, std::int64_t length) {
return static_cast<int>(::_chsize_s(descriptor, length));
}
static int NativeFlock(int descriptor, int operation) {
HANDLE handle = reinterpret_cast<HANDLE>(::_get_osfhandle(descriptor));
if (handle == INVALID_HANDLE_VALUE) {
return -1;
}
OVERLAPPED overlapped{};
if (operation & 8) {
return ::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1;
}
DWORD flags = 0;
if (operation & 2) flags |= LOCKFILE_EXCLUSIVE_LOCK;
if (operation & 4) flags |= LOCKFILE_FAIL_IMMEDIATELY;
return ::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1;
}
static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) {
if (nbytes > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
throw std::runtime_error("NativePread: nbytes exceeds platform limit");
}
std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR);
if (saved < 0) {
return -1;
}
if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) {
return -1;
}
int n = ::_read(descriptor, buf, static_cast<unsigned int>(nbytes));
::_lseeki64(descriptor, saved, SEEK_SET);
return n;
}
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
if (nbytes > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
throw std::runtime_error("NativePwrite: nbytes exceeds platform limit");
}
std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR);
if (saved < 0) {
return -1;
}
if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) {
return -1;
}
int n = ::_write(descriptor, buf, static_cast<unsigned int>(nbytes));
::_lseeki64(descriptor, saved, SEEK_SET);
return n;
}
#else
#include <unistd.h>
#include <sys/stat.h>
#include <sys/file.h>
static int NativeRmdir(const std::filesystem::path& path) {
return ::rmdir(path.c_str());
}
static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) {
return ::mkdir(path.c_str(), static_cast<mode_t>(mode));
}
static int NativeChmod(const std::filesystem::path& path, int mode) {
return ::chmod(path.c_str(), static_cast<mode_t>(mode));
}
static int NativeFtruncate(int descriptor, std::int64_t length) {
return ::ftruncate(descriptor, static_cast<off_t>(length));
}
static int NativeFlock(int descriptor, int operation) {
return ::flock(descriptor, operation);
}
static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) {
return static_cast<std::int64_t>(::pread(descriptor, buf, nbytes, static_cast<off_t>(offset)));
}
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
return static_cast<std::int64_t>(::pwrite(descriptor, buf, nbytes, static_cast<off_t>(offset)));
}
#endif
extern "C" {
int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
(void)path;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
if (path == nullptr) {
APS5_INVALID_ARG_EX;
}
auto native = ResolvePath_nid_no_patch(path);
if (NativeChmod(native, mode) != 0) {
throw std::runtime_error(std::string(__func__) + ": chmod failed for " + native.string() + ", errno=" + std::to_string(errno));
}
return 0;
}
int APS5_VABI close_nid_postfix(int d) {
@ -27,89 +121,136 @@ int APS5_VABI close_nid_postfix(int d) {
#endif
}
int APS5_VABI _close_nid_postfix(int descriptor) {
return close_nid_postfix(descriptor);
}
int APS5_VABI flock_nid_postfix(int d, int operation) {
(void)d;
(void)operation;
NotImplemented_nid_no_patch(__func__);
return 0;
if (NativeFlock(d, operation) != 0) {
#ifdef _WIN32
throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", error=" + std::to_string(::GetLastError()));
#else
throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
#endif
}
return 0;
}
int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) {
(void)d;
(void)sb;
NotImplemented_nid_no_patch(__func__);
return 0;
if (sb == nullptr) {
APS5_INVALID_ARG_EX;
}
File::FillFileStat(d, sb);
return 0;
}
int APS5_VABI ftruncate_nid_postfix(int d, int64_t length) {
(void)d;
(void)length;
NotImplemented_nid_no_patch(__func__);
return 0;
if (length < 0) {
APS5_INVALID_ARG_EX;
}
#ifdef _WIN32
int error = NativeFtruncate(d, length);
if (error != 0) {
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", error=" + std::to_string(error));
}
#else
if (NativeFtruncate(d, length) != 0) {
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
#endif
return 0;
}
int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) {
(void)d;
(void)offset;
(void)whence;
NotImplemented_nid_no_patch(__func__);
return 0;
return static_cast<int64_t>(sceKernelLseek(d, offset, whence));
}
int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) {
(void)path;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
if (path == nullptr) {
APS5_INVALID_ARG_EX;
}
auto native = ResolvePath_nid_no_patch(path);
if (NativeMkdir(native, mode) != 0) {
throw std::runtime_error(std::string(__func__) + ": mkdir failed for " + native.string() + ", errno=" + std::to_string(errno));
}
return 0;
}
int APS5_VABI open_nid_postfix(const char* path, int flags, int mode) {
(void)path;
(void)flags;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
return sceKernelOpen(path, flags, static_cast<std::uint16_t>(mode));
}
int APS5_VABI _open_nid_postfix(const char* path, int flags, ...) {
std::uint16_t mode = 0;
if (flags & SCE_KERNEL_O_CREAT) {
#ifdef _WIN32
__builtin_sysv_va_list arguments;
__builtin_sysv_va_start(arguments, flags);
mode = static_cast<std::uint16_t>(__builtin_va_arg(arguments, int));
__builtin_sysv_va_end(arguments);
#else
std::va_list arguments;
va_start(arguments, flags);
mode = static_cast<std::uint16_t>(va_arg(arguments, int));
va_end(arguments);
#endif
}
return sceKernelOpen(path, flags, mode);
}
int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t offset) {
(void)d;
(void)buf;
(void)nbytes;
(void)offset;
NotImplemented_nid_no_patch(__func__);
return 0;
if (buf == nullptr) {
APS5_INVALID_ARG_EX;
}
if (offset < 0) {
APS5_INVALID_ARG_EX;
}
auto n = NativePread(d, buf, nbytes, offset);
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": pread failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
return n;
}
int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset) {
(void)d;
(void)buf;
(void)nbytes;
(void)offset;
NotImplemented_nid_no_patch(__func__);
return 0;
if (buf == nullptr) {
APS5_INVALID_ARG_EX;
}
if (offset < 0) {
APS5_INVALID_ARG_EX;
}
auto n = NativePwrite(d, buf, nbytes, offset);
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": pwrite failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
return n;
}
int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) {
(void)d;
(void)buf;
(void)nbytes;
NotImplemented_nid_no_patch(__func__);
return 0;
return sceKernelRead(d, buf, static_cast<size_t>(nbytes));
}
std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) {
return sceKernelRead(descriptor, buffer, count);
}
int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) {
(void)d;
(void)str;
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
if (size < 0) {
APS5_INVALID_ARG_EX;
}
return sceKernelWrite(d, str, static_cast<size_t>(size));
}
std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) {
return sceKernelWrite(descriptor, buffer, count);
}
int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb) {
(void)path;
(void)sb;
NotImplemented_nid_no_patch(__func__);
return 0;
return sceKernelStat(path, sb);
}
int APS5_VABI unlink_nid_postfix(const char* path) {
return sceKernelUnlink(path);
}
int APS5_VABI sceKernelCheckReachability(const char* path) {
@ -181,9 +322,43 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) {
}
int APS5_VABI sceKernelRmdir(const char* path) {
(void)path;
NotImplemented_nid_no_patch(__func__);
return 0;
if (path == nullptr) {
throw std::invalid_argument(std::string(__func__) + ": path is null");
}
auto native = ResolvePath_nid_no_patch(path);
if (NativeRmdir(native) != 0) {
throw std::runtime_error(std::string(__func__) + ": rmdir failed for " + native.string() + ", errno=" + std::to_string(errno));
}
return 0;
}
int APS5_VABI rmdir_nid_postfix(const char* path) {
return sceKernelRmdir(path);
}
}
extern "C" {
int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) {
(void)path;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length) {
(void)path;
(void)length;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* times) {
(void)path;
(void)times;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -52,3 +52,21 @@ int APS5_VABI sceKernelStopUnloadModule(KernelModule handle, size_t args, const
}
}
extern "C" {
int APS5_VABI __elf_phdr_match_addr_nid_postfix(ModuleInfo* module, std::uint64_t address) {
(void)module;
(void)address;
NotImplemented_nid_no_patch(__func__);
return 0;
}
// unknown signature
std::int32_t APS5_VABI sceKernelInternalMemoryGetModuleSegmentInfo_nid_postfix(void* result) {
(void)result;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -1,61 +1,52 @@
#include "prx/libkernel/Pthread/include/Cond.hpp"
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include <stdexcept>
namespace {
int toPosix(int result) {
if (result == 0)
return 0;
const auto error = static_cast<std::uint32_t>(result);
if ((error & 0xffff0000u) != 0x80020000u)
throw std::runtime_error("Unexpected SCE condition variable error");
return static_cast<int>(error & 0xffffu);
}
}
extern "C" {
int APS5_VABI pthread_cond_broadcast_nid_postfix(PthreadCond* cond) {
(void)cond;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadCondBroadcast(cond));
}
int APS5_VABI pthread_cond_init_nid_postfix(PthreadCond* cond, const PthreadCondattr* attr) {
(void)cond;
(void)attr;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadCondInit(cond, attr, nullptr));
}
int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond) {
(void)cond;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadCondSignal(cond));
}
int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) {
(void)cond;
(void)mutex;
(void)abstime;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(CondOperations::AbsoluteTimedwait(cond, mutex, abstime));
}
int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex) {
(void)cond;
(void)mutex;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadCondWait(cond, mutex));
}
int APS5_VABI pthread_condattr_destroy_nid_postfix(PthreadCondattr* attr) {
(void)attr;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadCondattrDestroy(attr));
}
int APS5_VABI pthread_condattr_init_nid_postfix(PthreadCondattr* attr) {
(void)attr;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadCondattrInit(attr));
}
int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, KernelClockid clock_id) {
(void)attr;
(void)clock_id;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadCondattrSetclock(attr, clock_id));
}
}

View File

@ -1,72 +1,60 @@
#include "../include/Mutex.hpp"
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include <stdexcept>
namespace {
int toPosix(int result) {
if (result == 0)
return 0;
const auto error = static_cast<std::uint32_t>(result);
if ((error & 0xffff0000u) != 0x80020000u)
throw std::runtime_error("Unexpected SCE mutex error");
return static_cast<int>(error & 0xffffu);
}
}
extern "C" {
int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex) {
(void)mutex;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexDestroy(mutex));
}
int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr) {
(void)mutex;
(void)attr;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexInit(mutex, attr, nullptr));
}
int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex) {
(void)mutex;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexLock(mutex));
}
int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime) {
(void)mutex;
(void)abstime;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(MutexOperations::Timedlock(mutex, abstime));
}
int APS5_VABI pthread_mutex_trylock_nid_postfix(PthreadMutex* mutex) {
(void)mutex;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexTrylock(mutex));
}
int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex) {
(void)mutex;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexUnlock(mutex));
}
int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr) {
(void)attr;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexattrDestroy(attr));
}
int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr) {
(void)attr;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexattrInit(attr));
}
int APS5_VABI pthread_mutexattr_setprotocol_nid_postfix(PthreadMutexattr* attr, int protocol) {
(void)attr;
(void)protocol;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexattrSetprotocol(attr, protocol));
}
int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int type) {
(void)attr;
(void)type;
NotImplemented_nid_no_patch(__func__);
return 0;
return toPosix(scePthreadMutexattrSettype(attr, type));
}
}
}

View File

@ -1,3 +1,4 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <condition_variable>
#include <cstddef>
@ -41,3 +42,16 @@ extern "C" int APS5_VABI pthread_once_nid_postfix(
onceChanged.notify_all();
return 0;
}
using OnceRoutine = void (APS5_VABI *)();
extern "C" {
int APS5_VABI scePthreadOnce_nid_postfix(void* onceControl, OnceRoutine initRoutine) {
(void)onceControl;
(void)initRoutine;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -2,6 +2,20 @@
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "../include/Rwlock.hpp"
namespace {
int toPosix(int result) {
if (result == 0)
return 0;
const auto error = static_cast<std::uint32_t>(result);
if ((error & 0xffff0000u) != 0x80020000u)
throw std::runtime_error("Unexpected SCE rwlock error");
return static_cast<int>(error & 0xffffu);
}
}
extern "C" {
@ -18,6 +32,14 @@ int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock* rwlock, const Pthre
return 0;
}
int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock) {
return toPosix(scePthreadRwlockRdlock(rwlock));
}
int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock) {
return toPosix(scePthreadRwlockUnlock(rwlock));
}
int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock) {
(void)rwlock;
NotImplemented_nid_no_patch(__func__);

View File

@ -1,6 +1,7 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "../include/ThreadLifecycle.hpp"
#include "prx/libc/include/General.hpp"
@ -32,8 +33,7 @@ int APS5_VABI pthread_detach_nid_postfix(Pthread thread) {
}
void APS5_VABI pthread_exit_nid_postfix(void* value) {
(void)value;
NotImplemented_nid_no_patch(__func__);
scePthreadExit(value);
}
int APS5_VABI pthread_getschedparam_nid_postfix(Pthread thread, int* policy, KernelSchedParam* param) {

View File

@ -0,0 +1,26 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_COND_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_COND_HPP
#include "SceTypes.hpp"
class CondOperations {
public:
static int AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime);
};
extern "C" {
int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr);
int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr);
int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId);
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond);
int APS5_VABI scePthreadCondSignal(PthreadCond* cond);
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond);
int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread);
int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex);
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec);
}
#endif

View File

@ -0,0 +1,26 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_MUTEX_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_MUTEX_HPP
#include "SceTypes.hpp"
class MutexOperations {
public:
static int Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime);
};
extern "C" {
int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr);
int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr);
int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type);
int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol);
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name);
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec);
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex);
}
#endif

View File

@ -28,7 +28,7 @@ struct PthreadMutexPrivate {
std::atomic<std::thread::id> _owner;
int _count;
PthreadMutexPrivate() : _type(MutexType::Normal), _count(0) {}
PthreadMutexPrivate() : _type(MutexType::Normal), _owner(std::thread::id{}), _count(0) {}
};
struct PthreadCondattrPrivate {

View File

@ -0,0 +1,21 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_RWLOCK_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_RWLOCK_HPP
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI scePthreadRwlockattrInit(PthreadRwlockattr* attr);
int APS5_VABI scePthreadRwlockattrDestroy(PthreadRwlockattr* attr);
int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type);
int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr, const char* name);
int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockRdlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockTrywrlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock);
}
#endif

View File

@ -0,0 +1,15 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_THREADLIFECYCLE_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_THREADLIFECYCLE_HPP
#include "SceTypes.hpp"
class ThreadLifecycle {
public:
static void SetThreadDtors(thread_dtors_func_t callback);
static void SetThreadAtexitCount(get_thread_atexit_count_func_t callback);
static void SetThreadAtexitReport(thread_atexit_report_func_t callback);
};
extern "C" void APS5_VABI scePthreadExit(void* retval);
#endif

View File

@ -1,8 +1,10 @@
#include "../include/Pthread.hpp"
#include "prx/libkernel/Pthread/include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include <stdexcept>
#include <string>
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016;
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
static constexpr std::size_t DEFAULT_STACK_SIZE = 1u << 20;
@ -18,7 +20,7 @@ static constexpr int SCHED_FIFO_PS5 = 1;
extern "C" {
int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) {
if (!attr) throw std::runtime_error("scePthreadAttrInit: null attr");
if (!attr) throw std::runtime_error(std::string(__func__) + ": null attr");
auto* p = new (std::nothrow) PthreadAttrPrivate{};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
p->_stacksize = DEFAULT_STACK_SIZE;
@ -31,35 +33,35 @@ int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) {
}
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr) {
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrDestroy: null attr");
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
delete *attr;
*attr = nullptr;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int detachstate) {
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetdetachstate: null attr");
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (detachstate != DETACH_JOINABLE && detachstate != DETACH_DETACHED)
throw std::runtime_error("scePthreadAttrSetdetachstate: invalid state");
throw std::runtime_error(std::string(__func__) + ": invalid state");
(*attr)->_detachstate = detachstate;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param) {
if (!attr || !*attr || !param)
throw std::runtime_error("scePthreadAttrSetschedparam: null arg");
throw std::runtime_error(std::string(__func__) + ": null arg");
(*attr)->_schedpriority = param->sched_priority;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksize) {
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetstacksize: null attr");
if (stacksize < 16384) throw std::runtime_error("scePthreadAttrSetstacksize: too small");
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (stacksize < 16384) throw std::runtime_error(std::string(__func__) + ": too small");
#ifdef _WIN32
SYSTEM_INFO system{};
GetSystemInfo(&system);
if (stacksize % system.dwPageSize != 0 || stacksize > std::numeric_limits<unsigned>::max())
throw std::runtime_error("scePthreadAttrSetstacksize: invalid Windows stack size");
throw std::runtime_error(std::string(__func__) + ": invalid Windows stack size");
#endif
(*attr)->_stacksize = stacksize;
return SCE_OK;
@ -67,7 +69,7 @@ int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksiz
int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr, std::size_t* stacksize) {
if (!attr || !*attr || !stackaddr || !stacksize)
throw std::runtime_error("scePthreadAttrGetstack: null arg");
throw std::runtime_error(std::string(__func__) + ": null arg");
*stackaddr = (*attr)->stackAddress;
*stacksize = (*attr)->_stacksize;
return SCE_OK;
@ -75,7 +77,7 @@ int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr,
int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr) {
if (!thread || !attr || !*attr)
throw std::runtime_error("scePthreadAttrGet: null arg");
return SCE_KERNEL_ERROR_EINVAL;
(*attr)->_stacksize = thread->stackSize;
(*attr)->stackAddress = thread->stackAddress;
(*attr)->_detachstate = thread->_detached ? DETACH_DETACHED : DETACH_JOINABLE;
@ -93,10 +95,9 @@ int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask*
}
int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state) {
(void)attr;
(void)state;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !state) throw std::runtime_error(std::string(__func__) + ": null arg");
*state = (*attr)->_detachstate;
return SCE_OK;
}
int APS5_VABI scePthreadAttrGetguardsize(const PthreadAttr* attr, size_t* guard_size) {
@ -107,10 +108,9 @@ int APS5_VABI scePthreadAttrGetguardsize(const PthreadAttr* attr, size_t* guard_
}
int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param) {
(void)attr;
(void)param;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !param) throw std::runtime_error(std::string(__func__) + ": null arg");
param->sched_priority = (*attr)->_schedpriority;
return SCE_OK;
}
int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched) {
@ -121,17 +121,15 @@ int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched
}
int APS5_VABI scePthreadAttrGetstackaddr(const PthreadAttr* attr, void** stack_addr) {
(void)attr;
(void)stack_addr;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !stack_addr) throw std::runtime_error(std::string(__func__) + ": null arg");
*stack_addr = (*attr)->stackAddress;
return SCE_OK;
}
int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, size_t* stack_size) {
(void)attr;
(void)stack_size;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !stack_size) throw std::runtime_error(std::string(__func__) + ": null arg");
*stack_size = (*attr)->_stacksize;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask) {
@ -149,17 +147,15 @@ int APS5_VABI scePthreadAttrSetguardsize(PthreadAttr* attr, size_t guard_size) {
}
int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inherit_sched) {
(void)attr;
(void)inherit_sched;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
(*attr)->_inheritsched = inherit_sched;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy) {
(void)attr;
(void)policy;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
(*attr)->_schedpolicy = policy;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched) {
@ -170,18 +166,17 @@ int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched) {
}
int APS5_VABI scePthreadAttrSetstack(PthreadAttr* attr, void* addr, size_t size) {
(void)attr;
(void)addr;
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg");
if (size < 16384) throw std::runtime_error(std::string(__func__) + ": too small");
(*attr)->stackAddress = addr;
(*attr)->_stacksize = size;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetstackaddr(PthreadAttr* attr, void* addr) {
(void)attr;
(void)addr;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg");
(*attr)->stackAddress = addr;
return SCE_OK;
}
}

View File

@ -1,85 +1,153 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Pthread/include/Pthread.hpp"
#include "prx/libkernel/Pthread/include/Mutex.hpp"
#include "prx/libkernel/Pthread/include/Cond.hpp"
#include <chrono>
#include <memory>
#include <stdexcept>
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x80020062;
namespace {
constexpr int sceTimedOut = static_cast<int>(0x8002003cu);
std::mutex condInitializationMutex;
PthreadCond destroyedCond() {
return reinterpret_cast<PthreadCond>(std::uintptr_t{2});
}
PthreadCond resolveCond(PthreadCond* cond) {
if (!cond)
throw std::invalid_argument("Condition variable pointer is null");
std::lock_guard lock(condInitializationMutex);
if (*cond == destroyedCond())
throw std::runtime_error("Condition variable has been destroyed");
if (!*cond)
*cond = new PthreadCondPrivate();
return *cond;
}
PthreadMutex lockedMutex(PthreadMutex* mutex) {
if (!mutex || !*mutex)
throw std::invalid_argument("Mutex pointer is null");
auto* current = *mutex;
if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
throw std::runtime_error("Condition wait mutex is not owned by the current thread");
return current;
}
int waitUntil(PthreadCond* cond, PthreadMutex* mutex, const std::chrono::system_clock::time_point* deadline) {
auto* c = resolveCond(cond);
auto* m = lockedMutex(mutex);
if (m->_type == MutexType::Recursive) {
std::unique_lock<std::recursive_timed_mutex> lock(m->_rmtx, std::adopt_lock);
const auto previousCount = m->_count;
m->_count = 0;
m->_owner.store(std::thread::id{}, std::memory_order_release);
const bool timedOut = deadline && c->_cv.wait_until(lock, *deadline) == std::cv_status::timeout;
if (!deadline)
c->_cv.wait(lock);
m->_owner.store(std::this_thread::get_id(), std::memory_order_release);
m->_count = previousCount;
lock.release();
return timedOut ? sceTimedOut : 0;
}
std::unique_lock<std::timed_mutex> lock(m->_mtx, std::adopt_lock);
m->_owner.store(std::thread::id{}, std::memory_order_release);
const bool timedOut = deadline && c->_cv.wait_until(lock, *deadline) == std::cv_status::timeout;
if (!deadline)
c->_cv.wait(lock);
m->_owner.store(std::this_thread::get_id(), std::memory_order_release);
lock.release();
return timedOut ? sceTimedOut : 0;
}
std::chrono::system_clock::time_point toDeadline(const KernelTimespec* abstime) {
if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)
throw std::invalid_argument("Invalid absolute condition variable timeout");
const auto maximum = std::chrono::nanoseconds::max().count();
if (abstime->tv_sec > (maximum - abstime->tv_nsec) / 1000000000)
throw std::overflow_error("Absolute condition variable timeout exceeds the host clock range");
const auto duration = std::chrono::nanoseconds(abstime->tv_sec * 1000000000 + abstime->tv_nsec);
if (std::chrono::duration<long double>(duration) >= std::chrono::duration<long double>(std::chrono::system_clock::duration::max()))
throw std::overflow_error("Absolute condition variable timeout exceeds the host clock range");
return std::chrono::system_clock::time_point(std::chrono::duration_cast<std::chrono::system_clock::duration>(duration));
}
}
int CondOperations::AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) {
const auto deadline = toDeadline(abstime);
return waitUntil(cond, mutex, &deadline);
}
extern "C" {
int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr) {
if (!attr) throw std::runtime_error("scePthreadCondattrInit: null attr");
auto* p = new (std::nothrow) PthreadCondattrPrivate{0};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
*attr = p;
return SCE_OK;
if (!attr)
throw std::invalid_argument("Condition attribute pointer is null");
*attr = new PthreadCondattrPrivate{0};
return 0;
}
int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr) {
if (!attr || !*attr) throw std::runtime_error("scePthreadCondattrDestroy: null attr");
if (!attr || !*attr)
throw std::invalid_argument("Condition attributes are not initialized");
delete *attr;
*attr = nullptr;
return SCE_OK;
return 0;
}
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr*, const char*) {
if (!cond) throw std::runtime_error("scePthreadCondInit: null cond");
auto* p = new (std::nothrow) PthreadCondPrivate{};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
*cond = p;
return SCE_OK;
int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId) {
if (!attr || !*attr)
throw std::invalid_argument("Condition attributes are not initialized");
(*attr)->_clockid = static_cast<int>(clockId);
return 0;
}
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char*) {
if (!cond)
throw std::invalid_argument("Condition variable pointer is null");
if (attr && !*attr)
throw std::invalid_argument("Condition attributes are not initialized");
auto replacement = std::make_unique<PthreadCondPrivate>();
std::lock_guard lock(condInitializationMutex);
*cond = replacement.release();
return 0;
}
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondDestroy: null cond");
if (!cond)
throw std::invalid_argument("Condition variable pointer is null");
std::lock_guard lock(condInitializationMutex);
if (*cond == destroyedCond())
throw std::runtime_error("Condition variable has already been destroyed");
delete *cond;
*cond = nullptr;
return SCE_OK;
*cond = destroyedCond();
return 0;
}
int APS5_VABI scePthreadCondSignal(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignal: null cond");
(*cond)->_cv.notify_one();
return SCE_OK;
resolveCond(cond)->_cv.notify_one();
return 0;
}
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondBroadcast: null cond");
(*cond)->_cv.notify_all();
return SCE_OK;
}
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec) {
if (!cond || !*cond || !mutex || !*mutex)
throw std::runtime_error("scePthreadCondTimedwait: null arg");
auto* m = *mutex;
auto* c = *cond;
if (m->_type == MutexType::Recursive) {
std::unique_lock<std::recursive_timed_mutex> lk(m->_rmtx, std::adopt_lock);
auto res = c->_cv.wait_for(lk, std::chrono::microseconds(usec));
lk.release();
return res == std::cv_status::timeout ? SCE_KERNEL_ERROR_ETIMEDOUT : SCE_OK;
}
std::unique_lock<std::timed_mutex> lk(m->_mtx, std::adopt_lock);
auto res = c->_cv.wait_for(lk, std::chrono::microseconds(usec));
lk.release();
return res == std::cv_status::timeout ? SCE_KERNEL_ERROR_ETIMEDOUT : SCE_OK;
resolveCond(cond)->_cv.notify_all();
return 0;
}
int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread) {
(void)cond;
(void)thread;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)thread;
resolveCond(cond)->_cv.notify_all();
return 0;
}
int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex) {
(void)cond;
(void)mutex;
NotImplemented_nid_no_patch(__func__);
return 0;
return waitUntil(cond, mutex, nullptr);
}
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec) {
const auto deadline = std::chrono::system_clock::now() + std::chrono::microseconds(usec);
return waitUntil(cond, mutex, &deadline);
}
}

View File

@ -1,113 +1,164 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include <cerrno>
#include "../include/Mutex.hpp"
#include <chrono>
#include <limits>
#include <memory>
#include <stdexcept>
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x80020023;
static constexpr int SCE_KERNEL_ERROR_EPERM = 0x80020001;
namespace {
constexpr int sceBusy = static_cast<int>(0x80020010u);
constexpr int sceTimedOut = static_cast<int>(0x8002003cu);
std::mutex initializationMutex;
PthreadMutex destroyedMutex() {
return reinterpret_cast<PthreadMutex>(std::uintptr_t{2});
}
PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) {
if (!mutex)
throw std::invalid_argument("Mutex pointer is null");
std::lock_guard lock(initializationMutex);
if (*mutex == destroyedMutex())
throw std::runtime_error("Mutex has been destroyed");
if (reinterpret_cast<std::uintptr_t>(*mutex) == 1)
throw std::runtime_error("Adaptive mutex initializer is unsupported");
if (!*mutex) {
if (!initialize)
throw std::runtime_error("Mutex is not initialized");
*mutex = new PthreadMutexPrivate();
}
return *mutex;
}
template<typename TAcquire>
int acquireMutex(PthreadMutex mutex, TAcquire acquire, int unavailable, bool tryOnly) {
const auto thread = std::this_thread::get_id();
const bool owned = mutex->_owner.load(std::memory_order_acquire) == thread;
if (owned && mutex->_type != MutexType::Recursive) {
if (tryOnly)
return sceBusy;
throw std::runtime_error("Mutex is already owned by the current thread");
}
if (owned && mutex->_count == std::numeric_limits<int>::max())
throw std::overflow_error("Recursive mutex lock count overflow");
if (mutex->_type == MutexType::Recursive) {
if (!acquire(mutex->_rmtx))
return unavailable;
++mutex->_count;
} else {
if (!acquire(mutex->_mtx))
return unavailable;
}
mutex->_owner.store(thread, std::memory_order_release);
return 0;
}
}
int MutexOperations::Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime) {
if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)
throw std::invalid_argument("Invalid absolute mutex timeout");
const auto maximum = std::chrono::nanoseconds::max().count();
if (abstime->tv_sec > (maximum - abstime->tv_nsec) / 1000000000)
throw std::overflow_error("Absolute mutex timeout exceeds the host clock range");
const auto duration = std::chrono::nanoseconds(abstime->tv_sec * 1000000000 + abstime->tv_nsec);
if (std::chrono::duration<long double>(duration) >= std::chrono::duration<long double>(std::chrono::system_clock::duration::max()))
throw std::overflow_error("Absolute mutex timeout exceeds the host clock range");
const auto deadline = std::chrono::system_clock::time_point(std::chrono::duration_cast<std::chrono::system_clock::duration>(duration));
return acquireMutex(resolveMutex(mutex, true), [&](auto& native) { return native.try_lock_until(deadline); }, sceTimedOut, false);
}
extern "C" {
int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr) {
if (!attr) throw std::runtime_error("scePthreadMutexattrInit: null attr");
auto* p = new (std::nothrow) PthreadMutexattrPrivate{MutexType::Normal};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
*attr = p;
return SCE_OK;
if (!attr)
throw std::invalid_argument("Mutex attribute pointer is null");
*attr = new PthreadMutexattrPrivate{MutexType::Normal};
return 0;
}
int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr) {
if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrDestroy: null attr");
if (!attr || !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
delete *attr;
*attr = nullptr;
return SCE_OK;
return 0;
}
int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) {
if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrSettype: null attr");
if (!attr || !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
switch (type) {
case 1: (*attr)->type = MutexType::ErrorCheck; break;
case 2: (*attr)->type = MutexType::Recursive; break;
case 3: (*attr)->type = MutexType::Normal; break;
default: throw std::runtime_error("scePthreadMutexattrSettype: invalid type");
default: throw std::invalid_argument("Invalid mutex type");
}
return SCE_OK;
}
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) {
if (!mutex) throw std::runtime_error("scePthreadMutexInit: null mutex");
MutexType t = MutexType::Normal;
if (attr && *attr) t = (*attr)->type;
auto* p = new (std::nothrow) PthreadMutexPrivate();
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
p->_type = t;
*mutex = p;
return SCE_OK;
}
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexDestroy: null mutex");
delete *mutex;
*mutex = nullptr;
return SCE_OK;
}
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexLock: null mutex");
auto* m = *mutex;
const auto tid = std::this_thread::get_id();
if (m->_type == MutexType::Recursive) {
m->_rmtx.lock();
m->_owner.store(tid, std::memory_order_relaxed);
++m->_count;
return SCE_OK;
}
if (m->_type == MutexType::ErrorCheck) {
if (m->_owner.load(std::memory_order_acquire) == tid) return SCE_KERNEL_ERROR_EDEADLK;
}
m->_mtx.lock();
m->_owner.store(tid, std::memory_order_relaxed);
return SCE_OK;
}
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexUnlock: null mutex");
auto* m = *mutex;
if (m->_type == MutexType::ErrorCheck || m->_type == MutexType::Normal) {
if (m->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
return SCE_KERNEL_ERROR_EPERM;
}
if (m->_type == MutexType::Recursive) {
if (--m->_count == 0) m->_owner.store(std::thread::id{}, std::memory_order_relaxed);
m->_rmtx.unlock();
return SCE_OK;
}
m->_owner.store(std::thread::id{}, std::memory_order_relaxed);
m->_mtx.unlock();
return SCE_OK;
}
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) {
(void)mutex;
(void)usec;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) {
(void)mutex;
NotImplemented_nid_no_patch(__func__);
return 0;
return 0;
}
int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) {
(void)attr;
(void)protocol;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
if (protocol != 0)
throw std::invalid_argument("Mutex priority inheritance and protection are unsupported");
return 0;
}
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) {
if (!mutex)
throw std::invalid_argument("Mutex pointer is null");
if (attr && !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
auto replacement = std::make_unique<PthreadMutexPrivate>();
if (attr)
replacement->_type = (*attr)->type;
std::lock_guard lock(initializationMutex);
*mutex = replacement.release();
return 0;
}
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) {
if (!mutex)
throw std::invalid_argument("Mutex pointer is null");
std::lock_guard lock(initializationMutex);
if (*mutex == destroyedMutex())
throw std::runtime_error("Mutex has already been destroyed");
if (reinterpret_cast<std::uintptr_t>(*mutex) == 1)
throw std::runtime_error("Adaptive mutex initializer is unsupported");
if (*mutex && (*mutex)->_owner.load(std::memory_order_acquire) != std::thread::id{})
throw std::runtime_error("Cannot destroy a locked mutex");
delete *mutex;
*mutex = destroyedMutex();
return 0;
}
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) {
return acquireMutex(resolveMutex(mutex, true), [](auto& native) { native.lock(); return true; }, 0, false);
}
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) {
auto* current = resolveMutex(mutex, false);
if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
throw std::runtime_error("Cannot unlock a mutex owned by another thread");
if (current->_type == MutexType::Recursive) {
if (--current->_count == 0)
current->_owner.store(std::thread::id{}, std::memory_order_release);
current->_rmtx.unlock();
} else {
current->_owner.store(std::thread::id{}, std::memory_order_release);
current->_mtx.unlock();
}
return 0;
}
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) {
return acquireMutex(resolveMutex(mutex, true), [=](auto& native) { return native.try_lock_for(std::chrono::microseconds(usec)); }, sceTimedOut, false);
}
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) {
return acquireMutex(resolveMutex(mutex, true), [](auto& native) { return native.try_lock(); }, sceBusy, true);
}
}

View File

@ -1,4 +1,5 @@
#include "../include/Pthread.hpp"
#include "../include/Rwlock.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {

View File

@ -1,4 +1,5 @@
#include "../include/Pthread.hpp"
#include "../include/ThreadLifecycle.hpp"
#include "prx/libc/include/General.hpp"
#include <cstdlib>
#include <future>
@ -29,7 +30,44 @@ struct ThreadArgs {
PthreadPrivate* self;
};
static void FinishThread(PthreadPrivate* self, void* retval) {
static std::atomic<thread_dtors_func_t> threadDtors{nullptr};
static std::atomic<get_thread_atexit_count_func_t> threadAtexitCount{nullptr};
static std::atomic<thread_atexit_report_func_t> threadAtexitReport{nullptr};
static thread_local PthreadPrivate* currentThread = nullptr;
static thread_local bool threadFinishing = false;
void ThreadLifecycle::SetThreadDtors(thread_dtors_func_t callback) {
if (!callback)
throw std::runtime_error("Thread destructor callback is null");
thread_dtors_func_t expected = nullptr;
if (!threadDtors.compare_exchange_strong(expected, callback))
throw std::runtime_error("Thread destructor callback is already registered");
}
void ThreadLifecycle::SetThreadAtexitCount(get_thread_atexit_count_func_t callback) {
if (!callback)
throw std::runtime_error("Thread atexit count callback is null");
get_thread_atexit_count_func_t expected = nullptr;
if (!threadAtexitCount.compare_exchange_strong(expected, callback))
throw std::runtime_error("Thread atexit count callback is already registered");
}
void ThreadLifecycle::SetThreadAtexitReport(thread_atexit_report_func_t callback) {
if (!callback)
throw std::runtime_error("Thread atexit report callback is null");
thread_atexit_report_func_t expected = nullptr;
if (!threadAtexitReport.compare_exchange_strong(expected, callback))
throw std::runtime_error("Thread atexit report callback is already registered");
}
static void finishThread(PthreadPrivate* self, void* retval) {
if (!self || self != currentThread)
throw std::runtime_error("Finishing an unregistered guest thread");
if (threadFinishing)
throw std::runtime_error("Guest thread is already finishing");
threadFinishing = true;
if (const auto callback = threadDtors.load())
callback();
{
std::unique_lock<std::mutex> lk(self->_join_mtx);
self->_retval = retval;
@ -43,13 +81,13 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
const auto entry = args->entry;
void* arg = args->arg;
PthreadPrivate* self = args->self;
currentThread = self;
args.reset();
FinishThread(self, entry(arg));
finishThread(self, entry(arg));
currentThread = nullptr;
}
#ifdef _WIN32
static thread_local PthreadPrivate* currentThread = nullptr;
static void ReleaseThread(PthreadPrivate* thread) {
if (thread->references.fetch_sub(1, std::memory_order_acq_rel) != 1)
return;
@ -183,26 +221,22 @@ int APS5_VABI scePthreadDetach(Pthread thread) {
}
void APS5_VABI scePthreadExit(void* retval) {
#ifdef _WIN32
if (!currentThread)
throw std::runtime_error("scePthreadExit: current thread is not registered");
auto* self = currentThread;
FinishThread(self, retval);
finishThread(self, retval);
currentThread = nullptr;
#ifdef _WIN32
ReleaseThread(self);
_endthreadex(0);
#else
pthread_exit(retval);
#endif
__builtin_unreachable();
throw std::runtime_error("Native thread exit returned");
}
Pthread APS5_VABI scePthreadSelf() {
#ifdef _WIN32
return currentThread;
#else
return nullptr;
#endif
}
void APS5_VABI scePthreadYield() {
@ -284,3 +318,12 @@ int APS5_VABI scePthreadSetprio(Pthread thread, int prio) {
}
}
extern "C" {
void APS5_VABI __pthread_cxa_finalize_nid_postfix(void* argument) {
(void)argument;
NotImplemented_nid_no_patch(__func__);
}
}

View File

@ -1,81 +1,317 @@
#include <cstdint>
#include <cstddef>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sched.h>
#include <unistd.h>
#endif
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
#include <array>
#include <atomic>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <limits>
#include <memory>
#include <random>
#include <stdexcept>
#include <string>
#include <system_error>
#include <vector>
#ifndef _WIN32
#include <sys/resource.h>
#endif
namespace {
std::atomic<std::uint32_t> gpoBits{0};
constexpr std::array<std::uint8_t, 16> openPsId{'A', 'n', 'y', 'P', 'S', '5', 'O', 'p', 'e', 'n', 'P', 's', 'I', 'd', 0, 1};
class ProcessArguments {
public:
ProcessArguments() {
#ifdef _WIN32
std::array<char, 32768> path{};
const auto size = GetModuleFileNameA(nullptr, path.data(), static_cast<DWORD>(path.size()));
if (size == 0)
throw std::system_error(GetLastError(), std::system_category(), "Reading executable path");
if (size >= path.size())
throw std::runtime_error("Executable path exceeds the guest argument buffer");
arguments.emplace_back(path.data(), size);
#else
std::ifstream stream("/proc/self/cmdline", std::ios::binary);
if (!stream)
throw std::runtime_error("Cannot read process arguments");
std::string argument;
while (std::getline(stream, argument, '\0')) {
if (stream.eof())
throw std::runtime_error("Unterminated process argument");
arguments.push_back(argument);
}
if (stream.bad() || !stream.eof())
throw std::runtime_error("Reading process arguments failed");
#endif
if (arguments.empty() || arguments.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
throw std::runtime_error("Invalid process argument count");
for (const auto& argument : arguments)
pointers.push_back(argument.c_str());
pointers.push_back(nullptr);
}
int GetCount() const { return static_cast<int>(arguments.size()); }
const char** GetValues() { return pointers.data(); }
private:
std::vector<std::string> arguments;
std::vector<const char*> pointers;
};
ProcessArguments& getProcessArguments() {
static ProcessArguments arguments;
return arguments;
}
void validateSchedulingPolicy(int policy) {
if (policy != 1 && policy != 3)
throw std::invalid_argument("Unsupported guest scheduling policy");
}
#ifdef _WIN32
void syncVolumes() {
std::array<wchar_t, 32768> name{};
const auto first = FindFirstVolumeW(name.data(), static_cast<DWORD>(name.size()));
if (first == INVALID_HANDLE_VALUE)
throw std::system_error(GetLastError(), std::system_category(), "Enumerating volumes for sync");
const std::unique_ptr<void, decltype(&FindVolumeClose)> search(first, &FindVolumeClose);
for (;;) {
DWORD flags = 0;
if (!GetVolumeInformationW(name.data(), nullptr, 0, nullptr, nullptr, &flags, nullptr, 0))
throw std::system_error(GetLastError(), std::system_category(), "Reading volume properties for sync");
if ((flags & FILE_READ_ONLY_VOLUME) == 0) {
std::wstring path(name.data());
if (path.empty() || path.back() != L'\\')
throw std::runtime_error("Invalid volume path for sync");
path.pop_back();
const auto native = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (native == INVALID_HANDLE_VALUE)
throw std::system_error(GetLastError(), std::system_category(), "Opening volume for sync");
const std::unique_ptr<void, decltype(&CloseHandle)> volume(native, &CloseHandle);
if (!FlushFileBuffers(volume.get()))
throw std::system_error(GetLastError(), std::system_category(), "Flushing volume");
}
if (FindNextVolumeW(search.get(), name.data(), static_cast<DWORD>(name.size())))
continue;
const auto error = GetLastError();
if (error != ERROR_NO_MORE_FILES)
throw std::system_error(error, std::system_category(), "Enumerating volumes for sync");
break;
}
}
#endif
}
struct GuestResourceUsage {
KernelTimeval ru_utime;
KernelTimeval ru_stime;
std::int64_t ru_maxrss;
std::int64_t ru_ixrss;
std::int64_t ru_idrss;
std::int64_t ru_isrss;
std::int64_t ru_minflt;
std::int64_t ru_majflt;
std::int64_t ru_nswap;
std::int64_t ru_inblock;
std::int64_t ru_oublock;
std::int64_t ru_msgsnd;
std::int64_t ru_msgrcv;
std::int64_t ru_nsignals;
std::int64_t ru_nvcsw;
std::int64_t ru_nivcsw;
};
extern "C" {
// unknown data
const char* __progname_nid_postfix = "eboot.bin";
int APS5_VABI getargc_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return getProcessArguments().GetCount();
}
const char** APS5_VABI getargv_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
return getProcessArguments().GetValues();
}
int APS5_VABI getpagesize_nid_postfix(void) {
return PS5_PAGE_SIZE;
return PS5_PAGE_SIZE;
}
int APS5_VABI getpid_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
#ifdef _WIN32
const auto pid = GetCurrentProcessId();
#else
const auto pid = ::getpid();
#endif
if (pid == 0 || static_cast<std::uint64_t>(pid) > static_cast<std::uint64_t>(std::numeric_limits<int>::max()))
throw std::runtime_error("Process identifier is outside the guest range");
return static_cast<int>(pid);
}
void APS5_VABI exit_nid_postfix(int code) {
(void)code;
NotImplemented_nid_no_patch(__func__);
LibcExit_nid_no_patch(code);
}
[[noreturn]] void APS5_VABI _exit_nid_postfix(int status) {
std::_Exit(status);
}
int APS5_VABI sceKernelGetCurrentCpu(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
#ifdef _WIN32
PROCESSOR_NUMBER processor{};
GetCurrentProcessorNumberEx(&processor);
unsigned index = processor.Number;
for (WORD group = 0; group < processor.Group; ++group) {
const auto count = GetActiveProcessorCount(group);
if (count == 0)
throw std::system_error(GetLastError(), std::system_category(), "Reading processor group size");
index += count;
}
return static_cast<int>(index);
#else
const int cpu = ::sched_getcpu();
if (cpu < 0)
throw std::system_error(errno, std::generic_category(), "Reading current processor");
return cpu;
#endif
}
uint64_t APS5_VABI sceKernelGetGPI(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceKernelGetGPI(void) {
return gpoBits.load(std::memory_order_relaxed);
}
void APS5_VABI sceKernelSetGPO(uint32_t bits) {
(void)bits;
NotImplemented_nid_no_patch(__func__);
void APS5_VABI sceKernelSetGPO(std::uint32_t bits) {
gpoBits.store(bits, std::memory_order_relaxed);
}
int APS5_VABI sceKernelGetOpenPsId(void* open_ps_id) {
(void)open_ps_id;
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceKernelGetOpenPsId(void* openPsIdOutput) {
if (!openPsIdOutput)
throw std::invalid_argument("sceKernelGetOpenPsId: null output");
std::memcpy(openPsIdOutput, openPsId.data(), openPsId.size());
return 0;
}
void* APS5_VABI sceKernelGetProcParam(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
return const_cast<void*>(ApplicationProcessParameters_nid_no_patch());
}
int APS5_VABI sceKernelUuidCreate(uint32_t* uuid) {
(void)uuid;
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceKernelUuidCreate(std::uint32_t* uuid) {
if (!uuid)
throw std::invalid_argument("sceKernelUuidCreate: null output");
static thread_local std::random_device device;
std::uniform_int_distribution<std::uint32_t> distribution;
std::array<std::uint32_t, 4> value;
for (auto& word : value)
word = distribution(device);
value[1] = (value[1] & 0x0fffffffu) | 0x40000000u;
value[2] = (value[2] & 0xffffff3fu) | 0x80u;
std::memcpy(uuid, value.data(), sizeof(value));
return 0;
}
void APS5_VABI sceKernelSync(void) {
NotImplemented_nid_no_patch(__func__);
#ifdef _WIN32
syncVolumes();
#else
::sync();
#endif
}
int APS5_VABI sched_get_priority_max_nid_postfix(int policy) {
(void)policy;
NotImplemented_nid_no_patch(__func__);
return 0;
validateSchedulingPolicy(policy);
return 256;
}
int APS5_VABI sched_get_priority_min_nid_postfix(int policy) {
(void)policy;
NotImplemented_nid_no_patch(__func__);
return 0;
validateSchedulingPolicy(policy);
return 767;
}
int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage) {
if (usage == nullptr)
throw std::invalid_argument("getrusage: usage is null");
if (who != 0 && who != 1)
throw std::invalid_argument("getrusage: unsupported who");
#ifdef _WIN32
FILETIME creation{};
FILETIME exitTime{};
FILETIME kernel{};
FILETIME user{};
if (who == 0) {
if (!GetProcessTimes(GetCurrentProcess(), &creation, &exitTime, &kernel, &user))
throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetProcessTimes failed");
} else {
if (!GetThreadTimes(GetCurrentThread(), &creation, &exitTime, &kernel, &user))
throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetThreadTimes failed");
}
const auto toMicros = [](const FILETIME& time) {
return ((static_cast<std::uint64_t>(time.dwHighDateTime) << 32) | time.dwLowDateTime) / 10ULL;
};
const auto userMicros = toMicros(user);
const auto kernelMicros = toMicros(kernel);
usage->ru_utime.tv_sec = static_cast<std::int64_t>(userMicros / 1000000ULL);
usage->ru_utime.tv_usec = static_cast<std::int64_t>(userMicros % 1000000ULL);
usage->ru_stime.tv_sec = static_cast<std::int64_t>(kernelMicros / 1000000ULL);
usage->ru_stime.tv_usec = static_cast<std::int64_t>(kernelMicros % 1000000ULL);
usage->ru_maxrss = 0;
usage->ru_ixrss = 0;
usage->ru_idrss = 0;
usage->ru_isrss = 0;
usage->ru_minflt = 0;
usage->ru_majflt = 0;
usage->ru_nswap = 0;
usage->ru_inblock = 0;
usage->ru_oublock = 0;
usage->ru_msgsnd = 0;
usage->ru_msgrcv = 0;
usage->ru_nsignals = 0;
usage->ru_nvcsw = 0;
usage->ru_nivcsw = 0;
#else
if (who == 1)
throw std::invalid_argument("getrusage: RUSAGE_THREAD is not supported on this platform");
struct rusage native{};
if (::getrusage(RUSAGE_SELF, &native) != 0)
throw std::system_error(errno, std::generic_category(), "getrusage: getrusage failed");
usage->ru_utime.tv_sec = static_cast<std::int64_t>(native.ru_utime.tv_sec);
usage->ru_utime.tv_usec = static_cast<std::int64_t>(native.ru_utime.tv_usec);
usage->ru_stime.tv_sec = static_cast<std::int64_t>(native.ru_stime.tv_sec);
usage->ru_stime.tv_usec = static_cast<std::int64_t>(native.ru_stime.tv_usec);
usage->ru_maxrss = static_cast<std::int64_t>(native.ru_maxrss);
usage->ru_ixrss = static_cast<std::int64_t>(native.ru_ixrss);
usage->ru_idrss = static_cast<std::int64_t>(native.ru_idrss);
usage->ru_isrss = static_cast<std::int64_t>(native.ru_isrss);
usage->ru_minflt = static_cast<std::int64_t>(native.ru_minflt);
usage->ru_majflt = static_cast<std::int64_t>(native.ru_majflt);
usage->ru_nswap = static_cast<std::int64_t>(native.ru_nswap);
usage->ru_inblock = static_cast<std::int64_t>(native.ru_inblock);
usage->ru_oublock = static_cast<std::int64_t>(native.ru_oublock);
usage->ru_msgsnd = static_cast<std::int64_t>(native.ru_msgsnd);
usage->ru_msgrcv = static_cast<std::int64_t>(native.ru_msgrcv);
usage->ru_nsignals = static_cast<std::int64_t>(native.ru_nsignals);
usage->ru_nvcsw = static_cast<std::int64_t>(native.ru_nvcsw);
usage->ru_nivcsw = static_cast<std::int64_t>(native.ru_nivcsw);
#endif
return 0;
}
}

View File

@ -1,13 +1,37 @@
#include <cstdint>
#include <cstddef>
#include <array>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp"
namespace {
std::mutex rtldHeapMutex;
std::array<void*, 10> rtldHeapApi{};
void registerApplicationHeapApi(void* const* api) {
if (api == nullptr)
throw std::invalid_argument("RTLD application heap: null allocator API");
std::array<void*, 10> replacement;
std::memcpy(replacement.data(), api, sizeof(replacement));
if (replacement[0] == nullptr || replacement[1] == nullptr)
throw std::invalid_argument("RTLD application heap: malloc and free are required");
std::lock_guard lock(rtldHeapMutex);
if (rtldHeapApi[0] != nullptr && rtldHeapApi != replacement)
throw std::runtime_error("RTLD application heap: cannot replace an active allocator");
rtldHeapApi = replacement;
}
}
extern "C" {
void APS5_VABI sceKernelRtldSetApplicationHeapAPI(void* api[]) {
ApplicationHeapRegister_nid_no_patch(api);
registerApplicationHeapApi(api);
}
int APS5_VABI sceKernelRtldThreadAtexitDecrement(uint64_t* c) {
@ -23,18 +47,47 @@ int APS5_VABI sceKernelRtldThreadAtexitIncrement(uint64_t* c) {
}
void APS5_VABI sceKernelSetThreadAtexitCount(get_thread_atexit_count_func_t func) {
(void)func;
NotImplemented_nid_no_patch(__func__);
ThreadLifecycle::SetThreadAtexitCount(func);
}
void APS5_VABI sceKernelSetThreadAtexitReport(thread_atexit_report_func_t func) {
(void)func;
NotImplemented_nid_no_patch(__func__);
ThreadLifecycle::SetThreadAtexitReport(func);
}
void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors) {
(void)dtors;
NotImplemented_nid_no_patch(__func__);
ThreadLifecycle::SetThreadDtors(dtors);
}
}
extern "C" {
void APS5_VABI _sceKernelRtldSetApplicationHeapAPI_nid_postfix(void* api[]) {
registerApplicationHeapApi(api);
}
int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(std::uint64_t* counter) {
(void)counter;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(std::uint64_t* counter) {
(void)counter;
NotImplemented_nid_no_patch(__func__);
return 0;
}
void APS5_VABI _sceKernelSetThreadAtexitCount_nid_postfix(get_thread_atexit_count_func_t callback) {
ThreadLifecycle::SetThreadAtexitCount(callback);
}
void APS5_VABI _sceKernelSetThreadAtexitReport_nid_postfix(thread_atexit_report_func_t callback) {
ThreadLifecycle::SetThreadAtexitReport(callback);
}
void APS5_VABI _sceKernelSetThreadDtors_nid_postfix(thread_dtors_func_t callback) {
ThreadLifecycle::SetThreadDtors(callback);
}
}

View File

@ -1,23 +1,19 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceKernelIsAddressSanitizerEnabled(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 0;
}
MallocReplace* APS5_VABI sceKernelGetSanitizerMallocReplaceExternal(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
static MallocReplace replacement{};
return &replacement;
}
NewReplace* APS5_VABI sceKernelGetSanitizerNewReplaceExternal(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
static NewReplace replacement{};
return &replacement;
}
}

View File

@ -1,3 +1,4 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <atomic>
#include <csignal>
@ -9,6 +10,8 @@ namespace {
using GuestHandler = void (APS5_VABI *)(int);
std::atomic<GuestHandler> handlers[32]{};
static_assert(std::atomic<GuestHandler>::is_always_lock_free);
std::atomic<std::uint32_t> blockedMask{0};
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
std::mutex registration;
int NativeSignal(int guest) {
switch (guest) {
@ -30,10 +33,16 @@ void Dispatch(int native) {
// Preserve the guest's persistent registration across CRT delivery.
std::signal(native, Dispatch);
#endif
if ((blockedMask.load() & (1u << guest)) != 0) return;
const auto callback = handlers[guest].load();
if (reinterpret_cast<std::uintptr_t>(callback) > 1) callback(guest);
}
}
struct GuestSignalSet {
std::uint32_t bits[4];
};
extern "C" {
GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) {
const auto invalid = reinterpret_cast<GuestHandler>(static_cast<std::uintptr_t>(-1));
@ -57,4 +66,32 @@ int APS5_VABI raise_nid_postfix(int guest) {
if (result) *__error_nid_postfix() = 22;
return result ? -1 : 0;
}
int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) {
std::lock_guard lock(registration);
if (previousSet != nullptr) {
previousSet->bits[0] = blockedMask.load();
previousSet->bits[1] = 0;
previousSet->bits[2] = 0;
previousSet->bits[3] = 0;
}
if (set != nullptr) {
switch (how) {
case 1: blockedMask.fetch_or(set->bits[0]); break;
case 2: blockedMask.fetch_and(~set->bits[0]); break;
case 3: blockedMask.store(set->bits[0]); break;
default: throw std::invalid_argument("_sigprocmask: invalid how");
}
}
return 0;
}
}
extern "C" {
int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t programCounter) {
(void)programCounter;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -92,12 +92,16 @@ int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec*
return sceKernelNanosleep(rqtp, rmtp);
}
int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) {
return sceKernelNanosleep(rqtp, rmtp);
}
int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
if (tp == nullptr) {
APS5_INVALID_ARG_EX;
}
#ifdef _WIN32
if (clockId == 0 || clockId == 9 || clockId == 10) {
if (clockId == 0 || clockId == 9) {
FILETIME ft{};
GetSystemTimePreciseAsFileTime(&ft);
std::uint64_t t = (static_cast<std::uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
@ -107,6 +111,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
tp->tv_nsec = static_cast<std::int64_t>(t % 1000000000ULL);
return 0;
}
if (clockId == 10 || clockId == 13) {
FILETIME ft{};
GetSystemTimeAsFileTime(&ft);
std::uint64_t t = (static_cast<std::uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
t -= 116444736000000000ULL;
t *= 100ULL;
tp->tv_sec = static_cast<std::int64_t>(t / 1000000000ULL);
tp->tv_nsec = static_cast<std::int64_t>(t % 1000000000ULL);
return 0;
}
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) {
std::uint64_t nanos = GetMonotonicNanos();
tp->tv_sec = static_cast<std::int64_t>(nanos / 1000000000ULL);
@ -119,9 +133,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
switch (clockId) {
case 0:
case 9:
case 10:
nativeId = CLOCK_REALTIME;
break;
case 10:
case 13:
#ifdef CLOCK_REALTIME_COARSE
nativeId = CLOCK_REALTIME_COARSE;
#else
nativeId = CLOCK_REALTIME;
#endif
break;
case 4:
case 7:
case 11:
@ -187,7 +208,7 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) {
res->tv_nsec = 100LL;
return 0;
}
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) {
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12 || clockId == 13) {
static const std::uint64_t freq = [] {
LARGE_INTEGER f{};
QueryPerformanceFrequency(&f);
@ -204,9 +225,16 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) {
switch (clockId) {
case 0:
case 9:
case 10:
nativeId = CLOCK_REALTIME;
break;
case 10:
case 13:
#ifdef CLOCK_REALTIME_COARSE
nativeId = CLOCK_REALTIME_COARSE;
#else
nativeId = CLOCK_REALTIME;
#endif
break;
case 4:
case 7:
case 11:

View File

@ -18,8 +18,8 @@ int APS5_VABI ispunct_nid_postfix(int);
int APS5_VABI isxdigit_nid_postfix(int);
int APS5_VABI toupper_nid_postfix(int);
int APS5_VABI tolower_nid_postfix(int);
const short* APS5_VABI _Getptolower_nid_postfix();
const short* APS5_VABI _Getptoupper_nid_postfix();
const int* APS5_VABI _Getptolower_nid_postfix();
const int* APS5_VABI _Getptoupper_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {

View File

@ -1,158 +0,0 @@
#include "prx/libc/include/GuestLocale.hpp"
#include <array>
#include <cstring>
#include <cwchar>
#include <climits>
#include <iostream>
#include <stdexcept>
#include <thread>
#include <vector>
extern "C" {
extern GuestLocale::Implementation* _ZSt21_sceLibcClassicLocale_nid_postfix;
GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix();
GuestLocale::Implementation* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix();
void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(GuestLocale::Facet* self);
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName);
void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(GuestLocale::LocinfoStorage* self);
void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(GuestLocale::IosBase* self);
const short* APS5_VABI _Getpctype_nid_postfix();
const short* APS5_VABI _Getptolower_nid_postfix();
const short* APS5_VABI _Getptoupper_nid_postfix();
int APS5_VABI _Mbtowcx_nid_postfix(std::uint16_t* dst, const char* src, std::size_t count, std::mbstate_t* st);
int APS5_VABI _Wctombx_nid_postfix(char* dst, std::uint16_t src, std::mbstate_t* st);
void APS5_VABI _Locksyslock_nid_postfix();
void APS5_VABI _Unlocksyslock_nid_postfix();
}
static void Require(bool condition) {
if (!condition) throw std::runtime_error("Guest locale check failed");
}
template<typename TAction>
static void RequireException(TAction action) {
bool caught = false;
try {
action();
} catch (const std::exception&) {
caught = true;
}
Require(caught);
}
static void CheckGuestCalls() {
auto* locale = _ZSt21_sceLibcClassicLocale_nid_postfix;
Require(locale != nullptr && locale == _ZNSt6locale16_GetgloballocaleEv_nid_postfix());
Require(_ZNSt6locale5_InitEv_nid_postfix() == locale);
Require(_ZNSt6locale5_InitEv_nid_postfix() == locale);
const std::byte* table = nullptr;
std::memcpy(&table, locale, sizeof(table));
void (APS5_VABI *retain)(GuestLocale::Facet*) = nullptr;
GuestLocale::Facet* (APS5_VABI *release)(GuestLocale::Facet*) = nullptr;
std::memcpy(&retain, table + 0x10, sizeof(retain));
std::memcpy(&release, table + 0x18, sizeof(release));
const auto initial = locale->base.references;
retain(&locale->base);
Require(locale->base.references == initial + 1);
Require(release(&locale->base) == nullptr);
Require(locale->base.references == initial);
RequireException([&] { release(&locale->base); });
RequireException([&] { retain(nullptr); });
RequireException([&] { locale->base.vtable->deleteObject(&locale->base); });
Require(locale->base.references == initial);
std::vector<std::thread> threads;
for (int thread = 0; thread < 4; ++thread) {
threads.emplace_back([&] {
for (int iteration = 0; iteration < 10000; ++iteration) {
retain(&locale->base);
release(&locale->base);
}
});
}
for (auto& thread : threads) thread.join();
Require(locale->base.references == initial);
Require(locale->facetCount == 1 && locale->facets[0] == nullptr);
Require(std::strcmp(locale->name, "C") == 0 && !locale->transparent);
}
static void CheckLocinfoAlignment() {
alignas(16) std::array<std::byte, 80> bytes;
bytes.fill(std::byte{0x5a});
auto* storage = reinterpret_cast<GuestLocale::LocinfoStorage*>(bytes.data() + 8);
_ZNSt8_LocinfoC1EPKc_nid_postfix(storage, "C");
for (std::size_t index = 0; index < bytes.size(); ++index) Require(bytes[index] == (index >= 8 && index < 72 ? std::byte{} : std::byte{0x5a}));
_ZNSt8_LocinfoD1Ev_nid_postfix(storage);
RequireException([&] { _ZNSt8_LocinfoC1EPKc_nid_postfix(storage, "unknown-locale"); });
RequireException([] { _ZNSt8_LocinfoC1EPKc_nid_postfix(nullptr, "C"); });
}
static void CheckCharacterTables() {
const auto* classification = _Getpctype_nid_postfix();
const auto* lower = _Getptolower_nid_postfix();
const auto* upper = _Getptoupper_nid_postfix();
Require(classification[-1] == 0 && lower[-1] == -1 && upper[-1] == -1);
Require(classification['A'] == 0x03 && classification['a'] == 0x11);
Require(classification['G'] == 0x02 && classification['0'] == 0x21);
Require(classification[' '] == 0x04 && classification['\t'] == 0xc0);
Require(classification['\n'] == 0xc0 && classification['!'] == 0x08);
for (int value = 0; value < 256; ++value) {
Require(lower[value] == (value >= 'A' && value <= 'Z' ? value + 32 : value));
Require(upper[value] == (value >= 'a' && value <= 'z' ? value - 32 : value));
if (value >= 128) Require(classification[value] == 0);
}
}
static void CheckCharacterConversions() {
const auto* classification = _Getpctype_nid_postfix();
for (std::uint16_t value = 0; value < 128; ++value) {
std::array<char, MB_LEN_MAX + 1> bytes{};
bytes.fill('!');
std::mbstate_t encodeState{};
Require(_Wctombx_nid_postfix(bytes.data(), value, &encodeState) == 1);
Require(bytes[0] == static_cast<char>(value) && bytes[1] == '!');
std::array<std::uint16_t, 2> wide{0xffff, 0x1234};
std::mbstate_t decodeState{};
Require(_Mbtowcx_nid_postfix(wide.data(), bytes.data(), 2, &decodeState) == (value == 0 ? 0 : 1));
Require(wide[0] == value && wide[1] == 0x1234);
const bool alnum = (value >= '0' && value <= '9') || (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z');
Require(((classification[static_cast<unsigned char>(bytes[0])] & 0x232) != 0) == alnum);
}
std::mbstate_t state{};
char byte{};
std::uint16_t wide{};
RequireException([&] { _Mbtowcx_nid_postfix(&wide, "]", 0, &state); });
RequireException([&] { _Mbtowcx_nid_postfix(nullptr, "]", 1, &state); });
RequireException([&] { _Mbtowcx_nid_postfix(&wide, nullptr, 1, &state); });
RequireException([&] { _Mbtowcx_nid_postfix(&wide, "]", 1, nullptr); });
RequireException([&] { _Wctombx_nid_postfix(nullptr, ']', &state); });
RequireException([&] { _Wctombx_nid_postfix(&byte, ']', nullptr); });
}
static void CheckStreamDestruction() {
struct GuardedStream {
std::uint64_t before = 0x12345678;
GuestLocale::IosBase stream{};
std::uint64_t after = 0x87654321;
} object;
object.stream.locale = &_ZSt21_sceLibcClassicLocale_nid_postfix;
_ZNSt8ios_baseD2Ev_nid_postfix(&object.stream);
Require(object.stream.locale == nullptr && object.before == 0x12345678 && object.after == 0x87654321);
object.stream.locale = &_ZSt21_sceLibcClassicLocale_nid_postfix;
object.stream.callbacks = &object;
RequireException([&] { _ZNSt8ios_baseD2Ev_nid_postfix(&object.stream); });
Require(object.stream.locale == &_ZSt21_sceLibcClassicLocale_nid_postfix);
}
int main() {
CheckGuestCalls();
CheckLocinfoAlignment();
CheckCharacterTables();
CheckCharacterConversions();
CheckStreamDestruction();
_Locksyslock_nid_postfix();
_Locksyslock_nid_postfix();
_Unlocksyslock_nid_postfix();
_Unlocksyslock_nid_postfix();
std::cout << "Guest locale checks passed\n";
}

View File

@ -3,10 +3,16 @@ add_executable(relinker
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxElfPatcher.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestStartup.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/guest/GuestImageReader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/guest/GuestModuleBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPePatcher.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeFormat.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsLoadImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsRelocationBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsImportBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp
@ -31,6 +37,7 @@ add_executable(relinker
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CodeInstructionCollector.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp
@ -79,8 +86,10 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $<TARGET_FILE:relinker>)
add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $<TARGET_FILE:relinker>)
add_test(NAME tls_function_coverage COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $<TARGET_FILE:relinker>)
else()
message(STATUS "Python3 interpreter not found; skipping optional_plt and empty_tls tests")
message(STATUS "Python3 interpreter not found; skipping Python relinker tests")
endif()
add_executable(strict_nid_filter_tests
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/StrictNidFilterTests.cpp

View File

@ -8,6 +8,7 @@ namespace Cli {
struct Args {
bool skipSyscallCheck = false;
bool skipSceModule = false;
bool toIntel = false;
bool writeRegistry = false;
bool toWindows = false;

View File

@ -13,6 +13,8 @@ Args ParseArgs(int argc, char* argv[]) {
const std::string arg = argv[i];
if (arg == "--skip-syscall-check") {
args.skipSyscallCheck = true;
} else if (arg == "--skip-sce-module") {
args.skipSceModule = true;
} else if (arg == "--to-intel") {
args.toIntel = true;
} else if (arg.rfind("unused-filter=", 0) == 0) {
@ -51,7 +53,7 @@ Args ParseArgs(int argc, char* argv[]) {
if (args.inputPath.empty() || args.outputPath.empty())
throw std::runtime_error(
"Usage: relinker [--windows] [--windows-diagnostics] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
"Usage: relinker [--windows] [--windows-diagnostics] [--skip-syscall-check] [--skip-sce-module] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
"Example: relinker input.elf output.elf"
);

View File

@ -5,6 +5,8 @@
namespace Codegen::X64OpcodeConstants {
inline constexpr std::uint8_t OneByteIntImm8 = 0xCD;
inline constexpr std::uint8_t PrefixLock = 0xF0;
inline constexpr std::uint8_t PrefixRepne = 0xF2;
inline constexpr std::uint8_t PrefixRep = 0xF3;
@ -151,6 +153,8 @@ inline constexpr std::uint8_t TwoByteShldCl = 0xA5;
inline constexpr std::uint8_t TwoByteShrdImm8 = 0xAC;
inline constexpr std::uint8_t TwoByteShrdCl = 0xAD;
inline constexpr std::uint8_t TwoByteGrp15 = 0xAE;
inline constexpr std::uint8_t TwoBytePopcnt = 0xB8;
inline constexpr std::uint8_t TwoByteXaddRm8 = 0xC0;
inline constexpr std::uint8_t TwoByteXadd = 0xC1;
inline constexpr std::uint8_t TwoBytePextrw = 0xC5;
inline constexpr std::uint8_t TwoByteShufpsImm8 = 0xC2;

View File

@ -200,7 +200,7 @@ std::size_t X64InstructionDecoder::Decode(const std::uint8_t* data, std::size_t
immediateSize = ImmSize32;
}
if (opcode == OneByteJmpRel8 || opcode == OneByteJrcxz ||
if (opcode == OneByteJmpRel8 || opcode == OneByteJrcxz || opcode == OneByteIntImm8 ||
(opcode >= OneByteLoop && opcode <= OneByteLoopMax) ||
(opcode >= OneByteInOutImm8Min && opcode <= OneByteInOutImm8Max)) {
immediateSize = ImmSize8;
@ -246,7 +246,8 @@ std::size_t X64InstructionDecoder::Decode(const std::uint8_t* data, std::size_t
opcode == TwoByteImulRmModRm ||
opcode == TwoByteGrp7 ||
opcode == TwoByteGrp15 ||
opcode == TwoByteXadd ||
opcode == TwoBytePopcnt ||
opcode == TwoByteXaddRm8 || opcode == TwoByteXadd ||
opcode == TwoByteGrp9 ||
opcode == TwoByteNopModRm || opcode == TwoByteEndbr) {
hasModRm = true;
@ -497,7 +498,8 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction(
(op >= TwoBytePrefetchGrpMin && op <= TwoBytePrefetchGrpMax) ||
(op >= TwoByteSetccMin && op <= TwoByteSetccMax) ||
op == TwoByteImulRmModRm || op == TwoByteGrp7 ||
op == TwoByteGrp15 || op == TwoByteXadd ||
op == TwoBytePopcnt ||
op == TwoByteGrp15 || op == TwoByteXaddRm8 || op == TwoByteXadd ||
op == TwoByteGrp9 || op == TwoByteNopModRm ||
op == TwoByteEndbr || op == TwoByteMovImm8ModRm ||
(op >= TwoByteShiftImm8Min && op <= TwoByteShiftImm8Max) ||

View File

@ -0,0 +1,27 @@
#ifndef DOMAIN_GUESTRUNTIME_HPP
#define DOMAIN_GUESTRUNTIME_HPP
#include <cstdint>
#include <string>
#include <vector>
namespace Domain {
struct GuestImport {
std::string Name;
std::uint32_t TargetRva;
std::uint64_t Addend;
std::uint32_t RelocationType = 1;
};
struct GuestRuntime {
std::string Path;
std::vector<GuestImport> Imports;
std::uint32_t InitRva = 0;
std::uint32_t FiniRva = 0;
bool UsePlatformTlsResolver = true;
};
}
#endif

View File

@ -7,6 +7,7 @@
#include <map>
#include <memory>
#include <stdexcept>
#include <domain/GuestRuntime.hpp>
namespace Domain {
@ -92,6 +93,7 @@ struct SysVDynamicSection {
std::vector<std::uint8_t> DynStrData;
std::vector<std::uint8_t> RelaData;
std::vector<std::uint8_t> RelaPltData;
std::vector<GuestRuntime> GuestModules;
};
struct CallRegistryEntry {

View File

@ -0,0 +1,16 @@
#ifndef ELFPATCHER_GUESTMODULEWRITER_HPP
#define ELFPATCHER_GUESTMODULEWRITER_HPP
#include <relinker/guest/GuestImage.hpp>
namespace Elfpatcher {
class GuestModuleWriter {
public:
std::vector<std::uint8_t> WriteLinux(const Relinker::GuestImage& image, const std::vector<std::string>& dependencies, const std::string& runPath) const;
std::vector<std::uint8_t> WriteWindows(const Relinker::GuestImage& image, Domain::GuestRuntime& runtime) const;
};
}
#endif

View File

@ -19,7 +19,7 @@ struct WindowsEntryStub {
class WindowsEntryStubBuilder {
public:
WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics) const;
WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules = {}) const;
};
}

View File

@ -0,0 +1,21 @@
#ifndef ELFPATCHER_WINDOWSGUESTSTARTUP_HPP
#define ELFPATCHER_WINDOWSGUESTSTARTUP_HPP
#include <domain/GuestRuntime.hpp>
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
namespace Elfpatcher::Windows {
class WindowsGuestStartup {
public:
void WriteImport(WindowsStubEmitter& code, const PeImport& import, std::uint32_t handles) const;
void Initialize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, std::uint32_t handles) const;
void Finalize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, std::uint32_t handles, std::uint32_t finished) const;
std::uint32_t EmitTlsResolver(WindowsStubEmitter& code) const;
private:
void callLifecycle(WindowsStubEmitter& code, std::uint32_t handle, std::uint32_t rva) const;
};
}
#endif

View File

@ -32,6 +32,8 @@ struct PeImport {
std::string Name;
std::uint32_t TargetRva;
std::uint64_t Addend;
std::int32_t TargetModule = -1;
std::uint32_t RelocationType = 1;
};
struct PeRelocations {

View File

@ -7,7 +7,7 @@ namespace Elfpatcher::Windows {
class WindowsTlsBuilder {
public:
PeDirectory Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva) const;
PeDirectory Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva = nullptr) const;
};
}

View File

@ -0,0 +1,15 @@
#ifndef ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP
#define ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP
#include <elfpatcher/windows/WindowsLoadImage.hpp>
namespace Elfpatcher::Windows {
class WindowsTlsTemplateBuilder {
public:
std::vector<std::uint8_t> Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector<PeSection>& sections, std::uint32_t templateRva, std::vector<std::uint32_t>& relocations) const;
};
}
#endif

View File

@ -0,0 +1,131 @@
#include <elfpatcher/general/GuestModuleWriter.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <limits>
namespace Elfpatcher {
std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector<std::string>& dependencies, const std::string& runPath) const {
auto bytes = image.Bytes;
std::vector<Domain::ProgramHeader> headers;
std::uint64_t end = 0;
for (auto header : image.Headers) {
if (header.Type != 1 && header.Type != 7 && header.Type != 0x6474e550 && header.Type != 0x6474e551) continue;
if (header.Type == 1) {
header.Flags |= 4;
end = std::max(end, header.MappedAddress + header.MemorySize);
}
headers.push_back(header);
}
if (end > std::numeric_limits<std::uint64_t>::max() - 0x4000) throw Domain::RelinkerException("Guest virtual address overflow");
Io::AlignBuffer(bytes, 0x4000);
const auto extraOffset = bytes.size();
const auto extraAddress = Io::AlignUp64(end, 0x4000);
const auto address = [&] { return extraAddress + bytes.size() - extraOffset; };
auto strings = image.Dynamic.DynStrData;
auto symbols = image.Dynamic.DynSymData;
const auto addString = [&](const std::string& value) {
if (strings.size() > std::numeric_limits<std::uint32_t>::max()) throw Domain::RelinkerException("Guest string table too large");
const auto offset = static_cast<std::uint32_t>(strings.size());
Io::AppendString(strings, value);
return offset;
};
bool needsTlsResolver = false;
for (std::size_t index = 1; index < image.Symbols.size(); ++index) {
const auto& symbol = image.Symbols[index];
if (image.UsePlatformTlsResolver && symbol.Section == 0 && symbol.Name == "vNe1w4diLCs") {
Io::WriteU32(symbols, index * 24, addString("__tls_get_addr"));
needsTlsResolver = true;
}
if (symbol.Section == 0 && (symbol.Info >> 4) == 2) symbols[index * 24 + 4] = static_cast<std::uint8_t>(0x10 | (symbol.Info & 15));
}
std::vector<std::uint64_t> needed;
for (const auto& dependency : dependencies) needed.push_back(addString(dependency));
if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2"));
const auto soname = addString(image.OutputName);
const auto search = addString(runPath);
const auto strAddress = address();
bytes.insert(bytes.end(), strings.begin(), strings.end());
Io::AlignBuffer(bytes, 8);
const auto symAddress = address();
bytes.insert(bytes.end(), symbols.begin(), symbols.end());
const auto hashAddress = address();
const auto count = static_cast<std::uint32_t>(image.Symbols.size());
Io::AppendU32(bytes, 1);
Io::AppendU32(bytes, count);
Io::AppendU32(bytes, count > 1 ? 1 : 0);
for (std::uint32_t index = 0; index < count; ++index) Io::AppendU32(bytes, index != 0 && index + 1 < count ? index + 1 : 0);
Io::AlignBuffer(bytes, 8);
const auto relaAddress = address();
bytes.insert(bytes.end(), image.Dynamic.RelaData.begin(), image.Dynamic.RelaData.end());
const auto pltAddress = address();
bytes.insert(bytes.end(), image.Dynamic.RelaPltData.begin(), image.Dynamic.RelaPltData.end());
const auto lifecycle = [&](std::uint64_t target) {
if (target == 0) return std::uint64_t{};
const auto start = address();
bytes.insert(bytes.end(), {0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xe9});
const auto displacement = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(address() + 4);
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max()) throw Domain::RelinkerException("Guest initializer exceeds relative branch range");
Io::AppendU32(bytes, static_cast<std::uint32_t>(displacement));
return start;
};
const auto init = lifecycle(image.Init);
const auto fini = lifecycle(image.Fini);
Io::AlignBuffer(bytes, 8);
const auto dynamicOffset = bytes.size();
const auto dynamicAddress = address();
const auto tag = [&](std::uint64_t key, std::uint64_t value) { Io::AppendU64(bytes, key); Io::AppendU64(bytes, value); };
for (const auto offset : needed) tag(1, offset);
tag(14, soname);
tag(29, search);
tag(4, hashAddress);
tag(5, strAddress);
tag(10, strings.size());
tag(6, symAddress);
tag(11, 24);
if (!image.Dynamic.RelaData.empty()) {
tag(7, relaAddress);
tag(8, image.Dynamic.RelaData.size());
tag(9, 24);
}
if (!image.Dynamic.RelaPltData.empty()) {
tag(23, pltAddress);
tag(2, image.Dynamic.RelaPltData.size());
tag(20, 7);
tag(3, image.Got);
}
if (init != 0) tag(12, init);
if (fini != 0) tag(13, fini);
tag(30, 8);
tag(0, 0);
const auto dynamicSize = bytes.size() - dynamicOffset;
const auto phOffset = bytes.size();
const auto phCount = headers.size() + 2;
if (phCount > std::numeric_limits<std::uint16_t>::max()) throw Domain::RelinkerException("Too many guest program headers");
const auto extraSize = bytes.size() + phCount * 56 - extraOffset;
headers.push_back({1, 7, extraOffset, extraAddress, extraAddress, extraSize, extraSize, 0x4000});
headers.push_back({2, 6, dynamicOffset, dynamicAddress, dynamicAddress, dynamicSize, dynamicSize, 8});
for (const auto& header : headers) {
Io::AppendU32(bytes, header.Type);
Io::AppendU32(bytes, header.Flags);
Io::AppendU64(bytes, header.Offset);
Io::AppendU64(bytes, header.MappedAddress);
Io::AppendU64(bytes, header.PhysicalAddress);
Io::AppendU64(bytes, header.FileSize);
Io::AppendU64(bytes, header.MemorySize);
Io::AppendU64(bytes, header.Alignment);
}
bytes[7] = 0;
bytes[8] = 0;
Io::WriteU16(bytes, 16, 3);
Io::WriteU64(bytes, 24, 0);
Io::WriteU64(bytes, 32, phOffset);
Io::WriteU64(bytes, 40, 0);
Io::WriteU16(bytes, 56, static_cast<std::uint16_t>(phCount));
Io::WriteU16(bytes, 58, 0);
Io::WriteU16(bytes, 60, 0);
Io::WriteU16(bytes, 62, 0);
return bytes;
}
}

View File

@ -1,6 +1,7 @@
#include <elfpatcher/windows/WindowsEntryStubBuilder.hpp>
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
#include <elfpatcher/windows/WindowsDependencyStubBuilder.hpp>
#include <elfpatcher/windows/WindowsGuestStartup.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <optional>
@ -29,7 +30,7 @@ std::string normalizeRunPath(std::string path) {
}
WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics) const {
WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules) const {
if (!imports.empty() && libraries.empty())
throw Domain::RelinkerException("ELF imports have no DT_NEEDED libraries");
const auto path = normalizeRunPath(runPath);
@ -54,14 +55,17 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const auto programPath = reserve(PathCapacity);
const auto modulePath = reserve(PathCapacity);
const auto handles = reserve(libraries.size() * 8);
const auto guestFinished = reserve(4);
const WindowsGuestStartup guestStartup;
const auto functionTable = reserve(12 * 32);
const auto unwindRva = CheckedRva(dataRva + data.size());
data.insert(data.end(), {1, 10, 6, 0, 10, 0xb2, 6, 0xc0, 4, 0x70, 3, 0x60, 2, 0x50, 1, 0x30});
std::vector<std::uint32_t> libraryPaths;
std::vector<std::string> libraryNames;
for (const auto& library : libraries) {
auto name = path + library;
for (std::size_t index = 0; index < libraries.size(); ++index) {
auto name = index < guestModules.size() ? libraries[index] : path + libraries[index];
std::replace(name.begin(), name.end(), '/', '\\');
if (name.size() + 1 >= PathCapacity)
throw Domain::RelinkerException("Windows library path exceeds the startup buffer: " + name);
libraryPaths.push_back(addString(name));
@ -78,6 +82,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const auto loading = addString("Loading PRX: ");
const auto loaded = addString(" -> OK\n");
const auto loadFailed = addString(" -> FAILED\n");
const auto failedModule = addString("Failed to load module: ");
const auto errorPrefix = addString("GetLastError: ");
const auto messageSeparator = addString(" - ");
const auto newline = addString("\n");
@ -218,7 +223,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
code.Emit({0x49, 0xff, 0xc4});
for (std::size_t index = 0; index < libraries.size(); ++index) {
if (absolutePath) {
if (absolutePath && index >= guestModules.size()) {
code.Rip({0x48, 0x8d, 0x0d}, libraryPaths[index]);
} else {
code.Emit({0x4c, 0x89, 0xe0, 0x48, 0x29, 0xd8, 0x48, 0x05});
@ -253,6 +258,9 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const auto loadSucceeded = code.Branch({0x0f, 0x85});
captureLastError();
writeString(loadFailed, true);
writeString(failedModule, true);
writeString(resolvedPaths[index], true);
writeString(newline, true);
writeLastError();
if (dependencyDiagnostics) {
code.Rip({0x48, 0x8d, 0x0d}, resolvedPaths[index]);
@ -270,7 +278,14 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
std::vector<std::size_t> unresolvedBranches;
std::vector<std::size_t> lazyUnresolvedImports;
std::vector<std::size_t> tlsResolverAddresses;
for (std::size_t index = 0; index < imports.size(); ++index) {
if (!guestModules.empty() && guestModules.front().UsePlatformTlsResolver && imports[index].Name == "vNe1w4diLCs") {
if (imports[index].Addend != 0) throw Domain::RelinkerException("TLS resolver import has an addend");
tlsResolverAddresses.push_back(code.Branch({0x48, 0x8d, 0x05}));
guestStartup.WriteImport(code, imports[index], handles);
continue;
}
code.Rip({0x48, 0x8d, 0x1d}, handles);
code.Rip({0x48, 0x8d, 0x35}, symbolNames[index]);
code.Emit({0xbd});
@ -291,7 +306,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
code.U64(imports[index].Addend);
code.Emit({0x48, 0x01, 0xd0});
}
code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva);
guestStartup.WriteImport(code, imports[index], handles);
code.PatchBranch(skipGotWrite, code.GetRva());
continue;
}
@ -299,20 +314,26 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
writeString(errorRvas.at(3 + index), true);
unresolvedBranches.push_back(code.Branch({0xe9}));
code.PatchBranch(resolved, code.GetRva());
if (imports[index].RelocationType == 16) code.Emit({0x48, 0x8b, 0x00});
if (imports[index].RelocationType == 17) code.Emit({0x48, 0x8b, 0x40, 8});
if (imports[index].Addend != 0) {
code.Emit({0x48, 0xba});
code.U64(imports[index].Addend);
code.Emit({0x48, 0x01, 0xd0});
}
code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva);
guestStartup.WriteImport(code, imports[index], handles);
}
guestStartup.Initialize(code, guestModules, handles);
writeString(enteringElf);
code.Emit({0x48, 0xc7, 0x44, 0x24, 0x40, 1, 0, 0, 0});
code.Rip({0x48, 0x8d, 0x05}, programPath);
code.Emit({0x48, 0x89, 0x44, 0x24, 0x48, 0x31, 0xc0, 0x48, 0x89, 0x44, 0x24, 0x50, 0x48, 0x89, 0x44, 0x24, 0x58, 0x48, 0x8d, 0x7c, 0x24, 0x40});
const auto exitCallback = code.Branch({0x48, 0x8d, 0x35});
code.Rip({0xe8}, entryRva);
code.Emit({0x89, 0x44, 0x24, 0x58});
guestStartup.Finalize(code, guestModules, handles, guestFinished);
code.Emit({0x8b, 0x44, 0x24, 0x58});
code.Emit({0x89, 0xc1});
call("ExitProcess");
code.Emit({0x0f, 0x0b});
@ -332,7 +353,12 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
const auto functionEnd = code.GetRva();
code.PatchBranch(exitCallback, functionEnd);
code.Emit({0x48, 0x83, 0xec, 0x28});
guestStartup.Finalize(code, guestModules, handles, guestFinished);
code.Emit({0x48, 0x83, 0xc4, 0x28});
code.Emit({0xc3});
const auto tlsResolver = guestStartup.EmitTlsResolver(code);
for (const auto offset : tlsResolverAddresses) code.PatchBranch(offset, tlsResolver);
for (const auto index : lazyUnresolvedImports) {
const auto stubRva = code.GetRva();

View File

@ -0,0 +1,165 @@
#include <elfpatcher/general/GuestModuleWriter.hpp>
#include <elfpatcher/windows/WindowsLoadImage.hpp>
#include <elfpatcher/windows/WindowsTlsBuilder.hpp>
#include <elfpatcher/windows/WindowsPeWriter.hpp>
#include <elfpatcher/windows/WindowsRelocationBuilder.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <map>
#include <iterator>
namespace Elfpatcher {
std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestImage& guest, Domain::GuestRuntime& runtime) const {
using namespace Windows;
WindowsLoadImage image(guest.Bytes, guest.Headers);
std::vector<std::uint32_t> relocations;
std::vector<std::pair<std::uint64_t, std::uint32_t>> tlsModules;
std::map<std::uint64_t, std::uint64_t> targets;
const auto apply = [&](const std::vector<std::uint8_t>& table) {
for (std::size_t offset = 0; offset < table.size(); offset += 24) {
const auto target = Io::ReadU64(table, offset);
const auto info = Io::ReadU64(table, offset + 8);
const auto addend = Io::ReadU64(table, offset + 16);
const auto type = static_cast<std::uint32_t>(info);
const auto symbolIndex = info >> 32;
const auto& symbol = guest.Symbols.at(symbolIndex);
const auto rva = image.GetRva(target, 8);
for (const auto& header : guest.Headers) {
if (header.Type != 7 || header.FileSize == 0) continue;
const auto templateRva = image.GetRva(header.MappedAddress, header.FileSize);
const auto templateEnd = static_cast<std::uint64_t>(templateRva) + header.FileSize;
if (rva >= templateEnd || static_cast<std::uint64_t>(rva) + 8 <= templateRva) continue;
if (rva < templateRva || templateEnd - rva < 8) throw Domain::RelinkerException("Guest relocation crosses the TLS template boundary", target);
if (type == 16 || (symbolIndex != 0 && symbol.Section == 0)) throw Domain::RelinkerException("Runtime-bound guest TLS template relocation is not supported", target);
}
const auto next = targets.lower_bound(target);
if ((next != targets.end() && next->first < target + 8) || (next != targets.begin() && std::prev(next)->second > target)) throw Domain::RelinkerException("Overlapping guest relocations", target);
targets.emplace(target, target + 8);
image.RequireWritable(target, 8);
if (type == 8) {
if (symbolIndex != 0) throw Domain::RelinkerException("RELATIVE guest relocation has a symbol", target);
image.WritePointer(target, image.GetRelocatedAddress(addend));
relocations.push_back(rva);
} else if (type == 16) {
if (addend != 0 || (symbolIndex != 0 && (symbol.Info & 15) != 6)) throw Domain::RelinkerException("Invalid guest TLS module relocation", target);
if (symbolIndex != 0 && symbol.Section == 0) {
image.WritePointer(target, 0);
runtime.Imports.push_back({symbol.Name, rva, 0, 16});
} else tlsModules.emplace_back(target, rva);
} else if (type == 17) {
if (symbolIndex != 0 && (symbol.Info & 15) != 6) throw Domain::RelinkerException("Invalid guest TLS offset relocation", target);
if (symbolIndex != 0 && symbol.Section == 0) {
image.WritePointer(target, 0);
runtime.Imports.push_back({symbol.Name, rva, addend, 17});
} else image.WritePointer(target, symbol.Value + addend);
} else if (type == 18) {
const auto tls = std::find_if(guest.Headers.begin(), guest.Headers.end(), [](const auto& header) { return header.Type == 7; });
if (tls == guest.Headers.end() || (symbolIndex != 0 && (symbol.Section == 0 || (symbol.Info & 15) != 6))) throw Domain::RelinkerException("Unsupported external static TLS relocation", target);
const auto size = Io::AlignUp64(tls->MemorySize, std::max<std::uint64_t>(tls->Alignment, 16));
image.WritePointer(target, symbol.Value + addend - size);
} else if (type == 1 || type == 6 || type == 7) {
if (symbolIndex == 0 || (type != 1 && addend != 0) || (symbol.Info & 15) == 6) throw Domain::RelinkerException("Invalid guest symbol relocation", target);
if (symbol.Section != 0) {
if (symbol.Section >= 0xff00) throw Domain::RelinkerException("Unsupported special guest symbol section", target);
image.WritePointer(target, image.GetRelocatedAddress(symbol.Value) + addend);
relocations.push_back(rva);
} else {
if (symbol.Name.empty()) throw Domain::RelinkerException("Empty guest import", target);
image.WritePointer(target, 0);
runtime.Imports.push_back({symbol.Name, rva, addend});
}
} else throw Domain::RelinkerException("Unsupported Windows guest relocation " + std::to_string(type), target);
}
};
apply(guest.Dynamic.RelaData);
apply(guest.Dynamic.RelaPltData);
auto sections = image.BuildSections();
auto nextRva = image.GetEndRva();
std::array<PeDirectory, 16> directories{};
std::uint32_t tlsIndex = 0;
directories[9] = WindowsTlsBuilder().Build(guest.Bytes, guest.Headers, image, sections, relocations, nextRva, &tlsIndex);
for (const auto& [target, rva] : tlsModules) {
if (tlsIndex == 0) throw Domain::RelinkerException("Guest TLS relocation has no TLS block", target);
bool written = false;
for (auto& section : sections) {
if (rva < section.Rva || rva - section.Rva > section.Data.size() || section.Data.size() - (rva - section.Rva) < 8) continue;
Io::WriteU64(section.Data, rva - section.Rva, ImageBase + tlsIndex);
written = true;
break;
}
if (!written) throw Domain::RelinkerException("Guest TLS target is unmapped", target);
relocations.push_back(rva);
}
for (const auto& header : guest.Headers) {
if (header.Type != 0x6474e550) continue;
std::vector<std::uint8_t> metadata(4);
Io::WriteU32(metadata, 0, image.GetRva(header.MappedAddress, header.FileSize));
sections.push_back({".ehmeta", nextRva, SectionRead | 0x40u, std::move(metadata)});
nextRva = AlignRva(nextRva + sections.back().Data.size());
}
std::map<std::string, std::uint32_t> exports;
PeSection tlsExports{".tlsrefs", nextRva, SectionRead | 0x40u, {}};
for (const auto& symbol : guest.Symbols) {
if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
if (symbol.Section >= 0xff00) throw Domain::RelinkerException("Unsupported guest export section: " + symbol.Name);
std::uint32_t rva = 0;
if ((symbol.Info & 15) == 6) {
if (tlsIndex == 0) throw Domain::RelinkerException("Guest TLS export has no TLS block: " + symbol.Name);
rva = CheckedRva(tlsExports.Rva + tlsExports.Data.size());
Io::AppendU64(tlsExports.Data, ImageBase + tlsIndex);
Io::AppendU64(tlsExports.Data, symbol.Value);
relocations.push_back(rva);
} else rva = image.GetRva(symbol.Value, std::max<std::uint64_t>(symbol.Size, 1));
if (!exports.emplace(symbol.Name, rva).second) throw Domain::RelinkerException("Duplicate guest export: " + symbol.Name);
}
if (!tlsExports.Data.empty()) {
nextRva = AlignRva(nextRva + tlsExports.Data.size());
sections.push_back(std::move(tlsExports));
}
if (exports.size() > 65535) throw Domain::RelinkerException("Too many guest PE exports");
PeSection exportSection{".edata", nextRva, SectionRead | 0x40u, std::vector<std::uint8_t>(40)};
auto& data = exportSection.Data;
const auto count = CheckedRva(exports.size());
const auto functions = data.size();
data.resize(data.size() + count * 4);
const auto names = data.size();
data.resize(data.size() + count * 4);
const auto ordinals = data.size();
data.resize(data.size() + count * 2);
Io::WriteU32(data, 12, CheckedRva(nextRva + data.size()));
Io::AppendString(data, guest.OutputName);
Io::WriteU32(data, 16, 1);
Io::WriteU32(data, 20, count);
Io::WriteU32(data, 24, count);
Io::WriteU32(data, 28, CheckedRva(nextRva + functions));
Io::WriteU32(data, 32, CheckedRva(nextRva + names));
Io::WriteU32(data, 36, CheckedRva(nextRva + ordinals));
std::size_t index = 0;
for (const auto& [name, rva] : exports) {
Io::WriteU32(data, functions + index * 4, rva);
Io::WriteU32(data, names + index * 4, CheckedRva(nextRva + data.size()));
Io::WriteU16(data, ordinals + index * 2, static_cast<std::uint16_t>(index));
Io::AppendString(data, name);
++index;
}
directories[0] = {nextRva, CheckedRva(data.size())};
nextRva = AlignRva(nextRva + data.size());
sections.push_back(std::move(exportSection));
auto relocationData = WindowsRelocationBuilder().BuildBaseRelocations(relocations);
if (!relocationData.empty()) {
directories[5] = {nextRva, CheckedRva(relocationData.size())};
sections.push_back({".reloc", nextRva, SectionRead | 0x02000040u, std::move(relocationData)});
nextRva = AlignRva(nextRva + sections.back().Data.size());
}
const auto entry = nextRva;
sections.push_back({".dllmain", entry, SectionRead | SectionExecute | 0x20u, {0xb8, 1, 0, 0, 0, 0xc3}});
runtime.InitRva = guest.Init == 0 ? 0 : image.GetRva(guest.Init);
runtime.FiniRva = guest.Fini == 0 ? 0 : image.GetRva(guest.Fini);
auto result = WindowsPeWriter().Write(sections, entry, directories);
const auto characteristics = Io::ReadU16(result, 0x80 + 22);
Io::WriteU16(result, 0x80 + 22, static_cast<std::uint16_t>((characteristics | 0x2000) & ~1u));
return result;
}
}

View File

@ -0,0 +1,43 @@
#include <elfpatcher/windows/WindowsGuestStartup.hpp>
namespace Elfpatcher::Windows {
void WindowsGuestStartup::WriteImport(WindowsStubEmitter& code, const PeImport& import, const std::uint32_t handles) const {
if (import.TargetModule < 0) {
code.Rip({0x48, 0x89, 0x05}, import.TargetRva);
return;
}
code.Rip({0x48, 0x8b, 0x15}, CheckedRva(handles + static_cast<std::uint64_t>(import.TargetModule) * 8));
code.Emit({0x41, 0xb8});
code.U32(import.TargetRva);
code.Emit({0x4a, 0x89, 0x04, 0x02});
}
void WindowsGuestStartup::callLifecycle(WindowsStubEmitter& code, const std::uint32_t handle, const std::uint32_t rva) const {
if (rva == 0) return;
code.Rip({0x48, 0x8b, 0x05}, handle);
code.Emit({0xb9});
code.U32(rva);
code.Emit({0x48, 0x01, 0xc8, 0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xff, 0xd0});
}
void WindowsGuestStartup::Initialize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, const std::uint32_t handles) const {
for (std::size_t index = 0; index < modules.size(); ++index) callLifecycle(code, CheckedRva(handles + index * 8), modules[index].InitRva);
}
void WindowsGuestStartup::Finalize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, const std::uint32_t handles, const std::uint32_t finished) const {
code.Emit({0xb8, 1, 0, 0, 0});
code.Rip({0x87, 0x05}, finished);
code.Emit({0x85, 0xc0});
const auto skip = code.Branch({0x0f, 0x85});
for (std::size_t index = modules.size(); index > 0; --index) callLifecycle(code, CheckedRva(handles + (index - 1) * 8), modules[index - 1].FiniRva);
code.PatchBranch(skip, code.GetRva());
}
std::uint32_t WindowsGuestStartup::EmitTlsResolver(WindowsStubEmitter& code) const {
const auto entry = code.GetRva();
code.Emit({0x48, 0x8b, 0x07, 0x8b, 0x08, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x58, 0, 0, 0, 0x48, 0x8b, 0x04, 0xc8, 0x48, 0x03, 0x47, 8, 0xc3});
return entry;
}
}

View File

@ -78,10 +78,17 @@ std::vector<std::uint8_t> WindowsPePatcher::Patch(const std::vector<std::uint8_t
directories[1] = nativeImports.Directory;
directories[12] = nativeImports.AddressTable;
nextRva = AlignRva(nextRva + nativeImports.Section.Data.size());
const auto libraries = importBuilder.ReadLibraries(dynamicSection);
auto libraries = importBuilder.ReadLibraries(dynamicSection);
std::vector<std::string> guestPaths;
for (std::size_t index = 0; index < dynamicSection.GuestModules.size(); ++index) {
const auto& module = dynamicSection.GuestModules[index];
guestPaths.push_back(module.Path);
for (const auto& import : module.Imports) relocations.Imports.push_back({import.Name, import.TargetRva, import.Addend, static_cast<std::int32_t>(index), import.RelocationType});
}
libraries.insert(libraries.begin(), guestPaths.begin(), guestPaths.end());
if (dependencyDiagnostics)
writeDiagnosticsImports(relocations.Imports);
auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics);
auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics, dynamicSection.GuestModules);
directories[3] = entry.ExceptionDirectory;
const auto entryRva = entry.Code.Rva;
sections.push_back(std::move(nativeImports.Section));

View File

@ -1,6 +1,8 @@
#include <elfpatcher/windows/WindowsTlsBuilder.hpp>
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
#include <elfpatcher/windows/WindowsTlsTemplateBuilder.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <relinker/analysis/CodeInstructionCollector.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <bit>
@ -14,6 +16,8 @@ namespace {
struct TlsAccess {
std::uint32_t Rva;
std::size_t Length;
bool StoreImmediate;
std::uint32_t Immediate;
};
void patchAccess(std::vector<PeSection>& sections, const TlsAccess& access, const std::uint32_t target) {
@ -35,12 +39,13 @@ void patchAccess(std::vector<PeSection>& sections, const TlsAccess& access, cons
}
PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva) const {
PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva) const {
const Domain::ProgramHeader* tls = nullptr;
const Codegen::X64InstructionDecoder decoder;
std::vector<TlsAccess> accesses;
std::set<std::uint32_t> branchTargets;
const auto instructions = Relinker::CodeInstructionCollector().Collect(source, headers);
for (const auto& header : headers) {
if (header.Type == 7) {
@ -50,7 +55,8 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
}
if (header.Type != 1 || (header.Flags & 1) == 0)
continue;
for (std::uint64_t offset = 0; offset < header.FileSize;) {
for (auto instruction = instructions.lower_bound(header.MappedAddress); instruction != instructions.end() && *instruction - header.MappedAddress < header.FileSize; ++instruction) {
const auto offset = *instruction - header.MappedAddress;
const auto* bytes = source.data() + header.Offset + offset;
const auto info = decoder.DecodeInstruction(bytes, header.FileSize - offset);
const auto rva = image.GetRva(header.MappedAddress + offset, info.Length);
@ -63,11 +69,19 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
if (info.SegmentPrefix != 0) {
const auto position = info.OpcodeOffset;
if (info.SegmentPrefix != 0x64 || info.RexPrefix != 0x48 || info.Length - position != 7 || bytes[position] != 0x8b || bytes[position + 1] != 0x04 || bytes[position + 2] != 0x25 || Io::ReadU32(source, header.Offset + offset + position + 3) != 0)
bool hasOperandSizePrefix = false;
bool supportedPrefixes = info.SegmentPrefix == 0x64;
for (std::size_t prefix = 0; prefix < position; ++prefix) {
const auto value = bytes[prefix];
if (value == 0x66) hasOperandSizePrefix = true;
else if (value != 0x64 && !(prefix + 1 == position && value >= 0x40 && value <= 0x4f)) supportedPrefixes = false;
}
const bool loadPointer = supportedPrefixes && info.RexPrefix == 0x48 && info.Length - position == 7 && bytes[position] == 0x8b && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0;
const bool storeImmediate = supportedPrefixes && !hasOperandSizePrefix && (info.RexPrefix == 0 || info.RexPrefix == 0x40) && info.Length - position == 11 && bytes[position] == 0xc7 && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0x28;
if (!loadPointer && !storeImmediate)
throw Domain::RelinkerException("Unsupported Windows guest TLS instruction", header.Offset + offset);
accesses.push_back({rva, info.Length});
accesses.push_back({rva, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0});
}
offset += info.Length;
}
}
@ -77,7 +91,7 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
return {};
}
if (tls->FileSize > tls->MemorySize || tls->Offset > source.size() || tls->FileSize > source.size() - tls->Offset || !std::has_single_bit(tls->Alignment) || tls->Alignment > 8192 || tls->MemorySize > 0x7fff0000u)
if (tls->FileSize > tls->MemorySize || tls->Offset > source.size() || tls->FileSize > source.size() - tls->Offset || (tls->Alignment > 1 && !std::has_single_bit(tls->Alignment)) || tls->Alignment > 8192 || tls->MemorySize > 0x7fff0000u)
throw Domain::RelinkerException("Invalid or unsupported ELF TLS layout", tls->Offset);
// Native homebrew linkers can emit an empty PT_TLS placeholder.
// It needs no Windows TLS directory unless guest code accesses TLS.
@ -89,9 +103,11 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
const auto alignment = std::max<std::uint64_t>(tls->Alignment, 16);
const auto blockSize = CheckedRva((tls->MemorySize + alignment - 1) & ~(alignment - 1));
const auto templateOffset = CheckedRva((64 + alignment - 1) & ~(alignment - 1));
PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(templateOffset + blockSize + 16)};
constexpr std::uint32_t threadControlBlockSize = 0x30;
PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(templateOffset + blockSize + threadControlBlockSize)};
const auto indexRva = nextRva + 40;
if (tlsIndexRva != nullptr) *tlsIndexRva = indexRva;
const auto callbackTableRva = nextRva + 48;
const auto templateRva = nextRva + templateOffset;
const auto codeRva = AlignRva(nextRva + data.Data.size());
@ -120,19 +136,26 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
throw Domain::RelinkerException("Branch enters a guest TLS instruction", *target);
patchAccess(sections, access, code.GetRva());
code.Emit({0x48, 0x8d, 0x64, 0x24, 0x80, 0x51});
if (access.StoreImmediate) code.Emit({0x50});
loadPointer();
if (access.StoreImmediate) {
code.Emit({0xc7, 0x40, 0x28});
code.U32(access.Immediate);
code.Emit({0x58});
}
code.Emit({0x59, 0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0});
code.Rip({0xe9}, CheckedRva(access.Rva + access.Length));
}
std::copy_n(source.begin() + tls->Offset, tls->FileSize, data.Data.begin() + templateOffset);
const auto templateBytes = WindowsTlsTemplateBuilder().Build(*tls, image, sections, templateRva, relocations);
std::copy(templateBytes.begin(), templateBytes.end(), data.Data.begin() + templateOffset);
const auto writeAddress = [&](const std::size_t offset, const std::uint32_t rva) {
Io::WriteU64(data.Data, offset, ImageBase + rva);
relocations.push_back(CheckedRva(data.Rva + offset));
};
writeAddress(0, templateRva);
writeAddress(8, CheckedRva(templateRva + blockSize + 16));
writeAddress(8, CheckedRva(templateRva + blockSize + threadControlBlockSize));
writeAddress(16, indexRva);
writeAddress(24, callbackTableRva);
writeAddress(48, codeRva);

View File

@ -0,0 +1,32 @@
#include <elfpatcher/windows/WindowsTlsTemplateBuilder.hpp>
#include <algorithm>
namespace Elfpatcher::Windows {
std::vector<std::uint8_t> WindowsTlsTemplateBuilder::Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector<PeSection>& sections, const std::uint32_t templateRva, std::vector<std::uint32_t>& relocations) const {
if (tls.FileSize == 0) return {};
const auto sourceRva = image.GetRva(tls.MappedAddress, tls.FileSize);
const auto sourceEnd = static_cast<std::uint64_t>(sourceRva) + tls.FileSize;
std::vector<std::uint8_t> result;
result.reserve(static_cast<std::size_t>(tls.FileSize));
while (result.size() < tls.FileSize) {
const auto currentRva = static_cast<std::uint64_t>(sourceRva) + result.size();
const auto section = std::find_if(sections.begin(), sections.end(), [&](const auto& candidate) {
return currentRva >= candidate.Rva && currentRva - candidate.Rva < candidate.Data.size();
});
if (section == sections.end()) throw Domain::RelinkerException("TLS template is outside PE section data", currentRva);
const auto offset = static_cast<std::size_t>(currentRva - section->Rva);
const auto size = std::min<std::uint64_t>(tls.FileSize - result.size(), section->Data.size() - offset);
result.insert(result.end(), section->Data.begin() + offset, section->Data.begin() + offset + size);
}
const auto originalCount = relocations.size();
for (std::size_t index = 0; index < originalCount; ++index) {
const auto target = static_cast<std::uint64_t>(relocations[index]);
if (target >= sourceEnd || target + 8 <= sourceRva) continue;
if (target < sourceRva || sourceEnd - target < 8) throw Domain::RelinkerException("PE relocation crosses the TLS template boundary", target);
relocations.push_back(CheckedRva(static_cast<std::uint64_t>(templateRva) + target - sourceRva));
}
return result;
}
}

View File

@ -17,11 +17,13 @@
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
#include <relinker/output/CallRegistryWriter.hpp>
#include <relinker/pipeline/RelinkerPipeline.hpp>
#include <relinker/guest/GuestImage.hpp>
#include <codegen/IAmd64OnlyConverter.hpp>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
int main(const int argc, char* argv[]) {
Cli::Args args;
@ -53,10 +55,11 @@ int main(const int argc, char* argv[]) {
}
auto elfReader = std::make_shared<Relinker::ElfReader>(sourceBytes);
const std::shared_ptr<Relinker::ISyscallScanner> syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner();
const auto pipeline = std::make_shared<Relinker::RelinkerPipeline>(
elfReader,
args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(),
syscallScanner,
Relinker::MakeCallSiteResolver(),
std::make_shared<Relinker::ValidationPolicy>(),
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
@ -65,6 +68,7 @@ int main(const int argc, char* argv[]) {
);
std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n";
std::cout << "sce_module/sce_modules processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n';
auto result = pipeline->Relink(sourceBytes);
for (const auto& patch : result.Patches) {
if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset)
@ -72,6 +76,11 @@ int main(const int argc, char* argv[]) {
for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index];
}
std::vector<Relinker::GuestArtifact> guestArtifacts;
if (!args.skipSceModule) {
guestArtifacts = Relinker::GuestModuleBuilder().Build(args.inputPath, absPath, result.DynamicSection, args.toWindows, args.toIntel, *syscallScanner, args.lazyBinding, args.runPath);
}
if (args.writeRegistry) {
const std::filesystem::path outFsPath(absPath);
const std::string registryPath = (outFsPath.parent_path() / (outFsPath.stem().string() + ".registry.json")).string();
@ -95,7 +104,13 @@ int main(const int argc, char* argv[]) {
);
}
fileWriter.Write(absPath, patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics));
const auto executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics);
for (const auto& artifact : guestArtifacts) {
std::filesystem::create_directories(artifact.Path.parent_path());
fileWriter.Write(artifact.Path.string(), artifact.Bytes);
std::cout << "Guest module: " << artifact.Path.string() << '\n';
}
fileWriter.Write(absPath, executableBytes);
std::cout << "External prx references: " << result.RegistryEntries.size() << "\nOutput file: " << absPath << '\n';
if (args.autorun) return Cli::Autorun(absPath, args.toWindows);

View File

@ -0,0 +1,16 @@
#ifndef RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP
#define RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP
#include <domain/Types.hpp>
#include <set>
namespace Relinker {
class CodeInstructionCollector {
public:
std::set<Domain::VirtualAddress> Collect(const std::vector<std::uint8_t>& bytes, const std::vector<Domain::ProgramHeader>& headers) const;
};
}
#endif

View File

@ -0,0 +1,52 @@
#ifndef RELINKER_GUESTIMAGE_HPP
#define RELINKER_GUESTIMAGE_HPP
#include <domain/Types.hpp>
#include <relinker/domain/ISyscallScanner.hpp>
#include <filesystem>
namespace Relinker {
inline constexpr char GuestModuleSuffix[] = ".guest.prx";
struct GuestSymbol {
std::string Name;
std::uint8_t Info;
std::uint8_t Visibility;
std::uint16_t Section;
std::uint64_t Value;
std::uint64_t Size;
};
struct GuestImage {
std::filesystem::path SourcePath;
std::string OutputName;
std::vector<std::uint8_t> Bytes;
std::vector<Domain::ProgramHeader> Headers;
std::vector<GuestSymbol> Symbols;
std::vector<std::string> Dependencies;
Domain::SysVDynamicSection Dynamic;
std::uint64_t Init = 0;
std::uint64_t Fini = 0;
std::uint64_t Got = 0;
bool UsePlatformTlsResolver = true;
};
class GuestImageReader {
public:
GuestImage Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const;
};
struct GuestArtifact {
std::filesystem::path Path;
std::vector<std::uint8_t> Bytes;
};
class GuestModuleBuilder {
public:
std::vector<GuestArtifact> Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, bool windows, bool toIntel, ISyscallScanner& syscallScanner, bool lazyBinding, const std::string& runPath) const;
};
}
#endif

View File

@ -0,0 +1,225 @@
#include <relinker/analysis/CodeInstructionCollector.hpp>
#include <relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp>
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <codegen/CodegenException.hpp>
#include <io/BufferUtils.hpp>
#include <limits>
#include <map>
#include <string>
namespace Relinker {
namespace {
class CodePointers : public UnusedNidFilter::IRelativeRelocationIndex {
public:
std::map<Domain::VirtualAddress, Domain::VirtualAddress> Values;
std::optional<Domain::VirtualAddress> TargetOfSlot(Domain::VirtualAddress address) const override {
const auto found = Values.find(address);
if (found == Values.end()) return std::nullopt;
return found->second;
}
};
}
std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::vector<std::uint8_t>& bytes, const std::vector<Domain::ProgramHeader>& headers) const {
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
if (offset > bytes.size() || size > bytes.size() - offset) throw Domain::RelinkerException("Code analysis: file range exceeds image", offset);
};
const auto fileOffset = [&](std::uint64_t address, std::uint64_t size) {
for (const auto& header : headers) {
if (header.Type != 1 || address < header.MappedAddress || address - header.MappedAddress > header.FileSize || size > header.FileSize - (address - header.MappedAddress)) continue;
const auto offset = header.Offset + address - header.MappedAddress;
range(offset, size);
return offset;
}
throw Domain::RelinkerException("Code analysis: unmapped address", address);
};
const auto isCode = [&](std::uint64_t address) {
for (const auto& header : headers) if (header.Type == 1 && (header.Flags & 1) != 0 && address >= header.MappedAddress && address - header.MappedAddress < header.FileSize) return true;
return false;
};
std::map<std::uint64_t, std::uint64_t> tags;
std::optional<std::uint64_t> sceBase;
bool hasDynamic = false;
for (const auto& header : headers) {
if (header.Type == 0x61000000) sceBase = header.Offset;
if (header.Type != 2) continue;
if (hasDynamic || header.FileSize % 16 != 0) throw Domain::RelinkerException("Code analysis: invalid dynamic segment");
hasDynamic = true;
range(header.Offset, header.FileSize);
bool terminated = false;
for (std::uint64_t offset = header.Offset; offset < header.Offset + header.FileSize; offset += 16) {
const auto tag = Io::ReadU64(bytes, offset);
if (tag == 0) { terminated = true; break; }
const auto value = Io::ReadU64(bytes, offset + 8);
if (tag == 1 || (tag >= 0x61000000 && tag != 0x6100003f && (tag < 0x61000027 || tag > 0x6100003b))) continue;
if (!tags.emplace(tag, value).second) throw Domain::RelinkerException("Code analysis: duplicate dynamic tag", offset);
}
if (!terminated) throw Domain::RelinkerException("Code analysis: unterminated dynamic segment");
}
const auto value = [&](std::uint64_t standard, std::uint64_t sce) -> std::optional<std::uint64_t> {
if (tags.contains(standard) && tags.contains(sce)) throw Domain::RelinkerException("Code analysis: ambiguous dynamic tag");
if (tags.contains(standard)) return tags.at(standard);
if (tags.contains(sce)) return tags.at(sce);
return std::nullopt;
};
const auto table = [&](std::uint64_t standard, std::uint64_t sce, std::uint64_t size) {
const auto address = value(standard, sce);
if (!address) throw Domain::RelinkerException("Code analysis: missing table");
if (tags.contains(standard)) return fileOffset(*address, size);
if (!sceBase || *address > std::numeric_limits<std::uint64_t>::max() - *sceBase) throw Domain::RelinkerException("Code analysis: invalid SCE table");
const auto offset = *sceBase + *address;
range(offset, size);
return offset;
};
std::set<std::uint64_t> roots;
std::map<std::uint64_t, std::uint64_t> functions;
const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); };
const auto addFunction = [&](std::uint64_t begin, std::uint64_t size) {
if (size == 0) return;
if (!isCode(begin) || size > std::numeric_limits<std::uint64_t>::max() - begin)
throw Domain::RelinkerException("Code analysis: invalid function range", begin);
bool mapped = false;
for (const auto& header : headers) {
if (header.Type != 1 || (header.Flags & 1) == 0 || begin < header.MappedAddress || begin - header.MappedAddress >= header.FileSize) continue;
if (size > header.FileSize - (begin - header.MappedAddress))
throw Domain::RelinkerException("Code analysis: function exceeds executable segment", begin);
range(header.Offset + begin - header.MappedAddress, size);
mapped = true;
break;
}
if (!mapped) throw Domain::RelinkerException("Code analysis: function is not file-backed", begin);
const auto [position, inserted] = functions.emplace(begin, begin + size);
if (!inserted && position->second != begin + size)
throw Domain::RelinkerException("Code analysis: conflicting function ranges", begin);
roots.insert(begin);
};
range(0, 64);
if (const auto entry = Io::ReadU64(bytes, 24); entry != 0) addRoot(entry);
for (const auto tag : {12u, 13u}) if (const auto address = value(tag, 0x60000000 + tag); address && *address != 0) addRoot(*address);
const auto symbolBytes = value(0x6100003f, 0xffffffffffffffffull);
std::uint64_t symbolCount = 0;
if (symbolBytes) {
if (*symbolBytes % 24 != 0) throw Domain::RelinkerException("Code analysis: invalid symbol table size");
symbolCount = *symbolBytes / 24;
} else if (tags.contains(4)) symbolCount = Io::ReadU32(bytes, fileOffset(tags.at(4), 8) + 4);
std::uint64_t symbols = 0;
if (symbolCount != 0) {
if (value(11, 0x6100003b) != 24) throw Domain::RelinkerException("Code analysis: invalid symbol entry size");
symbols = table(6, 0x61000039, symbolCount * 24);
for (std::uint64_t index = 0; index < symbolCount; ++index) {
const auto offset = symbols + index * 24;
if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) {
const auto address = Io::ReadU64(bytes, offset + 8);
addRoot(address);
addFunction(address, Io::ReadU64(bytes, offset + 16));
}
}
}
CodePointers pointers;
std::set<std::uint64_t> importSlots;
const auto readRelocations = [&](std::uint64_t addressTag, std::uint64_t sceAddressTag, std::uint64_t sizeTag, std::uint64_t sceSizeTag) {
const auto size = value(sizeTag, sceSizeTag);
if (!size) return;
if (*size % 24 != 0) throw Domain::RelinkerException("Code analysis: invalid relocation table size");
if (*size == 0) return;
const auto start = table(addressTag, sceAddressTag, *size);
for (std::uint64_t offset = start; offset < start + *size; offset += 24) {
const auto slot = Io::ReadU64(bytes, offset);
const auto info = Io::ReadU64(bytes, offset + 8);
const auto type = static_cast<std::uint32_t>(info);
const auto index = info >> 32;
const auto addend = Io::ReadU64(bytes, offset + 16);
if (type == 8) {
if (index != 0 || !pointers.Values.emplace(slot, addend).second) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot);
addRoot(addend);
} else if (type == 1 || type == 6 || type == 7) {
if (index == 0 || index >= symbolCount) throw Domain::RelinkerException("Code analysis: invalid relocation symbol", slot);
const auto symbol = symbols + index * 24;
if (Io::ReadU16(bytes, symbol + 6) == 0) importSlots.insert(slot);
else {
const auto target = Io::ReadU64(bytes, symbol + 8) + addend;
if (!pointers.Values.emplace(slot, target).second) throw Domain::RelinkerException("Code analysis: duplicate pointer relocation", slot);
addRoot(target);
}
}
}
};
readRelocations(7, 0x6100002f, 8, 0x61000031);
readRelocations(23, 0x61000029, 2, 0x6100002d);
for (const auto& [addressTag, sizeTag] : {std::pair{25u, 27u}, std::pair{26u, 28u}, std::pair{32u, 33u}}) {
const auto address = value(addressTag, 0x60000000 + addressTag);
const auto size = value(sizeTag, 0x60000000 + sizeTag);
if (address.has_value() != size.has_value()) throw Domain::RelinkerException("Code analysis: incomplete initializer array");
if (!size || *size == 0) continue;
if (*size % 8 != 0) throw Domain::RelinkerException("Code analysis: invalid initializer array size");
const auto offset = fileOffset(*address, *size);
for (std::uint64_t index = 0; index < *size; index += 8) {
const auto relocated = pointers.TargetOfSlot(*address + index);
const auto target = relocated ? *relocated : Io::ReadU64(bytes, offset + index);
if (target == 0 || target == std::numeric_limits<std::uint64_t>::max()) continue;
if (!isCode(target)) throw Domain::RelinkerException("Code analysis: initializer lies outside code", target);
roots.insert(target);
}
}
for (const auto& function : UnusedNidFilter::ReadExceptionFunctions(bytes, headers, pointers.Values, importSlots)) {
if (function.End <= function.Begin) throw Domain::RelinkerException("Code analysis: invalid unwind function range", function.Begin);
addFunction(function.Begin, function.End - function.Begin);
for (const auto target : function.ExtraTargets) addRoot(target);
}
if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points");
std::set<std::uint64_t> instructions;
const Codegen::X64InstructionDecoder decoder;
for (const auto& [begin, end] : functions) {
const auto offset = fileOffset(begin, end - begin);
for (auto address = begin; address < end;) {
Codegen::DecodedInstructionInfo info;
try {
info = decoder.DecodeInstruction(bytes.data() + offset + address - begin, end - address);
} catch (const Codegen::CodegenException& error) {
throw Domain::RelinkerException(std::string("Code analysis: ") + error.what(), address);
}
if (info.Length == 0 || info.Length > end - address)
throw Domain::RelinkerException("Code analysis: instruction crosses function boundary", address);
instructions.insert(address);
if (info.HasBranchTarget && !info.HasRipRelativeDisp)
addRoot(address + info.Length + static_cast<std::uint64_t>(info.BranchDisp));
address += info.Length;
}
}
std::size_t previousRoots = 0;
do {
previousRoots = roots.size();
for (const auto& header : headers) {
if (header.Type != 1 || (header.Flags & 1) == 0 || header.FileSize == 0) continue;
range(header.Offset, header.FileSize);
std::vector<std::uint64_t> entries;
for (const auto root : roots) if (root >= header.MappedAddress && root - header.MappedAddress < header.FileSize) entries.push_back(root);
if (entries.empty()) continue;
const std::vector<std::uint8_t> text(bytes.begin() + header.Offset, bytes.begin() + header.Offset + header.FileSize);
const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers);
for (const auto address : graph->ReachableVaddrs()) {
instructions.insert(address);
const auto offset = address - header.MappedAddress;
const auto info = decoder.DecodeInstruction(text.data() + offset, text.size() - offset);
if (info.HasBranchTarget && !info.HasRipRelativeDisp) addRoot(static_cast<std::uint64_t>(static_cast<std::int64_t>(address + info.Length) + info.BranchDisp));
}
}
} while (roots.size() != previousRoots);
std::uint64_t previousEnd = 0;
for (const auto address : instructions) {
if (address < previousEnd) throw Domain::RelinkerException("Code analysis: overlapping instruction boundaries", address);
for (const auto& header : headers) {
if (header.Type != 1 || (header.Flags & 1) == 0 || address < header.MappedAddress || address - header.MappedAddress >= header.FileSize) continue;
const auto offset = address - header.MappedAddress;
previousEnd = address + decoder.DecodeInstruction(bytes.data() + header.Offset + offset, header.FileSize - offset).Length;
break;
}
}
return instructions;
}
}

View File

@ -0,0 +1,201 @@
#include <relinker/guest/GuestImage.hpp>
#include <relinker/parsing/ElfReader.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <bit>
#include <limits>
#include <iterator>
#include <set>
#include <sstream>
namespace Relinker {
GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const {
const auto fail = [&](const std::string& message) { throw Domain::RelinkerException(path.string() + ": " + message); };
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
if (offset > bytes.size() || size > bytes.size() - offset) {
std::ostringstream message;
message << "ELF range exceeds file: offset=0x" << std::hex << offset << ", size=0x" << size << ", fileSize=0x" << bytes.size();
fail(message.str());
}
};
range(0, 64);
if (Io::ReadU32(bytes, 0) != 0x464c457f || bytes[4] != 2 || bytes[5] != 1 || bytes[6] != 1 || Io::ReadU16(bytes, 18) != 62 || Io::ReadU32(bytes, 20) != 1) fail("Expected little-endian ELF64 x86-64");
const auto type = Io::ReadU16(bytes, 16);
if (type != 3 && type != 0xfe18) fail("ELF is not a shared module");
if (Io::ReadU16(bytes, 52) != 64 || Io::ReadU16(bytes, 54) != 56 || Io::ReadU16(bytes, 56) == 0) fail("Invalid ELF program header table");
range(Io::ReadU64(bytes, 32), static_cast<std::uint64_t>(Io::ReadU16(bytes, 56)) * 56);
GuestImage image;
image.SourcePath = path;
image.OutputName = path.filename().string() + GuestModuleSuffix;
image.Headers = ElfReader(bytes).ReadProgramHeaders();
const Domain::ProgramHeader* dynamic = nullptr;
const Domain::ProgramHeader* dynlib = nullptr;
const Domain::ProgramHeader* tls = nullptr;
std::map<std::uint64_t, std::uint64_t> loads;
constexpr std::uint32_t sceComment = 0x6fffff00;
constexpr std::uint32_t sceLibVersion = 0x6fffff01;
for (const auto& header : image.Headers) {
if (header.Type == sceComment || header.Type == sceLibVersion) continue;
range(header.Offset, header.FileSize);
if (header.Type == 1) {
if (header.FileSize > header.MemorySize || header.MemorySize > std::numeric_limits<std::uint64_t>::max() - header.MappedAddress || (header.Flags & ~7u) != 0) fail("Invalid load segment");
if (header.Alignment > 1 && (!std::has_single_bit(header.Alignment) || header.Offset % header.Alignment != header.MappedAddress % header.Alignment)) fail("Invalid load segment alignment");
const auto next = loads.lower_bound(header.MappedAddress);
if ((next != loads.end() && next->first < header.MappedAddress + header.MemorySize) || (next != loads.begin() && std::prev(next)->second > header.MappedAddress)) fail("Overlapping load segments");
loads.emplace(header.MappedAddress, header.MappedAddress + header.MemorySize);
}
if (header.Type == 2) {
if (dynamic != nullptr) fail("Duplicate dynamic segment");
dynamic = &header;
}
if (header.Type == 0x61000000) {
if (dynlib != nullptr) fail("Duplicate SCE dynamic data");
dynlib = &header;
}
if (header.Type == 7) {
if (tls != nullptr || header.FileSize > header.MemorySize || (header.Alignment > 1 && !std::has_single_bit(header.Alignment))) fail("Invalid TLS segment");
tls = &header;
}
}
if (loads.empty() || dynamic == nullptr || dynamic->FileSize % 16 != 0) fail("Missing or invalid ELF load/dynamic segments");
std::map<std::uint64_t, std::uint64_t> tags;
std::vector<std::uint64_t> needed;
bool terminated = false;
for (std::uint64_t offset = dynamic->Offset; offset < dynamic->Offset + dynamic->FileSize; offset += 16) {
const auto tag = Io::ReadU64(bytes, offset);
const auto value = Io::ReadU64(bytes, offset + 8);
if (tag == 0) { terminated = true; break; }
if (tag == 1) needed.push_back(value);
else if (tag < 0x60000000 || tag == 0x6100003f || (tag >= 0x61000027 && tag <= 0x6100003b)) {
if (!tags.emplace(tag, value).second) fail("Duplicate dynamic tag " + std::to_string(tag));
}
}
if (!terminated) fail("Unterminated dynamic segment");
for (const auto unsupported : {15u, 16u, 17u, 18u, 19u, 22u, 35u, 36u, 37u}) {
if (tags.contains(unsupported)) fail("Unsupported dynamic tag " + std::to_string(unsupported));
}
if (tags.contains(30) && (tags.at(30) & ~8ull) != 0) fail("Unsupported guest dynamic flags");
const auto mapped = [&](std::uint64_t address, std::uint64_t size, std::uint32_t flags) {
for (const auto& header : image.Headers) {
if (header.Type == 1 && (header.Flags & flags) == flags && address >= header.MappedAddress && address - header.MappedAddress <= header.MemorySize && size <= header.MemorySize - (address - header.MappedAddress)) return;
}
fail("Unmapped or inaccessible guest address " + std::to_string(address));
};
const auto translate = [&](std::uint64_t address, std::uint64_t size) {
for (const auto& header : image.Headers) {
if (header.Type == 1 && address >= header.MappedAddress && address - header.MappedAddress <= header.FileSize && size <= header.FileSize - (address - header.MappedAddress)) return header.Offset + address - header.MappedAddress;
}
fail("Unmapped ELF address " + std::to_string(address));
return std::uint64_t{};
};
const auto get = [&](std::uint64_t standard, std::uint64_t sce) {
if (tags.contains(standard) == tags.contains(sce)) fail("Missing or ambiguous dynamic tag " + std::to_string(standard));
return tags.at(tags.contains(standard) ? standard : sce);
};
const auto table = [&](std::uint64_t standard, std::uint64_t sce, std::uint64_t size) {
const auto value = get(standard, sce);
if (tags.contains(standard)) return translate(value, size);
if (dynlib == nullptr || value > dynlib->FileSize || size > dynlib->FileSize - value) fail("SCE table exceeds dynamic data segment");
return dynlib->Offset + value;
};
const auto strSize = get(10, 0x61000037);
const auto strOffset = table(5, 0x61000035, strSize);
const auto string = [&](std::uint64_t offset) {
if (offset >= strSize) fail("String offset out of bounds");
const auto start = bytes.begin() + static_cast<std::ptrdiff_t>(strOffset + offset);
const auto end = bytes.begin() + static_cast<std::ptrdiff_t>(strOffset + strSize);
const auto zero = std::find(start, end, 0);
if (zero == end) fail("Unterminated ELF string");
return std::string(start, zero);
};
if (strSize == 0 || bytes[strOffset] != 0 || get(11, 0x6100003b) != 24) fail("Invalid string or symbol table");
std::uint64_t symSize = 0;
if (tags.contains(0x6100003f)) symSize = tags.at(0x6100003f);
else if (tags.contains(4)) symSize = static_cast<std::uint64_t>(Io::ReadU32(bytes, translate(tags.at(4), 8) + 4)) * 24;
else fail("Missing dynamic symbol count");
if (symSize == 0 || symSize % 24 != 0 || symSize / 24 > std::numeric_limits<std::uint32_t>::max()) fail("Invalid dynamic symbol count");
const auto symOffset = table(6, 0x61000039, symSize);
image.Dynamic.DynSymData.assign(bytes.begin() + symOffset, bytes.begin() + symOffset + symSize);
image.Dynamic.DynStrData.push_back(0);
std::set<std::string> exports;
for (std::uint64_t offset = 0; offset < symSize; offset += 24) {
auto name = string(Io::ReadU32(bytes, symOffset + offset));
name = name.substr(0, name.find('#'));
const auto info = bytes[symOffset + offset + 4];
const auto visibility = bytes[symOffset + offset + 5];
const auto section = Io::ReadU16(bytes, symOffset + offset + 6);
const auto value = Io::ReadU64(bytes, symOffset + offset + 8);
const auto size = Io::ReadU64(bytes, symOffset + offset + 16);
if (offset == 0 && (info != 0 || visibility != 0 || section != 0 || value != 0 || size != 0 || !name.empty())) fail("Invalid null symbol");
if (offset == 0) {
image.Symbols.push_back({{}, 0, 0, 0, 0, 0});
continue;
}
if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name);
if (section != 0 && (info >> 4) != 0 && visibility != 1 && visibility != 2) {
if (name.empty() || !exports.insert(name).second) fail("Duplicate or empty export after stripping #: " + name);
}
if ((info & 15) == 6 && section != 0 && (tls == nullptr || value > tls->MemorySize || size > tls->MemorySize - value)) fail("TLS symbol exceeds TLS segment: " + name);
if (section != 0 && (info & 15) != 6) {
if (section >= 0xff00) fail("Unsupported special symbol section: " + name);
mapped(value, std::max<std::uint64_t>(size, 1), (info & 15) == 2 ? 1 : 0);
}
if (image.Dynamic.DynStrData.size() > std::numeric_limits<std::uint32_t>::max()) fail("String table too large");
Io::WriteU32(image.Dynamic.DynSymData, offset, static_cast<std::uint32_t>(image.Dynamic.DynStrData.size()));
Io::AppendString(image.Dynamic.DynStrData, name);
image.Symbols.push_back({name, info, visibility, section, value, size});
}
std::set<std::string> dependencies;
for (const auto offset : needed) {
const auto name = string(offset);
if (name.empty() || name.find_first_of("/\\:$") != std::string::npos || !dependencies.insert(name).second) fail("Invalid or duplicate dependency: " + name);
image.Dependencies.push_back(name);
}
std::map<std::uint64_t, std::uint64_t> relocationTargets;
const auto copyRelocations = [&](std::uint64_t addressTag, std::uint64_t sceAddressTag, std::uint64_t sizeTag, std::uint64_t sceSizeTag, std::vector<std::uint8_t>& output, bool plt) {
if (!tags.contains(addressTag) && !tags.contains(sceAddressTag) && !tags.contains(sizeTag) && !tags.contains(sceSizeTag)) return;
const auto size = get(sizeTag, sceSizeTag);
if (size % 24 != 0) fail("Invalid relocation table size");
const auto offset = table(addressTag, sceAddressTag, size);
output.assign(bytes.begin() + offset, bytes.begin() + offset + size);
for (std::uint64_t index = 0; index < size; index += 24) {
const auto info = Io::ReadU64(output, index + 8);
const auto relocation = static_cast<std::uint32_t>(info);
if ((info >> 32) >= image.Symbols.size() || (plt && relocation != 7) || (relocation != 1 && relocation != 6 && relocation != 7 && relocation != 8 && relocation != 16 && relocation != 17 && relocation != 18)) fail("Unsupported relocation or symbol index");
const auto target = Io::ReadU64(output, index);
const auto addend = Io::ReadU64(output, index + 16);
mapped(target, 8, 2);
const auto next = relocationTargets.lower_bound(target);
if ((next != relocationTargets.end() && next->first < target + 8) || (next != relocationTargets.begin() && std::prev(next)->second > target)) fail("Overlapping guest relocations");
relocationTargets.emplace(target, target + 8);
if (relocation == 8) {
if ((info >> 32) != 0) fail("RELATIVE relocation has a symbol");
mapped(addend, 0, 0);
} else if (relocation == 1 || relocation == 6 || relocation == 7) {
const auto& symbol = image.Symbols[info >> 32];
if ((info >> 32) == 0 || (symbol.Info & 15) == 6 || (relocation != 1 && addend != 0) || (symbol.Section == 0 && symbol.Name.empty())) fail("Invalid guest symbol relocation");
} else {
const auto& symbol = image.Symbols[info >> 32];
if (((info >> 32) == 0 && tls == nullptr) || ((info >> 32) != 0 && (symbol.Info & 15) != 6) || (relocation == 16 && addend != 0)) fail("Invalid guest TLS relocation");
}
}
};
if (tags.contains(7) || tags.contains(0x6100002f)) {
if (get(9, 0x61000033) != 24) fail("Invalid RELA entry size");
}
copyRelocations(7, 0x6100002f, 8, 0x61000031, image.Dynamic.RelaData, false);
copyRelocations(23, 0x61000029, 2, 0x6100002d, image.Dynamic.RelaPltData, true);
if (!image.Dynamic.RelaPltData.empty() && get(20, 0x6100002b) != 7) fail("Unsupported PLT relocation format");
if (tags.contains(3) || tags.contains(0x61000027)) image.Got = get(3, 0x61000027);
if (tags.contains(12)) image.Init = tags.at(12);
if (tags.contains(13)) image.Fini = tags.at(13);
if (image.Init != 0) mapped(image.Init, 1, 1);
if (image.Fini != 0) mapped(image.Fini, 1, 1);
if ((tags.contains(27) && tags.at(27) != 0) || (tags.contains(28) && tags.at(28) != 0)) fail("Separate guest INIT_ARRAY/FINI_ARRAY is not supported");
if (tags.contains(33) && tags.at(33) != 0 && image.Init == 0) fail("Guest PREINIT_ARRAY has no module initializer");
image.Bytes = std::move(bytes);
return image;
}
}

View File

@ -0,0 +1,153 @@
#include <relinker/guest/GuestImage.hpp>
#include <elfpatcher/general/GuestModuleWriter.hpp>
#include <codegen/IAmd64OnlyConverter.hpp>
#include <io/FileReader.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <set>
namespace Relinker {
std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath) const {
const auto root = std::filesystem::absolute(inputPath).parent_path();
const auto singular = root / "sce_module";
const auto plural = root / "sce_modules";
const bool hasSingular = std::filesystem::exists(singular);
const bool hasPlural = std::filesystem::exists(plural);
if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist beside the input executable");
if (!hasSingular && !hasPlural) {
std::cout << "WARNING: sce_module not found beside the input executable; it may be unnecessary or missing.\n";
return {};
}
const auto directory = hasSingular ? singular : plural;
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string());
std::vector<std::filesystem::path> paths;
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue;
if (!entry.is_regular_file()) continue;
std::ifstream stream(entry.path(), std::ios::binary);
if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string());
char magic[4]{};
stream.read(magic, 4);
if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string());
if (stream.gcount() == 4 && static_cast<unsigned char>(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path());
}
std::sort(paths.begin(), paths.end());
if (paths.empty()) return {};
if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible");
std::vector<GuestImage> images;
std::map<std::string, std::size_t> exports;
std::set<std::string> outputNames;
Io::FileReader reader;
for (const auto& path : paths) {
auto image = GuestImageReader().Read(path, reader.Read(path.string()));
if (image.OutputName.find_first_of("$\r\n") != std::string::npos) throw Domain::RelinkerException("Unsupported guest filename: " + image.OutputName);
std::string folded = image.OutputName;
if (windows) {
for (auto& value : folded) {
if (static_cast<unsigned char>(value) >= 128 || value == ':' || value == '$') throw Domain::RelinkerException("Unsupported Windows guest filename: " + image.OutputName);
if (value >= 'A' && value <= 'Z') value = static_cast<char>(value + ('a' - 'A'));
}
}
if (!outputNames.insert(folded).second) throw Domain::RelinkerException("Conflicting guest output filename: " + image.OutputName);
for (const auto& symbol : image.Symbols) {
if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
const auto [existing, inserted] = exports.emplace(symbol.Name, images.size());
if (!inserted) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + images.at(existing->second).SourcePath.string() + " and " + path.string());
}
std::vector<Domain::ProgramHeader> codeHeaders;
for (const auto& header : image.Headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeHeaders.push_back(header);
if (toIntel) image.Bytes = Codegen::MakeAmd64OnlyConverter()->Convert(std::move(image.Bytes), codeHeaders).Bytes;
for (const auto& header : codeHeaders) {
const std::vector<std::uint8_t> code(image.Bytes.begin() + header.Offset, image.Bytes.begin() + header.Offset + header.FileSize);
syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, header.FileSize);
}
images.push_back(std::move(image));
}
std::vector<std::set<std::size_t>> dependencies(images.size());
for (auto& image : images) image.UsePlatformTlsResolver = !exports.contains("vNe1w4diLCs");
for (std::size_t index = 0; index < images.size(); ++index) {
for (const auto& symbol : images[index].Symbols) {
if (symbol.Section != 0 || symbol.Name.empty()) continue;
const auto found = exports.find(symbol.Name);
if (found != exports.end()) {
const auto& provider = images[found->second];
const auto exported = std::find_if(provider.Symbols.begin(), provider.Symbols.end(), [&](const auto& candidate) { return candidate.Section != 0 && candidate.Name == symbol.Name && (candidate.Info >> 4) != 0 && candidate.Visibility != 1 && candidate.Visibility != 2; });
if (exported == provider.Symbols.end() || ((symbol.Info & 15) != 0 && (symbol.Info & 15) != (exported->Info & 15))) throw Domain::RelinkerException("Guest import/export type mismatch: " + symbol.Name);
if (found->second != index) dependencies[index].insert(found->second);
} else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name);
}
}
std::vector<std::size_t> order;
std::vector<unsigned char> states(images.size());
const std::function<void(std::size_t)> visit = [&](std::size_t index) {
if (states[index] == 1) throw Domain::RelinkerException("Cyclic guest initialization dependency: " + images[index].SourcePath.string());
if (states[index] == 2) return;
states[index] = 1;
for (const auto dependency : dependencies[index]) visit(dependency);
states[index] = 2;
order.push_back(index);
};
for (std::size_t index = 0; index < images.size(); ++index) visit(index);
std::vector<std::string> hostLibraries;
std::set<std::string> uniqueHosts;
const auto addHost = [&](const std::string& name) {
if (name.empty() || name.find_first_of("/\\:$") != std::string::npos) throw Domain::RelinkerException("Invalid host dependency: " + name);
if (uniqueHosts.insert(name).second) hostLibraries.push_back(name);
};
if (dynamic.DynamicSegmentData.size() % 16 != 0) throw Domain::RelinkerException("Invalid executable dependency table");
for (std::size_t offset = 0; offset < dynamic.DynamicSegmentData.size(); offset += 16) {
if (Io::ReadU64(dynamic.DynamicSegmentData, offset) != 1) throw Domain::RelinkerException("Unexpected executable dependency tag");
const auto nameOffset = Io::ReadU64(dynamic.DynamicSegmentData, offset + 8);
if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid dependency string offset");
const auto start = dynamic.DynStrData.begin() + nameOffset;
const auto end = std::find(start, dynamic.DynStrData.end(), 0);
if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated dependency string");
addHost(std::string(start, end));
}
for (const auto& image : images) for (const auto& dependency : image.Dependencies) addHost(dependency);
dynamic.DynamicSegmentData.clear();
const auto addNeeded = [&](const std::string& name) {
Io::AppendU64(dynamic.DynamicSegmentData, 1);
Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size());
Io::AppendString(dynamic.DynStrData, name);
};
const auto relativeDirectory = directory.filename().generic_string();
for (const auto index : order) if (!windows) addNeeded("$ORIGIN/" + relativeDirectory + "/" + images[index].OutputName);
for (const auto& name : hostLibraries) addNeeded(name);
std::string guestRunPath = runPath;
if (!windows) {
if (guestRunPath == "$ORIGIN") guestRunPath = "$ORIGIN/..";
else if (guestRunPath.starts_with("$ORIGIN/")) guestRunPath.insert(8, "../");
else if (!std::filesystem::path(guestRunPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN");
}
std::vector<GuestArtifact> artifacts;
const auto destination = std::filesystem::absolute(outputPath).parent_path() / directory.filename();
if (std::filesystem::exists(destination) && std::filesystem::equivalent(destination, directory)) throw Domain::RelinkerException("Guest output directory must differ from the source module directory");
for (const auto index : order) {
const auto& image = images[index];
const auto target = destination / image.OutputName;
if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output");
for (const auto& source : paths) if (std::filesystem::exists(target) && std::filesystem::equivalent(source, target)) throw Domain::RelinkerException("Guest output would overwrite an input module: " + target.string());
if (std::filesystem::exists(target) && std::filesystem::equivalent(inputPath, target)) throw Domain::RelinkerException("Guest output would overwrite the input executable");
Domain::GuestRuntime runtime;
runtime.UsePlatformTlsResolver = image.UsePlatformTlsResolver;
runtime.Path = relativeDirectory + "/" + image.OutputName;
std::vector<std::uint8_t> output;
if (windows) output = Elfpatcher::GuestModuleWriter().WriteWindows(image, runtime);
else {
std::vector<std::string> needed;
for (const auto dependency : dependencies[index]) needed.push_back("$ORIGIN/" + images[dependency].OutputName);
needed.insert(needed.end(), hostLibraries.begin(), hostLibraries.end());
output = Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath);
}
dynamic.GuestModules.push_back(std::move(runtime));
artifacts.push_back({target, std::move(output)});
}
return artifacts;
}
}

View File

@ -0,0 +1,116 @@
import os
from pathlib import Path
import struct
import subprocess
import sys
import tempfile
from test_optional_plt import fixture
TLS_LOAD = bytes.fromhex("66 66 66 64 48 8b 04 25 00 00 00 00")
def make_image(transfer, metadata, extent=None):
image = fixture()
image.extend(b"\x90" * 0x1000)
struct.pack_into("<Q", image, 24, 0x1200)
struct.pack_into("<I", image, 68, 6)
image[0x1200:0x1300] = b"\x90" * 0x100
target = 0x1240
if transfer == "table":
code = bytes.fromhex("31 c0 48 8d 0d") + struct.pack("<i", 0x780 - 0x1209)
code += bytes.fromhex("48 63 04 81 48 01 c8 ff e0")
struct.pack_into("<i", image, 0x780, target - 0x780)
elif transfer == "register":
code = bytes.fromhex("48 8d 05") + struct.pack("<i", target - 0x1207)
code += bytes.fromhex("ff e0")
elif transfer == "memory":
code = bytes.fromhex("48 8d 0d") + struct.pack("<i", target - 0x1207)
code += bytes.fromhex("48 89 4c 24 f8 ff 64 24 f8")
else:
raise ValueError(transfer)
image[0x1200:0x1200 + len(code)] = code
body = TLS_LOAD + bytes.fromhex("8b 40 f0 c3")
image[target:target + len(body)] = body
image[0x1850:0x1850 + len(TLS_LOAD)] = TLS_LOAD
struct.pack_into("<QQq", image, 0x700, 0x300, 8, 0x1200)
struct.pack_into("<Q", image, 0x800, 42)
struct.pack_into("<H", image, 56, 4 if metadata == "symbol" else 5)
struct.pack_into("<IIQQQQQQ", image, 176, 7, 4, 0x800, 0x800, 0x800, 8, 16, 16)
struct.pack_into("<IIQQQQQQ", image, 232 if metadata == "symbol" else 288, 1, 5, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000)
function_size = target + len(body) - 0x1200 if extent is None else extent
if metadata == "unwind":
struct.pack_into("<IIQQQQQQ", image, 232, 0x6474e550, 4, 0x980, 0x980, 0x980, 32, 32, 8)
struct.pack_into("<II", image, 0x900, 12, 0)
image[0x908:0x910] = bytes.fromhex("01 00 01 78 10 00 00 00")
struct.pack_into("<IIQQ", image, 0x910, 20, 0x14, 0x1200, function_size)
struct.pack_into("<BBBBQIQQ", image, 0x980, 1, 0, 3, 0, 0x900, 1, 0x1200, 0x910)
elif metadata == "symbol":
struct.pack_into("<IBBHQQ", image, 0x638, 0, 0x12, 0, 1, 0x1200, function_size)
struct.pack_into("<IIII", image, 0x680, 1, 2, 1, 0)
struct.pack_into("<qQqQ", image, 0x470, 4, 0x680, 0, 0)
struct.pack_into("<QQ", image, 160, 0x90, 8)
struct.pack_into("<Q", image, 152, 0x90)
else:
raise ValueError(metadata)
return image
def pe_bytes_at(pe, rva, size):
header = struct.unpack_from("<I", pe, 0x3c)[0]
count = struct.unpack_from("<H", pe, header + 6)[0]
sections = header + 24 + struct.unpack_from("<H", pe, header + 20)[0]
for index in range(count):
offset = sections + index * 40
address, length, position = struct.unpack_from("<III", pe, offset + 12)
if address <= rva and rva + size <= address + length:
return pe[position + rva - address:position + rva - address + size]
raise AssertionError(f"Unmapped PE RVA {rva:#x}")
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-tls-coverage-") as directory:
work = Path(directory)
def convert(name, image, error=None, tls_address=0x1240):
source = work / (name + ".elf")
output = source.with_suffix(".exe")
source.write_bytes(image)
result = subprocess.run([str(relinker), "--windows", str(source), str(output)],
capture_output=True, text=True, timeout=30)
if error is not None:
assert result.returncode == 2 and error in result.stderr and not output.exists(), result
return
assert result.returncode == 0, (name, result.stdout, result.stderr)
pe = output.read_bytes()
assert pe_bytes_at(pe, 0x10000 + tls_address, 1) == b"\xe9", name
assert pe_bytes_at(pe, 0x11850, len(TLS_LOAD)) == TLS_LOAD, name
if os.name == "nt":
executed = subprocess.run([str(output)], capture_output=True, timeout=30)
assert executed.returncode == 42, (name, executed.returncode, executed.stderr)
for metadata in ("unwind", "symbol"):
for transfer in ("table", "register", "memory"):
convert(metadata + "-" + transfer, make_image(transfer, metadata))
convert(metadata + "-truncated", make_image("register", metadata, 0x46), "Code analysis:")
convert(metadata + "-outside", make_image("register", metadata, 0x1000), "Code analysis: function exceeds executable segment")
overlapping = make_image("register", "unwind")
overlapping[0x1200:0x1205] = b"\xe9" + struct.pack("<i", 0x1245 - 0x1205)
convert("overlapping-entry", overlapping, "Code analysis: overlapping instruction boundaries")
external = make_image("register", "unwind")
external[0x1300:0x1310] = external[0x1240:0x1250]
external[0x1240:0x1250] = b"\xe8" + struct.pack("<i", 0x1300 - 0x1245) + b"\xc3" + b"\x90" * 10
convert("direct-call-from-indirect-block", external, tls_address=0x1300)
conflicting = make_image("register", "symbol")
unwind = make_image("register", "unwind", 0x51)
conflicting[0x900:0x9a0] = unwind[0x900:0x9a0]
conflicting[232:344] = unwind[232:344]
struct.pack_into("<H", conflicting, 56, 5)
convert("conflicting-function-extents", conflicting, "Code analysis: conflicting function ranges")
print("TLS function coverage integration tests passed")
if __name__ == "__main__":
main()

View File

@ -15,14 +15,18 @@ Throughout the project, every function at every stage either **does exactly what
- [libSceCommonDialog](../core/libs/prx/libSceCommonDialog/Export.cpp)
- The shader recompiler [skips baryctric coordinates](../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212).
### Unknown function names
### Unknown function info
- [zARR5aCmkoY](../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown signature
- [qj7QZpgr9Uw](../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc)
- [fd5Bp5tGTgo](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc)
- [dolOmWH+huQ](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc)
- [V++UgBtQhn0](../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc)
- [gQkqkLttcpw](../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown signature
- [zARR5aCmkoY](../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature
- [qj7QZpgr9Uw](../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name
- [fd5Bp5tGTgo](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
- [dolOmWH+huQ](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
- [V++UgBtQhn0](../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name
- [gQkqkLttcpw](../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature
- [sceKernelInternalMemoryGetModuleSegmentInfo](../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature
- [sceLibcInternalBacktraceForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
- [sceLibcInternalHeapErrorReportForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
- [__progname](../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export
### Functional