Merge pull request #10 from boykopovar/feat/sce-module-load
Feat/sce module load The game can expect its modified prx from the `sce_module`/`sce_modules` folder. The relinker now converts PRX files from sce_module to `dll`/`so` format with `.guest.prx` postfix. During linking, libraries from `sce_module` are given priority. If a library from `sce_module` fails to import a symbol or contains a syscall, an exception will be thrown. This made it possible to remove the GuestLocale formats that were customized for specific guests. Launched apps now use their own PRX files from the `sce_module` folder. Locale handling functions now use `std::locale`. Added a lot of posix functions to `libkernel` (they are thin wrappers and do what the existing non-posix versions do)
This commit is contained in:
commit
e06dbff7d6
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/HeapDiagnostics.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
||||
void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) {
|
||||
(void)info;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
LibcHeapTraceInfo_nid_no_patch(info);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/HeapDiagnostics.hpp"
|
||||
|
||||
uint32_t Need_sceLibc = 1;
|
||||
|
||||
|
|
@ -39,8 +40,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
void APS5_VABI LibcHeapGetTraceInfo_nid_postfix(LibcHeapInfo* info) {
|
||||
(void)info;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
LibcHeapTraceInfo_nid_no_patch(info);
|
||||
}
|
||||
|
||||
int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (*destructor)(void*), void* object, void* module_id) {
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLOCALE_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLOCALE_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "general/VabiMacros.hpp"
|
||||
|
||||
namespace GuestLocale {
|
||||
|
||||
struct LocinfoStorage {
|
||||
alignas(std::uint64_t) unsigned char bytes[64];
|
||||
};
|
||||
|
||||
struct Facet;
|
||||
|
||||
struct FacetVtable {
|
||||
void (APS5_VABI *destroy)(Facet* self);
|
||||
void (APS5_VABI *deleteObject)(Facet* self);
|
||||
void (APS5_VABI *retain)(Facet* self);
|
||||
Facet* (APS5_VABI *release)(Facet* self);
|
||||
};
|
||||
|
||||
struct Facet {
|
||||
const FacetVtable* vtable;
|
||||
std::uint32_t references;
|
||||
std::uint32_t reserved;
|
||||
};
|
||||
|
||||
struct Implementation {
|
||||
Facet base;
|
||||
Facet** facets;
|
||||
std::uint64_t facetCount;
|
||||
std::uint32_t category;
|
||||
bool transparent;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct IosBase {
|
||||
const void* vtable;
|
||||
std::int64_t standardStream;
|
||||
std::int32_t state;
|
||||
std::int32_t exceptions;
|
||||
std::int32_t flags;
|
||||
std::int32_t precision;
|
||||
std::int32_t width;
|
||||
std::uint32_t reserved;
|
||||
void* storage;
|
||||
void* callbacks;
|
||||
Implementation** locale;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Facet) == 0x10);
|
||||
static_assert(offsetof(Implementation, facets) == 0x10);
|
||||
static_assert(offsetof(Implementation, facetCount) == 0x18);
|
||||
static_assert(offsetof(Implementation, transparent) == 0x24);
|
||||
static_assert(offsetof(Implementation, name) == 0x28);
|
||||
static_assert(offsetof(IosBase, state) == 0x10);
|
||||
static_assert(offsetof(IosBase, storage) == 0x28);
|
||||
static_assert(offsetof(IosBase, callbacks) == 0x30);
|
||||
static_assert(offsetof(IosBase, locale) == 0x38);
|
||||
static_assert(sizeof(IosBase) == 0x40);
|
||||
static_assert(alignof(LocinfoStorage) == 8);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/HeapDiagnostics.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
std::uint64_t mspaceAtomicIdMask = 0;
|
||||
std::array<std::uint64_t, 64> mstateTable{};
|
||||
|
||||
static_assert(sizeof(LibcHeapInfo) == 32);
|
||||
static_assert(offsetof(LibcHeapInfo, mspace_atomic_id_mask) == 16);
|
||||
static_assert(offsetof(LibcHeapInfo, mstate_table) == 24);
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info) {
|
||||
if (info == nullptr)
|
||||
throw std::invalid_argument("heap trace info: null output");
|
||||
if (info->size != sizeof(LibcHeapInfo))
|
||||
throw std::invalid_argument("heap trace info: unsupported structure size");
|
||||
info->unknown2 = 0;
|
||||
info->mspace_atomic_id_mask = &mspaceAtomicIdMask;
|
||||
info->mstate_table = mstateTable.data();
|
||||
}
|
||||
|
||||
// unknown signature
|
||||
void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName) {
|
||||
(void)heapName;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
// unknown signature
|
||||
void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error) {
|
||||
(void)heap;
|
||||
(void)block;
|
||||
(void)error;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,203 +1,141 @@
|
|||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cwchar>
|
||||
#include <cwctype>
|
||||
#include <ios>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libc/include/GuestLocale.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex g_localeInitMutex;
|
||||
bool g_localeInitialized = false;
|
||||
std::vector<GuestLocale::Facet*> g_registeredFacets;
|
||||
|
||||
void APS5_VABI DestroyClassicLocale(GuestLocale::Facet*) {
|
||||
throw std::runtime_error("Cannot destroy the classic locale");
|
||||
void ValidateCharacter(int value) {
|
||||
if (value != EOF && (value < 0 || value > UCHAR_MAX)) throw std::invalid_argument("Invalid character value");
|
||||
}
|
||||
|
||||
void APS5_VABI RetainLocale(GuestLocale::Facet* self) {
|
||||
if (self == nullptr) throw std::invalid_argument("locale retain: null facet");
|
||||
std::atomic_ref<std::uint32_t> references(self->references);
|
||||
auto count = references.load();
|
||||
do {
|
||||
if (count == 0 || count == UINT32_MAX) throw std::runtime_error("locale retain: invalid reference count");
|
||||
} while (!references.compare_exchange_weak(count, count + 1));
|
||||
int ClassifyCharacter(int value, std::ctype_base::mask mask) {
|
||||
ValidateCharacter(value);
|
||||
if (value == EOF) return 0;
|
||||
return std::use_facet<std::ctype<char>>(std::locale::classic()).is(mask, static_cast<char>(value));
|
||||
}
|
||||
|
||||
GuestLocale::Facet* APS5_VABI ReleaseLocale(GuestLocale::Facet* self) {
|
||||
if (self == nullptr) throw std::invalid_argument("locale release: null facet");
|
||||
std::atomic_ref<std::uint32_t> references(self->references);
|
||||
auto count = references.load();
|
||||
do {
|
||||
if (count <= 1) throw std::runtime_error("classic locale: unbalanced release");
|
||||
} while (!references.compare_exchange_weak(count, count - 1));
|
||||
return nullptr;
|
||||
int ConvertCharacter(int value, bool upper) {
|
||||
ValidateCharacter(value);
|
||||
if (value == EOF) return EOF;
|
||||
const auto& facet = std::use_facet<std::ctype<char>>(std::locale::classic());
|
||||
const auto character = static_cast<char>(value);
|
||||
return static_cast<unsigned char>(upper ? facet.toupper(character) : facet.tolower(character));
|
||||
}
|
||||
|
||||
const GuestLocale::FacetVtable g_localeVtable{DestroyClassicLocale, DestroyClassicLocale, RetainLocale, ReleaseLocale};
|
||||
GuestLocale::Facet* g_classicFacets[1]{};
|
||||
GuestLocale::Implementation g_classicLocale{{&g_localeVtable, 1, 0}, g_classicFacets, 1, 0, false, "C"};
|
||||
|
||||
constexpr std::array<short, 257> MakeClassificationTable() {
|
||||
std::array<short, 257> table{};
|
||||
for (unsigned int value = 0; value < 256; ++value) {
|
||||
short mask = 0;
|
||||
if (value < 32 || value == 127) mask |= 0x80;
|
||||
if (value == ' ') mask |= 0x04;
|
||||
if (value >= '\t' && value <= '\r') mask |= 0x40;
|
||||
if (value >= 'A' && value <= 'Z') mask |= 0x02;
|
||||
if (value >= 'a' && value <= 'z') mask |= 0x10;
|
||||
if (value >= '0' && value <= '9') mask |= 0x20;
|
||||
if ((value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f')) mask |= 0x01;
|
||||
if (value >= 33 && value <= 126 && (mask & 0x232) == 0) mask |= 0x08;
|
||||
table[value + 1] = mask;
|
||||
}
|
||||
std::array<int, UCHAR_MAX + 2> MakeCaseTable(bool upper) {
|
||||
std::array<int, UCHAR_MAX + 2> table{};
|
||||
table[0] = EOF;
|
||||
for (int value = 0; value <= UCHAR_MAX; ++value) table[value + 1] = ConvertCharacter(value, upper);
|
||||
return table;
|
||||
}
|
||||
|
||||
constexpr std::array<short, 257> MakeCaseTable(bool upper) {
|
||||
std::array<short, 257> table{};
|
||||
table[0] = -1;
|
||||
for (unsigned int value = 0; value < 256; ++value) {
|
||||
auto converted = value;
|
||||
if (upper && value >= 'a' && value <= 'z') converted -= 'a' - 'A';
|
||||
if (!upper && value >= 'A' && value <= 'Z') converted += 'a' - 'A';
|
||||
table[value + 1] = static_cast<short>(converted);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
constexpr auto g_classificationTable = MakeClassificationTable();
|
||||
constexpr auto g_lowerTable = MakeCaseTable(false);
|
||||
constexpr auto g_upperTable = MakeCaseTable(true);
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// The runtime currently exposes the classic C locale. Keep byte classification
|
||||
// independent of any locale selected by host-side libraries.
|
||||
int APS5_VABI isupper_nid_postfix(int c) { return c >= 'A' && c <= 'Z'; }
|
||||
int APS5_VABI islower_nid_postfix(int c) { return c >= 'a' && c <= 'z'; }
|
||||
int APS5_VABI isalpha_nid_postfix(int c) { return isupper_nid_postfix(c) || islower_nid_postfix(c); }
|
||||
int APS5_VABI isdigit_nid_postfix(int c) { return c >= '0' && c <= '9'; }
|
||||
int APS5_VABI isalnum_nid_postfix(int c) { return isalpha_nid_postfix(c) || isdigit_nid_postfix(c); }
|
||||
int APS5_VABI isspace_nid_postfix(int c) { return c == ' ' || (c >= '\t' && c <= '\r'); }
|
||||
int APS5_VABI isblank_nid_postfix(int c) { return c == ' ' || c == '\t'; }
|
||||
int APS5_VABI iscntrl_nid_postfix(int c) { return (c >= 0 && c < 32) || c == 127; }
|
||||
int APS5_VABI isprint_nid_postfix(int c) { return c >= 32 && c <= 126; }
|
||||
int APS5_VABI isgraph_nid_postfix(int c) { return c >= 33 && c <= 126; }
|
||||
int APS5_VABI ispunct_nid_postfix(int c) { return isgraph_nid_postfix(c) && !isalnum_nid_postfix(c); }
|
||||
int APS5_VABI isxdigit_nid_postfix(int c) {
|
||||
return isdigit_nid_postfix(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
int APS5_VABI toupper_nid_postfix(int c) { return islower_nid_postfix(c) ? c - ('a' - 'A') : c; }
|
||||
int APS5_VABI tolower_nid_postfix(int c) { return isupper_nid_postfix(c) ? c + ('a' - 'A') : c; }
|
||||
|
||||
std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix = 0;
|
||||
std::uint64_t _ZNSt5ctypeIwE2idE_nid_postfix = 0;
|
||||
std::uint64_t _ZNSt7collateIwE2idE_nid_postfix = 0;
|
||||
std::uint64_t _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
|
||||
std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[12] {};
|
||||
int APS5_VABI isupper_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::upper); }
|
||||
int APS5_VABI islower_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::lower); }
|
||||
int APS5_VABI isalpha_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alpha); }
|
||||
int APS5_VABI isdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::digit); }
|
||||
int APS5_VABI isalnum_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alnum); }
|
||||
int APS5_VABI isspace_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::space); }
|
||||
int APS5_VABI isblank_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::blank); }
|
||||
int APS5_VABI iscntrl_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::cntrl); }
|
||||
int APS5_VABI isprint_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::print); }
|
||||
int APS5_VABI isgraph_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::graph); }
|
||||
int APS5_VABI ispunct_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::punct); }
|
||||
int APS5_VABI isxdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::xdigit); }
|
||||
int APS5_VABI toupper_nid_postfix(int c) { return ConvertCharacter(c, true); }
|
||||
int APS5_VABI tolower_nid_postfix(int c) { return ConvertCharacter(c, false); }
|
||||
|
||||
std::streamoff _ZSt7_BADOFF_nid_postfix = -1;
|
||||
std::fpos_t _ZSt4_Fpz_nid_postfix {};
|
||||
std::int32_t _ZNSt6locale2id7_Id_cntE_nid_postfix = 0;
|
||||
std::fpos_t _ZSt4_Fpz_nid_postfix{};
|
||||
|
||||
GuestLocale::Implementation* _ZSt21_sceLibcClassicLocale_nid_postfix = &g_classicLocale;
|
||||
const std::locale* _ZSt21_sceLibcClassicLocale_nid_postfix = &std::locale::classic();
|
||||
|
||||
void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(GuestLocale::IosBase* self) {
|
||||
void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(std::ios_base* self) {
|
||||
if (self == nullptr) throw std::invalid_argument("ios_base destructor: null object");
|
||||
if (self->standardStream != 0 || self->storage != nullptr || self->callbacks != nullptr) throw std::runtime_error("ios_base destructor: unsupported stream storage or callbacks");
|
||||
if (self->locale != &_ZSt21_sceLibcClassicLocale_nid_postfix) throw std::runtime_error("ios_base destructor: unsupported locale ownership");
|
||||
self->locale = nullptr;
|
||||
self->std::ios_base::~ios_base();
|
||||
}
|
||||
|
||||
GuestLocale::Implementation* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix() {
|
||||
std::lock_guard<std::mutex> lock(g_localeInitMutex);
|
||||
if (!g_localeInitialized) {
|
||||
g_localeInitialized = true;
|
||||
}
|
||||
return &g_classicLocale;
|
||||
const std::locale* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix() {
|
||||
return &std::locale::classic();
|
||||
}
|
||||
|
||||
void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(GuestLocale::Facet* self) {
|
||||
if (self == nullptr || self->vtable == nullptr) throw std::invalid_argument("locale register: invalid facet");
|
||||
std::lock_guard<std::mutex> lock(g_localeInitMutex);
|
||||
for (const auto* facet : g_registeredFacets) {
|
||||
if (facet == self) throw std::runtime_error("locale register: duplicate facet");
|
||||
}
|
||||
g_registeredFacets.push_back(self);
|
||||
void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(std::locale::facet*) {
|
||||
NotImplemented_nid_no_patch("locale::facet::_Register");
|
||||
}
|
||||
|
||||
GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix() {
|
||||
return &g_classicLocale;
|
||||
const std::locale* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix() {
|
||||
thread_local std::locale locale;
|
||||
locale = std::locale();
|
||||
return &locale;
|
||||
}
|
||||
|
||||
void APS5_VABI _ZNSt7collateIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet**, const GuestLocale::Implementation*) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
void APS5_VABI _ZNSt7collateIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(const std::locale::facet**, const std::locale*) {
|
||||
NotImplemented_nid_no_patch("collate<wchar_t>::_Getcat");
|
||||
}
|
||||
|
||||
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName) {
|
||||
if (self == nullptr || localeName == nullptr || std::strcmp(localeName, "C") != 0) throw std::invalid_argument("_Locinfo: only the C locale is supported");
|
||||
new (self) GuestLocale::LocinfoStorage{};
|
||||
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(std::locale* self, const char* localeName) {
|
||||
if (self == nullptr || localeName == nullptr) throw std::invalid_argument("Locale constructor: null argument");
|
||||
std::construct_at(self, localeName);
|
||||
}
|
||||
|
||||
void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(GuestLocale::LocinfoStorage* self) {
|
||||
if (self == nullptr) throw std::invalid_argument("_Locinfo destructor: null object");
|
||||
void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(std::locale* self) {
|
||||
if (self == nullptr) throw std::invalid_argument("Locale destructor: null object");
|
||||
std::destroy_at(self);
|
||||
}
|
||||
|
||||
int APS5_VABI _Mbtowcx_nid_postfix(std::uint16_t* dst, const char* src, std::size_t count, mbstate_t* st) {
|
||||
if (dst == nullptr || src == nullptr || st == nullptr || count == 0) throw std::invalid_argument("_Mbtowcx: invalid conversion arguments");
|
||||
wchar_t converted{};
|
||||
const auto result = std::mbrtowc(&converted, src, count, st);
|
||||
std::size_t APS5_VABI _Mbtowcx_nid_postfix(wchar_t* dst, const char* src, std::size_t count, std::mbstate_t* state) {
|
||||
if (dst == nullptr || src == nullptr || state == nullptr || count == 0) throw std::invalid_argument("_Mbtowcx: invalid conversion arguments");
|
||||
const auto result = std::mbrtowc(dst, src, count, state);
|
||||
if (result == static_cast<std::size_t>(-1)) throw std::runtime_error("_Mbtowcx: invalid multibyte character");
|
||||
if (result == static_cast<std::size_t>(-2)) throw std::runtime_error("_Mbtowcx: incomplete multibyte character");
|
||||
if (result > static_cast<std::size_t>(std::numeric_limits<int>::max()) || static_cast<std::uint32_t>(converted) > 0xffff) throw std::runtime_error("_Mbtowcx: conversion exceeds guest character limits");
|
||||
*dst = static_cast<std::uint16_t>(converted);
|
||||
return static_cast<int>(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int APS5_VABI _Wctombx_nid_postfix(char* dst, std::uint16_t src, mbstate_t* st) {
|
||||
if (dst == nullptr || st == nullptr) throw std::invalid_argument("_Wctombx: invalid conversion arguments");
|
||||
const auto result = std::wcrtomb(dst, static_cast<wchar_t>(src), st);
|
||||
std::size_t APS5_VABI _Wctombx_nid_postfix(char* dst, wchar_t src, std::mbstate_t* state) {
|
||||
if (dst == nullptr || state == nullptr) throw std::invalid_argument("_Wctombx: invalid conversion arguments");
|
||||
const auto result = std::wcrtomb(dst, src, state);
|
||||
if (result == static_cast<std::size_t>(-1)) throw std::runtime_error("_Wctombx: invalid wide character");
|
||||
if (result > static_cast<std::size_t>(std::numeric_limits<int>::max())) throw std::runtime_error("_Wctombx: conversion size exceeds guest limits");
|
||||
return static_cast<int>(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
const short* APS5_VABI _Getpctype_nid_postfix() {
|
||||
return g_classificationTable.data() + 1;
|
||||
const std::ctype<char>::mask* APS5_VABI _Getpctype_nid_postfix() {
|
||||
return std::ctype<char>::classic_table();
|
||||
}
|
||||
|
||||
const short* APS5_VABI _Getptolower_nid_postfix() {
|
||||
return g_lowerTable.data() + 1;
|
||||
const int* APS5_VABI _Getptolower_nid_postfix() {
|
||||
static const auto table = MakeCaseTable(false);
|
||||
return table.data() + 1;
|
||||
}
|
||||
|
||||
const short* APS5_VABI _Getptoupper_nid_postfix() {
|
||||
return g_upperTable.data() + 1;
|
||||
const int* APS5_VABI _Getptoupper_nid_postfix() {
|
||||
static const auto table = MakeCaseTable(true);
|
||||
return table.data() + 1;
|
||||
}
|
||||
|
||||
mbstate_t* APS5_VABI _Getpmbstate_nid_postfix() {
|
||||
thread_local mbstate_t state {};
|
||||
std::mbstate_t* APS5_VABI _Getpmbstate_nid_postfix() {
|
||||
thread_local std::mbstate_t state{};
|
||||
return &state;
|
||||
}
|
||||
|
||||
mbstate_t* APS5_VABI _Getpwcstate_nid_postfix() {
|
||||
thread_local mbstate_t state {};
|
||||
std::mbstate_t* APS5_VABI _Getpwcstate_nid_postfix() {
|
||||
thread_local std::mbstate_t state{};
|
||||
return &state;
|
||||
}
|
||||
|
||||
wint_t APS5_VABI _Towctrans_nid_postfix(wint_t c, wctrans_t desc) {
|
||||
std::wint_t APS5_VABI _Towctrans_nid_postfix(std::wint_t c, std::wctrans_t desc) {
|
||||
if (desc == std::wctrans_t{}) throw std::invalid_argument("_Towctrans: invalid transformation");
|
||||
return std::towctrans(c, desc);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
namespace File {
|
||||
|
||||
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb);
|
||||
void FillFileStat(int nativeDescriptor, FileStat* sb);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,21 +10,21 @@ using NativeStat = struct __stat64;
|
|||
static int DoStat(const std::filesystem::path& p, NativeStat* st) {
|
||||
return _wstat64(p.wstring().c_str(), st);
|
||||
}
|
||||
static int DoFstat(int fd, NativeStat* st) {
|
||||
return _fstat64(fd, st);
|
||||
}
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
using NativeStat = struct stat;
|
||||
static int DoStat(const std::filesystem::path& p, NativeStat* st) {
|
||||
return ::stat(p.c_str(), st);
|
||||
}
|
||||
static int DoFstat(int fd, NativeStat* st) {
|
||||
return ::fstat(fd, st);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace File {
|
||||
|
||||
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
|
||||
NativeStat st{};
|
||||
if (DoStat(nativePath, &st) != 0) {
|
||||
throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string());
|
||||
}
|
||||
static void CopyNativeStat(const NativeStat& st, FileStat* sb) {
|
||||
*sb = FileStat{};
|
||||
sb->st_mode = static_cast<std::uint16_t>(st.st_mode);
|
||||
sb->st_size = static_cast<std::int64_t>(st.st_size);
|
||||
|
|
@ -72,4 +72,22 @@ void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
|
|||
#endif
|
||||
}
|
||||
|
||||
namespace File {
|
||||
|
||||
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
|
||||
NativeStat st{};
|
||||
if (DoStat(nativePath, &st) != 0) {
|
||||
throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string());
|
||||
}
|
||||
CopyNativeStat(st, sb);
|
||||
}
|
||||
|
||||
void FillFileStat(int nativeDescriptor, FileStat* sb) {
|
||||
NativeStat st{};
|
||||
if (DoFstat(nativeDescriptor, &st) != 0) {
|
||||
throw std::runtime_error(std::string("FillFileStat: fstat failed for fd ") + std::to_string(nativeDescriptor));
|
||||
}
|
||||
CopyNativeStat(st, sb);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,115 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/File/include/File.hpp"
|
||||
#include "prx/libkernel/File/include/FileFlags.hpp"
|
||||
#include "prx/libkernel/File/include/NativeStat.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <sys/stat.h>
|
||||
static int NativeRmdir(const std::filesystem::path& path) {
|
||||
return ::_wrmdir(path.wstring().c_str());
|
||||
}
|
||||
static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) {
|
||||
(void)mode;
|
||||
return ::_wmkdir(path.wstring().c_str());
|
||||
}
|
||||
static int NativeChmod(const std::filesystem::path& path, int mode) {
|
||||
return ::_wchmod(path.wstring().c_str(), mode);
|
||||
}
|
||||
static int NativeFtruncate(int descriptor, std::int64_t length) {
|
||||
return static_cast<int>(::_chsize_s(descriptor, length));
|
||||
}
|
||||
static int NativeFlock(int descriptor, int operation) {
|
||||
HANDLE handle = reinterpret_cast<HANDLE>(::_get_osfhandle(descriptor));
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
OVERLAPPED overlapped{};
|
||||
if (operation & 8) {
|
||||
return ::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1;
|
||||
}
|
||||
DWORD flags = 0;
|
||||
if (operation & 2) flags |= LOCKFILE_EXCLUSIVE_LOCK;
|
||||
if (operation & 4) flags |= LOCKFILE_FAIL_IMMEDIATELY;
|
||||
return ::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1;
|
||||
}
|
||||
static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) {
|
||||
if (nbytes > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
|
||||
throw std::runtime_error("NativePread: nbytes exceeds platform limit");
|
||||
}
|
||||
std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR);
|
||||
if (saved < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) {
|
||||
return -1;
|
||||
}
|
||||
int n = ::_read(descriptor, buf, static_cast<unsigned int>(nbytes));
|
||||
::_lseeki64(descriptor, saved, SEEK_SET);
|
||||
return n;
|
||||
}
|
||||
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
|
||||
if (nbytes > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
|
||||
throw std::runtime_error("NativePwrite: nbytes exceeds platform limit");
|
||||
}
|
||||
std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR);
|
||||
if (saved < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) {
|
||||
return -1;
|
||||
}
|
||||
int n = ::_write(descriptor, buf, static_cast<unsigned int>(nbytes));
|
||||
::_lseeki64(descriptor, saved, SEEK_SET);
|
||||
return n;
|
||||
}
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
static int NativeRmdir(const std::filesystem::path& path) {
|
||||
return ::rmdir(path.c_str());
|
||||
}
|
||||
static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) {
|
||||
return ::mkdir(path.c_str(), static_cast<mode_t>(mode));
|
||||
}
|
||||
static int NativeChmod(const std::filesystem::path& path, int mode) {
|
||||
return ::chmod(path.c_str(), static_cast<mode_t>(mode));
|
||||
}
|
||||
static int NativeFtruncate(int descriptor, std::int64_t length) {
|
||||
return ::ftruncate(descriptor, static_cast<off_t>(length));
|
||||
}
|
||||
static int NativeFlock(int descriptor, int operation) {
|
||||
return ::flock(descriptor, operation);
|
||||
}
|
||||
static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) {
|
||||
return static_cast<std::int64_t>(::pread(descriptor, buf, nbytes, static_cast<off_t>(offset)));
|
||||
}
|
||||
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
|
||||
return static_cast<std::int64_t>(::pwrite(descriptor, buf, nbytes, static_cast<off_t>(offset)));
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
|
||||
(void)path;
|
||||
(void)mode;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (path == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
auto native = ResolvePath_nid_no_patch(path);
|
||||
if (NativeChmod(native, mode) != 0) {
|
||||
throw std::runtime_error(std::string(__func__) + ": chmod failed for " + native.string() + ", errno=" + std::to_string(errno));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI close_nid_postfix(int d) {
|
||||
|
|
@ -27,89 +121,136 @@ int APS5_VABI close_nid_postfix(int d) {
|
|||
#endif
|
||||
}
|
||||
|
||||
int APS5_VABI _close_nid_postfix(int descriptor) {
|
||||
return close_nid_postfix(descriptor);
|
||||
}
|
||||
|
||||
int APS5_VABI flock_nid_postfix(int d, int operation) {
|
||||
(void)d;
|
||||
(void)operation;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (NativeFlock(d, operation) != 0) {
|
||||
#ifdef _WIN32
|
||||
throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", error=" + std::to_string(::GetLastError()));
|
||||
#else
|
||||
throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) {
|
||||
(void)d;
|
||||
(void)sb;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (sb == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
File::FillFileStat(d, sb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI ftruncate_nid_postfix(int d, int64_t length) {
|
||||
(void)d;
|
||||
(void)length;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (length < 0) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
int error = NativeFtruncate(d, length);
|
||||
if (error != 0) {
|
||||
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", error=" + std::to_string(error));
|
||||
}
|
||||
#else
|
||||
if (NativeFtruncate(d, length) != 0) {
|
||||
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) {
|
||||
(void)d;
|
||||
(void)offset;
|
||||
(void)whence;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return static_cast<int64_t>(sceKernelLseek(d, offset, whence));
|
||||
}
|
||||
|
||||
int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) {
|
||||
(void)path;
|
||||
(void)mode;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (path == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
auto native = ResolvePath_nid_no_patch(path);
|
||||
if (NativeMkdir(native, mode) != 0) {
|
||||
throw std::runtime_error(std::string(__func__) + ": mkdir failed for " + native.string() + ", errno=" + std::to_string(errno));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI open_nid_postfix(const char* path, int flags, int mode) {
|
||||
(void)path;
|
||||
(void)flags;
|
||||
(void)mode;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return sceKernelOpen(path, flags, static_cast<std::uint16_t>(mode));
|
||||
}
|
||||
|
||||
int APS5_VABI _open_nid_postfix(const char* path, int flags, ...) {
|
||||
std::uint16_t mode = 0;
|
||||
if (flags & SCE_KERNEL_O_CREAT) {
|
||||
#ifdef _WIN32
|
||||
__builtin_sysv_va_list arguments;
|
||||
__builtin_sysv_va_start(arguments, flags);
|
||||
mode = static_cast<std::uint16_t>(__builtin_va_arg(arguments, int));
|
||||
__builtin_sysv_va_end(arguments);
|
||||
#else
|
||||
std::va_list arguments;
|
||||
va_start(arguments, flags);
|
||||
mode = static_cast<std::uint16_t>(va_arg(arguments, int));
|
||||
va_end(arguments);
|
||||
#endif
|
||||
}
|
||||
return sceKernelOpen(path, flags, mode);
|
||||
}
|
||||
|
||||
int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t offset) {
|
||||
(void)d;
|
||||
(void)buf;
|
||||
(void)nbytes;
|
||||
(void)offset;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (buf == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
if (offset < 0) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
auto n = NativePread(d, buf, nbytes, offset);
|
||||
if (n < 0) {
|
||||
throw std::runtime_error(std::string(__func__) + ": pread failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset) {
|
||||
(void)d;
|
||||
(void)buf;
|
||||
(void)nbytes;
|
||||
(void)offset;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (buf == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
if (offset < 0) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
auto n = NativePwrite(d, buf, nbytes, offset);
|
||||
if (n < 0) {
|
||||
throw std::runtime_error(std::string(__func__) + ": pwrite failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) {
|
||||
(void)d;
|
||||
(void)buf;
|
||||
(void)nbytes;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return sceKernelRead(d, buf, static_cast<size_t>(nbytes));
|
||||
}
|
||||
|
||||
std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) {
|
||||
return sceKernelRead(descriptor, buffer, count);
|
||||
}
|
||||
|
||||
int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) {
|
||||
(void)d;
|
||||
(void)str;
|
||||
(void)size;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (size < 0) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
return sceKernelWrite(d, str, static_cast<size_t>(size));
|
||||
}
|
||||
|
||||
std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) {
|
||||
return sceKernelWrite(descriptor, buffer, count);
|
||||
}
|
||||
|
||||
int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb) {
|
||||
(void)path;
|
||||
(void)sb;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return sceKernelStat(path, sb);
|
||||
}
|
||||
|
||||
int APS5_VABI unlink_nid_postfix(const char* path) {
|
||||
return sceKernelUnlink(path);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelCheckReachability(const char* path) {
|
||||
|
|
@ -181,9 +322,43 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) {
|
|||
}
|
||||
|
||||
int APS5_VABI sceKernelRmdir(const char* path) {
|
||||
(void)path;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (path == nullptr) {
|
||||
throw std::invalid_argument(std::string(__func__) + ": path is null");
|
||||
}
|
||||
auto native = ResolvePath_nid_no_patch(path);
|
||||
if (NativeRmdir(native) != 0) {
|
||||
throw std::runtime_error(std::string(__func__) + ": rmdir failed for " + native.string() + ", errno=" + std::to_string(errno));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI rmdir_nid_postfix(const char* path) {
|
||||
return sceKernelRmdir(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) {
|
||||
(void)path;
|
||||
(void)mode;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length) {
|
||||
(void)path;
|
||||
(void)length;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* times) {
|
||||
(void)path;
|
||||
(void)times;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,52 @@
|
|||
#include "prx/libkernel/Pthread/include/Cond.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
int toPosix(int result) {
|
||||
if (result == 0)
|
||||
return 0;
|
||||
const auto error = static_cast<std::uint32_t>(result);
|
||||
if ((error & 0xffff0000u) != 0x80020000u)
|
||||
throw std::runtime_error("Unexpected SCE condition variable error");
|
||||
return static_cast<int>(error & 0xffffu);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI pthread_cond_broadcast_nid_postfix(PthreadCond* cond) {
|
||||
(void)cond;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadCondBroadcast(cond));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_cond_init_nid_postfix(PthreadCond* cond, const PthreadCondattr* attr) {
|
||||
(void)cond;
|
||||
(void)attr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadCondInit(cond, attr, nullptr));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond) {
|
||||
(void)cond;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadCondSignal(cond));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) {
|
||||
(void)cond;
|
||||
(void)mutex;
|
||||
(void)abstime;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(CondOperations::AbsoluteTimedwait(cond, mutex, abstime));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex) {
|
||||
(void)cond;
|
||||
(void)mutex;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadCondWait(cond, mutex));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_condattr_destroy_nid_postfix(PthreadCondattr* attr) {
|
||||
(void)attr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadCondattrDestroy(attr));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_condattr_init_nid_postfix(PthreadCondattr* attr) {
|
||||
(void)attr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadCondattrInit(attr));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, KernelClockid clock_id) {
|
||||
(void)attr;
|
||||
(void)clock_id;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadCondattrSetclock(attr, clock_id));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,72 +1,60 @@
|
|||
#include "../include/Mutex.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
int toPosix(int result) {
|
||||
if (result == 0)
|
||||
return 0;
|
||||
const auto error = static_cast<std::uint32_t>(result);
|
||||
if ((error & 0xffff0000u) != 0x80020000u)
|
||||
throw std::runtime_error("Unexpected SCE mutex error");
|
||||
return static_cast<int>(error & 0xffffu);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex) {
|
||||
(void)mutex;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexDestroy(mutex));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr) {
|
||||
(void)mutex;
|
||||
(void)attr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexInit(mutex, attr, nullptr));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex) {
|
||||
(void)mutex;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexLock(mutex));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime) {
|
||||
(void)mutex;
|
||||
(void)abstime;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(MutexOperations::Timedlock(mutex, abstime));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutex_trylock_nid_postfix(PthreadMutex* mutex) {
|
||||
(void)mutex;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexTrylock(mutex));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex) {
|
||||
(void)mutex;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexUnlock(mutex));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr) {
|
||||
(void)attr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexattrDestroy(attr));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr) {
|
||||
(void)attr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexattrInit(attr));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutexattr_setprotocol_nid_postfix(PthreadMutexattr* attr, int protocol) {
|
||||
(void)attr;
|
||||
(void)protocol;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexattrSetprotocol(attr, protocol));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int type) {
|
||||
(void)attr;
|
||||
(void)type;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return toPosix(scePthreadMutexattrSettype(attr, type));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
|
|
@ -41,3 +42,16 @@ extern "C" int APS5_VABI pthread_once_nid_postfix(
|
|||
onceChanged.notify_all();
|
||||
return 0;
|
||||
}
|
||||
|
||||
using OnceRoutine = void (APS5_VABI *)();
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI scePthreadOnce_nid_postfix(void* onceControl, OnceRoutine initRoutine) {
|
||||
(void)onceControl;
|
||||
(void)initRoutine;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,20 @@
|
|||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "../include/Rwlock.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int toPosix(int result) {
|
||||
if (result == 0)
|
||||
return 0;
|
||||
const auto error = static_cast<std::uint32_t>(result);
|
||||
if ((error & 0xffff0000u) != 0x80020000u)
|
||||
throw std::runtime_error("Unexpected SCE rwlock error");
|
||||
return static_cast<int>(error & 0xffffu);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
|
@ -18,6 +32,14 @@ int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock* rwlock, const Pthre
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock) {
|
||||
return toPosix(scePthreadRwlockRdlock(rwlock));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock) {
|
||||
return toPosix(scePthreadRwlockUnlock(rwlock));
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock) {
|
||||
(void)rwlock;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "../include/ThreadLifecycle.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
|
||||
|
|
@ -32,8 +33,7 @@ int APS5_VABI pthread_detach_nid_postfix(Pthread thread) {
|
|||
}
|
||||
|
||||
void APS5_VABI pthread_exit_nid_postfix(void* value) {
|
||||
(void)value;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
scePthreadExit(value);
|
||||
}
|
||||
|
||||
int APS5_VABI pthread_getschedparam_nid_postfix(Pthread thread, int* policy, KernelSchedParam* param) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -28,7 +28,7 @@ struct PthreadMutexPrivate {
|
|||
std::atomic<std::thread::id> _owner;
|
||||
int _count;
|
||||
|
||||
PthreadMutexPrivate() : _type(MutexType::Normal), _count(0) {}
|
||||
PthreadMutexPrivate() : _type(MutexType::Normal), _owner(std::thread::id{}), _count(0) {}
|
||||
};
|
||||
|
||||
struct PthreadCondattrPrivate {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
#include "../include/Pthread.hpp"
|
||||
#include "prx/libkernel/Pthread/include/Pthread.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016;
|
||||
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
|
||||
|
||||
static constexpr std::size_t DEFAULT_STACK_SIZE = 1u << 20;
|
||||
|
|
@ -18,7 +20,7 @@ static constexpr int SCHED_FIFO_PS5 = 1;
|
|||
extern "C" {
|
||||
|
||||
int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) {
|
||||
if (!attr) throw std::runtime_error("scePthreadAttrInit: null attr");
|
||||
if (!attr) throw std::runtime_error(std::string(__func__) + ": null attr");
|
||||
auto* p = new (std::nothrow) PthreadAttrPrivate{};
|
||||
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
p->_stacksize = DEFAULT_STACK_SIZE;
|
||||
|
|
@ -31,35 +33,35 @@ int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) {
|
|||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr) {
|
||||
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrDestroy: null attr");
|
||||
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
|
||||
delete *attr;
|
||||
*attr = nullptr;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int detachstate) {
|
||||
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetdetachstate: null attr");
|
||||
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
|
||||
if (detachstate != DETACH_JOINABLE && detachstate != DETACH_DETACHED)
|
||||
throw std::runtime_error("scePthreadAttrSetdetachstate: invalid state");
|
||||
throw std::runtime_error(std::string(__func__) + ": invalid state");
|
||||
(*attr)->_detachstate = detachstate;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param) {
|
||||
if (!attr || !*attr || !param)
|
||||
throw std::runtime_error("scePthreadAttrSetschedparam: null arg");
|
||||
throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
(*attr)->_schedpriority = param->sched_priority;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksize) {
|
||||
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetstacksize: null attr");
|
||||
if (stacksize < 16384) throw std::runtime_error("scePthreadAttrSetstacksize: too small");
|
||||
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
|
||||
if (stacksize < 16384) throw std::runtime_error(std::string(__func__) + ": too small");
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO system{};
|
||||
GetSystemInfo(&system);
|
||||
if (stacksize % system.dwPageSize != 0 || stacksize > std::numeric_limits<unsigned>::max())
|
||||
throw std::runtime_error("scePthreadAttrSetstacksize: invalid Windows stack size");
|
||||
throw std::runtime_error(std::string(__func__) + ": invalid Windows stack size");
|
||||
#endif
|
||||
(*attr)->_stacksize = stacksize;
|
||||
return SCE_OK;
|
||||
|
|
@ -67,7 +69,7 @@ int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksiz
|
|||
|
||||
int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr, std::size_t* stacksize) {
|
||||
if (!attr || !*attr || !stackaddr || !stacksize)
|
||||
throw std::runtime_error("scePthreadAttrGetstack: null arg");
|
||||
throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
*stackaddr = (*attr)->stackAddress;
|
||||
*stacksize = (*attr)->_stacksize;
|
||||
return SCE_OK;
|
||||
|
|
@ -75,7 +77,7 @@ int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr,
|
|||
|
||||
int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr) {
|
||||
if (!thread || !attr || !*attr)
|
||||
throw std::runtime_error("scePthreadAttrGet: null arg");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
(*attr)->_stacksize = thread->stackSize;
|
||||
(*attr)->stackAddress = thread->stackAddress;
|
||||
(*attr)->_detachstate = thread->_detached ? DETACH_DETACHED : DETACH_JOINABLE;
|
||||
|
|
@ -93,10 +95,9 @@ int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask*
|
|||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state) {
|
||||
(void)attr;
|
||||
(void)state;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr || !state) throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
*state = (*attr)->_detachstate;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrGetguardsize(const PthreadAttr* attr, size_t* guard_size) {
|
||||
|
|
@ -107,10 +108,9 @@ int APS5_VABI scePthreadAttrGetguardsize(const PthreadAttr* attr, size_t* guard_
|
|||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param) {
|
||||
(void)attr;
|
||||
(void)param;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr || !param) throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
param->sched_priority = (*attr)->_schedpriority;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched) {
|
||||
|
|
@ -121,17 +121,15 @@ int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched
|
|||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrGetstackaddr(const PthreadAttr* attr, void** stack_addr) {
|
||||
(void)attr;
|
||||
(void)stack_addr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr || !stack_addr) throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
*stack_addr = (*attr)->stackAddress;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, size_t* stack_size) {
|
||||
(void)attr;
|
||||
(void)stack_size;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr || !stack_size) throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
*stack_size = (*attr)->_stacksize;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask) {
|
||||
|
|
@ -149,17 +147,15 @@ int APS5_VABI scePthreadAttrSetguardsize(PthreadAttr* attr, size_t guard_size) {
|
|||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inherit_sched) {
|
||||
(void)attr;
|
||||
(void)inherit_sched;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
|
||||
(*attr)->_inheritsched = inherit_sched;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy) {
|
||||
(void)attr;
|
||||
(void)policy;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
|
||||
(*attr)->_schedpolicy = policy;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched) {
|
||||
|
|
@ -170,18 +166,17 @@ int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched) {
|
|||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetstack(PthreadAttr* attr, void* addr, size_t size) {
|
||||
(void)attr;
|
||||
(void)addr;
|
||||
(void)size;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
if (size < 16384) throw std::runtime_error(std::string(__func__) + ": too small");
|
||||
(*attr)->stackAddress = addr;
|
||||
(*attr)->_stacksize = size;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadAttrSetstackaddr(PthreadAttr* attr, void* addr) {
|
||||
(void)attr;
|
||||
(void)addr;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg");
|
||||
(*attr)->stackAddress = addr;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +1,153 @@
|
|||
#include "../include/Pthread.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/Pthread/include/Pthread.hpp"
|
||||
#include "prx/libkernel/Pthread/include/Mutex.hpp"
|
||||
#include "prx/libkernel/Pthread/include/Cond.hpp"
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
|
||||
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x80020062;
|
||||
namespace {
|
||||
|
||||
constexpr int sceTimedOut = static_cast<int>(0x8002003cu);
|
||||
std::mutex condInitializationMutex;
|
||||
|
||||
PthreadCond destroyedCond() {
|
||||
return reinterpret_cast<PthreadCond>(std::uintptr_t{2});
|
||||
}
|
||||
|
||||
PthreadCond resolveCond(PthreadCond* cond) {
|
||||
if (!cond)
|
||||
throw std::invalid_argument("Condition variable pointer is null");
|
||||
std::lock_guard lock(condInitializationMutex);
|
||||
if (*cond == destroyedCond())
|
||||
throw std::runtime_error("Condition variable has been destroyed");
|
||||
if (!*cond)
|
||||
*cond = new PthreadCondPrivate();
|
||||
return *cond;
|
||||
}
|
||||
|
||||
PthreadMutex lockedMutex(PthreadMutex* mutex) {
|
||||
if (!mutex || !*mutex)
|
||||
throw std::invalid_argument("Mutex pointer is null");
|
||||
auto* current = *mutex;
|
||||
if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
|
||||
throw std::runtime_error("Condition wait mutex is not owned by the current thread");
|
||||
return current;
|
||||
}
|
||||
|
||||
int waitUntil(PthreadCond* cond, PthreadMutex* mutex, const std::chrono::system_clock::time_point* deadline) {
|
||||
auto* c = resolveCond(cond);
|
||||
auto* m = lockedMutex(mutex);
|
||||
if (m->_type == MutexType::Recursive) {
|
||||
std::unique_lock<std::recursive_timed_mutex> lock(m->_rmtx, std::adopt_lock);
|
||||
const auto previousCount = m->_count;
|
||||
m->_count = 0;
|
||||
m->_owner.store(std::thread::id{}, std::memory_order_release);
|
||||
const bool timedOut = deadline && c->_cv.wait_until(lock, *deadline) == std::cv_status::timeout;
|
||||
if (!deadline)
|
||||
c->_cv.wait(lock);
|
||||
m->_owner.store(std::this_thread::get_id(), std::memory_order_release);
|
||||
m->_count = previousCount;
|
||||
lock.release();
|
||||
return timedOut ? sceTimedOut : 0;
|
||||
}
|
||||
std::unique_lock<std::timed_mutex> lock(m->_mtx, std::adopt_lock);
|
||||
m->_owner.store(std::thread::id{}, std::memory_order_release);
|
||||
const bool timedOut = deadline && c->_cv.wait_until(lock, *deadline) == std::cv_status::timeout;
|
||||
if (!deadline)
|
||||
c->_cv.wait(lock);
|
||||
m->_owner.store(std::this_thread::get_id(), std::memory_order_release);
|
||||
lock.release();
|
||||
return timedOut ? sceTimedOut : 0;
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point toDeadline(const KernelTimespec* abstime) {
|
||||
if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)
|
||||
throw std::invalid_argument("Invalid absolute condition variable timeout");
|
||||
const auto maximum = std::chrono::nanoseconds::max().count();
|
||||
if (abstime->tv_sec > (maximum - abstime->tv_nsec) / 1000000000)
|
||||
throw std::overflow_error("Absolute condition variable timeout exceeds the host clock range");
|
||||
const auto duration = std::chrono::nanoseconds(abstime->tv_sec * 1000000000 + abstime->tv_nsec);
|
||||
if (std::chrono::duration<long double>(duration) >= std::chrono::duration<long double>(std::chrono::system_clock::duration::max()))
|
||||
throw std::overflow_error("Absolute condition variable timeout exceeds the host clock range");
|
||||
return std::chrono::system_clock::time_point(std::chrono::duration_cast<std::chrono::system_clock::duration>(duration));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int CondOperations::AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) {
|
||||
const auto deadline = toDeadline(abstime);
|
||||
return waitUntil(cond, mutex, &deadline);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr) {
|
||||
if (!attr) throw std::runtime_error("scePthreadCondattrInit: null attr");
|
||||
auto* p = new (std::nothrow) PthreadCondattrPrivate{0};
|
||||
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
*attr = p;
|
||||
return SCE_OK;
|
||||
if (!attr)
|
||||
throw std::invalid_argument("Condition attribute pointer is null");
|
||||
*attr = new PthreadCondattrPrivate{0};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr) {
|
||||
if (!attr || !*attr) throw std::runtime_error("scePthreadCondattrDestroy: null attr");
|
||||
if (!attr || !*attr)
|
||||
throw std::invalid_argument("Condition attributes are not initialized");
|
||||
delete *attr;
|
||||
*attr = nullptr;
|
||||
return SCE_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr*, const char*) {
|
||||
if (!cond) throw std::runtime_error("scePthreadCondInit: null cond");
|
||||
auto* p = new (std::nothrow) PthreadCondPrivate{};
|
||||
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
*cond = p;
|
||||
return SCE_OK;
|
||||
int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId) {
|
||||
if (!attr || !*attr)
|
||||
throw std::invalid_argument("Condition attributes are not initialized");
|
||||
(*attr)->_clockid = static_cast<int>(clockId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char*) {
|
||||
if (!cond)
|
||||
throw std::invalid_argument("Condition variable pointer is null");
|
||||
if (attr && !*attr)
|
||||
throw std::invalid_argument("Condition attributes are not initialized");
|
||||
auto replacement = std::make_unique<PthreadCondPrivate>();
|
||||
std::lock_guard lock(condInitializationMutex);
|
||||
*cond = replacement.release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond) {
|
||||
if (!cond || !*cond) throw std::runtime_error("scePthreadCondDestroy: null cond");
|
||||
if (!cond)
|
||||
throw std::invalid_argument("Condition variable pointer is null");
|
||||
std::lock_guard lock(condInitializationMutex);
|
||||
if (*cond == destroyedCond())
|
||||
throw std::runtime_error("Condition variable has already been destroyed");
|
||||
delete *cond;
|
||||
*cond = nullptr;
|
||||
return SCE_OK;
|
||||
*cond = destroyedCond();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondSignal(PthreadCond* cond) {
|
||||
if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignal: null cond");
|
||||
(*cond)->_cv.notify_one();
|
||||
return SCE_OK;
|
||||
resolveCond(cond)->_cv.notify_one();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond) {
|
||||
if (!cond || !*cond) throw std::runtime_error("scePthreadCondBroadcast: null cond");
|
||||
(*cond)->_cv.notify_all();
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec) {
|
||||
if (!cond || !*cond || !mutex || !*mutex)
|
||||
throw std::runtime_error("scePthreadCondTimedwait: null arg");
|
||||
auto* m = *mutex;
|
||||
auto* c = *cond;
|
||||
if (m->_type == MutexType::Recursive) {
|
||||
std::unique_lock<std::recursive_timed_mutex> lk(m->_rmtx, std::adopt_lock);
|
||||
auto res = c->_cv.wait_for(lk, std::chrono::microseconds(usec));
|
||||
lk.release();
|
||||
return res == std::cv_status::timeout ? SCE_KERNEL_ERROR_ETIMEDOUT : SCE_OK;
|
||||
}
|
||||
std::unique_lock<std::timed_mutex> lk(m->_mtx, std::adopt_lock);
|
||||
auto res = c->_cv.wait_for(lk, std::chrono::microseconds(usec));
|
||||
lk.release();
|
||||
return res == std::cv_status::timeout ? SCE_KERNEL_ERROR_ETIMEDOUT : SCE_OK;
|
||||
resolveCond(cond)->_cv.notify_all();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread) {
|
||||
(void)cond;
|
||||
(void)thread;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
(void)thread;
|
||||
resolveCond(cond)->_cv.notify_all();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex) {
|
||||
(void)cond;
|
||||
(void)mutex;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return waitUntil(cond, mutex, nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec) {
|
||||
const auto deadline = std::chrono::system_clock::now() + std::chrono::microseconds(usec);
|
||||
return waitUntil(cond, mutex, &deadline);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,113 +1,164 @@
|
|||
#include "../include/Pthread.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <cerrno>
|
||||
#include "../include/Mutex.hpp"
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
|
||||
static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x80020023;
|
||||
static constexpr int SCE_KERNEL_ERROR_EPERM = 0x80020001;
|
||||
namespace {
|
||||
|
||||
constexpr int sceBusy = static_cast<int>(0x80020010u);
|
||||
constexpr int sceTimedOut = static_cast<int>(0x8002003cu);
|
||||
std::mutex initializationMutex;
|
||||
|
||||
PthreadMutex destroyedMutex() {
|
||||
return reinterpret_cast<PthreadMutex>(std::uintptr_t{2});
|
||||
}
|
||||
|
||||
PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) {
|
||||
if (!mutex)
|
||||
throw std::invalid_argument("Mutex pointer is null");
|
||||
std::lock_guard lock(initializationMutex);
|
||||
if (*mutex == destroyedMutex())
|
||||
throw std::runtime_error("Mutex has been destroyed");
|
||||
if (reinterpret_cast<std::uintptr_t>(*mutex) == 1)
|
||||
throw std::runtime_error("Adaptive mutex initializer is unsupported");
|
||||
if (!*mutex) {
|
||||
if (!initialize)
|
||||
throw std::runtime_error("Mutex is not initialized");
|
||||
*mutex = new PthreadMutexPrivate();
|
||||
}
|
||||
return *mutex;
|
||||
}
|
||||
|
||||
template<typename TAcquire>
|
||||
int acquireMutex(PthreadMutex mutex, TAcquire acquire, int unavailable, bool tryOnly) {
|
||||
const auto thread = std::this_thread::get_id();
|
||||
const bool owned = mutex->_owner.load(std::memory_order_acquire) == thread;
|
||||
if (owned && mutex->_type != MutexType::Recursive) {
|
||||
if (tryOnly)
|
||||
return sceBusy;
|
||||
throw std::runtime_error("Mutex is already owned by the current thread");
|
||||
}
|
||||
if (owned && mutex->_count == std::numeric_limits<int>::max())
|
||||
throw std::overflow_error("Recursive mutex lock count overflow");
|
||||
if (mutex->_type == MutexType::Recursive) {
|
||||
if (!acquire(mutex->_rmtx))
|
||||
return unavailable;
|
||||
++mutex->_count;
|
||||
} else {
|
||||
if (!acquire(mutex->_mtx))
|
||||
return unavailable;
|
||||
}
|
||||
mutex->_owner.store(thread, std::memory_order_release);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int MutexOperations::Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime) {
|
||||
if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)
|
||||
throw std::invalid_argument("Invalid absolute mutex timeout");
|
||||
const auto maximum = std::chrono::nanoseconds::max().count();
|
||||
if (abstime->tv_sec > (maximum - abstime->tv_nsec) / 1000000000)
|
||||
throw std::overflow_error("Absolute mutex timeout exceeds the host clock range");
|
||||
const auto duration = std::chrono::nanoseconds(abstime->tv_sec * 1000000000 + abstime->tv_nsec);
|
||||
if (std::chrono::duration<long double>(duration) >= std::chrono::duration<long double>(std::chrono::system_clock::duration::max()))
|
||||
throw std::overflow_error("Absolute mutex timeout exceeds the host clock range");
|
||||
const auto deadline = std::chrono::system_clock::time_point(std::chrono::duration_cast<std::chrono::system_clock::duration>(duration));
|
||||
return acquireMutex(resolveMutex(mutex, true), [&](auto& native) { return native.try_lock_until(deadline); }, sceTimedOut, false);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr) {
|
||||
if (!attr) throw std::runtime_error("scePthreadMutexattrInit: null attr");
|
||||
auto* p = new (std::nothrow) PthreadMutexattrPrivate{MutexType::Normal};
|
||||
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
*attr = p;
|
||||
return SCE_OK;
|
||||
if (!attr)
|
||||
throw std::invalid_argument("Mutex attribute pointer is null");
|
||||
*attr = new PthreadMutexattrPrivate{MutexType::Normal};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr) {
|
||||
if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrDestroy: null attr");
|
||||
if (!attr || !*attr)
|
||||
throw std::invalid_argument("Mutex attributes are not initialized");
|
||||
delete *attr;
|
||||
*attr = nullptr;
|
||||
return SCE_OK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) {
|
||||
if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrSettype: null attr");
|
||||
if (!attr || !*attr)
|
||||
throw std::invalid_argument("Mutex attributes are not initialized");
|
||||
switch (type) {
|
||||
case 1: (*attr)->type = MutexType::ErrorCheck; break;
|
||||
case 2: (*attr)->type = MutexType::Recursive; break;
|
||||
case 3: (*attr)->type = MutexType::Normal; break;
|
||||
default: throw std::runtime_error("scePthreadMutexattrSettype: invalid type");
|
||||
default: throw std::invalid_argument("Invalid mutex type");
|
||||
}
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) {
|
||||
if (!mutex) throw std::runtime_error("scePthreadMutexInit: null mutex");
|
||||
MutexType t = MutexType::Normal;
|
||||
if (attr && *attr) t = (*attr)->type;
|
||||
auto* p = new (std::nothrow) PthreadMutexPrivate();
|
||||
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
|
||||
p->_type = t;
|
||||
*mutex = p;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) {
|
||||
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexDestroy: null mutex");
|
||||
delete *mutex;
|
||||
*mutex = nullptr;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) {
|
||||
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexLock: null mutex");
|
||||
auto* m = *mutex;
|
||||
const auto tid = std::this_thread::get_id();
|
||||
if (m->_type == MutexType::Recursive) {
|
||||
m->_rmtx.lock();
|
||||
m->_owner.store(tid, std::memory_order_relaxed);
|
||||
++m->_count;
|
||||
return SCE_OK;
|
||||
}
|
||||
if (m->_type == MutexType::ErrorCheck) {
|
||||
if (m->_owner.load(std::memory_order_acquire) == tid) return SCE_KERNEL_ERROR_EDEADLK;
|
||||
}
|
||||
m->_mtx.lock();
|
||||
m->_owner.store(tid, std::memory_order_relaxed);
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) {
|
||||
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexUnlock: null mutex");
|
||||
auto* m = *mutex;
|
||||
if (m->_type == MutexType::ErrorCheck || m->_type == MutexType::Normal) {
|
||||
if (m->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
|
||||
return SCE_KERNEL_ERROR_EPERM;
|
||||
}
|
||||
if (m->_type == MutexType::Recursive) {
|
||||
if (--m->_count == 0) m->_owner.store(std::thread::id{}, std::memory_order_relaxed);
|
||||
m->_rmtx.unlock();
|
||||
return SCE_OK;
|
||||
}
|
||||
m->_owner.store(std::thread::id{}, std::memory_order_relaxed);
|
||||
m->_mtx.unlock();
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) {
|
||||
(void)mutex;
|
||||
(void)usec;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) {
|
||||
(void)mutex;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) {
|
||||
(void)attr;
|
||||
(void)protocol;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (!attr || !*attr)
|
||||
throw std::invalid_argument("Mutex attributes are not initialized");
|
||||
if (protocol != 0)
|
||||
throw std::invalid_argument("Mutex priority inheritance and protection are unsupported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) {
|
||||
if (!mutex)
|
||||
throw std::invalid_argument("Mutex pointer is null");
|
||||
if (attr && !*attr)
|
||||
throw std::invalid_argument("Mutex attributes are not initialized");
|
||||
auto replacement = std::make_unique<PthreadMutexPrivate>();
|
||||
if (attr)
|
||||
replacement->_type = (*attr)->type;
|
||||
std::lock_guard lock(initializationMutex);
|
||||
*mutex = replacement.release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) {
|
||||
if (!mutex)
|
||||
throw std::invalid_argument("Mutex pointer is null");
|
||||
std::lock_guard lock(initializationMutex);
|
||||
if (*mutex == destroyedMutex())
|
||||
throw std::runtime_error("Mutex has already been destroyed");
|
||||
if (reinterpret_cast<std::uintptr_t>(*mutex) == 1)
|
||||
throw std::runtime_error("Adaptive mutex initializer is unsupported");
|
||||
if (*mutex && (*mutex)->_owner.load(std::memory_order_acquire) != std::thread::id{})
|
||||
throw std::runtime_error("Cannot destroy a locked mutex");
|
||||
delete *mutex;
|
||||
*mutex = destroyedMutex();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) {
|
||||
return acquireMutex(resolveMutex(mutex, true), [](auto& native) { native.lock(); return true; }, 0, false);
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) {
|
||||
auto* current = resolveMutex(mutex, false);
|
||||
if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
|
||||
throw std::runtime_error("Cannot unlock a mutex owned by another thread");
|
||||
if (current->_type == MutexType::Recursive) {
|
||||
if (--current->_count == 0)
|
||||
current->_owner.store(std::thread::id{}, std::memory_order_release);
|
||||
current->_rmtx.unlock();
|
||||
} else {
|
||||
current->_owner.store(std::thread::id{}, std::memory_order_release);
|
||||
current->_mtx.unlock();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) {
|
||||
return acquireMutex(resolveMutex(mutex, true), [=](auto& native) { return native.try_lock_for(std::chrono::microseconds(usec)); }, sceTimedOut, false);
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) {
|
||||
return acquireMutex(resolveMutex(mutex, true), [](auto& native) { return native.try_lock(); }, sceBusy, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "../include/Pthread.hpp"
|
||||
#include "../include/Rwlock.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "../include/Pthread.hpp"
|
||||
#include "../include/ThreadLifecycle.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
|
|
@ -29,7 +30,44 @@ struct ThreadArgs {
|
|||
PthreadPrivate* self;
|
||||
};
|
||||
|
||||
static void FinishThread(PthreadPrivate* self, void* retval) {
|
||||
static std::atomic<thread_dtors_func_t> threadDtors{nullptr};
|
||||
static std::atomic<get_thread_atexit_count_func_t> threadAtexitCount{nullptr};
|
||||
static std::atomic<thread_atexit_report_func_t> threadAtexitReport{nullptr};
|
||||
static thread_local PthreadPrivate* currentThread = nullptr;
|
||||
static thread_local bool threadFinishing = false;
|
||||
|
||||
void ThreadLifecycle::SetThreadDtors(thread_dtors_func_t callback) {
|
||||
if (!callback)
|
||||
throw std::runtime_error("Thread destructor callback is null");
|
||||
thread_dtors_func_t expected = nullptr;
|
||||
if (!threadDtors.compare_exchange_strong(expected, callback))
|
||||
throw std::runtime_error("Thread destructor callback is already registered");
|
||||
}
|
||||
|
||||
void ThreadLifecycle::SetThreadAtexitCount(get_thread_atexit_count_func_t callback) {
|
||||
if (!callback)
|
||||
throw std::runtime_error("Thread atexit count callback is null");
|
||||
get_thread_atexit_count_func_t expected = nullptr;
|
||||
if (!threadAtexitCount.compare_exchange_strong(expected, callback))
|
||||
throw std::runtime_error("Thread atexit count callback is already registered");
|
||||
}
|
||||
|
||||
void ThreadLifecycle::SetThreadAtexitReport(thread_atexit_report_func_t callback) {
|
||||
if (!callback)
|
||||
throw std::runtime_error("Thread atexit report callback is null");
|
||||
thread_atexit_report_func_t expected = nullptr;
|
||||
if (!threadAtexitReport.compare_exchange_strong(expected, callback))
|
||||
throw std::runtime_error("Thread atexit report callback is already registered");
|
||||
}
|
||||
|
||||
static void finishThread(PthreadPrivate* self, void* retval) {
|
||||
if (!self || self != currentThread)
|
||||
throw std::runtime_error("Finishing an unregistered guest thread");
|
||||
if (threadFinishing)
|
||||
throw std::runtime_error("Guest thread is already finishing");
|
||||
threadFinishing = true;
|
||||
if (const auto callback = threadDtors.load())
|
||||
callback();
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(self->_join_mtx);
|
||||
self->_retval = retval;
|
||||
|
|
@ -43,13 +81,13 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
|
|||
const auto entry = args->entry;
|
||||
void* arg = args->arg;
|
||||
PthreadPrivate* self = args->self;
|
||||
currentThread = self;
|
||||
args.reset();
|
||||
FinishThread(self, entry(arg));
|
||||
finishThread(self, entry(arg));
|
||||
currentThread = nullptr;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static thread_local PthreadPrivate* currentThread = nullptr;
|
||||
|
||||
static void ReleaseThread(PthreadPrivate* thread) {
|
||||
if (thread->references.fetch_sub(1, std::memory_order_acq_rel) != 1)
|
||||
return;
|
||||
|
|
@ -183,26 +221,22 @@ int APS5_VABI scePthreadDetach(Pthread thread) {
|
|||
}
|
||||
|
||||
void APS5_VABI scePthreadExit(void* retval) {
|
||||
#ifdef _WIN32
|
||||
if (!currentThread)
|
||||
throw std::runtime_error("scePthreadExit: current thread is not registered");
|
||||
auto* self = currentThread;
|
||||
FinishThread(self, retval);
|
||||
finishThread(self, retval);
|
||||
currentThread = nullptr;
|
||||
#ifdef _WIN32
|
||||
ReleaseThread(self);
|
||||
_endthreadex(0);
|
||||
#else
|
||||
pthread_exit(retval);
|
||||
#endif
|
||||
__builtin_unreachable();
|
||||
throw std::runtime_error("Native thread exit returned");
|
||||
}
|
||||
|
||||
Pthread APS5_VABI scePthreadSelf() {
|
||||
#ifdef _WIN32
|
||||
return currentThread;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void APS5_VABI scePthreadYield() {
|
||||
|
|
@ -284,3 +318,12 @@ int APS5_VABI scePthreadSetprio(Pthread thread, int prio) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void APS5_VABI __pthread_cxa_finalize_nid_postfix(void* argument) {
|
||||
(void)argument;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,81 +1,317 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
#ifndef _WIN32
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<std::uint32_t> gpoBits{0};
|
||||
constexpr std::array<std::uint8_t, 16> openPsId{'A', 'n', 'y', 'P', 'S', '5', 'O', 'p', 'e', 'n', 'P', 's', 'I', 'd', 0, 1};
|
||||
|
||||
class ProcessArguments {
|
||||
public:
|
||||
ProcessArguments() {
|
||||
#ifdef _WIN32
|
||||
std::array<char, 32768> path{};
|
||||
const auto size = GetModuleFileNameA(nullptr, path.data(), static_cast<DWORD>(path.size()));
|
||||
if (size == 0)
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Reading executable path");
|
||||
if (size >= path.size())
|
||||
throw std::runtime_error("Executable path exceeds the guest argument buffer");
|
||||
arguments.emplace_back(path.data(), size);
|
||||
#else
|
||||
std::ifstream stream("/proc/self/cmdline", std::ios::binary);
|
||||
if (!stream)
|
||||
throw std::runtime_error("Cannot read process arguments");
|
||||
std::string argument;
|
||||
while (std::getline(stream, argument, '\0')) {
|
||||
if (stream.eof())
|
||||
throw std::runtime_error("Unterminated process argument");
|
||||
arguments.push_back(argument);
|
||||
}
|
||||
if (stream.bad() || !stream.eof())
|
||||
throw std::runtime_error("Reading process arguments failed");
|
||||
#endif
|
||||
if (arguments.empty() || arguments.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
|
||||
throw std::runtime_error("Invalid process argument count");
|
||||
for (const auto& argument : arguments)
|
||||
pointers.push_back(argument.c_str());
|
||||
pointers.push_back(nullptr);
|
||||
}
|
||||
|
||||
int GetCount() const { return static_cast<int>(arguments.size()); }
|
||||
const char** GetValues() { return pointers.data(); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> arguments;
|
||||
std::vector<const char*> pointers;
|
||||
};
|
||||
|
||||
ProcessArguments& getProcessArguments() {
|
||||
static ProcessArguments arguments;
|
||||
return arguments;
|
||||
}
|
||||
|
||||
void validateSchedulingPolicy(int policy) {
|
||||
if (policy != 1 && policy != 3)
|
||||
throw std::invalid_argument("Unsupported guest scheduling policy");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void syncVolumes() {
|
||||
std::array<wchar_t, 32768> name{};
|
||||
const auto first = FindFirstVolumeW(name.data(), static_cast<DWORD>(name.size()));
|
||||
if (first == INVALID_HANDLE_VALUE)
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Enumerating volumes for sync");
|
||||
const std::unique_ptr<void, decltype(&FindVolumeClose)> search(first, &FindVolumeClose);
|
||||
for (;;) {
|
||||
DWORD flags = 0;
|
||||
if (!GetVolumeInformationW(name.data(), nullptr, 0, nullptr, nullptr, &flags, nullptr, 0))
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Reading volume properties for sync");
|
||||
if ((flags & FILE_READ_ONLY_VOLUME) == 0) {
|
||||
std::wstring path(name.data());
|
||||
if (path.empty() || path.back() != L'\\')
|
||||
throw std::runtime_error("Invalid volume path for sync");
|
||||
path.pop_back();
|
||||
const auto native = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (native == INVALID_HANDLE_VALUE)
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Opening volume for sync");
|
||||
const std::unique_ptr<void, decltype(&CloseHandle)> volume(native, &CloseHandle);
|
||||
if (!FlushFileBuffers(volume.get()))
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Flushing volume");
|
||||
}
|
||||
if (FindNextVolumeW(search.get(), name.data(), static_cast<DWORD>(name.size())))
|
||||
continue;
|
||||
const auto error = GetLastError();
|
||||
if (error != ERROR_NO_MORE_FILES)
|
||||
throw std::system_error(error, std::system_category(), "Enumerating volumes for sync");
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
struct GuestResourceUsage {
|
||||
KernelTimeval ru_utime;
|
||||
KernelTimeval ru_stime;
|
||||
std::int64_t ru_maxrss;
|
||||
std::int64_t ru_ixrss;
|
||||
std::int64_t ru_idrss;
|
||||
std::int64_t ru_isrss;
|
||||
std::int64_t ru_minflt;
|
||||
std::int64_t ru_majflt;
|
||||
std::int64_t ru_nswap;
|
||||
std::int64_t ru_inblock;
|
||||
std::int64_t ru_oublock;
|
||||
std::int64_t ru_msgsnd;
|
||||
std::int64_t ru_msgrcv;
|
||||
std::int64_t ru_nsignals;
|
||||
std::int64_t ru_nvcsw;
|
||||
std::int64_t ru_nivcsw;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
// unknown data
|
||||
const char* __progname_nid_postfix = "eboot.bin";
|
||||
|
||||
int APS5_VABI getargc_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return getProcessArguments().GetCount();
|
||||
}
|
||||
|
||||
const char** APS5_VABI getargv_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return nullptr;
|
||||
return getProcessArguments().GetValues();
|
||||
}
|
||||
|
||||
int APS5_VABI getpagesize_nid_postfix(void) {
|
||||
return PS5_PAGE_SIZE;
|
||||
return PS5_PAGE_SIZE;
|
||||
}
|
||||
|
||||
int APS5_VABI getpid_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
#ifdef _WIN32
|
||||
const auto pid = GetCurrentProcessId();
|
||||
#else
|
||||
const auto pid = ::getpid();
|
||||
#endif
|
||||
if (pid == 0 || static_cast<std::uint64_t>(pid) > static_cast<std::uint64_t>(std::numeric_limits<int>::max()))
|
||||
throw std::runtime_error("Process identifier is outside the guest range");
|
||||
return static_cast<int>(pid);
|
||||
}
|
||||
|
||||
void APS5_VABI exit_nid_postfix(int code) {
|
||||
(void)code;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
LibcExit_nid_no_patch(code);
|
||||
}
|
||||
|
||||
[[noreturn]] void APS5_VABI _exit_nid_postfix(int status) {
|
||||
std::_Exit(status);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelGetCurrentCpu(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
#ifdef _WIN32
|
||||
PROCESSOR_NUMBER processor{};
|
||||
GetCurrentProcessorNumberEx(&processor);
|
||||
unsigned index = processor.Number;
|
||||
for (WORD group = 0; group < processor.Group; ++group) {
|
||||
const auto count = GetActiveProcessorCount(group);
|
||||
if (count == 0)
|
||||
throw std::system_error(GetLastError(), std::system_category(), "Reading processor group size");
|
||||
index += count;
|
||||
}
|
||||
return static_cast<int>(index);
|
||||
#else
|
||||
const int cpu = ::sched_getcpu();
|
||||
if (cpu < 0)
|
||||
throw std::system_error(errno, std::generic_category(), "Reading current processor");
|
||||
return cpu;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t APS5_VABI sceKernelGetGPI(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
std::uint64_t APS5_VABI sceKernelGetGPI(void) {
|
||||
return gpoBits.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void APS5_VABI sceKernelSetGPO(uint32_t bits) {
|
||||
(void)bits;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
void APS5_VABI sceKernelSetGPO(std::uint32_t bits) {
|
||||
gpoBits.store(bits, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelGetOpenPsId(void* open_ps_id) {
|
||||
(void)open_ps_id;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceKernelGetOpenPsId(void* openPsIdOutput) {
|
||||
if (!openPsIdOutput)
|
||||
throw std::invalid_argument("sceKernelGetOpenPsId: null output");
|
||||
std::memcpy(openPsIdOutput, openPsId.data(), openPsId.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* APS5_VABI sceKernelGetProcParam(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return nullptr;
|
||||
return const_cast<void*>(ApplicationProcessParameters_nid_no_patch());
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelUuidCreate(uint32_t* uuid) {
|
||||
(void)uuid;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceKernelUuidCreate(std::uint32_t* uuid) {
|
||||
if (!uuid)
|
||||
throw std::invalid_argument("sceKernelUuidCreate: null output");
|
||||
static thread_local std::random_device device;
|
||||
std::uniform_int_distribution<std::uint32_t> distribution;
|
||||
std::array<std::uint32_t, 4> value;
|
||||
for (auto& word : value)
|
||||
word = distribution(device);
|
||||
value[1] = (value[1] & 0x0fffffffu) | 0x40000000u;
|
||||
value[2] = (value[2] & 0xffffff3fu) | 0x80u;
|
||||
std::memcpy(uuid, value.data(), sizeof(value));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void APS5_VABI sceKernelSync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
#ifdef _WIN32
|
||||
syncVolumes();
|
||||
#else
|
||||
::sync();
|
||||
#endif
|
||||
}
|
||||
|
||||
int APS5_VABI sched_get_priority_max_nid_postfix(int policy) {
|
||||
(void)policy;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
validateSchedulingPolicy(policy);
|
||||
return 256;
|
||||
}
|
||||
|
||||
int APS5_VABI sched_get_priority_min_nid_postfix(int policy) {
|
||||
(void)policy;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
validateSchedulingPolicy(policy);
|
||||
return 767;
|
||||
}
|
||||
|
||||
int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage) {
|
||||
if (usage == nullptr)
|
||||
throw std::invalid_argument("getrusage: usage is null");
|
||||
if (who != 0 && who != 1)
|
||||
throw std::invalid_argument("getrusage: unsupported who");
|
||||
#ifdef _WIN32
|
||||
FILETIME creation{};
|
||||
FILETIME exitTime{};
|
||||
FILETIME kernel{};
|
||||
FILETIME user{};
|
||||
if (who == 0) {
|
||||
if (!GetProcessTimes(GetCurrentProcess(), &creation, &exitTime, &kernel, &user))
|
||||
throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetProcessTimes failed");
|
||||
} else {
|
||||
if (!GetThreadTimes(GetCurrentThread(), &creation, &exitTime, &kernel, &user))
|
||||
throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetThreadTimes failed");
|
||||
}
|
||||
const auto toMicros = [](const FILETIME& time) {
|
||||
return ((static_cast<std::uint64_t>(time.dwHighDateTime) << 32) | time.dwLowDateTime) / 10ULL;
|
||||
};
|
||||
const auto userMicros = toMicros(user);
|
||||
const auto kernelMicros = toMicros(kernel);
|
||||
usage->ru_utime.tv_sec = static_cast<std::int64_t>(userMicros / 1000000ULL);
|
||||
usage->ru_utime.tv_usec = static_cast<std::int64_t>(userMicros % 1000000ULL);
|
||||
usage->ru_stime.tv_sec = static_cast<std::int64_t>(kernelMicros / 1000000ULL);
|
||||
usage->ru_stime.tv_usec = static_cast<std::int64_t>(kernelMicros % 1000000ULL);
|
||||
usage->ru_maxrss = 0;
|
||||
usage->ru_ixrss = 0;
|
||||
usage->ru_idrss = 0;
|
||||
usage->ru_isrss = 0;
|
||||
usage->ru_minflt = 0;
|
||||
usage->ru_majflt = 0;
|
||||
usage->ru_nswap = 0;
|
||||
usage->ru_inblock = 0;
|
||||
usage->ru_oublock = 0;
|
||||
usage->ru_msgsnd = 0;
|
||||
usage->ru_msgrcv = 0;
|
||||
usage->ru_nsignals = 0;
|
||||
usage->ru_nvcsw = 0;
|
||||
usage->ru_nivcsw = 0;
|
||||
#else
|
||||
if (who == 1)
|
||||
throw std::invalid_argument("getrusage: RUSAGE_THREAD is not supported on this platform");
|
||||
struct rusage native{};
|
||||
if (::getrusage(RUSAGE_SELF, &native) != 0)
|
||||
throw std::system_error(errno, std::generic_category(), "getrusage: getrusage failed");
|
||||
usage->ru_utime.tv_sec = static_cast<std::int64_t>(native.ru_utime.tv_sec);
|
||||
usage->ru_utime.tv_usec = static_cast<std::int64_t>(native.ru_utime.tv_usec);
|
||||
usage->ru_stime.tv_sec = static_cast<std::int64_t>(native.ru_stime.tv_sec);
|
||||
usage->ru_stime.tv_usec = static_cast<std::int64_t>(native.ru_stime.tv_usec);
|
||||
usage->ru_maxrss = static_cast<std::int64_t>(native.ru_maxrss);
|
||||
usage->ru_ixrss = static_cast<std::int64_t>(native.ru_ixrss);
|
||||
usage->ru_idrss = static_cast<std::int64_t>(native.ru_idrss);
|
||||
usage->ru_isrss = static_cast<std::int64_t>(native.ru_isrss);
|
||||
usage->ru_minflt = static_cast<std::int64_t>(native.ru_minflt);
|
||||
usage->ru_majflt = static_cast<std::int64_t>(native.ru_majflt);
|
||||
usage->ru_nswap = static_cast<std::int64_t>(native.ru_nswap);
|
||||
usage->ru_inblock = static_cast<std::int64_t>(native.ru_inblock);
|
||||
usage->ru_oublock = static_cast<std::int64_t>(native.ru_oublock);
|
||||
usage->ru_msgsnd = static_cast<std::int64_t>(native.ru_msgsnd);
|
||||
usage->ru_msgrcv = static_cast<std::int64_t>(native.ru_msgrcv);
|
||||
usage->ru_nsignals = static_cast<std::int64_t>(native.ru_nsignals);
|
||||
usage->ru_nvcsw = static_cast<std::int64_t>(native.ru_nvcsw);
|
||||
usage->ru_nivcsw = static_cast<std::int64_t>(native.ru_nivcsw);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,37 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex rtldHeapMutex;
|
||||
std::array<void*, 10> rtldHeapApi{};
|
||||
|
||||
void registerApplicationHeapApi(void* const* api) {
|
||||
if (api == nullptr)
|
||||
throw std::invalid_argument("RTLD application heap: null allocator API");
|
||||
std::array<void*, 10> replacement;
|
||||
std::memcpy(replacement.data(), api, sizeof(replacement));
|
||||
if (replacement[0] == nullptr || replacement[1] == nullptr)
|
||||
throw std::invalid_argument("RTLD application heap: malloc and free are required");
|
||||
std::lock_guard lock(rtldHeapMutex);
|
||||
if (rtldHeapApi[0] != nullptr && rtldHeapApi != replacement)
|
||||
throw std::runtime_error("RTLD application heap: cannot replace an active allocator");
|
||||
rtldHeapApi = replacement;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void APS5_VABI sceKernelRtldSetApplicationHeapAPI(void* api[]) {
|
||||
ApplicationHeapRegister_nid_no_patch(api);
|
||||
registerApplicationHeapApi(api);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelRtldThreadAtexitDecrement(uint64_t* c) {
|
||||
|
|
@ -23,18 +47,47 @@ int APS5_VABI sceKernelRtldThreadAtexitIncrement(uint64_t* c) {
|
|||
}
|
||||
|
||||
void APS5_VABI sceKernelSetThreadAtexitCount(get_thread_atexit_count_func_t func) {
|
||||
(void)func;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
ThreadLifecycle::SetThreadAtexitCount(func);
|
||||
}
|
||||
|
||||
void APS5_VABI sceKernelSetThreadAtexitReport(thread_atexit_report_func_t func) {
|
||||
(void)func;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
ThreadLifecycle::SetThreadAtexitReport(func);
|
||||
}
|
||||
|
||||
void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors) {
|
||||
(void)dtors;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
ThreadLifecycle::SetThreadDtors(dtors);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void APS5_VABI _sceKernelRtldSetApplicationHeapAPI_nid_postfix(void* api[]) {
|
||||
registerApplicationHeapApi(api);
|
||||
}
|
||||
|
||||
int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(std::uint64_t* counter) {
|
||||
(void)counter;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(std::uint64_t* counter) {
|
||||
(void)counter;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void APS5_VABI _sceKernelSetThreadAtexitCount_nid_postfix(get_thread_atexit_count_func_t callback) {
|
||||
ThreadLifecycle::SetThreadAtexitCount(callback);
|
||||
}
|
||||
|
||||
void APS5_VABI _sceKernelSetThreadAtexitReport_nid_postfix(thread_atexit_report_func_t callback) {
|
||||
ThreadLifecycle::SetThreadAtexitReport(callback);
|
||||
}
|
||||
|
||||
void APS5_VABI _sceKernelSetThreadDtors_nid_postfix(thread_dtors_func_t callback) {
|
||||
ThreadLifecycle::SetThreadDtors(callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceKernelIsAddressSanitizerEnabled(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
MallocReplace* APS5_VABI sceKernelGetSanitizerMallocReplaceExternal(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return nullptr;
|
||||
static MallocReplace replacement{};
|
||||
return &replacement;
|
||||
}
|
||||
|
||||
NewReplace* APS5_VABI sceKernelGetSanitizerNewReplaceExternal(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return nullptr;
|
||||
static NewReplace replacement{};
|
||||
return &replacement;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
|
|
@ -9,6 +10,8 @@ namespace {
|
|||
using GuestHandler = void (APS5_VABI *)(int);
|
||||
std::atomic<GuestHandler> handlers[32]{};
|
||||
static_assert(std::atomic<GuestHandler>::is_always_lock_free);
|
||||
std::atomic<std::uint32_t> blockedMask{0};
|
||||
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
|
||||
std::mutex registration;
|
||||
int NativeSignal(int guest) {
|
||||
switch (guest) {
|
||||
|
|
@ -30,10 +33,16 @@ void Dispatch(int native) {
|
|||
// Preserve the guest's persistent registration across CRT delivery.
|
||||
std::signal(native, Dispatch);
|
||||
#endif
|
||||
if ((blockedMask.load() & (1u << guest)) != 0) return;
|
||||
const auto callback = handlers[guest].load();
|
||||
if (reinterpret_cast<std::uintptr_t>(callback) > 1) callback(guest);
|
||||
}
|
||||
}
|
||||
|
||||
struct GuestSignalSet {
|
||||
std::uint32_t bits[4];
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) {
|
||||
const auto invalid = reinterpret_cast<GuestHandler>(static_cast<std::uintptr_t>(-1));
|
||||
|
|
@ -57,4 +66,32 @@ int APS5_VABI raise_nid_postfix(int guest) {
|
|||
if (result) *__error_nid_postfix() = 22;
|
||||
return result ? -1 : 0;
|
||||
}
|
||||
int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) {
|
||||
std::lock_guard lock(registration);
|
||||
if (previousSet != nullptr) {
|
||||
previousSet->bits[0] = blockedMask.load();
|
||||
previousSet->bits[1] = 0;
|
||||
previousSet->bits[2] = 0;
|
||||
previousSet->bits[3] = 0;
|
||||
}
|
||||
if (set != nullptr) {
|
||||
switch (how) {
|
||||
case 1: blockedMask.fetch_or(set->bits[0]); break;
|
||||
case 2: blockedMask.fetch_and(~set->bits[0]); break;
|
||||
case 3: blockedMask.store(set->bits[0]); break;
|
||||
default: throw std::invalid_argument("_sigprocmask: invalid how");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t programCounter) {
|
||||
(void)programCounter;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,12 +92,16 @@ int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec*
|
|||
return sceKernelNanosleep(rqtp, rmtp);
|
||||
}
|
||||
|
||||
int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) {
|
||||
return sceKernelNanosleep(rqtp, rmtp);
|
||||
}
|
||||
|
||||
int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
|
||||
if (tp == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (clockId == 0 || clockId == 9 || clockId == 10) {
|
||||
if (clockId == 0 || clockId == 9) {
|
||||
FILETIME ft{};
|
||||
GetSystemTimePreciseAsFileTime(&ft);
|
||||
std::uint64_t t = (static_cast<std::uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||
|
|
@ -107,6 +111,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
|
|||
tp->tv_nsec = static_cast<std::int64_t>(t % 1000000000ULL);
|
||||
return 0;
|
||||
}
|
||||
if (clockId == 10 || clockId == 13) {
|
||||
FILETIME ft{};
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
std::uint64_t t = (static_cast<std::uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||
t -= 116444736000000000ULL;
|
||||
t *= 100ULL;
|
||||
tp->tv_sec = static_cast<std::int64_t>(t / 1000000000ULL);
|
||||
tp->tv_nsec = static_cast<std::int64_t>(t % 1000000000ULL);
|
||||
return 0;
|
||||
}
|
||||
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) {
|
||||
std::uint64_t nanos = GetMonotonicNanos();
|
||||
tp->tv_sec = static_cast<std::int64_t>(nanos / 1000000000ULL);
|
||||
|
|
@ -119,9 +133,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
|
|||
switch (clockId) {
|
||||
case 0:
|
||||
case 9:
|
||||
case 10:
|
||||
nativeId = CLOCK_REALTIME;
|
||||
break;
|
||||
case 10:
|
||||
case 13:
|
||||
#ifdef CLOCK_REALTIME_COARSE
|
||||
nativeId = CLOCK_REALTIME_COARSE;
|
||||
#else
|
||||
nativeId = CLOCK_REALTIME;
|
||||
#endif
|
||||
break;
|
||||
case 4:
|
||||
case 7:
|
||||
case 11:
|
||||
|
|
@ -187,7 +208,7 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) {
|
|||
res->tv_nsec = 100LL;
|
||||
return 0;
|
||||
}
|
||||
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) {
|
||||
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12 || clockId == 13) {
|
||||
static const std::uint64_t freq = [] {
|
||||
LARGE_INTEGER f{};
|
||||
QueryPerformanceFrequency(&f);
|
||||
|
|
@ -204,9 +225,16 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) {
|
|||
switch (clockId) {
|
||||
case 0:
|
||||
case 9:
|
||||
case 10:
|
||||
nativeId = CLOCK_REALTIME;
|
||||
break;
|
||||
case 10:
|
||||
case 13:
|
||||
#ifdef CLOCK_REALTIME_COARSE
|
||||
nativeId = CLOCK_REALTIME_COARSE;
|
||||
#else
|
||||
nativeId = CLOCK_REALTIME;
|
||||
#endif
|
||||
break;
|
||||
case 4:
|
||||
case 7:
|
||||
case 11:
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -1,158 +0,0 @@
|
|||
#include "prx/libc/include/GuestLocale.hpp"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <climits>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
extern GuestLocale::Implementation* _ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_postfix();
|
||||
GuestLocale::Implementation* APS5_VABI _ZNSt6locale5_InitEv_nid_postfix();
|
||||
void APS5_VABI _ZNSt6locale5facet9_RegisterEv_nid_postfix(GuestLocale::Facet* self);
|
||||
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName);
|
||||
void APS5_VABI _ZNSt8_LocinfoD1Ev_nid_postfix(GuestLocale::LocinfoStorage* self);
|
||||
void APS5_VABI _ZNSt8ios_baseD2Ev_nid_postfix(GuestLocale::IosBase* self);
|
||||
const short* APS5_VABI _Getpctype_nid_postfix();
|
||||
const short* APS5_VABI _Getptolower_nid_postfix();
|
||||
const short* APS5_VABI _Getptoupper_nid_postfix();
|
||||
int APS5_VABI _Mbtowcx_nid_postfix(std::uint16_t* dst, const char* src, std::size_t count, std::mbstate_t* st);
|
||||
int APS5_VABI _Wctombx_nid_postfix(char* dst, std::uint16_t src, std::mbstate_t* st);
|
||||
void APS5_VABI _Locksyslock_nid_postfix();
|
||||
void APS5_VABI _Unlocksyslock_nid_postfix();
|
||||
|
||||
}
|
||||
|
||||
static void Require(bool condition) {
|
||||
if (!condition) throw std::runtime_error("Guest locale check failed");
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
static void RequireException(TAction action) {
|
||||
bool caught = false;
|
||||
try {
|
||||
action();
|
||||
} catch (const std::exception&) {
|
||||
caught = true;
|
||||
}
|
||||
Require(caught);
|
||||
}
|
||||
|
||||
static void CheckGuestCalls() {
|
||||
auto* locale = _ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
Require(locale != nullptr && locale == _ZNSt6locale16_GetgloballocaleEv_nid_postfix());
|
||||
Require(_ZNSt6locale5_InitEv_nid_postfix() == locale);
|
||||
Require(_ZNSt6locale5_InitEv_nid_postfix() == locale);
|
||||
const std::byte* table = nullptr;
|
||||
std::memcpy(&table, locale, sizeof(table));
|
||||
void (APS5_VABI *retain)(GuestLocale::Facet*) = nullptr;
|
||||
GuestLocale::Facet* (APS5_VABI *release)(GuestLocale::Facet*) = nullptr;
|
||||
std::memcpy(&retain, table + 0x10, sizeof(retain));
|
||||
std::memcpy(&release, table + 0x18, sizeof(release));
|
||||
const auto initial = locale->base.references;
|
||||
retain(&locale->base);
|
||||
Require(locale->base.references == initial + 1);
|
||||
Require(release(&locale->base) == nullptr);
|
||||
Require(locale->base.references == initial);
|
||||
RequireException([&] { release(&locale->base); });
|
||||
RequireException([&] { retain(nullptr); });
|
||||
RequireException([&] { locale->base.vtable->deleteObject(&locale->base); });
|
||||
Require(locale->base.references == initial);
|
||||
std::vector<std::thread> threads;
|
||||
for (int thread = 0; thread < 4; ++thread) {
|
||||
threads.emplace_back([&] {
|
||||
for (int iteration = 0; iteration < 10000; ++iteration) {
|
||||
retain(&locale->base);
|
||||
release(&locale->base);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto& thread : threads) thread.join();
|
||||
Require(locale->base.references == initial);
|
||||
Require(locale->facetCount == 1 && locale->facets[0] == nullptr);
|
||||
Require(std::strcmp(locale->name, "C") == 0 && !locale->transparent);
|
||||
}
|
||||
|
||||
static void CheckLocinfoAlignment() {
|
||||
alignas(16) std::array<std::byte, 80> bytes;
|
||||
bytes.fill(std::byte{0x5a});
|
||||
auto* storage = reinterpret_cast<GuestLocale::LocinfoStorage*>(bytes.data() + 8);
|
||||
_ZNSt8_LocinfoC1EPKc_nid_postfix(storage, "C");
|
||||
for (std::size_t index = 0; index < bytes.size(); ++index) Require(bytes[index] == (index >= 8 && index < 72 ? std::byte{} : std::byte{0x5a}));
|
||||
_ZNSt8_LocinfoD1Ev_nid_postfix(storage);
|
||||
RequireException([&] { _ZNSt8_LocinfoC1EPKc_nid_postfix(storage, "unknown-locale"); });
|
||||
RequireException([] { _ZNSt8_LocinfoC1EPKc_nid_postfix(nullptr, "C"); });
|
||||
}
|
||||
|
||||
static void CheckCharacterTables() {
|
||||
const auto* classification = _Getpctype_nid_postfix();
|
||||
const auto* lower = _Getptolower_nid_postfix();
|
||||
const auto* upper = _Getptoupper_nid_postfix();
|
||||
Require(classification[-1] == 0 && lower[-1] == -1 && upper[-1] == -1);
|
||||
Require(classification['A'] == 0x03 && classification['a'] == 0x11);
|
||||
Require(classification['G'] == 0x02 && classification['0'] == 0x21);
|
||||
Require(classification[' '] == 0x04 && classification['\t'] == 0xc0);
|
||||
Require(classification['\n'] == 0xc0 && classification['!'] == 0x08);
|
||||
for (int value = 0; value < 256; ++value) {
|
||||
Require(lower[value] == (value >= 'A' && value <= 'Z' ? value + 32 : value));
|
||||
Require(upper[value] == (value >= 'a' && value <= 'z' ? value - 32 : value));
|
||||
if (value >= 128) Require(classification[value] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckCharacterConversions() {
|
||||
const auto* classification = _Getpctype_nid_postfix();
|
||||
for (std::uint16_t value = 0; value < 128; ++value) {
|
||||
std::array<char, MB_LEN_MAX + 1> bytes{};
|
||||
bytes.fill('!');
|
||||
std::mbstate_t encodeState{};
|
||||
Require(_Wctombx_nid_postfix(bytes.data(), value, &encodeState) == 1);
|
||||
Require(bytes[0] == static_cast<char>(value) && bytes[1] == '!');
|
||||
std::array<std::uint16_t, 2> wide{0xffff, 0x1234};
|
||||
std::mbstate_t decodeState{};
|
||||
Require(_Mbtowcx_nid_postfix(wide.data(), bytes.data(), 2, &decodeState) == (value == 0 ? 0 : 1));
|
||||
Require(wide[0] == value && wide[1] == 0x1234);
|
||||
const bool alnum = (value >= '0' && value <= '9') || (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z');
|
||||
Require(((classification[static_cast<unsigned char>(bytes[0])] & 0x232) != 0) == alnum);
|
||||
}
|
||||
std::mbstate_t state{};
|
||||
char byte{};
|
||||
std::uint16_t wide{};
|
||||
RequireException([&] { _Mbtowcx_nid_postfix(&wide, "]", 0, &state); });
|
||||
RequireException([&] { _Mbtowcx_nid_postfix(nullptr, "]", 1, &state); });
|
||||
RequireException([&] { _Mbtowcx_nid_postfix(&wide, nullptr, 1, &state); });
|
||||
RequireException([&] { _Mbtowcx_nid_postfix(&wide, "]", 1, nullptr); });
|
||||
RequireException([&] { _Wctombx_nid_postfix(nullptr, ']', &state); });
|
||||
RequireException([&] { _Wctombx_nid_postfix(&byte, ']', nullptr); });
|
||||
}
|
||||
|
||||
static void CheckStreamDestruction() {
|
||||
struct GuardedStream {
|
||||
std::uint64_t before = 0x12345678;
|
||||
GuestLocale::IosBase stream{};
|
||||
std::uint64_t after = 0x87654321;
|
||||
} object;
|
||||
object.stream.locale = &_ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
_ZNSt8ios_baseD2Ev_nid_postfix(&object.stream);
|
||||
Require(object.stream.locale == nullptr && object.before == 0x12345678 && object.after == 0x87654321);
|
||||
object.stream.locale = &_ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
object.stream.callbacks = &object;
|
||||
RequireException([&] { _ZNSt8ios_baseD2Ev_nid_postfix(&object.stream); });
|
||||
Require(object.stream.locale == &_ZSt21_sceLibcClassicLocale_nid_postfix);
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckGuestCalls();
|
||||
CheckLocinfoAlignment();
|
||||
CheckCharacterTables();
|
||||
CheckCharacterConversions();
|
||||
CheckStreamDestruction();
|
||||
_Locksyslock_nid_postfix();
|
||||
_Locksyslock_nid_postfix();
|
||||
_Unlocksyslock_nid_postfix();
|
||||
_Unlocksyslock_nid_postfix();
|
||||
std::cout << "Guest locale checks passed\n";
|
||||
}
|
||||
|
|
@ -3,10 +3,16 @@ add_executable(relinker
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxElfPatcher.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestStartup.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/guest/GuestImageReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/guest/GuestModuleBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPePatcher.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeFormat.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsLoadImage.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsRelocationBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsImportBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp
|
||||
|
|
@ -31,6 +37,7 @@ add_executable(relinker
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CodeInstructionCollector.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp
|
||||
|
|
@ -79,8 +86,10 @@ if(BUILD_TESTING)
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME tls_function_coverage COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $<TARGET_FILE:relinker>)
|
||||
else()
|
||||
message(STATUS "Python3 interpreter not found; skipping optional_plt and empty_tls tests")
|
||||
message(STATUS "Python3 interpreter not found; skipping Python relinker tests")
|
||||
endif()
|
||||
add_executable(strict_nid_filter_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/StrictNidFilterTests.cpp
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ namespace Cli {
|
|||
|
||||
struct Args {
|
||||
bool skipSyscallCheck = false;
|
||||
bool skipSceModule = false;
|
||||
bool toIntel = false;
|
||||
bool writeRegistry = false;
|
||||
bool toWindows = false;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ Args ParseArgs(int argc, char* argv[]) {
|
|||
const std::string arg = argv[i];
|
||||
if (arg == "--skip-syscall-check") {
|
||||
args.skipSyscallCheck = true;
|
||||
} else if (arg == "--skip-sce-module") {
|
||||
args.skipSceModule = true;
|
||||
} else if (arg == "--to-intel") {
|
||||
args.toIntel = true;
|
||||
} else if (arg.rfind("unused-filter=", 0) == 0) {
|
||||
|
|
@ -51,7 +53,7 @@ Args ParseArgs(int argc, char* argv[]) {
|
|||
|
||||
if (args.inputPath.empty() || args.outputPath.empty())
|
||||
throw std::runtime_error(
|
||||
"Usage: relinker [--windows] [--windows-diagnostics] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
|
||||
"Usage: relinker [--windows] [--windows-diagnostics] [--skip-syscall-check] [--skip-sce-module] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
|
||||
"Example: relinker input.elf output.elf"
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) ||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef DOMAIN_GUESTRUNTIME_HPP
|
||||
#define DOMAIN_GUESTRUNTIME_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Domain {
|
||||
|
||||
struct GuestImport {
|
||||
std::string Name;
|
||||
std::uint32_t TargetRva;
|
||||
std::uint64_t Addend;
|
||||
std::uint32_t RelocationType = 1;
|
||||
};
|
||||
|
||||
struct GuestRuntime {
|
||||
std::string Path;
|
||||
std::vector<GuestImport> Imports;
|
||||
std::uint32_t InitRva = 0;
|
||||
std::uint32_t FiniRva = 0;
|
||||
bool UsePlatformTlsResolver = true;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <domain/GuestRuntime.hpp>
|
||||
|
||||
namespace Domain {
|
||||
|
||||
|
|
@ -92,6 +93,7 @@ struct SysVDynamicSection {
|
|||
std::vector<std::uint8_t> DynStrData;
|
||||
std::vector<std::uint8_t> RelaData;
|
||||
std::vector<std::uint8_t> RelaPltData;
|
||||
std::vector<GuestRuntime> GuestModules;
|
||||
};
|
||||
|
||||
struct CallRegistryEntry {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef ELFPATCHER_GUESTMODULEWRITER_HPP
|
||||
#define ELFPATCHER_GUESTMODULEWRITER_HPP
|
||||
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
|
||||
namespace Elfpatcher {
|
||||
|
||||
class GuestModuleWriter {
|
||||
public:
|
||||
std::vector<std::uint8_t> WriteLinux(const Relinker::GuestImage& image, const std::vector<std::string>& dependencies, const std::string& runPath) const;
|
||||
std::vector<std::uint8_t> WriteWindows(const Relinker::GuestImage& image, Domain::GuestRuntime& runtime) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -19,7 +19,7 @@ struct WindowsEntryStub {
|
|||
|
||||
class WindowsEntryStubBuilder {
|
||||
public:
|
||||
WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics) const;
|
||||
WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules = {}) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef ELFPATCHER_WINDOWSGUESTSTARTUP_HPP
|
||||
#define ELFPATCHER_WINDOWSGUESTSTARTUP_HPP
|
||||
|
||||
#include <domain/GuestRuntime.hpp>
|
||||
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
class WindowsGuestStartup {
|
||||
public:
|
||||
void WriteImport(WindowsStubEmitter& code, const PeImport& import, std::uint32_t handles) const;
|
||||
void Initialize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, std::uint32_t handles) const;
|
||||
void Finalize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, std::uint32_t handles, std::uint32_t finished) const;
|
||||
std::uint32_t EmitTlsResolver(WindowsStubEmitter& code) const;
|
||||
private:
|
||||
void callLifecycle(WindowsStubEmitter& code, std::uint32_t handle, std::uint32_t rva) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Elfpatcher::Windows {
|
|||
|
||||
class WindowsTlsBuilder {
|
||||
public:
|
||||
PeDirectory Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva) const;
|
||||
PeDirectory Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva = nullptr) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP
|
||||
#define ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP
|
||||
|
||||
#include <elfpatcher/windows/WindowsLoadImage.hpp>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
class WindowsTlsTemplateBuilder {
|
||||
public:
|
||||
std::vector<std::uint8_t> Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector<PeSection>& sections, std::uint32_t templateRva, std::vector<std::uint32_t>& relocations) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace Elfpatcher {
|
||||
|
||||
std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector<std::string>& dependencies, const std::string& runPath) const {
|
||||
auto bytes = image.Bytes;
|
||||
std::vector<Domain::ProgramHeader> headers;
|
||||
std::uint64_t end = 0;
|
||||
for (auto header : image.Headers) {
|
||||
if (header.Type != 1 && header.Type != 7 && header.Type != 0x6474e550 && header.Type != 0x6474e551) continue;
|
||||
if (header.Type == 1) {
|
||||
header.Flags |= 4;
|
||||
end = std::max(end, header.MappedAddress + header.MemorySize);
|
||||
}
|
||||
headers.push_back(header);
|
||||
}
|
||||
if (end > std::numeric_limits<std::uint64_t>::max() - 0x4000) throw Domain::RelinkerException("Guest virtual address overflow");
|
||||
Io::AlignBuffer(bytes, 0x4000);
|
||||
const auto extraOffset = bytes.size();
|
||||
const auto extraAddress = Io::AlignUp64(end, 0x4000);
|
||||
const auto address = [&] { return extraAddress + bytes.size() - extraOffset; };
|
||||
auto strings = image.Dynamic.DynStrData;
|
||||
auto symbols = image.Dynamic.DynSymData;
|
||||
const auto addString = [&](const std::string& value) {
|
||||
if (strings.size() > std::numeric_limits<std::uint32_t>::max()) throw Domain::RelinkerException("Guest string table too large");
|
||||
const auto offset = static_cast<std::uint32_t>(strings.size());
|
||||
Io::AppendString(strings, value);
|
||||
return offset;
|
||||
};
|
||||
bool needsTlsResolver = false;
|
||||
for (std::size_t index = 1; index < image.Symbols.size(); ++index) {
|
||||
const auto& symbol = image.Symbols[index];
|
||||
if (image.UsePlatformTlsResolver && symbol.Section == 0 && symbol.Name == "vNe1w4diLCs") {
|
||||
Io::WriteU32(symbols, index * 24, addString("__tls_get_addr"));
|
||||
needsTlsResolver = true;
|
||||
}
|
||||
if (symbol.Section == 0 && (symbol.Info >> 4) == 2) symbols[index * 24 + 4] = static_cast<std::uint8_t>(0x10 | (symbol.Info & 15));
|
||||
}
|
||||
std::vector<std::uint64_t> needed;
|
||||
for (const auto& dependency : dependencies) needed.push_back(addString(dependency));
|
||||
if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2"));
|
||||
const auto soname = addString(image.OutputName);
|
||||
const auto search = addString(runPath);
|
||||
const auto strAddress = address();
|
||||
bytes.insert(bytes.end(), strings.begin(), strings.end());
|
||||
Io::AlignBuffer(bytes, 8);
|
||||
const auto symAddress = address();
|
||||
bytes.insert(bytes.end(), symbols.begin(), symbols.end());
|
||||
const auto hashAddress = address();
|
||||
const auto count = static_cast<std::uint32_t>(image.Symbols.size());
|
||||
Io::AppendU32(bytes, 1);
|
||||
Io::AppendU32(bytes, count);
|
||||
Io::AppendU32(bytes, count > 1 ? 1 : 0);
|
||||
for (std::uint32_t index = 0; index < count; ++index) Io::AppendU32(bytes, index != 0 && index + 1 < count ? index + 1 : 0);
|
||||
Io::AlignBuffer(bytes, 8);
|
||||
const auto relaAddress = address();
|
||||
bytes.insert(bytes.end(), image.Dynamic.RelaData.begin(), image.Dynamic.RelaData.end());
|
||||
const auto pltAddress = address();
|
||||
bytes.insert(bytes.end(), image.Dynamic.RelaPltData.begin(), image.Dynamic.RelaPltData.end());
|
||||
const auto lifecycle = [&](std::uint64_t target) {
|
||||
if (target == 0) return std::uint64_t{};
|
||||
const auto start = address();
|
||||
bytes.insert(bytes.end(), {0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xe9});
|
||||
const auto displacement = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(address() + 4);
|
||||
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max()) throw Domain::RelinkerException("Guest initializer exceeds relative branch range");
|
||||
Io::AppendU32(bytes, static_cast<std::uint32_t>(displacement));
|
||||
return start;
|
||||
};
|
||||
const auto init = lifecycle(image.Init);
|
||||
const auto fini = lifecycle(image.Fini);
|
||||
Io::AlignBuffer(bytes, 8);
|
||||
const auto dynamicOffset = bytes.size();
|
||||
const auto dynamicAddress = address();
|
||||
const auto tag = [&](std::uint64_t key, std::uint64_t value) { Io::AppendU64(bytes, key); Io::AppendU64(bytes, value); };
|
||||
for (const auto offset : needed) tag(1, offset);
|
||||
tag(14, soname);
|
||||
tag(29, search);
|
||||
tag(4, hashAddress);
|
||||
tag(5, strAddress);
|
||||
tag(10, strings.size());
|
||||
tag(6, symAddress);
|
||||
tag(11, 24);
|
||||
if (!image.Dynamic.RelaData.empty()) {
|
||||
tag(7, relaAddress);
|
||||
tag(8, image.Dynamic.RelaData.size());
|
||||
tag(9, 24);
|
||||
}
|
||||
if (!image.Dynamic.RelaPltData.empty()) {
|
||||
tag(23, pltAddress);
|
||||
tag(2, image.Dynamic.RelaPltData.size());
|
||||
tag(20, 7);
|
||||
tag(3, image.Got);
|
||||
}
|
||||
if (init != 0) tag(12, init);
|
||||
if (fini != 0) tag(13, fini);
|
||||
tag(30, 8);
|
||||
tag(0, 0);
|
||||
const auto dynamicSize = bytes.size() - dynamicOffset;
|
||||
const auto phOffset = bytes.size();
|
||||
const auto phCount = headers.size() + 2;
|
||||
if (phCount > std::numeric_limits<std::uint16_t>::max()) throw Domain::RelinkerException("Too many guest program headers");
|
||||
const auto extraSize = bytes.size() + phCount * 56 - extraOffset;
|
||||
headers.push_back({1, 7, extraOffset, extraAddress, extraAddress, extraSize, extraSize, 0x4000});
|
||||
headers.push_back({2, 6, dynamicOffset, dynamicAddress, dynamicAddress, dynamicSize, dynamicSize, 8});
|
||||
for (const auto& header : headers) {
|
||||
Io::AppendU32(bytes, header.Type);
|
||||
Io::AppendU32(bytes, header.Flags);
|
||||
Io::AppendU64(bytes, header.Offset);
|
||||
Io::AppendU64(bytes, header.MappedAddress);
|
||||
Io::AppendU64(bytes, header.PhysicalAddress);
|
||||
Io::AppendU64(bytes, header.FileSize);
|
||||
Io::AppendU64(bytes, header.MemorySize);
|
||||
Io::AppendU64(bytes, header.Alignment);
|
||||
}
|
||||
bytes[7] = 0;
|
||||
bytes[8] = 0;
|
||||
Io::WriteU16(bytes, 16, 3);
|
||||
Io::WriteU64(bytes, 24, 0);
|
||||
Io::WriteU64(bytes, 32, phOffset);
|
||||
Io::WriteU64(bytes, 40, 0);
|
||||
Io::WriteU16(bytes, 56, static_cast<std::uint16_t>(phCount));
|
||||
Io::WriteU16(bytes, 58, 0);
|
||||
Io::WriteU16(bytes, 60, 0);
|
||||
Io::WriteU16(bytes, 62, 0);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include <elfpatcher/windows/WindowsEntryStubBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
|
||||
#include <elfpatcher/windows/WindowsDependencyStubBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsGuestStartup.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
|
|
@ -29,7 +30,7 @@ std::string normalizeRunPath(std::string path) {
|
|||
|
||||
}
|
||||
|
||||
WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics) const {
|
||||
WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules) const {
|
||||
if (!imports.empty() && libraries.empty())
|
||||
throw Domain::RelinkerException("ELF imports have no DT_NEEDED libraries");
|
||||
const auto path = normalizeRunPath(runPath);
|
||||
|
|
@ -54,14 +55,17 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
const auto programPath = reserve(PathCapacity);
|
||||
const auto modulePath = reserve(PathCapacity);
|
||||
const auto handles = reserve(libraries.size() * 8);
|
||||
const auto guestFinished = reserve(4);
|
||||
const WindowsGuestStartup guestStartup;
|
||||
const auto functionTable = reserve(12 * 32);
|
||||
const auto unwindRva = CheckedRva(dataRva + data.size());
|
||||
data.insert(data.end(), {1, 10, 6, 0, 10, 0xb2, 6, 0xc0, 4, 0x70, 3, 0x60, 2, 0x50, 1, 0x30});
|
||||
|
||||
std::vector<std::uint32_t> libraryPaths;
|
||||
std::vector<std::string> libraryNames;
|
||||
for (const auto& library : libraries) {
|
||||
auto name = path + library;
|
||||
for (std::size_t index = 0; index < libraries.size(); ++index) {
|
||||
auto name = index < guestModules.size() ? libraries[index] : path + libraries[index];
|
||||
std::replace(name.begin(), name.end(), '/', '\\');
|
||||
if (name.size() + 1 >= PathCapacity)
|
||||
throw Domain::RelinkerException("Windows library path exceeds the startup buffer: " + name);
|
||||
libraryPaths.push_back(addString(name));
|
||||
|
|
@ -78,6 +82,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
const auto loading = addString("Loading PRX: ");
|
||||
const auto loaded = addString(" -> OK\n");
|
||||
const auto loadFailed = addString(" -> FAILED\n");
|
||||
const auto failedModule = addString("Failed to load module: ");
|
||||
const auto errorPrefix = addString("GetLastError: ");
|
||||
const auto messageSeparator = addString(" - ");
|
||||
const auto newline = addString("\n");
|
||||
|
|
@ -218,7 +223,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
code.Emit({0x49, 0xff, 0xc4});
|
||||
|
||||
for (std::size_t index = 0; index < libraries.size(); ++index) {
|
||||
if (absolutePath) {
|
||||
if (absolutePath && index >= guestModules.size()) {
|
||||
code.Rip({0x48, 0x8d, 0x0d}, libraryPaths[index]);
|
||||
} else {
|
||||
code.Emit({0x4c, 0x89, 0xe0, 0x48, 0x29, 0xd8, 0x48, 0x05});
|
||||
|
|
@ -253,6 +258,9 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
const auto loadSucceeded = code.Branch({0x0f, 0x85});
|
||||
captureLastError();
|
||||
writeString(loadFailed, true);
|
||||
writeString(failedModule, true);
|
||||
writeString(resolvedPaths[index], true);
|
||||
writeString(newline, true);
|
||||
writeLastError();
|
||||
if (dependencyDiagnostics) {
|
||||
code.Rip({0x48, 0x8d, 0x0d}, resolvedPaths[index]);
|
||||
|
|
@ -270,7 +278,14 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
|
||||
std::vector<std::size_t> unresolvedBranches;
|
||||
std::vector<std::size_t> lazyUnresolvedImports;
|
||||
std::vector<std::size_t> tlsResolverAddresses;
|
||||
for (std::size_t index = 0; index < imports.size(); ++index) {
|
||||
if (!guestModules.empty() && guestModules.front().UsePlatformTlsResolver && imports[index].Name == "vNe1w4diLCs") {
|
||||
if (imports[index].Addend != 0) throw Domain::RelinkerException("TLS resolver import has an addend");
|
||||
tlsResolverAddresses.push_back(code.Branch({0x48, 0x8d, 0x05}));
|
||||
guestStartup.WriteImport(code, imports[index], handles);
|
||||
continue;
|
||||
}
|
||||
code.Rip({0x48, 0x8d, 0x1d}, handles);
|
||||
code.Rip({0x48, 0x8d, 0x35}, symbolNames[index]);
|
||||
code.Emit({0xbd});
|
||||
|
|
@ -291,7 +306,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
code.U64(imports[index].Addend);
|
||||
code.Emit({0x48, 0x01, 0xd0});
|
||||
}
|
||||
code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva);
|
||||
guestStartup.WriteImport(code, imports[index], handles);
|
||||
code.PatchBranch(skipGotWrite, code.GetRva());
|
||||
continue;
|
||||
}
|
||||
|
|
@ -299,20 +314,26 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
writeString(errorRvas.at(3 + index), true);
|
||||
unresolvedBranches.push_back(code.Branch({0xe9}));
|
||||
code.PatchBranch(resolved, code.GetRva());
|
||||
if (imports[index].RelocationType == 16) code.Emit({0x48, 0x8b, 0x00});
|
||||
if (imports[index].RelocationType == 17) code.Emit({0x48, 0x8b, 0x40, 8});
|
||||
if (imports[index].Addend != 0) {
|
||||
code.Emit({0x48, 0xba});
|
||||
code.U64(imports[index].Addend);
|
||||
code.Emit({0x48, 0x01, 0xd0});
|
||||
}
|
||||
code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva);
|
||||
guestStartup.WriteImport(code, imports[index], handles);
|
||||
}
|
||||
|
||||
guestStartup.Initialize(code, guestModules, handles);
|
||||
writeString(enteringElf);
|
||||
code.Emit({0x48, 0xc7, 0x44, 0x24, 0x40, 1, 0, 0, 0});
|
||||
code.Rip({0x48, 0x8d, 0x05}, programPath);
|
||||
code.Emit({0x48, 0x89, 0x44, 0x24, 0x48, 0x31, 0xc0, 0x48, 0x89, 0x44, 0x24, 0x50, 0x48, 0x89, 0x44, 0x24, 0x58, 0x48, 0x8d, 0x7c, 0x24, 0x40});
|
||||
const auto exitCallback = code.Branch({0x48, 0x8d, 0x35});
|
||||
code.Rip({0xe8}, entryRva);
|
||||
code.Emit({0x89, 0x44, 0x24, 0x58});
|
||||
guestStartup.Finalize(code, guestModules, handles, guestFinished);
|
||||
code.Emit({0x8b, 0x44, 0x24, 0x58});
|
||||
code.Emit({0x89, 0xc1});
|
||||
call("ExitProcess");
|
||||
code.Emit({0x0f, 0x0b});
|
||||
|
|
@ -332,7 +353,12 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
|
||||
const auto functionEnd = code.GetRva();
|
||||
code.PatchBranch(exitCallback, functionEnd);
|
||||
code.Emit({0x48, 0x83, 0xec, 0x28});
|
||||
guestStartup.Finalize(code, guestModules, handles, guestFinished);
|
||||
code.Emit({0x48, 0x83, 0xc4, 0x28});
|
||||
code.Emit({0xc3});
|
||||
const auto tlsResolver = guestStartup.EmitTlsResolver(code);
|
||||
for (const auto offset : tlsResolverAddresses) code.PatchBranch(offset, tlsResolver);
|
||||
|
||||
for (const auto index : lazyUnresolvedImports) {
|
||||
const auto stubRva = code.GetRva();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
||||
#include <elfpatcher/windows/WindowsLoadImage.hpp>
|
||||
#include <elfpatcher/windows/WindowsTlsBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsPeWriter.hpp>
|
||||
#include <elfpatcher/windows/WindowsRelocationBuilder.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
|
||||
namespace Elfpatcher {
|
||||
|
||||
std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestImage& guest, Domain::GuestRuntime& runtime) const {
|
||||
using namespace Windows;
|
||||
WindowsLoadImage image(guest.Bytes, guest.Headers);
|
||||
std::vector<std::uint32_t> relocations;
|
||||
std::vector<std::pair<std::uint64_t, std::uint32_t>> tlsModules;
|
||||
std::map<std::uint64_t, std::uint64_t> targets;
|
||||
const auto apply = [&](const std::vector<std::uint8_t>& table) {
|
||||
for (std::size_t offset = 0; offset < table.size(); offset += 24) {
|
||||
const auto target = Io::ReadU64(table, offset);
|
||||
const auto info = Io::ReadU64(table, offset + 8);
|
||||
const auto addend = Io::ReadU64(table, offset + 16);
|
||||
const auto type = static_cast<std::uint32_t>(info);
|
||||
const auto symbolIndex = info >> 32;
|
||||
const auto& symbol = guest.Symbols.at(symbolIndex);
|
||||
const auto rva = image.GetRva(target, 8);
|
||||
for (const auto& header : guest.Headers) {
|
||||
if (header.Type != 7 || header.FileSize == 0) continue;
|
||||
const auto templateRva = image.GetRva(header.MappedAddress, header.FileSize);
|
||||
const auto templateEnd = static_cast<std::uint64_t>(templateRva) + header.FileSize;
|
||||
if (rva >= templateEnd || static_cast<std::uint64_t>(rva) + 8 <= templateRva) continue;
|
||||
if (rva < templateRva || templateEnd - rva < 8) throw Domain::RelinkerException("Guest relocation crosses the TLS template boundary", target);
|
||||
if (type == 16 || (symbolIndex != 0 && symbol.Section == 0)) throw Domain::RelinkerException("Runtime-bound guest TLS template relocation is not supported", target);
|
||||
}
|
||||
const auto next = targets.lower_bound(target);
|
||||
if ((next != targets.end() && next->first < target + 8) || (next != targets.begin() && std::prev(next)->second > target)) throw Domain::RelinkerException("Overlapping guest relocations", target);
|
||||
targets.emplace(target, target + 8);
|
||||
image.RequireWritable(target, 8);
|
||||
if (type == 8) {
|
||||
if (symbolIndex != 0) throw Domain::RelinkerException("RELATIVE guest relocation has a symbol", target);
|
||||
image.WritePointer(target, image.GetRelocatedAddress(addend));
|
||||
relocations.push_back(rva);
|
||||
} else if (type == 16) {
|
||||
if (addend != 0 || (symbolIndex != 0 && (symbol.Info & 15) != 6)) throw Domain::RelinkerException("Invalid guest TLS module relocation", target);
|
||||
if (symbolIndex != 0 && symbol.Section == 0) {
|
||||
image.WritePointer(target, 0);
|
||||
runtime.Imports.push_back({symbol.Name, rva, 0, 16});
|
||||
} else tlsModules.emplace_back(target, rva);
|
||||
} else if (type == 17) {
|
||||
if (symbolIndex != 0 && (symbol.Info & 15) != 6) throw Domain::RelinkerException("Invalid guest TLS offset relocation", target);
|
||||
if (symbolIndex != 0 && symbol.Section == 0) {
|
||||
image.WritePointer(target, 0);
|
||||
runtime.Imports.push_back({symbol.Name, rva, addend, 17});
|
||||
} else image.WritePointer(target, symbol.Value + addend);
|
||||
} else if (type == 18) {
|
||||
const auto tls = std::find_if(guest.Headers.begin(), guest.Headers.end(), [](const auto& header) { return header.Type == 7; });
|
||||
if (tls == guest.Headers.end() || (symbolIndex != 0 && (symbol.Section == 0 || (symbol.Info & 15) != 6))) throw Domain::RelinkerException("Unsupported external static TLS relocation", target);
|
||||
const auto size = Io::AlignUp64(tls->MemorySize, std::max<std::uint64_t>(tls->Alignment, 16));
|
||||
image.WritePointer(target, symbol.Value + addend - size);
|
||||
} else if (type == 1 || type == 6 || type == 7) {
|
||||
if (symbolIndex == 0 || (type != 1 && addend != 0) || (symbol.Info & 15) == 6) throw Domain::RelinkerException("Invalid guest symbol relocation", target);
|
||||
if (symbol.Section != 0) {
|
||||
if (symbol.Section >= 0xff00) throw Domain::RelinkerException("Unsupported special guest symbol section", target);
|
||||
image.WritePointer(target, image.GetRelocatedAddress(symbol.Value) + addend);
|
||||
relocations.push_back(rva);
|
||||
} else {
|
||||
if (symbol.Name.empty()) throw Domain::RelinkerException("Empty guest import", target);
|
||||
image.WritePointer(target, 0);
|
||||
runtime.Imports.push_back({symbol.Name, rva, addend});
|
||||
}
|
||||
} else throw Domain::RelinkerException("Unsupported Windows guest relocation " + std::to_string(type), target);
|
||||
}
|
||||
};
|
||||
apply(guest.Dynamic.RelaData);
|
||||
apply(guest.Dynamic.RelaPltData);
|
||||
auto sections = image.BuildSections();
|
||||
auto nextRva = image.GetEndRva();
|
||||
std::array<PeDirectory, 16> directories{};
|
||||
std::uint32_t tlsIndex = 0;
|
||||
directories[9] = WindowsTlsBuilder().Build(guest.Bytes, guest.Headers, image, sections, relocations, nextRva, &tlsIndex);
|
||||
for (const auto& [target, rva] : tlsModules) {
|
||||
if (tlsIndex == 0) throw Domain::RelinkerException("Guest TLS relocation has no TLS block", target);
|
||||
bool written = false;
|
||||
for (auto& section : sections) {
|
||||
if (rva < section.Rva || rva - section.Rva > section.Data.size() || section.Data.size() - (rva - section.Rva) < 8) continue;
|
||||
Io::WriteU64(section.Data, rva - section.Rva, ImageBase + tlsIndex);
|
||||
written = true;
|
||||
break;
|
||||
}
|
||||
if (!written) throw Domain::RelinkerException("Guest TLS target is unmapped", target);
|
||||
relocations.push_back(rva);
|
||||
}
|
||||
for (const auto& header : guest.Headers) {
|
||||
if (header.Type != 0x6474e550) continue;
|
||||
std::vector<std::uint8_t> metadata(4);
|
||||
Io::WriteU32(metadata, 0, image.GetRva(header.MappedAddress, header.FileSize));
|
||||
sections.push_back({".ehmeta", nextRva, SectionRead | 0x40u, std::move(metadata)});
|
||||
nextRva = AlignRva(nextRva + sections.back().Data.size());
|
||||
}
|
||||
std::map<std::string, std::uint32_t> exports;
|
||||
PeSection tlsExports{".tlsrefs", nextRva, SectionRead | 0x40u, {}};
|
||||
for (const auto& symbol : guest.Symbols) {
|
||||
if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
|
||||
if (symbol.Section >= 0xff00) throw Domain::RelinkerException("Unsupported guest export section: " + symbol.Name);
|
||||
std::uint32_t rva = 0;
|
||||
if ((symbol.Info & 15) == 6) {
|
||||
if (tlsIndex == 0) throw Domain::RelinkerException("Guest TLS export has no TLS block: " + symbol.Name);
|
||||
rva = CheckedRva(tlsExports.Rva + tlsExports.Data.size());
|
||||
Io::AppendU64(tlsExports.Data, ImageBase + tlsIndex);
|
||||
Io::AppendU64(tlsExports.Data, symbol.Value);
|
||||
relocations.push_back(rva);
|
||||
} else rva = image.GetRva(symbol.Value, std::max<std::uint64_t>(symbol.Size, 1));
|
||||
if (!exports.emplace(symbol.Name, rva).second) throw Domain::RelinkerException("Duplicate guest export: " + symbol.Name);
|
||||
}
|
||||
if (!tlsExports.Data.empty()) {
|
||||
nextRva = AlignRva(nextRva + tlsExports.Data.size());
|
||||
sections.push_back(std::move(tlsExports));
|
||||
}
|
||||
if (exports.size() > 65535) throw Domain::RelinkerException("Too many guest PE exports");
|
||||
PeSection exportSection{".edata", nextRva, SectionRead | 0x40u, std::vector<std::uint8_t>(40)};
|
||||
auto& data = exportSection.Data;
|
||||
const auto count = CheckedRva(exports.size());
|
||||
const auto functions = data.size();
|
||||
data.resize(data.size() + count * 4);
|
||||
const auto names = data.size();
|
||||
data.resize(data.size() + count * 4);
|
||||
const auto ordinals = data.size();
|
||||
data.resize(data.size() + count * 2);
|
||||
Io::WriteU32(data, 12, CheckedRva(nextRva + data.size()));
|
||||
Io::AppendString(data, guest.OutputName);
|
||||
Io::WriteU32(data, 16, 1);
|
||||
Io::WriteU32(data, 20, count);
|
||||
Io::WriteU32(data, 24, count);
|
||||
Io::WriteU32(data, 28, CheckedRva(nextRva + functions));
|
||||
Io::WriteU32(data, 32, CheckedRva(nextRva + names));
|
||||
Io::WriteU32(data, 36, CheckedRva(nextRva + ordinals));
|
||||
std::size_t index = 0;
|
||||
for (const auto& [name, rva] : exports) {
|
||||
Io::WriteU32(data, functions + index * 4, rva);
|
||||
Io::WriteU32(data, names + index * 4, CheckedRva(nextRva + data.size()));
|
||||
Io::WriteU16(data, ordinals + index * 2, static_cast<std::uint16_t>(index));
|
||||
Io::AppendString(data, name);
|
||||
++index;
|
||||
}
|
||||
directories[0] = {nextRva, CheckedRva(data.size())};
|
||||
nextRva = AlignRva(nextRva + data.size());
|
||||
sections.push_back(std::move(exportSection));
|
||||
auto relocationData = WindowsRelocationBuilder().BuildBaseRelocations(relocations);
|
||||
if (!relocationData.empty()) {
|
||||
directories[5] = {nextRva, CheckedRva(relocationData.size())};
|
||||
sections.push_back({".reloc", nextRva, SectionRead | 0x02000040u, std::move(relocationData)});
|
||||
nextRva = AlignRva(nextRva + sections.back().Data.size());
|
||||
}
|
||||
const auto entry = nextRva;
|
||||
sections.push_back({".dllmain", entry, SectionRead | SectionExecute | 0x20u, {0xb8, 1, 0, 0, 0, 0xc3}});
|
||||
runtime.InitRva = guest.Init == 0 ? 0 : image.GetRva(guest.Init);
|
||||
runtime.FiniRva = guest.Fini == 0 ? 0 : image.GetRva(guest.Fini);
|
||||
auto result = WindowsPeWriter().Write(sections, entry, directories);
|
||||
const auto characteristics = Io::ReadU16(result, 0x80 + 22);
|
||||
Io::WriteU16(result, 0x80 + 22, static_cast<std::uint16_t>((characteristics | 0x2000) & ~1u));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#include <elfpatcher/windows/WindowsGuestStartup.hpp>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
void WindowsGuestStartup::WriteImport(WindowsStubEmitter& code, const PeImport& import, const std::uint32_t handles) const {
|
||||
if (import.TargetModule < 0) {
|
||||
code.Rip({0x48, 0x89, 0x05}, import.TargetRva);
|
||||
return;
|
||||
}
|
||||
code.Rip({0x48, 0x8b, 0x15}, CheckedRva(handles + static_cast<std::uint64_t>(import.TargetModule) * 8));
|
||||
code.Emit({0x41, 0xb8});
|
||||
code.U32(import.TargetRva);
|
||||
code.Emit({0x4a, 0x89, 0x04, 0x02});
|
||||
}
|
||||
|
||||
void WindowsGuestStartup::callLifecycle(WindowsStubEmitter& code, const std::uint32_t handle, const std::uint32_t rva) const {
|
||||
if (rva == 0) return;
|
||||
code.Rip({0x48, 0x8b, 0x05}, handle);
|
||||
code.Emit({0xb9});
|
||||
code.U32(rva);
|
||||
code.Emit({0x48, 0x01, 0xc8, 0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xff, 0xd0});
|
||||
}
|
||||
|
||||
void WindowsGuestStartup::Initialize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, const std::uint32_t handles) const {
|
||||
for (std::size_t index = 0; index < modules.size(); ++index) callLifecycle(code, CheckedRva(handles + index * 8), modules[index].InitRva);
|
||||
}
|
||||
|
||||
void WindowsGuestStartup::Finalize(WindowsStubEmitter& code, const std::vector<Domain::GuestRuntime>& modules, const std::uint32_t handles, const std::uint32_t finished) const {
|
||||
code.Emit({0xb8, 1, 0, 0, 0});
|
||||
code.Rip({0x87, 0x05}, finished);
|
||||
code.Emit({0x85, 0xc0});
|
||||
const auto skip = code.Branch({0x0f, 0x85});
|
||||
for (std::size_t index = modules.size(); index > 0; --index) callLifecycle(code, CheckedRva(handles + (index - 1) * 8), modules[index - 1].FiniRva);
|
||||
code.PatchBranch(skip, code.GetRva());
|
||||
}
|
||||
|
||||
std::uint32_t WindowsGuestStartup::EmitTlsResolver(WindowsStubEmitter& code) const {
|
||||
const auto entry = code.GetRva();
|
||||
code.Emit({0x48, 0x8b, 0x07, 0x8b, 0x08, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x58, 0, 0, 0, 0x48, 0x8b, 0x04, 0xc8, 0x48, 0x03, 0x47, 8, 0xc3});
|
||||
return entry;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -78,10 +78,17 @@ std::vector<std::uint8_t> WindowsPePatcher::Patch(const std::vector<std::uint8_t
|
|||
directories[1] = nativeImports.Directory;
|
||||
directories[12] = nativeImports.AddressTable;
|
||||
nextRva = AlignRva(nextRva + nativeImports.Section.Data.size());
|
||||
const auto libraries = importBuilder.ReadLibraries(dynamicSection);
|
||||
auto libraries = importBuilder.ReadLibraries(dynamicSection);
|
||||
std::vector<std::string> guestPaths;
|
||||
for (std::size_t index = 0; index < dynamicSection.GuestModules.size(); ++index) {
|
||||
const auto& module = dynamicSection.GuestModules[index];
|
||||
guestPaths.push_back(module.Path);
|
||||
for (const auto& import : module.Imports) relocations.Imports.push_back({import.Name, import.TargetRva, import.Addend, static_cast<std::int32_t>(index), import.RelocationType});
|
||||
}
|
||||
libraries.insert(libraries.begin(), guestPaths.begin(), guestPaths.end());
|
||||
if (dependencyDiagnostics)
|
||||
writeDiagnosticsImports(relocations.Imports);
|
||||
auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics);
|
||||
auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics, dynamicSection.GuestModules);
|
||||
directories[3] = entry.ExceptionDirectory;
|
||||
const auto entryRva = entry.Code.Rva;
|
||||
sections.push_back(std::move(nativeImports.Section));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include <elfpatcher/windows/WindowsTlsBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
|
||||
#include <elfpatcher/windows/WindowsTlsTemplateBuilder.hpp>
|
||||
#include <codegen/x86/X64InstructionDecoder.hpp>
|
||||
#include <relinker/analysis/CodeInstructionCollector.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
|
|
@ -14,6 +16,8 @@ namespace {
|
|||
struct TlsAccess {
|
||||
std::uint32_t Rva;
|
||||
std::size_t Length;
|
||||
bool StoreImmediate;
|
||||
std::uint32_t Immediate;
|
||||
};
|
||||
|
||||
void patchAccess(std::vector<PeSection>& sections, const TlsAccess& access, const std::uint32_t target) {
|
||||
|
|
@ -35,12 +39,13 @@ void patchAccess(std::vector<PeSection>& sections, const TlsAccess& access, cons
|
|||
|
||||
}
|
||||
|
||||
PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva) const {
|
||||
PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& headers, const WindowsLoadImage& image, std::vector<PeSection>& sections, std::vector<std::uint32_t>& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva) const {
|
||||
const Domain::ProgramHeader* tls = nullptr;
|
||||
const Codegen::X64InstructionDecoder decoder;
|
||||
|
||||
std::vector<TlsAccess> accesses;
|
||||
std::set<std::uint32_t> branchTargets;
|
||||
const auto instructions = Relinker::CodeInstructionCollector().Collect(source, headers);
|
||||
|
||||
for (const auto& header : headers) {
|
||||
if (header.Type == 7) {
|
||||
|
|
@ -50,7 +55,8 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
}
|
||||
if (header.Type != 1 || (header.Flags & 1) == 0)
|
||||
continue;
|
||||
for (std::uint64_t offset = 0; offset < header.FileSize;) {
|
||||
for (auto instruction = instructions.lower_bound(header.MappedAddress); instruction != instructions.end() && *instruction - header.MappedAddress < header.FileSize; ++instruction) {
|
||||
const auto offset = *instruction - header.MappedAddress;
|
||||
const auto* bytes = source.data() + header.Offset + offset;
|
||||
const auto info = decoder.DecodeInstruction(bytes, header.FileSize - offset);
|
||||
const auto rva = image.GetRva(header.MappedAddress + offset, info.Length);
|
||||
|
|
@ -63,11 +69,19 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
|
||||
if (info.SegmentPrefix != 0) {
|
||||
const auto position = info.OpcodeOffset;
|
||||
if (info.SegmentPrefix != 0x64 || info.RexPrefix != 0x48 || info.Length - position != 7 || bytes[position] != 0x8b || bytes[position + 1] != 0x04 || bytes[position + 2] != 0x25 || Io::ReadU32(source, header.Offset + offset + position + 3) != 0)
|
||||
bool hasOperandSizePrefix = false;
|
||||
bool supportedPrefixes = info.SegmentPrefix == 0x64;
|
||||
for (std::size_t prefix = 0; prefix < position; ++prefix) {
|
||||
const auto value = bytes[prefix];
|
||||
if (value == 0x66) hasOperandSizePrefix = true;
|
||||
else if (value != 0x64 && !(prefix + 1 == position && value >= 0x40 && value <= 0x4f)) supportedPrefixes = false;
|
||||
}
|
||||
const bool loadPointer = supportedPrefixes && info.RexPrefix == 0x48 && info.Length - position == 7 && bytes[position] == 0x8b && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0;
|
||||
const bool storeImmediate = supportedPrefixes && !hasOperandSizePrefix && (info.RexPrefix == 0 || info.RexPrefix == 0x40) && info.Length - position == 11 && bytes[position] == 0xc7 && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0x28;
|
||||
if (!loadPointer && !storeImmediate)
|
||||
throw Domain::RelinkerException("Unsupported Windows guest TLS instruction", header.Offset + offset);
|
||||
accesses.push_back({rva, info.Length});
|
||||
accesses.push_back({rva, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0});
|
||||
}
|
||||
offset += info.Length;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +91,7 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
return {};
|
||||
}
|
||||
|
||||
if (tls->FileSize > tls->MemorySize || tls->Offset > source.size() || tls->FileSize > source.size() - tls->Offset || !std::has_single_bit(tls->Alignment) || tls->Alignment > 8192 || tls->MemorySize > 0x7fff0000u)
|
||||
if (tls->FileSize > tls->MemorySize || tls->Offset > source.size() || tls->FileSize > source.size() - tls->Offset || (tls->Alignment > 1 && !std::has_single_bit(tls->Alignment)) || tls->Alignment > 8192 || tls->MemorySize > 0x7fff0000u)
|
||||
throw Domain::RelinkerException("Invalid or unsupported ELF TLS layout", tls->Offset);
|
||||
// Native homebrew linkers can emit an empty PT_TLS placeholder.
|
||||
// It needs no Windows TLS directory unless guest code accesses TLS.
|
||||
|
|
@ -89,9 +103,11 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
const auto alignment = std::max<std::uint64_t>(tls->Alignment, 16);
|
||||
const auto blockSize = CheckedRva((tls->MemorySize + alignment - 1) & ~(alignment - 1));
|
||||
const auto templateOffset = CheckedRva((64 + alignment - 1) & ~(alignment - 1));
|
||||
PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(templateOffset + blockSize + 16)};
|
||||
constexpr std::uint32_t threadControlBlockSize = 0x30;
|
||||
PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(templateOffset + blockSize + threadControlBlockSize)};
|
||||
|
||||
const auto indexRva = nextRva + 40;
|
||||
if (tlsIndexRva != nullptr) *tlsIndexRva = indexRva;
|
||||
const auto callbackTableRva = nextRva + 48;
|
||||
const auto templateRva = nextRva + templateOffset;
|
||||
const auto codeRva = AlignRva(nextRva + data.Data.size());
|
||||
|
|
@ -120,19 +136,26 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
|
|||
throw Domain::RelinkerException("Branch enters a guest TLS instruction", *target);
|
||||
patchAccess(sections, access, code.GetRva());
|
||||
code.Emit({0x48, 0x8d, 0x64, 0x24, 0x80, 0x51});
|
||||
if (access.StoreImmediate) code.Emit({0x50});
|
||||
loadPointer();
|
||||
if (access.StoreImmediate) {
|
||||
code.Emit({0xc7, 0x40, 0x28});
|
||||
code.U32(access.Immediate);
|
||||
code.Emit({0x58});
|
||||
}
|
||||
code.Emit({0x59, 0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0});
|
||||
code.Rip({0xe9}, CheckedRva(access.Rva + access.Length));
|
||||
}
|
||||
|
||||
std::copy_n(source.begin() + tls->Offset, tls->FileSize, data.Data.begin() + templateOffset);
|
||||
const auto templateBytes = WindowsTlsTemplateBuilder().Build(*tls, image, sections, templateRva, relocations);
|
||||
std::copy(templateBytes.begin(), templateBytes.end(), data.Data.begin() + templateOffset);
|
||||
const auto writeAddress = [&](const std::size_t offset, const std::uint32_t rva) {
|
||||
Io::WriteU64(data.Data, offset, ImageBase + rva);
|
||||
relocations.push_back(CheckedRva(data.Rva + offset));
|
||||
};
|
||||
|
||||
writeAddress(0, templateRva);
|
||||
writeAddress(8, CheckedRva(templateRva + blockSize + 16));
|
||||
writeAddress(8, CheckedRva(templateRva + blockSize + threadControlBlockSize));
|
||||
writeAddress(16, indexRva);
|
||||
writeAddress(24, callbackTableRva);
|
||||
writeAddress(48, codeRva);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
#include <elfpatcher/windows/WindowsTlsTemplateBuilder.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
std::vector<std::uint8_t> WindowsTlsTemplateBuilder::Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector<PeSection>& sections, const std::uint32_t templateRva, std::vector<std::uint32_t>& relocations) const {
|
||||
if (tls.FileSize == 0) return {};
|
||||
const auto sourceRva = image.GetRva(tls.MappedAddress, tls.FileSize);
|
||||
const auto sourceEnd = static_cast<std::uint64_t>(sourceRva) + tls.FileSize;
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(static_cast<std::size_t>(tls.FileSize));
|
||||
while (result.size() < tls.FileSize) {
|
||||
const auto currentRva = static_cast<std::uint64_t>(sourceRva) + result.size();
|
||||
const auto section = std::find_if(sections.begin(), sections.end(), [&](const auto& candidate) {
|
||||
return currentRva >= candidate.Rva && currentRva - candidate.Rva < candidate.Data.size();
|
||||
});
|
||||
if (section == sections.end()) throw Domain::RelinkerException("TLS template is outside PE section data", currentRva);
|
||||
const auto offset = static_cast<std::size_t>(currentRva - section->Rva);
|
||||
const auto size = std::min<std::uint64_t>(tls.FileSize - result.size(), section->Data.size() - offset);
|
||||
result.insert(result.end(), section->Data.begin() + offset, section->Data.begin() + offset + size);
|
||||
}
|
||||
const auto originalCount = relocations.size();
|
||||
for (std::size_t index = 0; index < originalCount; ++index) {
|
||||
const auto target = static_cast<std::uint64_t>(relocations[index]);
|
||||
if (target >= sourceEnd || target + 8 <= sourceRva) continue;
|
||||
if (target < sourceRva || sourceEnd - target < 8) throw Domain::RelinkerException("PE relocation crosses the TLS template boundary", target);
|
||||
relocations.push_back(CheckedRva(static_cast<std::uint64_t>(templateRva) + target - sourceRva));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,11 +17,13 @@
|
|||
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
|
||||
#include <relinker/output/CallRegistryWriter.hpp>
|
||||
#include <relinker/pipeline/RelinkerPipeline.hpp>
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <codegen/IAmd64OnlyConverter.hpp>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
int main(const int argc, char* argv[]) {
|
||||
Cli::Args args;
|
||||
|
|
@ -53,10 +55,11 @@ int main(const int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
auto elfReader = std::make_shared<Relinker::ElfReader>(sourceBytes);
|
||||
const std::shared_ptr<Relinker::ISyscallScanner> syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner();
|
||||
|
||||
const auto pipeline = std::make_shared<Relinker::RelinkerPipeline>(
|
||||
elfReader,
|
||||
args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(),
|
||||
syscallScanner,
|
||||
Relinker::MakeCallSiteResolver(),
|
||||
std::make_shared<Relinker::ValidationPolicy>(),
|
||||
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
|
||||
|
|
@ -65,6 +68,7 @@ int main(const int argc, char* argv[]) {
|
|||
);
|
||||
|
||||
std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n";
|
||||
std::cout << "sce_module/sce_modules processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n';
|
||||
auto result = pipeline->Relink(sourceBytes);
|
||||
for (const auto& patch : result.Patches) {
|
||||
if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset)
|
||||
|
|
@ -72,6 +76,11 @@ int main(const int argc, char* argv[]) {
|
|||
for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index];
|
||||
}
|
||||
|
||||
std::vector<Relinker::GuestArtifact> guestArtifacts;
|
||||
if (!args.skipSceModule) {
|
||||
guestArtifacts = Relinker::GuestModuleBuilder().Build(args.inputPath, absPath, result.DynamicSection, args.toWindows, args.toIntel, *syscallScanner, args.lazyBinding, args.runPath);
|
||||
}
|
||||
|
||||
if (args.writeRegistry) {
|
||||
const std::filesystem::path outFsPath(absPath);
|
||||
const std::string registryPath = (outFsPath.parent_path() / (outFsPath.stem().string() + ".registry.json")).string();
|
||||
|
|
@ -95,7 +104,13 @@ int main(const int argc, char* argv[]) {
|
|||
);
|
||||
}
|
||||
|
||||
fileWriter.Write(absPath, patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics));
|
||||
const auto executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics);
|
||||
for (const auto& artifact : guestArtifacts) {
|
||||
std::filesystem::create_directories(artifact.Path.parent_path());
|
||||
fileWriter.Write(artifact.Path.string(), artifact.Bytes);
|
||||
std::cout << "Guest module: " << artifact.Path.string() << '\n';
|
||||
}
|
||||
fileWriter.Write(absPath, executableBytes);
|
||||
std::cout << "External prx references: " << result.RegistryEntries.size() << "\nOutput file: " << absPath << '\n';
|
||||
|
||||
if (args.autorun) return Cli::Autorun(absPath, args.toWindows);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP
|
||||
#define RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP
|
||||
|
||||
#include <domain/Types.hpp>
|
||||
#include <set>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
class CodeInstructionCollector {
|
||||
public:
|
||||
std::set<Domain::VirtualAddress> Collect(const std::vector<std::uint8_t>& bytes, const std::vector<Domain::ProgramHeader>& headers) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef RELINKER_GUESTIMAGE_HPP
|
||||
#define RELINKER_GUESTIMAGE_HPP
|
||||
|
||||
#include <domain/Types.hpp>
|
||||
#include <relinker/domain/ISyscallScanner.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
inline constexpr char GuestModuleSuffix[] = ".guest.prx";
|
||||
|
||||
struct GuestSymbol {
|
||||
std::string Name;
|
||||
std::uint8_t Info;
|
||||
std::uint8_t Visibility;
|
||||
std::uint16_t Section;
|
||||
std::uint64_t Value;
|
||||
std::uint64_t Size;
|
||||
};
|
||||
|
||||
struct GuestImage {
|
||||
std::filesystem::path SourcePath;
|
||||
std::string OutputName;
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
std::vector<Domain::ProgramHeader> Headers;
|
||||
std::vector<GuestSymbol> Symbols;
|
||||
std::vector<std::string> Dependencies;
|
||||
Domain::SysVDynamicSection Dynamic;
|
||||
std::uint64_t Init = 0;
|
||||
std::uint64_t Fini = 0;
|
||||
std::uint64_t Got = 0;
|
||||
bool UsePlatformTlsResolver = true;
|
||||
};
|
||||
|
||||
class GuestImageReader {
|
||||
public:
|
||||
GuestImage Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const;
|
||||
};
|
||||
|
||||
struct GuestArtifact {
|
||||
std::filesystem::path Path;
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
};
|
||||
|
||||
class GuestModuleBuilder {
|
||||
public:
|
||||
std::vector<GuestArtifact> Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, bool windows, bool toIntel, ISyscallScanner& syscallScanner, bool lazyBinding, const std::string& runPath) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
#include <relinker/analysis/CodeInstructionCollector.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <codegen/x86/X64InstructionDecoder.hpp>
|
||||
#include <codegen/CodegenException.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
namespace {
|
||||
|
||||
class CodePointers : public UnusedNidFilter::IRelativeRelocationIndex {
|
||||
public:
|
||||
std::map<Domain::VirtualAddress, Domain::VirtualAddress> Values;
|
||||
std::optional<Domain::VirtualAddress> TargetOfSlot(Domain::VirtualAddress address) const override {
|
||||
const auto found = Values.find(address);
|
||||
if (found == Values.end()) return std::nullopt;
|
||||
return found->second;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::vector<std::uint8_t>& bytes, const std::vector<Domain::ProgramHeader>& headers) const {
|
||||
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
|
||||
if (offset > bytes.size() || size > bytes.size() - offset) throw Domain::RelinkerException("Code analysis: file range exceeds image", offset);
|
||||
};
|
||||
const auto fileOffset = [&](std::uint64_t address, std::uint64_t size) {
|
||||
for (const auto& header : headers) {
|
||||
if (header.Type != 1 || address < header.MappedAddress || address - header.MappedAddress > header.FileSize || size > header.FileSize - (address - header.MappedAddress)) continue;
|
||||
const auto offset = header.Offset + address - header.MappedAddress;
|
||||
range(offset, size);
|
||||
return offset;
|
||||
}
|
||||
throw Domain::RelinkerException("Code analysis: unmapped address", address);
|
||||
};
|
||||
const auto isCode = [&](std::uint64_t address) {
|
||||
for (const auto& header : headers) if (header.Type == 1 && (header.Flags & 1) != 0 && address >= header.MappedAddress && address - header.MappedAddress < header.FileSize) return true;
|
||||
return false;
|
||||
};
|
||||
std::map<std::uint64_t, std::uint64_t> tags;
|
||||
std::optional<std::uint64_t> sceBase;
|
||||
bool hasDynamic = false;
|
||||
for (const auto& header : headers) {
|
||||
if (header.Type == 0x61000000) sceBase = header.Offset;
|
||||
if (header.Type != 2) continue;
|
||||
if (hasDynamic || header.FileSize % 16 != 0) throw Domain::RelinkerException("Code analysis: invalid dynamic segment");
|
||||
hasDynamic = true;
|
||||
range(header.Offset, header.FileSize);
|
||||
bool terminated = false;
|
||||
for (std::uint64_t offset = header.Offset; offset < header.Offset + header.FileSize; offset += 16) {
|
||||
const auto tag = Io::ReadU64(bytes, offset);
|
||||
if (tag == 0) { terminated = true; break; }
|
||||
const auto value = Io::ReadU64(bytes, offset + 8);
|
||||
if (tag == 1 || (tag >= 0x61000000 && tag != 0x6100003f && (tag < 0x61000027 || tag > 0x6100003b))) continue;
|
||||
if (!tags.emplace(tag, value).second) throw Domain::RelinkerException("Code analysis: duplicate dynamic tag", offset);
|
||||
}
|
||||
if (!terminated) throw Domain::RelinkerException("Code analysis: unterminated dynamic segment");
|
||||
}
|
||||
const auto value = [&](std::uint64_t standard, std::uint64_t sce) -> std::optional<std::uint64_t> {
|
||||
if (tags.contains(standard) && tags.contains(sce)) throw Domain::RelinkerException("Code analysis: ambiguous dynamic tag");
|
||||
if (tags.contains(standard)) return tags.at(standard);
|
||||
if (tags.contains(sce)) return tags.at(sce);
|
||||
return std::nullopt;
|
||||
};
|
||||
const auto table = [&](std::uint64_t standard, std::uint64_t sce, std::uint64_t size) {
|
||||
const auto address = value(standard, sce);
|
||||
if (!address) throw Domain::RelinkerException("Code analysis: missing table");
|
||||
if (tags.contains(standard)) return fileOffset(*address, size);
|
||||
if (!sceBase || *address > std::numeric_limits<std::uint64_t>::max() - *sceBase) throw Domain::RelinkerException("Code analysis: invalid SCE table");
|
||||
const auto offset = *sceBase + *address;
|
||||
range(offset, size);
|
||||
return offset;
|
||||
};
|
||||
std::set<std::uint64_t> roots;
|
||||
std::map<std::uint64_t, std::uint64_t> functions;
|
||||
const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); };
|
||||
const auto addFunction = [&](std::uint64_t begin, std::uint64_t size) {
|
||||
if (size == 0) return;
|
||||
if (!isCode(begin) || size > std::numeric_limits<std::uint64_t>::max() - begin)
|
||||
throw Domain::RelinkerException("Code analysis: invalid function range", begin);
|
||||
bool mapped = false;
|
||||
for (const auto& header : headers) {
|
||||
if (header.Type != 1 || (header.Flags & 1) == 0 || begin < header.MappedAddress || begin - header.MappedAddress >= header.FileSize) continue;
|
||||
if (size > header.FileSize - (begin - header.MappedAddress))
|
||||
throw Domain::RelinkerException("Code analysis: function exceeds executable segment", begin);
|
||||
range(header.Offset + begin - header.MappedAddress, size);
|
||||
mapped = true;
|
||||
break;
|
||||
}
|
||||
if (!mapped) throw Domain::RelinkerException("Code analysis: function is not file-backed", begin);
|
||||
const auto [position, inserted] = functions.emplace(begin, begin + size);
|
||||
if (!inserted && position->second != begin + size)
|
||||
throw Domain::RelinkerException("Code analysis: conflicting function ranges", begin);
|
||||
roots.insert(begin);
|
||||
};
|
||||
range(0, 64);
|
||||
if (const auto entry = Io::ReadU64(bytes, 24); entry != 0) addRoot(entry);
|
||||
for (const auto tag : {12u, 13u}) if (const auto address = value(tag, 0x60000000 + tag); address && *address != 0) addRoot(*address);
|
||||
const auto symbolBytes = value(0x6100003f, 0xffffffffffffffffull);
|
||||
std::uint64_t symbolCount = 0;
|
||||
if (symbolBytes) {
|
||||
if (*symbolBytes % 24 != 0) throw Domain::RelinkerException("Code analysis: invalid symbol table size");
|
||||
symbolCount = *symbolBytes / 24;
|
||||
} else if (tags.contains(4)) symbolCount = Io::ReadU32(bytes, fileOffset(tags.at(4), 8) + 4);
|
||||
std::uint64_t symbols = 0;
|
||||
if (symbolCount != 0) {
|
||||
if (value(11, 0x6100003b) != 24) throw Domain::RelinkerException("Code analysis: invalid symbol entry size");
|
||||
symbols = table(6, 0x61000039, symbolCount * 24);
|
||||
for (std::uint64_t index = 0; index < symbolCount; ++index) {
|
||||
const auto offset = symbols + index * 24;
|
||||
if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) {
|
||||
const auto address = Io::ReadU64(bytes, offset + 8);
|
||||
addRoot(address);
|
||||
addFunction(address, Io::ReadU64(bytes, offset + 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
CodePointers pointers;
|
||||
std::set<std::uint64_t> importSlots;
|
||||
const auto readRelocations = [&](std::uint64_t addressTag, std::uint64_t sceAddressTag, std::uint64_t sizeTag, std::uint64_t sceSizeTag) {
|
||||
const auto size = value(sizeTag, sceSizeTag);
|
||||
if (!size) return;
|
||||
if (*size % 24 != 0) throw Domain::RelinkerException("Code analysis: invalid relocation table size");
|
||||
if (*size == 0) return;
|
||||
const auto start = table(addressTag, sceAddressTag, *size);
|
||||
for (std::uint64_t offset = start; offset < start + *size; offset += 24) {
|
||||
const auto slot = Io::ReadU64(bytes, offset);
|
||||
const auto info = Io::ReadU64(bytes, offset + 8);
|
||||
const auto type = static_cast<std::uint32_t>(info);
|
||||
const auto index = info >> 32;
|
||||
const auto addend = Io::ReadU64(bytes, offset + 16);
|
||||
if (type == 8) {
|
||||
if (index != 0 || !pointers.Values.emplace(slot, addend).second) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot);
|
||||
addRoot(addend);
|
||||
} else if (type == 1 || type == 6 || type == 7) {
|
||||
if (index == 0 || index >= symbolCount) throw Domain::RelinkerException("Code analysis: invalid relocation symbol", slot);
|
||||
const auto symbol = symbols + index * 24;
|
||||
if (Io::ReadU16(bytes, symbol + 6) == 0) importSlots.insert(slot);
|
||||
else {
|
||||
const auto target = Io::ReadU64(bytes, symbol + 8) + addend;
|
||||
if (!pointers.Values.emplace(slot, target).second) throw Domain::RelinkerException("Code analysis: duplicate pointer relocation", slot);
|
||||
addRoot(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
readRelocations(7, 0x6100002f, 8, 0x61000031);
|
||||
readRelocations(23, 0x61000029, 2, 0x6100002d);
|
||||
for (const auto& [addressTag, sizeTag] : {std::pair{25u, 27u}, std::pair{26u, 28u}, std::pair{32u, 33u}}) {
|
||||
const auto address = value(addressTag, 0x60000000 + addressTag);
|
||||
const auto size = value(sizeTag, 0x60000000 + sizeTag);
|
||||
if (address.has_value() != size.has_value()) throw Domain::RelinkerException("Code analysis: incomplete initializer array");
|
||||
if (!size || *size == 0) continue;
|
||||
if (*size % 8 != 0) throw Domain::RelinkerException("Code analysis: invalid initializer array size");
|
||||
const auto offset = fileOffset(*address, *size);
|
||||
for (std::uint64_t index = 0; index < *size; index += 8) {
|
||||
const auto relocated = pointers.TargetOfSlot(*address + index);
|
||||
const auto target = relocated ? *relocated : Io::ReadU64(bytes, offset + index);
|
||||
if (target == 0 || target == std::numeric_limits<std::uint64_t>::max()) continue;
|
||||
if (!isCode(target)) throw Domain::RelinkerException("Code analysis: initializer lies outside code", target);
|
||||
roots.insert(target);
|
||||
}
|
||||
}
|
||||
for (const auto& function : UnusedNidFilter::ReadExceptionFunctions(bytes, headers, pointers.Values, importSlots)) {
|
||||
if (function.End <= function.Begin) throw Domain::RelinkerException("Code analysis: invalid unwind function range", function.Begin);
|
||||
addFunction(function.Begin, function.End - function.Begin);
|
||||
for (const auto target : function.ExtraTargets) addRoot(target);
|
||||
}
|
||||
if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points");
|
||||
std::set<std::uint64_t> instructions;
|
||||
const Codegen::X64InstructionDecoder decoder;
|
||||
for (const auto& [begin, end] : functions) {
|
||||
const auto offset = fileOffset(begin, end - begin);
|
||||
for (auto address = begin; address < end;) {
|
||||
Codegen::DecodedInstructionInfo info;
|
||||
try {
|
||||
info = decoder.DecodeInstruction(bytes.data() + offset + address - begin, end - address);
|
||||
} catch (const Codegen::CodegenException& error) {
|
||||
throw Domain::RelinkerException(std::string("Code analysis: ") + error.what(), address);
|
||||
}
|
||||
if (info.Length == 0 || info.Length > end - address)
|
||||
throw Domain::RelinkerException("Code analysis: instruction crosses function boundary", address);
|
||||
instructions.insert(address);
|
||||
if (info.HasBranchTarget && !info.HasRipRelativeDisp)
|
||||
addRoot(address + info.Length + static_cast<std::uint64_t>(info.BranchDisp));
|
||||
address += info.Length;
|
||||
}
|
||||
}
|
||||
std::size_t previousRoots = 0;
|
||||
do {
|
||||
previousRoots = roots.size();
|
||||
for (const auto& header : headers) {
|
||||
if (header.Type != 1 || (header.Flags & 1) == 0 || header.FileSize == 0) continue;
|
||||
range(header.Offset, header.FileSize);
|
||||
std::vector<std::uint64_t> entries;
|
||||
for (const auto root : roots) if (root >= header.MappedAddress && root - header.MappedAddress < header.FileSize) entries.push_back(root);
|
||||
if (entries.empty()) continue;
|
||||
const std::vector<std::uint8_t> text(bytes.begin() + header.Offset, bytes.begin() + header.Offset + header.FileSize);
|
||||
const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers);
|
||||
for (const auto address : graph->ReachableVaddrs()) {
|
||||
instructions.insert(address);
|
||||
const auto offset = address - header.MappedAddress;
|
||||
const auto info = decoder.DecodeInstruction(text.data() + offset, text.size() - offset);
|
||||
if (info.HasBranchTarget && !info.HasRipRelativeDisp) addRoot(static_cast<std::uint64_t>(static_cast<std::int64_t>(address + info.Length) + info.BranchDisp));
|
||||
}
|
||||
}
|
||||
} while (roots.size() != previousRoots);
|
||||
std::uint64_t previousEnd = 0;
|
||||
for (const auto address : instructions) {
|
||||
if (address < previousEnd) throw Domain::RelinkerException("Code analysis: overlapping instruction boundaries", address);
|
||||
for (const auto& header : headers) {
|
||||
if (header.Type != 1 || (header.Flags & 1) == 0 || address < header.MappedAddress || address - header.MappedAddress >= header.FileSize) continue;
|
||||
const auto offset = address - header.MappedAddress;
|
||||
previousEnd = address + decoder.DecodeInstruction(bytes.data() + header.Offset + offset, header.FileSize - offset).Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <relinker/parsing/ElfReader.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const {
|
||||
const auto fail = [&](const std::string& message) { throw Domain::RelinkerException(path.string() + ": " + message); };
|
||||
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
|
||||
if (offset > bytes.size() || size > bytes.size() - offset) {
|
||||
std::ostringstream message;
|
||||
message << "ELF range exceeds file: offset=0x" << std::hex << offset << ", size=0x" << size << ", fileSize=0x" << bytes.size();
|
||||
fail(message.str());
|
||||
}
|
||||
};
|
||||
range(0, 64);
|
||||
if (Io::ReadU32(bytes, 0) != 0x464c457f || bytes[4] != 2 || bytes[5] != 1 || bytes[6] != 1 || Io::ReadU16(bytes, 18) != 62 || Io::ReadU32(bytes, 20) != 1) fail("Expected little-endian ELF64 x86-64");
|
||||
const auto type = Io::ReadU16(bytes, 16);
|
||||
if (type != 3 && type != 0xfe18) fail("ELF is not a shared module");
|
||||
if (Io::ReadU16(bytes, 52) != 64 || Io::ReadU16(bytes, 54) != 56 || Io::ReadU16(bytes, 56) == 0) fail("Invalid ELF program header table");
|
||||
range(Io::ReadU64(bytes, 32), static_cast<std::uint64_t>(Io::ReadU16(bytes, 56)) * 56);
|
||||
GuestImage image;
|
||||
image.SourcePath = path;
|
||||
image.OutputName = path.filename().string() + GuestModuleSuffix;
|
||||
image.Headers = ElfReader(bytes).ReadProgramHeaders();
|
||||
const Domain::ProgramHeader* dynamic = nullptr;
|
||||
const Domain::ProgramHeader* dynlib = nullptr;
|
||||
const Domain::ProgramHeader* tls = nullptr;
|
||||
std::map<std::uint64_t, std::uint64_t> loads;
|
||||
constexpr std::uint32_t sceComment = 0x6fffff00;
|
||||
constexpr std::uint32_t sceLibVersion = 0x6fffff01;
|
||||
for (const auto& header : image.Headers) {
|
||||
if (header.Type == sceComment || header.Type == sceLibVersion) continue;
|
||||
range(header.Offset, header.FileSize);
|
||||
if (header.Type == 1) {
|
||||
if (header.FileSize > header.MemorySize || header.MemorySize > std::numeric_limits<std::uint64_t>::max() - header.MappedAddress || (header.Flags & ~7u) != 0) fail("Invalid load segment");
|
||||
if (header.Alignment > 1 && (!std::has_single_bit(header.Alignment) || header.Offset % header.Alignment != header.MappedAddress % header.Alignment)) fail("Invalid load segment alignment");
|
||||
const auto next = loads.lower_bound(header.MappedAddress);
|
||||
if ((next != loads.end() && next->first < header.MappedAddress + header.MemorySize) || (next != loads.begin() && std::prev(next)->second > header.MappedAddress)) fail("Overlapping load segments");
|
||||
loads.emplace(header.MappedAddress, header.MappedAddress + header.MemorySize);
|
||||
}
|
||||
if (header.Type == 2) {
|
||||
if (dynamic != nullptr) fail("Duplicate dynamic segment");
|
||||
dynamic = &header;
|
||||
}
|
||||
if (header.Type == 0x61000000) {
|
||||
if (dynlib != nullptr) fail("Duplicate SCE dynamic data");
|
||||
dynlib = &header;
|
||||
}
|
||||
if (header.Type == 7) {
|
||||
if (tls != nullptr || header.FileSize > header.MemorySize || (header.Alignment > 1 && !std::has_single_bit(header.Alignment))) fail("Invalid TLS segment");
|
||||
tls = &header;
|
||||
}
|
||||
}
|
||||
if (loads.empty() || dynamic == nullptr || dynamic->FileSize % 16 != 0) fail("Missing or invalid ELF load/dynamic segments");
|
||||
std::map<std::uint64_t, std::uint64_t> tags;
|
||||
std::vector<std::uint64_t> needed;
|
||||
bool terminated = false;
|
||||
for (std::uint64_t offset = dynamic->Offset; offset < dynamic->Offset + dynamic->FileSize; offset += 16) {
|
||||
const auto tag = Io::ReadU64(bytes, offset);
|
||||
const auto value = Io::ReadU64(bytes, offset + 8);
|
||||
if (tag == 0) { terminated = true; break; }
|
||||
if (tag == 1) needed.push_back(value);
|
||||
else if (tag < 0x60000000 || tag == 0x6100003f || (tag >= 0x61000027 && tag <= 0x6100003b)) {
|
||||
if (!tags.emplace(tag, value).second) fail("Duplicate dynamic tag " + std::to_string(tag));
|
||||
}
|
||||
}
|
||||
if (!terminated) fail("Unterminated dynamic segment");
|
||||
for (const auto unsupported : {15u, 16u, 17u, 18u, 19u, 22u, 35u, 36u, 37u}) {
|
||||
if (tags.contains(unsupported)) fail("Unsupported dynamic tag " + std::to_string(unsupported));
|
||||
}
|
||||
if (tags.contains(30) && (tags.at(30) & ~8ull) != 0) fail("Unsupported guest dynamic flags");
|
||||
const auto mapped = [&](std::uint64_t address, std::uint64_t size, std::uint32_t flags) {
|
||||
for (const auto& header : image.Headers) {
|
||||
if (header.Type == 1 && (header.Flags & flags) == flags && address >= header.MappedAddress && address - header.MappedAddress <= header.MemorySize && size <= header.MemorySize - (address - header.MappedAddress)) return;
|
||||
}
|
||||
fail("Unmapped or inaccessible guest address " + std::to_string(address));
|
||||
};
|
||||
const auto translate = [&](std::uint64_t address, std::uint64_t size) {
|
||||
for (const auto& header : image.Headers) {
|
||||
if (header.Type == 1 && address >= header.MappedAddress && address - header.MappedAddress <= header.FileSize && size <= header.FileSize - (address - header.MappedAddress)) return header.Offset + address - header.MappedAddress;
|
||||
}
|
||||
fail("Unmapped ELF address " + std::to_string(address));
|
||||
return std::uint64_t{};
|
||||
};
|
||||
const auto get = [&](std::uint64_t standard, std::uint64_t sce) {
|
||||
if (tags.contains(standard) == tags.contains(sce)) fail("Missing or ambiguous dynamic tag " + std::to_string(standard));
|
||||
return tags.at(tags.contains(standard) ? standard : sce);
|
||||
};
|
||||
const auto table = [&](std::uint64_t standard, std::uint64_t sce, std::uint64_t size) {
|
||||
const auto value = get(standard, sce);
|
||||
if (tags.contains(standard)) return translate(value, size);
|
||||
if (dynlib == nullptr || value > dynlib->FileSize || size > dynlib->FileSize - value) fail("SCE table exceeds dynamic data segment");
|
||||
return dynlib->Offset + value;
|
||||
};
|
||||
const auto strSize = get(10, 0x61000037);
|
||||
const auto strOffset = table(5, 0x61000035, strSize);
|
||||
const auto string = [&](std::uint64_t offset) {
|
||||
if (offset >= strSize) fail("String offset out of bounds");
|
||||
const auto start = bytes.begin() + static_cast<std::ptrdiff_t>(strOffset + offset);
|
||||
const auto end = bytes.begin() + static_cast<std::ptrdiff_t>(strOffset + strSize);
|
||||
const auto zero = std::find(start, end, 0);
|
||||
if (zero == end) fail("Unterminated ELF string");
|
||||
return std::string(start, zero);
|
||||
};
|
||||
if (strSize == 0 || bytes[strOffset] != 0 || get(11, 0x6100003b) != 24) fail("Invalid string or symbol table");
|
||||
std::uint64_t symSize = 0;
|
||||
if (tags.contains(0x6100003f)) symSize = tags.at(0x6100003f);
|
||||
else if (tags.contains(4)) symSize = static_cast<std::uint64_t>(Io::ReadU32(bytes, translate(tags.at(4), 8) + 4)) * 24;
|
||||
else fail("Missing dynamic symbol count");
|
||||
if (symSize == 0 || symSize % 24 != 0 || symSize / 24 > std::numeric_limits<std::uint32_t>::max()) fail("Invalid dynamic symbol count");
|
||||
const auto symOffset = table(6, 0x61000039, symSize);
|
||||
image.Dynamic.DynSymData.assign(bytes.begin() + symOffset, bytes.begin() + symOffset + symSize);
|
||||
image.Dynamic.DynStrData.push_back(0);
|
||||
std::set<std::string> exports;
|
||||
for (std::uint64_t offset = 0; offset < symSize; offset += 24) {
|
||||
auto name = string(Io::ReadU32(bytes, symOffset + offset));
|
||||
name = name.substr(0, name.find('#'));
|
||||
const auto info = bytes[symOffset + offset + 4];
|
||||
const auto visibility = bytes[symOffset + offset + 5];
|
||||
const auto section = Io::ReadU16(bytes, symOffset + offset + 6);
|
||||
const auto value = Io::ReadU64(bytes, symOffset + offset + 8);
|
||||
const auto size = Io::ReadU64(bytes, symOffset + offset + 16);
|
||||
if (offset == 0 && (info != 0 || visibility != 0 || section != 0 || value != 0 || size != 0 || !name.empty())) fail("Invalid null symbol");
|
||||
if (offset == 0) {
|
||||
image.Symbols.push_back({{}, 0, 0, 0, 0, 0});
|
||||
continue;
|
||||
}
|
||||
if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name);
|
||||
if (section != 0 && (info >> 4) != 0 && visibility != 1 && visibility != 2) {
|
||||
if (name.empty() || !exports.insert(name).second) fail("Duplicate or empty export after stripping #: " + name);
|
||||
}
|
||||
if ((info & 15) == 6 && section != 0 && (tls == nullptr || value > tls->MemorySize || size > tls->MemorySize - value)) fail("TLS symbol exceeds TLS segment: " + name);
|
||||
if (section != 0 && (info & 15) != 6) {
|
||||
if (section >= 0xff00) fail("Unsupported special symbol section: " + name);
|
||||
mapped(value, std::max<std::uint64_t>(size, 1), (info & 15) == 2 ? 1 : 0);
|
||||
}
|
||||
if (image.Dynamic.DynStrData.size() > std::numeric_limits<std::uint32_t>::max()) fail("String table too large");
|
||||
Io::WriteU32(image.Dynamic.DynSymData, offset, static_cast<std::uint32_t>(image.Dynamic.DynStrData.size()));
|
||||
Io::AppendString(image.Dynamic.DynStrData, name);
|
||||
image.Symbols.push_back({name, info, visibility, section, value, size});
|
||||
}
|
||||
std::set<std::string> dependencies;
|
||||
for (const auto offset : needed) {
|
||||
const auto name = string(offset);
|
||||
if (name.empty() || name.find_first_of("/\\:$") != std::string::npos || !dependencies.insert(name).second) fail("Invalid or duplicate dependency: " + name);
|
||||
image.Dependencies.push_back(name);
|
||||
}
|
||||
std::map<std::uint64_t, std::uint64_t> relocationTargets;
|
||||
const auto copyRelocations = [&](std::uint64_t addressTag, std::uint64_t sceAddressTag, std::uint64_t sizeTag, std::uint64_t sceSizeTag, std::vector<std::uint8_t>& output, bool plt) {
|
||||
if (!tags.contains(addressTag) && !tags.contains(sceAddressTag) && !tags.contains(sizeTag) && !tags.contains(sceSizeTag)) return;
|
||||
const auto size = get(sizeTag, sceSizeTag);
|
||||
if (size % 24 != 0) fail("Invalid relocation table size");
|
||||
const auto offset = table(addressTag, sceAddressTag, size);
|
||||
output.assign(bytes.begin() + offset, bytes.begin() + offset + size);
|
||||
for (std::uint64_t index = 0; index < size; index += 24) {
|
||||
const auto info = Io::ReadU64(output, index + 8);
|
||||
const auto relocation = static_cast<std::uint32_t>(info);
|
||||
if ((info >> 32) >= image.Symbols.size() || (plt && relocation != 7) || (relocation != 1 && relocation != 6 && relocation != 7 && relocation != 8 && relocation != 16 && relocation != 17 && relocation != 18)) fail("Unsupported relocation or symbol index");
|
||||
const auto target = Io::ReadU64(output, index);
|
||||
const auto addend = Io::ReadU64(output, index + 16);
|
||||
mapped(target, 8, 2);
|
||||
const auto next = relocationTargets.lower_bound(target);
|
||||
if ((next != relocationTargets.end() && next->first < target + 8) || (next != relocationTargets.begin() && std::prev(next)->second > target)) fail("Overlapping guest relocations");
|
||||
relocationTargets.emplace(target, target + 8);
|
||||
if (relocation == 8) {
|
||||
if ((info >> 32) != 0) fail("RELATIVE relocation has a symbol");
|
||||
mapped(addend, 0, 0);
|
||||
} else if (relocation == 1 || relocation == 6 || relocation == 7) {
|
||||
const auto& symbol = image.Symbols[info >> 32];
|
||||
if ((info >> 32) == 0 || (symbol.Info & 15) == 6 || (relocation != 1 && addend != 0) || (symbol.Section == 0 && symbol.Name.empty())) fail("Invalid guest symbol relocation");
|
||||
} else {
|
||||
const auto& symbol = image.Symbols[info >> 32];
|
||||
if (((info >> 32) == 0 && tls == nullptr) || ((info >> 32) != 0 && (symbol.Info & 15) != 6) || (relocation == 16 && addend != 0)) fail("Invalid guest TLS relocation");
|
||||
}
|
||||
}
|
||||
};
|
||||
if (tags.contains(7) || tags.contains(0x6100002f)) {
|
||||
if (get(9, 0x61000033) != 24) fail("Invalid RELA entry size");
|
||||
}
|
||||
copyRelocations(7, 0x6100002f, 8, 0x61000031, image.Dynamic.RelaData, false);
|
||||
copyRelocations(23, 0x61000029, 2, 0x6100002d, image.Dynamic.RelaPltData, true);
|
||||
if (!image.Dynamic.RelaPltData.empty() && get(20, 0x6100002b) != 7) fail("Unsupported PLT relocation format");
|
||||
if (tags.contains(3) || tags.contains(0x61000027)) image.Got = get(3, 0x61000027);
|
||||
if (tags.contains(12)) image.Init = tags.at(12);
|
||||
if (tags.contains(13)) image.Fini = tags.at(13);
|
||||
if (image.Init != 0) mapped(image.Init, 1, 1);
|
||||
if (image.Fini != 0) mapped(image.Fini, 1, 1);
|
||||
if ((tags.contains(27) && tags.at(27) != 0) || (tags.contains(28) && tags.at(28) != 0)) fail("Separate guest INIT_ARRAY/FINI_ARRAY is not supported");
|
||||
if (tags.contains(33) && tags.at(33) != 0 && image.Init == 0) fail("Guest PREINIT_ARRAY has no module initializer");
|
||||
image.Bytes = std::move(bytes);
|
||||
return image;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
||||
#include <codegen/IAmd64OnlyConverter.hpp>
|
||||
#include <io/FileReader.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath) const {
|
||||
const auto root = std::filesystem::absolute(inputPath).parent_path();
|
||||
const auto singular = root / "sce_module";
|
||||
const auto plural = root / "sce_modules";
|
||||
const bool hasSingular = std::filesystem::exists(singular);
|
||||
const bool hasPlural = std::filesystem::exists(plural);
|
||||
if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist beside the input executable");
|
||||
if (!hasSingular && !hasPlural) {
|
||||
std::cout << "WARNING: sce_module not found beside the input executable; it may be unnecessary or missing.\n";
|
||||
return {};
|
||||
}
|
||||
const auto directory = hasSingular ? singular : plural;
|
||||
if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string());
|
||||
std::vector<std::filesystem::path> paths;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue;
|
||||
if (!entry.is_regular_file()) continue;
|
||||
std::ifstream stream(entry.path(), std::ios::binary);
|
||||
if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string());
|
||||
char magic[4]{};
|
||||
stream.read(magic, 4);
|
||||
if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string());
|
||||
if (stream.gcount() == 4 && static_cast<unsigned char>(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path());
|
||||
}
|
||||
std::sort(paths.begin(), paths.end());
|
||||
if (paths.empty()) return {};
|
||||
if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible");
|
||||
std::vector<GuestImage> images;
|
||||
std::map<std::string, std::size_t> exports;
|
||||
std::set<std::string> outputNames;
|
||||
Io::FileReader reader;
|
||||
for (const auto& path : paths) {
|
||||
auto image = GuestImageReader().Read(path, reader.Read(path.string()));
|
||||
if (image.OutputName.find_first_of("$\r\n") != std::string::npos) throw Domain::RelinkerException("Unsupported guest filename: " + image.OutputName);
|
||||
std::string folded = image.OutputName;
|
||||
if (windows) {
|
||||
for (auto& value : folded) {
|
||||
if (static_cast<unsigned char>(value) >= 128 || value == ':' || value == '$') throw Domain::RelinkerException("Unsupported Windows guest filename: " + image.OutputName);
|
||||
if (value >= 'A' && value <= 'Z') value = static_cast<char>(value + ('a' - 'A'));
|
||||
}
|
||||
}
|
||||
if (!outputNames.insert(folded).second) throw Domain::RelinkerException("Conflicting guest output filename: " + image.OutputName);
|
||||
for (const auto& symbol : image.Symbols) {
|
||||
if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
|
||||
const auto [existing, inserted] = exports.emplace(symbol.Name, images.size());
|
||||
if (!inserted) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + images.at(existing->second).SourcePath.string() + " and " + path.string());
|
||||
}
|
||||
std::vector<Domain::ProgramHeader> codeHeaders;
|
||||
for (const auto& header : image.Headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeHeaders.push_back(header);
|
||||
if (toIntel) image.Bytes = Codegen::MakeAmd64OnlyConverter()->Convert(std::move(image.Bytes), codeHeaders).Bytes;
|
||||
for (const auto& header : codeHeaders) {
|
||||
const std::vector<std::uint8_t> code(image.Bytes.begin() + header.Offset, image.Bytes.begin() + header.Offset + header.FileSize);
|
||||
syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, header.FileSize);
|
||||
}
|
||||
images.push_back(std::move(image));
|
||||
}
|
||||
std::vector<std::set<std::size_t>> dependencies(images.size());
|
||||
for (auto& image : images) image.UsePlatformTlsResolver = !exports.contains("vNe1w4diLCs");
|
||||
for (std::size_t index = 0; index < images.size(); ++index) {
|
||||
for (const auto& symbol : images[index].Symbols) {
|
||||
if (symbol.Section != 0 || symbol.Name.empty()) continue;
|
||||
const auto found = exports.find(symbol.Name);
|
||||
if (found != exports.end()) {
|
||||
const auto& provider = images[found->second];
|
||||
const auto exported = std::find_if(provider.Symbols.begin(), provider.Symbols.end(), [&](const auto& candidate) { return candidate.Section != 0 && candidate.Name == symbol.Name && (candidate.Info >> 4) != 0 && candidate.Visibility != 1 && candidate.Visibility != 2; });
|
||||
if (exported == provider.Symbols.end() || ((symbol.Info & 15) != 0 && (symbol.Info & 15) != (exported->Info & 15))) throw Domain::RelinkerException("Guest import/export type mismatch: " + symbol.Name);
|
||||
if (found->second != index) dependencies[index].insert(found->second);
|
||||
} else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name);
|
||||
}
|
||||
}
|
||||
std::vector<std::size_t> order;
|
||||
std::vector<unsigned char> states(images.size());
|
||||
const std::function<void(std::size_t)> visit = [&](std::size_t index) {
|
||||
if (states[index] == 1) throw Domain::RelinkerException("Cyclic guest initialization dependency: " + images[index].SourcePath.string());
|
||||
if (states[index] == 2) return;
|
||||
states[index] = 1;
|
||||
for (const auto dependency : dependencies[index]) visit(dependency);
|
||||
states[index] = 2;
|
||||
order.push_back(index);
|
||||
};
|
||||
for (std::size_t index = 0; index < images.size(); ++index) visit(index);
|
||||
std::vector<std::string> hostLibraries;
|
||||
std::set<std::string> uniqueHosts;
|
||||
const auto addHost = [&](const std::string& name) {
|
||||
if (name.empty() || name.find_first_of("/\\:$") != std::string::npos) throw Domain::RelinkerException("Invalid host dependency: " + name);
|
||||
if (uniqueHosts.insert(name).second) hostLibraries.push_back(name);
|
||||
};
|
||||
if (dynamic.DynamicSegmentData.size() % 16 != 0) throw Domain::RelinkerException("Invalid executable dependency table");
|
||||
for (std::size_t offset = 0; offset < dynamic.DynamicSegmentData.size(); offset += 16) {
|
||||
if (Io::ReadU64(dynamic.DynamicSegmentData, offset) != 1) throw Domain::RelinkerException("Unexpected executable dependency tag");
|
||||
const auto nameOffset = Io::ReadU64(dynamic.DynamicSegmentData, offset + 8);
|
||||
if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid dependency string offset");
|
||||
const auto start = dynamic.DynStrData.begin() + nameOffset;
|
||||
const auto end = std::find(start, dynamic.DynStrData.end(), 0);
|
||||
if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated dependency string");
|
||||
addHost(std::string(start, end));
|
||||
}
|
||||
for (const auto& image : images) for (const auto& dependency : image.Dependencies) addHost(dependency);
|
||||
dynamic.DynamicSegmentData.clear();
|
||||
const auto addNeeded = [&](const std::string& name) {
|
||||
Io::AppendU64(dynamic.DynamicSegmentData, 1);
|
||||
Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size());
|
||||
Io::AppendString(dynamic.DynStrData, name);
|
||||
};
|
||||
const auto relativeDirectory = directory.filename().generic_string();
|
||||
for (const auto index : order) if (!windows) addNeeded("$ORIGIN/" + relativeDirectory + "/" + images[index].OutputName);
|
||||
for (const auto& name : hostLibraries) addNeeded(name);
|
||||
std::string guestRunPath = runPath;
|
||||
if (!windows) {
|
||||
if (guestRunPath == "$ORIGIN") guestRunPath = "$ORIGIN/..";
|
||||
else if (guestRunPath.starts_with("$ORIGIN/")) guestRunPath.insert(8, "../");
|
||||
else if (!std::filesystem::path(guestRunPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN");
|
||||
}
|
||||
std::vector<GuestArtifact> artifacts;
|
||||
const auto destination = std::filesystem::absolute(outputPath).parent_path() / directory.filename();
|
||||
if (std::filesystem::exists(destination) && std::filesystem::equivalent(destination, directory)) throw Domain::RelinkerException("Guest output directory must differ from the source module directory");
|
||||
for (const auto index : order) {
|
||||
const auto& image = images[index];
|
||||
const auto target = destination / image.OutputName;
|
||||
if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output");
|
||||
for (const auto& source : paths) if (std::filesystem::exists(target) && std::filesystem::equivalent(source, target)) throw Domain::RelinkerException("Guest output would overwrite an input module: " + target.string());
|
||||
if (std::filesystem::exists(target) && std::filesystem::equivalent(inputPath, target)) throw Domain::RelinkerException("Guest output would overwrite the input executable");
|
||||
Domain::GuestRuntime runtime;
|
||||
runtime.UsePlatformTlsResolver = image.UsePlatformTlsResolver;
|
||||
runtime.Path = relativeDirectory + "/" + image.OutputName;
|
||||
std::vector<std::uint8_t> output;
|
||||
if (windows) output = Elfpatcher::GuestModuleWriter().WriteWindows(image, runtime);
|
||||
else {
|
||||
std::vector<std::string> needed;
|
||||
for (const auto dependency : dependencies[index]) needed.push_back("$ORIGIN/" + images[dependency].OutputName);
|
||||
needed.insert(needed.end(), hostLibraries.begin(), hostLibraries.end());
|
||||
output = Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath);
|
||||
}
|
||||
dynamic.GuestModules.push_back(std::move(runtime));
|
||||
artifacts.push_back({target, std::move(output)});
|
||||
}
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_optional_plt import fixture
|
||||
|
||||
|
||||
TLS_LOAD = bytes.fromhex("66 66 66 64 48 8b 04 25 00 00 00 00")
|
||||
|
||||
|
||||
def make_image(transfer, metadata, extent=None):
|
||||
image = fixture()
|
||||
image.extend(b"\x90" * 0x1000)
|
||||
struct.pack_into("<Q", image, 24, 0x1200)
|
||||
struct.pack_into("<I", image, 68, 6)
|
||||
image[0x1200:0x1300] = b"\x90" * 0x100
|
||||
target = 0x1240
|
||||
if transfer == "table":
|
||||
code = bytes.fromhex("31 c0 48 8d 0d") + struct.pack("<i", 0x780 - 0x1209)
|
||||
code += bytes.fromhex("48 63 04 81 48 01 c8 ff e0")
|
||||
struct.pack_into("<i", image, 0x780, target - 0x780)
|
||||
elif transfer == "register":
|
||||
code = bytes.fromhex("48 8d 05") + struct.pack("<i", target - 0x1207)
|
||||
code += bytes.fromhex("ff e0")
|
||||
elif transfer == "memory":
|
||||
code = bytes.fromhex("48 8d 0d") + struct.pack("<i", target - 0x1207)
|
||||
code += bytes.fromhex("48 89 4c 24 f8 ff 64 24 f8")
|
||||
else:
|
||||
raise ValueError(transfer)
|
||||
image[0x1200:0x1200 + len(code)] = code
|
||||
body = TLS_LOAD + bytes.fromhex("8b 40 f0 c3")
|
||||
image[target:target + len(body)] = body
|
||||
image[0x1850:0x1850 + len(TLS_LOAD)] = TLS_LOAD
|
||||
struct.pack_into("<QQq", image, 0x700, 0x300, 8, 0x1200)
|
||||
struct.pack_into("<Q", image, 0x800, 42)
|
||||
struct.pack_into("<H", image, 56, 4 if metadata == "symbol" else 5)
|
||||
struct.pack_into("<IIQQQQQQ", image, 176, 7, 4, 0x800, 0x800, 0x800, 8, 16, 16)
|
||||
struct.pack_into("<IIQQQQQQ", image, 232 if metadata == "symbol" else 288, 1, 5, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000)
|
||||
function_size = target + len(body) - 0x1200 if extent is None else extent
|
||||
if metadata == "unwind":
|
||||
struct.pack_into("<IIQQQQQQ", image, 232, 0x6474e550, 4, 0x980, 0x980, 0x980, 32, 32, 8)
|
||||
struct.pack_into("<II", image, 0x900, 12, 0)
|
||||
image[0x908:0x910] = bytes.fromhex("01 00 01 78 10 00 00 00")
|
||||
struct.pack_into("<IIQQ", image, 0x910, 20, 0x14, 0x1200, function_size)
|
||||
struct.pack_into("<BBBBQIQQ", image, 0x980, 1, 0, 3, 0, 0x900, 1, 0x1200, 0x910)
|
||||
elif metadata == "symbol":
|
||||
struct.pack_into("<IBBHQQ", image, 0x638, 0, 0x12, 0, 1, 0x1200, function_size)
|
||||
struct.pack_into("<IIII", image, 0x680, 1, 2, 1, 0)
|
||||
struct.pack_into("<qQqQ", image, 0x470, 4, 0x680, 0, 0)
|
||||
struct.pack_into("<QQ", image, 160, 0x90, 8)
|
||||
struct.pack_into("<Q", image, 152, 0x90)
|
||||
else:
|
||||
raise ValueError(metadata)
|
||||
return image
|
||||
|
||||
|
||||
def pe_bytes_at(pe, rva, size):
|
||||
header = struct.unpack_from("<I", pe, 0x3c)[0]
|
||||
count = struct.unpack_from("<H", pe, header + 6)[0]
|
||||
sections = header + 24 + struct.unpack_from("<H", pe, header + 20)[0]
|
||||
for index in range(count):
|
||||
offset = sections + index * 40
|
||||
address, length, position = struct.unpack_from("<III", pe, offset + 12)
|
||||
if address <= rva and rva + size <= address + length:
|
||||
return pe[position + rva - address:position + rva - address + size]
|
||||
raise AssertionError(f"Unmapped PE RVA {rva:#x}")
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-tls-coverage-") as directory:
|
||||
work = Path(directory)
|
||||
|
||||
def convert(name, image, error=None, tls_address=0x1240):
|
||||
source = work / (name + ".elf")
|
||||
output = source.with_suffix(".exe")
|
||||
source.write_bytes(image)
|
||||
result = subprocess.run([str(relinker), "--windows", str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
if error is not None:
|
||||
assert result.returncode == 2 and error in result.stderr and not output.exists(), result
|
||||
return
|
||||
assert result.returncode == 0, (name, result.stdout, result.stderr)
|
||||
pe = output.read_bytes()
|
||||
assert pe_bytes_at(pe, 0x10000 + tls_address, 1) == b"\xe9", name
|
||||
assert pe_bytes_at(pe, 0x11850, len(TLS_LOAD)) == TLS_LOAD, name
|
||||
if os.name == "nt":
|
||||
executed = subprocess.run([str(output)], capture_output=True, timeout=30)
|
||||
assert executed.returncode == 42, (name, executed.returncode, executed.stderr)
|
||||
|
||||
for metadata in ("unwind", "symbol"):
|
||||
for transfer in ("table", "register", "memory"):
|
||||
convert(metadata + "-" + transfer, make_image(transfer, metadata))
|
||||
convert(metadata + "-truncated", make_image("register", metadata, 0x46), "Code analysis:")
|
||||
convert(metadata + "-outside", make_image("register", metadata, 0x1000), "Code analysis: function exceeds executable segment")
|
||||
overlapping = make_image("register", "unwind")
|
||||
overlapping[0x1200:0x1205] = b"\xe9" + struct.pack("<i", 0x1245 - 0x1205)
|
||||
convert("overlapping-entry", overlapping, "Code analysis: overlapping instruction boundaries")
|
||||
external = make_image("register", "unwind")
|
||||
external[0x1300:0x1310] = external[0x1240:0x1250]
|
||||
external[0x1240:0x1250] = b"\xe8" + struct.pack("<i", 0x1300 - 0x1245) + b"\xc3" + b"\x90" * 10
|
||||
convert("direct-call-from-indirect-block", external, tls_address=0x1300)
|
||||
conflicting = make_image("register", "symbol")
|
||||
unwind = make_image("register", "unwind", 0x51)
|
||||
conflicting[0x900:0x9a0] = unwind[0x900:0x9a0]
|
||||
conflicting[232:344] = unwind[232:344]
|
||||
struct.pack_into("<H", conflicting, 56, 5)
|
||||
convert("conflicting-function-extents", conflicting, "Code analysis: conflicting function ranges")
|
||||
print("TLS function coverage integration tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -15,14 +15,18 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [libSceCommonDialog](../core/libs/prx/libSceCommonDialog/Export.cpp)
|
||||
- The shader recompiler [skips baryctric coordinates](../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212).
|
||||
|
||||
### Unknown function names
|
||||
### Unknown function info
|
||||
|
||||
- [zARR5aCmkoY](../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown signature
|
||||
- [qj7QZpgr9Uw](../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc)
|
||||
- [fd5Bp5tGTgo](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc)
|
||||
- [dolOmWH+huQ](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc)
|
||||
- [V++UgBtQhn0](../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc)
|
||||
- [gQkqkLttcpw](../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown signature
|
||||
- [zARR5aCmkoY](../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature
|
||||
- [qj7QZpgr9Uw](../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name
|
||||
- [fd5Bp5tGTgo](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
|
||||
- [dolOmWH+huQ](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name
|
||||
- [V++UgBtQhn0](../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name
|
||||
- [gQkqkLttcpw](../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature
|
||||
- [sceKernelInternalMemoryGetModuleSegmentInfo](../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature
|
||||
- [sceLibcInternalBacktraceForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
|
||||
- [sceLibcInternalHeapErrorReportForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
|
||||
- [__progname](../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export
|
||||
|
||||
### Functional
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue