diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index ac01dac0e..933cd9093 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -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}") diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 3b149bce3..51b0a9caf 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -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; diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 7541f8ed1..6597f5f5b 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,14 +1,9 @@ -#include -#include -#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); } } diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index c81a2711a..872c317cd 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -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 diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index a7bacbfde..4e0c3813a 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -2,6 +2,7 @@ #include #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) { diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp deleted file mode 100644 index 1e46c9210..000000000 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLOCALE_HPP -#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLOCALE_HPP - -#include -#include -#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 diff --git a/core/libs/prx/libc/include/HeapDiagnostics.hpp b/core/libs/prx/libc/include/HeapDiagnostics.hpp new file mode 100644 index 000000000..7d3d62454 --- /dev/null +++ b/core/libs/prx/libc/include/HeapDiagnostics.hpp @@ -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 diff --git a/core/libs/prx/libc/include/Shutdown.hpp b/core/libs/prx/libc/include/Shutdown.hpp index 048505884..c1ff16604 100644 --- a/core/libs/prx/libc/include/Shutdown.hpp +++ b/core/libs/prx/libc/include/Shutdown.hpp @@ -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 diff --git a/core/libs/prx/libc/src/HeapDiagnostics.cpp b/core/libs/prx/libc/src/HeapDiagnostics.cpp new file mode 100644 index 000000000..39aaf3951 --- /dev/null +++ b/core/libs/prx/libc/src/HeapDiagnostics.cpp @@ -0,0 +1,45 @@ +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/HeapDiagnostics.hpp" +#include +#include +#include +#include + +namespace { + +std::uint64_t mspaceAtomicIdMask = 0; +std::array 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__); +} + +} diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 0d178f899..8d3d0e7b4 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -1,203 +1,141 @@ +#include +#include #include -#include +#include #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include #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 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 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::locale::classic()).is(mask, static_cast(value)); } -GuestLocale::Facet* APS5_VABI ReleaseLocale(GuestLocale::Facet* self) { - if (self == nullptr) throw std::invalid_argument("locale release: null facet"); - std::atomic_ref 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::locale::classic()); + const auto character = static_cast(value); + return static_cast(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 MakeClassificationTable() { - std::array 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 MakeCaseTable(bool upper) { + std::array table{}; + table[0] = EOF; + for (int value = 0; value <= UCHAR_MAX; ++value) table[value + 1] = ConvertCharacter(value, upper); return table; } -constexpr std::array MakeCaseTable(bool upper) { - std::array 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(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 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 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::_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(-1)) throw std::runtime_error("_Mbtowcx: invalid multibyte character"); if (result == static_cast(-2)) throw std::runtime_error("_Mbtowcx: incomplete multibyte character"); - if (result > static_cast(std::numeric_limits::max()) || static_cast(converted) > 0xffff) throw std::runtime_error("_Mbtowcx: conversion exceeds guest character limits"); - *dst = static_cast(converted); - return static_cast(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(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(-1)) throw std::runtime_error("_Wctombx: invalid wide character"); - if (result > static_cast(std::numeric_limits::max())) throw std::runtime_error("_Wctombx: conversion size exceeds guest limits"); - return static_cast(result); + return result; } -const short* APS5_VABI _Getpctype_nid_postfix() { - return g_classificationTable.data() + 1; +const std::ctype::mask* APS5_VABI _Getpctype_nid_postfix() { + return std::ctype::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); } diff --git a/core/libs/prx/libc/src/Process.cpp b/core/libs/prx/libc/src/Process.cpp index 01b63f769..dad4e628e 100644 --- a/core/libs/prx/libc/src/Process.cpp +++ b/core/libs/prx/libc/src/Process.cpp @@ -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 diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 0ef1a9b98..986f5616b 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -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; +} + +} diff --git a/core/libs/prx/libkernel/File/include/NativeStat.hpp b/core/libs/prx/libkernel/File/include/NativeStat.hpp index 8ea664c41..07ee81c91 100644 --- a/core/libs/prx/libkernel/File/include/NativeStat.hpp +++ b/core/libs/prx/libkernel/File/include/NativeStat.hpp @@ -7,6 +7,7 @@ namespace File { void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb); +void FillFileStat(int nativeDescriptor, FileStat* sb); } diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index 5f92973b8..f25a72e98 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -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 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(st.st_mode); sb->st_size = static_cast(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); +} + } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 714948a58..ef652c617 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1,21 +1,115 @@ #include #include +#include #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 +#include +#include #ifdef _WIN32 +#include #include +#include +#include +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(::_chsize_s(descriptor, length)); +} +static int NativeFlock(int descriptor, int operation) { + HANDLE handle = reinterpret_cast(::_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::numeric_limits::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(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::numeric_limits::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(nbytes)); + ::_lseeki64(descriptor, saved, SEEK_SET); + return n; +} #else #include +#include +#include +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)); +} +static int NativeChmod(const std::filesystem::path& path, int mode) { + return ::chmod(path.c_str(), static_cast(mode)); +} +static int NativeFtruncate(int descriptor, std::int64_t length) { + return ::ftruncate(descriptor, static_cast(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(::pread(descriptor, buf, nbytes, static_cast(offset))); +} +static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) { + return static_cast(::pwrite(descriptor, buf, nbytes, static_cast(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(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(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(__builtin_va_arg(arguments, int)); + __builtin_sysv_va_end(arguments); +#else + std::va_list arguments; + va_start(arguments, flags); + mode = static_cast(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(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)); +} + +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; } } diff --git a/core/libs/prx/libkernel/Module/src/Module.cpp b/core/libs/prx/libkernel/Module/src/Module.cpp index c34c4577a..4b0f12136 100644 --- a/core/libs/prx/libkernel/Module/src/Module.cpp +++ b/core/libs/prx/libkernel/Module/src/Module.cpp @@ -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; +} + +} diff --git a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp index b860a2262..3c202b17d 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp @@ -1,61 +1,52 @@ +#include "prx/libkernel/Pthread/include/Cond.hpp" #include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include + +namespace { + +int toPosix(int result) { + if (result == 0) + return 0; + const auto error = static_cast(result); + if ((error & 0xffff0000u) != 0x80020000u) + throw std::runtime_error("Unexpected SCE condition variable error"); + return static_cast(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)); } } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp index 1409b3450..5482859ba 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp @@ -1,72 +1,60 @@ +#include "../include/Mutex.hpp" #include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include + +namespace { + +int toPosix(int result) { + if (result == 0) + return 0; + const auto error = static_cast(result); + if ((error & 0xffff0000u) != 0x80020000u) + throw std::runtime_error("Unexpected SCE mutex error"); + return static_cast(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)); } -} +} \ No newline at end of file diff --git a/core/libs/prx/libkernel/Pthread/Posix/Once.cpp b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp index bd7d8a24f..8702a17a0 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Once.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -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; +} + +} diff --git a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp index 2f1ee6a98..52f67399e 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp @@ -2,6 +2,20 @@ #include #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(result); + if ((error & 0xffff0000u) != 0x80020000u) + throw std::runtime_error("Unexpected SCE rwlock error"); + return static_cast(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__); diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index da9083fc4..3b585b583 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -1,6 +1,7 @@ #include #include #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) { diff --git a/core/libs/prx/libkernel/Pthread/include/Cond.hpp b/core/libs/prx/libkernel/Pthread/include/Cond.hpp new file mode 100644 index 000000000..936b95714 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/Cond.hpp @@ -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 diff --git a/core/libs/prx/libkernel/Pthread/include/Mutex.hpp b/core/libs/prx/libkernel/Pthread/include/Mutex.hpp new file mode 100644 index 000000000..ca734c603 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/Mutex.hpp @@ -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 diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index e0adf92b8..b9b503a1b 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -28,7 +28,7 @@ struct PthreadMutexPrivate { std::atomic _owner; int _count; - PthreadMutexPrivate() : _type(MutexType::Normal), _count(0) {} + PthreadMutexPrivate() : _type(MutexType::Normal), _owner(std::thread::id{}), _count(0) {} }; struct PthreadCondattrPrivate { diff --git a/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp b/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp new file mode 100644 index 000000000..0c9668003 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp @@ -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 diff --git a/core/libs/prx/libkernel/Pthread/include/ThreadLifecycle.hpp b/core/libs/prx/libkernel/Pthread/include/ThreadLifecycle.hpp new file mode 100644 index 000000000..f342bb0ca --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/ThreadLifecycle.hpp @@ -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 diff --git a/core/libs/prx/libkernel/Pthread/src/Attr.cpp b/core/libs/prx/libkernel/Pthread/src/Attr.cpp index 7f71adb80..d32525564 100644 --- a/core/libs/prx/libkernel/Pthread/src/Attr.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Attr.cpp @@ -1,8 +1,10 @@ -#include "../include/Pthread.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libc/include/General.hpp" #include +#include 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::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; } } diff --git a/core/libs/prx/libkernel/Pthread/src/Cond.cpp b/core/libs/prx/libkernel/Pthread/src/Cond.cpp index 754a076f3..93c2902fe 100644 --- a/core/libs/prx/libkernel/Pthread/src/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Cond.cpp @@ -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 +#include #include -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(0x8002003cu); +std::mutex condInitializationMutex; + +PthreadCond destroyedCond() { + return reinterpret_cast(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 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 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(duration) >= std::chrono::duration(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(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(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(); + 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 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 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); } } diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index cc9ac1881..ccf895382 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -1,113 +1,164 @@ #include "../include/Pthread.hpp" -#include "prx/libc/include/General.hpp" -#include +#include "../include/Mutex.hpp" #include +#include +#include #include -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(0x80020010u); +constexpr int sceTimedOut = static_cast(0x8002003cu); +std::mutex initializationMutex; + +PthreadMutex destroyedMutex() { + return reinterpret_cast(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(*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 +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::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(duration) >= std::chrono::duration(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(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(); + 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(*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); } } diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index 12a6040a2..4804e2dac 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -1,4 +1,5 @@ #include "../include/Pthread.hpp" +#include "../include/Rwlock.hpp" #include "prx/libc/include/General.hpp" extern "C" { diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index b594ba7a1..6be2a0565 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -1,4 +1,5 @@ #include "../include/Pthread.hpp" +#include "../include/ThreadLifecycle.hpp" #include "prx/libc/include/General.hpp" #include #include @@ -29,7 +30,44 @@ struct ThreadArgs { PthreadPrivate* self; }; -static void FinishThread(PthreadPrivate* self, void* retval) { +static std::atomic threadDtors{nullptr}; +static std::atomic threadAtexitCount{nullptr}; +static std::atomic 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 lk(self->_join_mtx); self->_retval = retval; @@ -43,13 +81,13 @@ static void RunThread(std::unique_ptr 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__); +} + +} diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index 821e2cff3..db034a210 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -1,81 +1,317 @@ -#include -#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +namespace { + +std::atomic gpoBits{0}; +constexpr std::array 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 path{}; + const auto size = GetModuleFileNameA(nullptr, path.data(), static_cast(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::numeric_limits::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(arguments.size()); } + const char** GetValues() { return pointers.data(); } + +private: + std::vector arguments; + std::vector 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 name{}; + const auto first = FindFirstVolumeW(name.data(), static_cast(name.size())); + if (first == INVALID_HANDLE_VALUE) + throw std::system_error(GetLastError(), std::system_category(), "Enumerating volumes for sync"); + const std::unique_ptr 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 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(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(pid) > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Process identifier is outside the guest range"); + return static_cast(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(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(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 distribution; + std::array 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(time.dwHighDateTime) << 32) | time.dwLowDateTime) / 10ULL; + }; + const auto userMicros = toMicros(user); + const auto kernelMicros = toMicros(kernel); + usage->ru_utime.tv_sec = static_cast(userMicros / 1000000ULL); + usage->ru_utime.tv_usec = static_cast(userMicros % 1000000ULL); + usage->ru_stime.tv_sec = static_cast(kernelMicros / 1000000ULL); + usage->ru_stime.tv_usec = static_cast(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(native.ru_utime.tv_sec); + usage->ru_utime.tv_usec = static_cast(native.ru_utime.tv_usec); + usage->ru_stime.tv_sec = static_cast(native.ru_stime.tv_sec); + usage->ru_stime.tv_usec = static_cast(native.ru_stime.tv_usec); + usage->ru_maxrss = static_cast(native.ru_maxrss); + usage->ru_ixrss = static_cast(native.ru_ixrss); + usage->ru_idrss = static_cast(native.ru_idrss); + usage->ru_isrss = static_cast(native.ru_isrss); + usage->ru_minflt = static_cast(native.ru_minflt); + usage->ru_majflt = static_cast(native.ru_majflt); + usage->ru_nswap = static_cast(native.ru_nswap); + usage->ru_inblock = static_cast(native.ru_inblock); + usage->ru_oublock = static_cast(native.ru_oublock); + usage->ru_msgsnd = static_cast(native.ru_msgsnd); + usage->ru_msgrcv = static_cast(native.ru_msgrcv); + usage->ru_nsignals = static_cast(native.ru_nsignals); + usage->ru_nvcsw = static_cast(native.ru_nvcsw); + usage->ru_nivcsw = static_cast(native.ru_nivcsw); +#endif + return 0; } } diff --git a/core/libs/prx/libkernel/System/src/Rtld.cpp b/core/libs/prx/libkernel/System/src/Rtld.cpp index e8b44eae9..7a2f27d81 100644 --- a/core/libs/prx/libkernel/System/src/Rtld.cpp +++ b/core/libs/prx/libkernel/System/src/Rtld.cpp @@ -1,13 +1,37 @@ #include #include +#include +#include +#include +#include #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 rtldHeapApi{}; + +void registerApplicationHeapApi(void* const* api) { + if (api == nullptr) + throw std::invalid_argument("RTLD application heap: null allocator API"); + std::array 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); } } diff --git a/core/libs/prx/libkernel/System/src/Sanitizer.cpp b/core/libs/prx/libkernel/System/src/Sanitizer.cpp index 452a93743..15f2d3f5f 100644 --- a/core/libs/prx/libkernel/System/src/Sanitizer.cpp +++ b/core/libs/prx/libkernel/System/src/Sanitizer.cpp @@ -1,23 +1,19 @@ -#include -#include #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; } } diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index c662d4858..94a4e3be5 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -9,6 +10,8 @@ namespace { using GuestHandler = void (APS5_VABI *)(int); std::atomic handlers[32]{}; static_assert(std::atomic::is_always_lock_free); +std::atomic blockedMask{0}; +static_assert(std::atomic::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(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(static_cast(-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; +} + } diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index aeb9cafb9..28cd5647e 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -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(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(t % 1000000000ULL); return 0; } + if (clockId == 10 || clockId == 13) { + FILETIME ft{}; + GetSystemTimeAsFileTime(&ft); + std::uint64_t t = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + t -= 116444736000000000ULL; + t *= 100ULL; + tp->tv_sec = static_cast(t / 1000000000ULL); + tp->tv_nsec = static_cast(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(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: diff --git a/core/libs/tests/GuestCharacter.cpp b/core/libs/tests/GuestCharacter.cpp index 60331fc87..ab299a450 100644 --- a/core/libs/tests/GuestCharacter.cpp +++ b/core/libs/tests/GuestCharacter.cpp @@ -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() { diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp deleted file mode 100644 index dc1635dd9..000000000 --- a/core/libs/tests/GuestLocale.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "prx/libc/include/GuestLocale.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -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 -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 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 bytes; - bytes.fill(std::byte{0x5a}); - auto* storage = reinterpret_cast(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 bytes{}; - bytes.fill('!'); - std::mbstate_t encodeState{}; - Require(_Wctombx_nid_postfix(bytes.data(), value, &encodeState) == 1); - Require(bytes[0] == static_cast(value) && bytes[1] == '!'); - std::array 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(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"; -} diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index a48325819..606259877 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -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 $) add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) + add_test(NAME tls_function_coverage COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $) 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 diff --git a/core/relinker/cli/include/Cli.hpp b/core/relinker/cli/include/Cli.hpp index 369cb7f60..63c1bd38b 100644 --- a/core/relinker/cli/include/Cli.hpp +++ b/core/relinker/cli/include/Cli.hpp @@ -8,6 +8,7 @@ namespace Cli { struct Args { bool skipSyscallCheck = false; + bool skipSceModule = false; bool toIntel = false; bool writeRegistry = false; bool toWindows = false; diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index 12b1631f0..7629f6802 100644 --- a/core/relinker/cli/src/CliArgs.cpp +++ b/core/relinker/cli/src/CliArgs.cpp @@ -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 ] [--lazy-binding] [--autorun] \n" + "Usage: relinker [--windows] [--windows-diagnostics] [--skip-syscall-check] [--skip-sce-module] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" "Example: relinker input.elf output.elf" ); diff --git a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp index 69bc3598f..7448093df 100644 --- a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp +++ b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp @@ -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; diff --git a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp index 85c42cb9c..665cf6fd2 100644 --- a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp +++ b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp @@ -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) || diff --git a/core/relinker/domain/include/domain/GuestRuntime.hpp b/core/relinker/domain/include/domain/GuestRuntime.hpp new file mode 100644 index 000000000..43aae70b0 --- /dev/null +++ b/core/relinker/domain/include/domain/GuestRuntime.hpp @@ -0,0 +1,27 @@ +#ifndef DOMAIN_GUESTRUNTIME_HPP +#define DOMAIN_GUESTRUNTIME_HPP + +#include +#include +#include + +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 Imports; + std::uint32_t InitRva = 0; + std::uint32_t FiniRva = 0; + bool UsePlatformTlsResolver = true; +}; + +} + +#endif diff --git a/core/relinker/domain/include/domain/Types.hpp b/core/relinker/domain/include/domain/Types.hpp index f75570c5c..40bd960ce 100644 --- a/core/relinker/domain/include/domain/Types.hpp +++ b/core/relinker/domain/include/domain/Types.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Domain { @@ -92,6 +93,7 @@ struct SysVDynamicSection { std::vector DynStrData; std::vector RelaData; std::vector RelaPltData; + std::vector GuestModules; }; struct CallRegistryEntry { diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp new file mode 100644 index 000000000..c820c3879 --- /dev/null +++ b/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp @@ -0,0 +1,16 @@ +#ifndef ELFPATCHER_GUESTMODULEWRITER_HPP +#define ELFPATCHER_GUESTMODULEWRITER_HPP + +#include + +namespace Elfpatcher { + +class GuestModuleWriter { +public: + std::vector WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath) const; + std::vector WriteWindows(const Relinker::GuestImage& image, Domain::GuestRuntime& runtime) const; +}; + +} + +#endif diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp index 1ab27beca..a7a1225c1 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp @@ -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& libraries, const std::vector& 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& libraries, const std::vector& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector& guestModules = {}) const; }; } diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsGuestStartup.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsGuestStartup.hpp new file mode 100644 index 000000000..209c8d00b --- /dev/null +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsGuestStartup.hpp @@ -0,0 +1,21 @@ +#ifndef ELFPATCHER_WINDOWSGUESTSTARTUP_HPP +#define ELFPATCHER_WINDOWSGUESTSTARTUP_HPP + +#include +#include + +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& modules, std::uint32_t handles) const; + void Finalize(WindowsStubEmitter& code, const std::vector& 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 diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp index 9c2d5036d..bc1c0984d 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp @@ -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 { diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp index 297ef5885..e7dc1ed7d 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp @@ -7,7 +7,7 @@ namespace Elfpatcher::Windows { class WindowsTlsBuilder { public: - PeDirectory Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva) const; + PeDirectory Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva = nullptr) const; }; } diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsTemplateBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsTemplateBuilder.hpp new file mode 100644 index 000000000..4441a73c8 --- /dev/null +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsTemplateBuilder.hpp @@ -0,0 +1,15 @@ +#ifndef ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP +#define ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP + +#include + +namespace Elfpatcher::Windows { + +class WindowsTlsTemplateBuilder { +public: + std::vector Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector& sections, std::uint32_t templateRva, std::vector& relocations) const; +}; + +} + +#endif diff --git a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp new file mode 100644 index 000000000..31168ebd6 --- /dev/null +++ b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include + +namespace Elfpatcher { + +std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath) const { + auto bytes = image.Bytes; + std::vector 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::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::max()) throw Domain::RelinkerException("Guest string table too large"); + const auto offset = static_cast(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(0x10 | (symbol.Info & 15)); + } + std::vector 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(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(target) - static_cast(address() + 4); + if (displacement < std::numeric_limits::min() || displacement > std::numeric_limits::max()) throw Domain::RelinkerException("Guest initializer exceeds relative branch range"); + Io::AppendU32(bytes, static_cast(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::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(phCount)); + Io::WriteU16(bytes, 58, 0); + Io::WriteU16(bytes, 60, 0); + Io::WriteU16(bytes, 62, 0); + return bytes; +} + +} diff --git a/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp index 51f41f827..94d25b832 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -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& libraries, const std::vector& 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& libraries, const std::vector& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector& 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 libraryPaths; std::vector 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 unresolvedBranches; std::vector lazyUnresolvedImports; + std::vector 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(); diff --git a/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp new file mode 100644 index 000000000..78b7c1d83 --- /dev/null +++ b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Elfpatcher { + +std::vector GuestModuleWriter::WriteWindows(const Relinker::GuestImage& guest, Domain::GuestRuntime& runtime) const { + using namespace Windows; + WindowsLoadImage image(guest.Bytes, guest.Headers); + std::vector relocations; + std::vector> tlsModules; + std::map targets; + const auto apply = [&](const std::vector& 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(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(templateRva) + header.FileSize; + if (rva >= templateEnd || static_cast(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(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 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 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 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(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(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(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((characteristics | 0x2000) & ~1u)); + return result; +} + +} diff --git a/core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp b/core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp new file mode 100644 index 000000000..b5e212bc9 --- /dev/null +++ b/core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp @@ -0,0 +1,43 @@ +#include + +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(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& 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& 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; +} + +} diff --git a/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp b/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp index 0910e7aad..a9e5e2a64 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp @@ -78,10 +78,17 @@ std::vector WindowsPePatcher::Patch(const std::vector 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(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)); diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index ea58d8138..f0dd778e7 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -1,6 +1,8 @@ #include #include +#include #include +#include #include #include #include @@ -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& sections, const TlsAccess& access, const std::uint32_t target) { @@ -35,12 +39,13 @@ void patchAccess(std::vector& sections, const TlsAccess& access, cons } -PeDirectory WindowsTlsBuilder::Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva) const { +PeDirectory WindowsTlsBuilder::Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva) const { const Domain::ProgramHeader* tls = nullptr; const Codegen::X64InstructionDecoder decoder; std::vector accesses; std::set 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& 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& 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& 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& source, co const auto alignment = std::max(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(templateOffset + blockSize + 16)}; + constexpr std::uint32_t threadControlBlockSize = 0x30; + PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector(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& 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); diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp new file mode 100644 index 000000000..4ec5556f0 --- /dev/null +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp @@ -0,0 +1,32 @@ +#include +#include + +namespace Elfpatcher::Windows { + +std::vector WindowsTlsTemplateBuilder::Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector& sections, const std::uint32_t templateRva, std::vector& relocations) const { + if (tls.FileSize == 0) return {}; + const auto sourceRva = image.GetRva(tls.MappedAddress, tls.FileSize); + const auto sourceEnd = static_cast(sourceRva) + tls.FileSize; + std::vector result; + result.reserve(static_cast(tls.FileSize)); + while (result.size() < tls.FileSize) { + const auto currentRva = static_cast(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(currentRva - section->Rva); + const auto size = std::min(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(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(templateRva) + target - sourceRva)); + } + return result; +} + +} diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 90f087e60..64e3994e1 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -17,11 +17,13 @@ #include #include #include +#include #include #include #include #include #include +#include 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(sourceBytes); + const std::shared_ptr syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(); const auto pipeline = std::make_shared( elfReader, - args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(), + syscallScanner, Relinker::MakeCallSiteResolver(), std::make_shared(), std::make_shared(), @@ -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 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); diff --git a/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp b/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp new file mode 100644 index 000000000..2146ab9cd --- /dev/null +++ b/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp @@ -0,0 +1,16 @@ +#ifndef RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP +#define RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP + +#include +#include + +namespace Relinker { + +class CodeInstructionCollector { +public: + std::set Collect(const std::vector& bytes, const std::vector& headers) const; +}; + +} + +#endif diff --git a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp new file mode 100644 index 000000000..71cca90fa --- /dev/null +++ b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp @@ -0,0 +1,52 @@ +#ifndef RELINKER_GUESTIMAGE_HPP +#define RELINKER_GUESTIMAGE_HPP + +#include +#include +#include + +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 Bytes; + std::vector Headers; + std::vector Symbols; + std::vector 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 bytes) const; +}; + +struct GuestArtifact { + std::filesystem::path Path; + std::vector Bytes; +}; + +class GuestModuleBuilder { +public: + std::vector 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 diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp new file mode 100644 index 000000000..0b009c39f --- /dev/null +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +namespace { + +class CodePointers : public UnusedNidFilter::IRelativeRelocationIndex { +public: + std::map Values; + std::optional TargetOfSlot(Domain::VirtualAddress address) const override { + const auto found = Values.find(address); + if (found == Values.end()) return std::nullopt; + return found->second; + } +}; + +} + +std::set CodeInstructionCollector::Collect(const std::vector& bytes, const std::vector& 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 tags; + std::optional 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 { + 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::max() - *sceBase) throw Domain::RelinkerException("Code analysis: invalid SCE table"); + const auto offset = *sceBase + *address; + range(offset, size); + return offset; + }; + std::set roots; + std::map 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::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 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(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::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 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(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 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 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(static_cast(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; +} + +} diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp new file mode 100644 index 000000000..8dec3cf8a --- /dev/null +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector 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(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 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::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 tags; + std::vector 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(strOffset + offset); + const auto end = bytes.begin() + static_cast(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(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::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 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(size, 1), (info & 15) == 2 ? 1 : 0); + } + if (image.Dynamic.DynStrData.size() > std::numeric_limits::max()) fail("String table too large"); + Io::WriteU32(image.Dynamic.DynSymData, offset, static_cast(image.Dynamic.DynStrData.size())); + Io::AppendString(image.Dynamic.DynStrData, name); + image.Symbols.push_back({name, info, visibility, section, value, size}); + } + std::set 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 relocationTargets; + const auto copyRelocations = [&](std::uint64_t addressTag, std::uint64_t sceAddressTag, std::uint64_t sizeTag, std::uint64_t sceSizeTag, std::vector& 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(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; +} + +} diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp new file mode 100644 index 000000000..f8fd3e803 --- /dev/null +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +std::vector 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 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(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 images; + std::map exports; + std::set 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(value) >= 128 || value == ':' || value == '$') throw Domain::RelinkerException("Unsupported Windows guest filename: " + image.OutputName); + if (value >= 'A' && value <= 'Z') value = static_cast(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 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 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> 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 order; + std::vector states(images.size()); + const std::function 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 hostLibraries; + std::set 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 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 output; + if (windows) output = Elfpatcher::GuestModuleWriter().WriteWindows(image, runtime); + else { + std::vector 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; +} + +} diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py new file mode 100644 index 000000000..c0bc8145b --- /dev/null +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -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("