From 728249f58d05f94739fbb559a3d9049cbb6f3545 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:08:18 +0300 Subject: [PATCH] core: Properly implement TLS (#164) * core: Split module code from linker * linker: Properly implement thread local storage * kernel: Fix a few memory functions * kernel: Implement module loading * Now it's easy to do anyway with new module rework --- CMakeLists.txt | 3 +- src/common/alignment.h | 6 +- src/core/address_space.cpp | 6 +- src/core/address_space.h | 2 +- src/core/libraries/kernel/libkernel.cpp | 43 + .../libraries/kernel/memory_management.cpp | 26 +- src/core/libraries/kernel/memory_management.h | 3 + src/core/libraries/kernel/physical_memory.cpp | 2 +- .../libraries/kernel/thread_management.cpp | 15 + src/core/linker.cpp | 901 +++++------------- src/core/linker.h | 151 +-- src/core/loader/elf.cpp | 2 +- src/core/loader/elf.h | 7 +- src/core/loader/symbols_resolver.cpp | 23 +- src/core/loader/symbols_resolver.h | 17 +- src/core/memory.cpp | 8 +- src/core/memory.h | 4 +- src/core/module.cpp | 419 ++++++++ src/core/module.h | 183 ++++ src/core/tls.cpp | 17 +- src/core/tls.h | 18 +- src/main.cpp | 4 +- .../renderer_vulkan/vk_rasterizer.cpp | 1 - .../renderer_vulkan/vk_rasterizer.h | 1 - .../renderer_vulkan/vk_stream_buffer.cpp | 6 +- .../texture_cache/texture_cache.cpp | 2 +- 26 files changed, 1047 insertions(+), 823 deletions(-) create mode 100644 src/core/module.cpp create mode 100644 src/core/module.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 181b39db..ba180f89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,8 +287,9 @@ set(CORE src/core/aerolib/stubs.cpp src/core/linker.h src/core/memory.cpp src/core/memory.h + src/core/module.cpp + src/core/module.h src/core/platform.h - src/core/memory.h src/core/tls.cpp src/core/tls.h src/core/virtual_memory.cpp diff --git a/src/common/alignment.h b/src/common/alignment.h index b55a54cf..367efb6b 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -9,7 +9,7 @@ namespace Common { template -[[nodiscard]] constexpr T alignUp(T value, std::size_t size) { +[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) { static_assert(std::is_unsigned_v, "T must be an unsigned value."); auto mod{static_cast(value % size)}; value -= mod; @@ -17,14 +17,14 @@ template } template -[[nodiscard]] constexpr T alignDown(T value, std::size_t size) { +[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) { static_assert(std::is_unsigned_v, "T must be an unsigned value."); return static_cast(value - value % size); } template requires std::is_integral_v -[[nodiscard]] constexpr bool is16KBAligned(T value) { +[[nodiscard]] constexpr bool Is16KBAligned(T value) { return (value & 0x3FFF) == 0; } diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index f10527b9..ceafa7ba 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -102,14 +102,14 @@ struct AddressSpace::Impl { // Perform the map. void* ptr = nullptr; - if (phys_addr) { + if (phys_addr != -1) { ptr = MapViewOfFile3(backing_handle, process, reinterpret_cast(virtual_addr), phys_addr, size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0); } else { ptr = VirtualAlloc2(process, reinterpret_cast(virtual_addr), size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0); } - ASSERT(ptr); + ASSERT_MSG(ptr, "{}", Common::GetLastErrorMsg()); return ptr; } @@ -121,7 +121,7 @@ struct AddressSpace::Impl { // (virtual_addr == 0 ? reinterpret_cast(SYSTEM_MANAGED_MIN) // : reinterpret_cast(virtual_addr)); req.HighestEndingAddress = reinterpret_cast(SYSTEM_MANAGED_MAX); - req.Alignment = alignment; + req.Alignment = alignment < 64_KB ? 0 : alignment; param.Type = MemExtendedParameterAddressRequirements; param.Pointer = &req; ULONG alloc_type = MEM_COMMIT | MEM_RESERVE | (alignment > 2_MB ? MEM_LARGE_PAGES : 0); diff --git a/src/core/address_space.h b/src/core/address_space.h index 963e9afa..322ab9c7 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -42,7 +42,7 @@ public: * If zero is provided the mapping is considered as private. * @return A pointer to the mapped memory. */ - void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = 0); + void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = -1); /// Unmaps specified virtual memory area. void Unmap(VAddr virtual_addr, size_t size); diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index a8c3975e..a215fcb4 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -6,6 +6,7 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/singleton.h" +#include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/cpu_management.h" #include "core/libraries/kernel/event_flag/event_flag.h" @@ -199,11 +200,48 @@ s64 PS4_SYSV_ABI ps4__read(int d, void* buf, u64 nbytes) { strlen(std::fgets(static_cast(buf), static_cast(nbytes), stdin))); } +s32 PS4_SYSV_ABI sceKernelLoadStartModule(const char* moduleFileName, size_t args, const void* argp, + u32 flags, const void* pOpt, int* pRes) { + LOG_INFO(Lib_Kernel, "called filename = {}, args = {}", moduleFileName, args); + + if (flags != 0) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + auto* mnt = Common::Singleton::Instance(); + const auto path = mnt->GetHostFile(moduleFileName); + + // Load PRX module. + auto* linker = Common::Singleton::Instance(); + u32 handle = linker->LoadModule(path); + auto* module = linker->GetModule(handle); + linker->Relocate(module); + + // Retrieve and verify proc param according to libkernel. + u64* param = module->GetProcParam(); + ASSERT_MSG(!param || param[0] >= 0x18, "Invalid module param size: {}", param[0]); + module->Start(args, argp, param); + + return handle; +} + +s32 PS4_SYSV_ABI sceKernelDlsym(s32 handle, const char* symbol, void** addrp) { + auto* linker = Common::Singleton::Instance(); + auto* module = linker->GetModule(handle); + *addrp = module->FindByName(symbol); + if (*addrp == nullptr) { + return ORBIS_KERNEL_ERROR_ESRCH; + } + return ORBIS_OK; +} + void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { // obj LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard); // memory LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, sceKernelAllocateDirectMemory); + LIB_FUNCTION("B+vc2AO2Zrc", "libkernel", 1, "libkernel", 1, 1, + sceKernelAllocateMainDirectMemory); LIB_FUNCTION("pO96TwzOm5E", "libkernel", 1, "libkernel", 1, 1, sceKernelGetDirectMemorySize); LIB_FUNCTION("L-Q3LEjIbgA", "libkernel", 1, "libkernel", 1, 1, sceKernelMapDirectMemory); LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection); @@ -212,6 +250,11 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap); LIB_FUNCTION("mL8NDH86iQI", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedFlexibleMemory); LIB_FUNCTION("IWIBBdTHit4", "libkernel", 1, "libkernel", 1, 1, sceKernelMapFlexibleMemory); + LIB_FUNCTION("p5EcQeEeJAE", "libkernel", 1, "libkernel", 1, 1, + _sceKernelRtldSetApplicationHeapAPI); + LIB_FUNCTION("wzvqT4UqKX8", "libkernel", 1, "libkernel", 1, 1, sceKernelLoadStartModule); + LIB_FUNCTION("LwG8g3niqwA", "libkernel", 1, "libkernel", 1, 1, sceKernelDlsym); + // equeue LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue); LIB_FUNCTION("jpFjmgAC5AE", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteEqueue); diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index 9e540107..6b7780ee 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -7,6 +7,7 @@ #include "common/singleton.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" +#include "core/linker.h" #include "core/memory.h" namespace Libraries::Kernel { @@ -22,12 +23,12 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!"); return SCE_KERNEL_ERROR_EINVAL; } - const bool is_in_range = (searchStart < len && searchEnd > len); - if (len <= 0 || !Common::is16KBAligned(len) || !is_in_range) { + const bool is_in_range = searchEnd - searchStart >= len; + if (len <= 0 || !Common::Is16KBAligned(len) || !is_in_range) { LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!"); return SCE_KERNEL_ERROR_EINVAL; } - if ((alignment != 0 || Common::is16KBAligned(alignment)) && !std::has_single_bit(alignment)) { + if (alignment != 0 && !Common::Is16KBAligned(alignment)) { LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!"); return SCE_KERNEL_ERROR_EINVAL; } @@ -48,6 +49,12 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u return SCE_OK; } +s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, + s64* physAddrOut) { + return sceKernelAllocateDirectMemory(0, SCE_KERNEL_MAIN_DMEM_SIZE, len, alignment, memoryType, + physAddrOut); +} + int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, s64 directMemoryStart, u64 alignment) { LOG_INFO( @@ -55,16 +62,16 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl "len = {:#x}, prot = {:#x}, flags = {:#x}, directMemoryStart = {:#x}, alignment = {:#x}", len, prot, flags, directMemoryStart, alignment); - if (len == 0 || !Common::is16KBAligned(len)) { + if (len == 0 || !Common::Is16KBAligned(len)) { LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!"); return SCE_KERNEL_ERROR_EINVAL; } - if (!Common::is16KBAligned(directMemoryStart)) { + if (!Common::Is16KBAligned(directMemoryStart)) { LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!"); return SCE_KERNEL_ERROR_EINVAL; } if (alignment != 0) { - if ((!std::has_single_bit(alignment) && !Common::is16KBAligned(alignment))) { + if ((!std::has_single_bit(alignment) && !Common::Is16KBAligned(alignment))) { LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!"); return SCE_KERNEL_ERROR_EINVAL; } @@ -81,7 +88,7 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t len, int prot, int flags, const char* name) { - if (len == 0 || !Common::is16KBAligned(len)) { + if (len == 0 || !Common::Is16KBAligned(len)) { LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple"); return ORBIS_KERNEL_ERROR_EINVAL; } @@ -127,4 +134,9 @@ int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInf return memory->DirectMemoryQuery(offset, flags == 1, query_info); } +void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func) { + auto* linker = Common::Singleton::Instance(); + linker->SetHeapApiFunc(func); +} + } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index be0d8514..23d25a22 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -41,6 +41,8 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u u64 alignment, int memoryType, s64* physAddrOut); int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, s64 directMemoryStart, u64 alignment); +s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, + s64* physAddrOut); s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot, int flags, const char* name); s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, @@ -49,5 +51,6 @@ int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, size_t infoSize); +void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func); } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/physical_memory.cpp b/src/core/libraries/kernel/physical_memory.cpp index 82884a63..d81bbe5f 100644 --- a/src/core/libraries/kernel/physical_memory.cpp +++ b/src/core/libraries/kernel/physical_memory.cpp @@ -20,7 +20,7 @@ bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignmen } // Align free position - find_free_pos = Common::alignUp(find_free_pos, alignment); + find_free_pos = Common::AlignUp(find_free_pos, alignment); // If the new position is between searchStart - searchEnd , allocate a new block if (find_free_pos >= searchStart && find_free_pos + len <= searchEnd) { diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 32f179a5..aa51c635 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -5,10 +5,12 @@ #include #include "common/assert.h" #include "common/logging/log.h" +#include "common/singleton.h" #include "common/thread.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libs.h" +#include "core/linker.h" #ifdef _WIN64 #include #endif @@ -829,6 +831,8 @@ static void cleanup_thread(void* arg) { static void* run_thread(void* arg) { auto* thread = static_cast(arg); Common::SetCurrentThreadName(thread->name.c_str()); + auto* linker = Common::Singleton::Instance(); + linker->InitTlsForThread(false); void* ret = nullptr; g_pthread_self = thread; pthread_cleanup_push(cleanup_thread, thread); @@ -1022,6 +1026,16 @@ int PS4_SYSV_ABI scePthreadEqual(ScePthread thread1, ScePthread thread2) { return (thread1 == thread2 ? 1 : 0); } +struct TlsIndex { + u64 ti_module; + u64 ti_offset; +}; + +void* PS4_SYSV_ABI __tls_get_addr(TlsIndex* index) { + auto* linker = Common::Singleton::Instance(); + return linker->TlsGetAddr(index->ti_module, index->ti_offset); +} + void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("4+h9EzwKF4I", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetschedpolicy); LIB_FUNCTION("-Wreprtu0Qs", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetdetachstate); @@ -1038,6 +1052,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("8+s5BzZjxSg", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetaffinity); LIB_FUNCTION("x1X76arYMxU", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGet); LIB_FUNCTION("UTXzJbWhhTE", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetstacksize); + LIB_FUNCTION("vNe1w4diLCs", "libkernel", 1, "libkernel", 1, 1, __tls_get_addr); LIB_FUNCTION("bt3CTBKmGyI", "libkernel", 1, "libkernel", 1, 1, scePthreadSetaffinity); LIB_FUNCTION("6UgtwV+0zb4", "libkernel", 1, "libkernel", 1, 1, scePthreadCreate); diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 81248072..7867b180 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -1,9 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include +#include "common/alignment.h" +#include "common/assert.h" #include "common/config.h" #include "common/logging/log.h" #include "common/path_util.h" @@ -11,6 +10,7 @@ #include "common/thread.h" #include "core/aerolib/aerolib.h" #include "core/aerolib/stubs.h" +#include "core/libraries/kernel/memory_management.h" #include "core/libraries/kernel/thread_management.h" #include "core/linker.h" #include "core/tls.h" @@ -18,641 +18,15 @@ namespace Core { -static u64 LoadAddress = SYSTEM_RESERVED + CODE_BASE_OFFSET; -static constexpr u64 CODE_BASE_INCR = 0x010000000u; - -static u64 GetAlignedSize(const elf_program_header& phdr) { - return (phdr.p_align != 0 ? (phdr.p_memsz + (phdr.p_align - 1)) & ~(phdr.p_align - 1) - : phdr.p_memsz); -} - -static u64 CalculateBaseSize(const elf_header& ehdr, std::span phdr) { - u64 base_size = 0; - for (u16 i = 0; i < ehdr.e_phnum; i++) { - if (phdr[i].p_memsz != 0 && (phdr[i].p_type == PT_LOAD || phdr[i].p_type == PT_SCE_RELRO)) { - u64 last_addr = phdr[i].p_vaddr + GetAlignedSize(phdr[i]); - if (last_addr > base_size) { - base_size = last_addr; - } - } - } - return base_size; -} - -static std::string EncodeId(u64 nVal) { - std::string enc; - const char pCodes[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; - if (nVal < 0x40u) { - enc += pCodes[nVal]; - } else { - if (nVal < 0x1000u) { - enc += pCodes[static_cast(nVal >> 6u) & 0x3fu]; - enc += pCodes[nVal & 0x3fu]; - } else { - enc += pCodes[static_cast(nVal >> 12u) & 0x3fu]; - enc += pCodes[static_cast(nVal >> 6u) & 0x3fu]; - enc += pCodes[nVal & 0x3fu]; - } - } - return enc; -} - -Linker::Linker() = default; - -Linker::~Linker() = default; - -Module* Linker::LoadModule(const std::filesystem::path& elf_name) { - std::scoped_lock lock{m_mutex}; - - if (!std::filesystem::exists(elf_name)) { - LOG_ERROR(Core_Linker, "Provided module {} does not exist", elf_name.string()); - return nullptr; - } - - auto& m = m_modules.emplace_back(); - m = std::make_unique(); - m->elf.Open(elf_name); - m->file_name = std::filesystem::path(elf_name).filename().string(); - - if (m->elf.IsElfFile()) { - LoadModuleToMemory(m.get()); - LoadDynamicInfo(m.get()); - LoadSymbols(m.get()); - } else { - m_modules.pop_back(); - return nullptr; // It is not a valid elf file //TODO check it why! - } - - return m.get(); -} - -void Linker::LoadModuleToMemory(Module* m) { - // get elf header, program header - const auto elf_header = m->elf.GetElfHeader(); - const auto elf_pheader = m->elf.GetProgramHeader(); - - u64 base_size = CalculateBaseSize(elf_header, elf_pheader); - m->aligned_base_size = (base_size & ~(static_cast(0x1000) - 1)) + - 0x1000; // align base size to 0x1000 block size (TODO is that the default - // block size or it can be changed? - - static constexpr u64 TrampolineSize = 8_MB; - m->base_virtual_addr = - VirtualMemory::memory_alloc(LoadAddress, m->aligned_base_size + TrampolineSize, - VirtualMemory::MemoryMode::ExecuteReadWrite); - - LoadAddress += CODE_BASE_INCR * (1 + m->aligned_base_size / CODE_BASE_INCR); - - void* trampoline_addr = reinterpret_cast(m->base_virtual_addr + m->aligned_base_size); - Xbyak::CodeGenerator c(TrampolineSize, trampoline_addr); - - LOG_INFO(Core_Linker, "======== Load Module to Memory ========"); - LOG_INFO(Core_Linker, "base_virtual_addr ......: {:#018x}", m->base_virtual_addr); - LOG_INFO(Core_Linker, "base_size ..............: {:#018x}", base_size); - LOG_INFO(Core_Linker, "aligned_base_size ......: {:#018x}", m->aligned_base_size); - - for (u16 i = 0; i < elf_header.e_phnum; i++) { - switch (elf_pheader[i].p_type) { - case PT_LOAD: - case PT_SCE_RELRO: - if (elf_pheader[i].p_memsz != 0) { - u64 segment_addr = elf_pheader[i].p_vaddr + m->base_virtual_addr; - u64 segment_file_size = elf_pheader[i].p_filesz; - u64 segment_memory_size = GetAlignedSize(elf_pheader[i]); - auto segment_mode = m->elf.ElfPheaderFlagsStr(elf_pheader[i].p_flags); - LOG_INFO(Core_Linker, "program header = [{}] type = {}", i, - m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type)); - LOG_INFO(Core_Linker, "segment_addr ..........: {:#018x}", segment_addr); - LOG_INFO(Core_Linker, "segment_file_size .....: {}", segment_file_size); - LOG_INFO(Core_Linker, "segment_memory_size ...: {}", segment_memory_size); - LOG_INFO(Core_Linker, "segment_mode ..........: {}", segment_mode); - - m->elf.LoadSegment(segment_addr, elf_pheader[i].p_offset, segment_file_size); - - if (elf_pheader[i].p_flags & PF_EXEC) { - PatchTLS(segment_addr, segment_file_size, c); - } - } else { - LOG_ERROR(Core_Linker, "p_memsz==0 in type {}", - m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type)); - } - break; - case PT_DYNAMIC: - if (elf_pheader[i].p_filesz != 0) { - m->m_dynamic.resize(elf_pheader[i].p_filesz); - m->elf.LoadSegment(reinterpret_cast(m->m_dynamic.data()), - elf_pheader[i].p_offset, elf_pheader[i].p_filesz); - } else { - LOG_ERROR(Core_Linker, "p_filesz==0 in type {}", - m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type)); - } - break; - case PT_SCE_DYNLIBDATA: - if (elf_pheader[i].p_filesz != 0) { - m->m_dynamic_data.resize(elf_pheader[i].p_filesz); - m->elf.LoadSegment(reinterpret_cast(m->m_dynamic_data.data()), - elf_pheader[i].p_offset, elf_pheader[i].p_filesz); - } else { - LOG_ERROR(Core_Linker, "p_filesz==0 in type {}", - m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type)); - } - break; - case PT_TLS: - m->tls.image_virtual_addr = elf_pheader[i].p_vaddr + m->base_virtual_addr; - m->tls.image_size = GetAlignedSize(elf_pheader[i]); - LOG_INFO(Core_Linker, "TLS virtual address = {:#x}", m->tls.image_virtual_addr); - LOG_INFO(Core_Linker, "TLS image size = {}", m->tls.image_size); - break; - case PT_SCE_PROCPARAM: - m->proc_param_virtual_addr = elf_pheader[i].p_vaddr + m->base_virtual_addr; - break; - default: - LOG_ERROR(Core_Linker, "Unimplemented type {}", - m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type)); - } - } - LOG_INFO(Core_Linker, "program entry addr ..........: {:#018x}", - m->elf.GetElfEntry() + m->base_virtual_addr); -} - -void Linker::LoadDynamicInfo(Module* m) { - for (const auto* dyn = reinterpret_cast(m->m_dynamic.data()); - dyn->d_tag != DT_NULL; dyn++) { - switch (dyn->d_tag) { - case DT_SCE_HASH: // Offset of the hash table. - m->dynamic_info.hash_table = - reinterpret_cast(m->m_dynamic_data.data() + dyn->d_un.d_ptr); - break; - case DT_SCE_HASHSZ: // Size of the hash table - m->dynamic_info.hash_table_size = dyn->d_un.d_val; - break; - case DT_SCE_STRTAB: // Offset of the string table. - m->dynamic_info.str_table = - reinterpret_cast(m->m_dynamic_data.data() + dyn->d_un.d_ptr); - break; - case DT_SCE_STRSZ: // Size of the string table. - m->dynamic_info.str_table_size = dyn->d_un.d_val; - break; - case DT_SCE_SYMTAB: // Offset of the symbol table. - m->dynamic_info.symbol_table = - reinterpret_cast(m->m_dynamic_data.data() + dyn->d_un.d_ptr); - break; - case DT_SCE_SYMTABSZ: // Size of the symbol table. - m->dynamic_info.symbol_table_total_size = dyn->d_un.d_val; - break; - case DT_INIT: - m->dynamic_info.init_virtual_addr = dyn->d_un.d_ptr; - break; - case DT_FINI: - m->dynamic_info.fini_virtual_addr = dyn->d_un.d_ptr; - break; - case DT_SCE_PLTGOT: // Offset of the global offset table. - m->dynamic_info.pltgot_virtual_addr = dyn->d_un.d_ptr; - break; - case DT_SCE_JMPREL: // Offset of the table containing jump slots. - m->dynamic_info.jmp_relocation_table = - reinterpret_cast(m->m_dynamic_data.data() + dyn->d_un.d_ptr); - break; - case DT_SCE_PLTRELSZ: // Size of the global offset table. - m->dynamic_info.jmp_relocation_table_size = dyn->d_un.d_val; - break; - case DT_SCE_PLTREL: // The type of relocations in the relocation table. Should be DT_RELA - m->dynamic_info.jmp_relocation_type = dyn->d_un.d_val; - if (m->dynamic_info.jmp_relocation_type != DT_RELA) { - LOG_WARNING(Core_Linker, "DT_SCE_PLTREL is NOT DT_RELA should check!"); - } - break; - case DT_SCE_RELA: // Offset of the relocation table. - m->dynamic_info.relocation_table = - reinterpret_cast(m->m_dynamic_data.data() + dyn->d_un.d_ptr); - break; - case DT_SCE_RELASZ: // Size of the relocation table. - m->dynamic_info.relocation_table_size = dyn->d_un.d_val; - break; - case DT_SCE_RELAENT: // The size of relocation table entries. - m->dynamic_info.relocation_table_entries_size = dyn->d_un.d_val; - if (m->dynamic_info.relocation_table_entries_size != - 0x18) // this value should always be 0x18 - { - LOG_WARNING(Core_Linker, "DT_SCE_RELAENT is NOT 0x18 should check!"); - } - break; - case DT_INIT_ARRAY: // Address of the array of pointers to initialization functions - m->dynamic_info.init_array_virtual_addr = dyn->d_un.d_ptr; - break; - case DT_FINI_ARRAY: // Address of the array of pointers to termination functions - m->dynamic_info.fini_array_virtual_addr = dyn->d_un.d_ptr; - break; - case DT_INIT_ARRAYSZ: // Size in bytes of the array of initialization functions - m->dynamic_info.init_array_size = dyn->d_un.d_val; - break; - case DT_FINI_ARRAYSZ: // Size in bytes of the array of terminationfunctions - m->dynamic_info.fini_array_size = dyn->d_un.d_val; - break; - case DT_PREINIT_ARRAY: // Address of the array of pointers to pre - initialization functions - m->dynamic_info.preinit_array_virtual_addr = dyn->d_un.d_ptr; - break; - case DT_PREINIT_ARRAYSZ: // Size in bytes of the array of pre - initialization functions - m->dynamic_info.preinit_array_size = dyn->d_un.d_val; - break; - case DT_SCE_SYMENT: // The size of symbol table entries - m->dynamic_info.symbol_table_entries_size = dyn->d_un.d_val; - if (m->dynamic_info.symbol_table_entries_size != - 0x18) // this value should always be 0x18 - { - LOG_WARNING(Core_Linker, "DT_SCE_SYMENT is NOT 0x18 should check!"); - } - break; - case DT_DEBUG: - m->dynamic_info.debug = dyn->d_un.d_val; - break; - case DT_TEXTREL: - m->dynamic_info.textrel = dyn->d_un.d_val; - break; - case DT_FLAGS: - m->dynamic_info.flags = dyn->d_un.d_val; - if (m->dynamic_info.flags != 0x04) // this value should always be DF_TEXTREL (0x04) - { - LOG_WARNING(Core_Linker, "DT_FLAGS is NOT 0x04 should check!"); - } - break; - case DT_NEEDED: // Offset of the library string in the string table to be linked in. - if (m->dynamic_info.str_table != - nullptr) // in theory this should already be filled from about just make a test case - { - m->dynamic_info.needed.push_back(m->dynamic_info.str_table + dyn->d_un.d_val); - } else { - LOG_ERROR(Core_Linker, "DT_NEEDED str table is not loaded should check!"); - } - break; - case DT_SCE_NEEDED_MODULE: { - ModuleInfo info{}; - info.value = dyn->d_un.d_val; - info.name = m->dynamic_info.str_table + info.name_offset; - info.enc_id = EncodeId(info.id); - m->dynamic_info.import_modules.push_back(info); - } break; - case DT_SCE_IMPORT_LIB: { - LibraryInfo info{}; - info.value = dyn->d_un.d_val; - info.name = m->dynamic_info.str_table + info.name_offset; - info.enc_id = EncodeId(info.id); - m->dynamic_info.import_libs.push_back(info); - } break; - case DT_SCE_FINGERPRINT: - // The fingerprint is a 24 byte (0x18) size buffer that contains a unique identifier for - // the given app. How exactly this is generated isn't known, however it is not necessary - // to have a valid fingerprint. While an invalid fingerprint will cause a warning to be - // printed to the kernel log, the ELF will still load and run. - LOG_INFO(Core_Linker, "unsupported DT_SCE_FINGERPRINT value = ..........: {:#018x}", - dyn->d_un.d_val); - break; - case DT_SCE_IMPORT_LIB_ATTR: - // The upper 32-bits should contain the module index multiplied by 0x10000. The lower - // 32-bits should be a constant 0x9. - LOG_INFO(Core_Linker, "unsupported DT_SCE_IMPORT_LIB_ATTR value = ......: {:#018x}", - dyn->d_un.d_val); - break; - case DT_SCE_ORIGINAL_FILENAME: - m->dynamic_info.filename = m->dynamic_info.str_table + dyn->d_un.d_val; - break; - case DT_SCE_MODULE_INFO: // probably only useable in shared modules - { - ModuleInfo info{}; - info.value = dyn->d_un.d_val; - info.name = m->dynamic_info.str_table + info.name_offset; - info.enc_id = EncodeId(info.id); - m->dynamic_info.export_modules.push_back(info); - } break; - case DT_SCE_MODULE_ATTR: - // TODO? - LOG_INFO(Core_Linker, "unsupported DT_SCE_MODULE_ATTR value = ..........: {:#018x}", - dyn->d_un.d_val); - break; - case DT_SCE_EXPORT_LIB: { - LibraryInfo info{}; - info.value = dyn->d_un.d_val; - info.name = m->dynamic_info.str_table + info.name_offset; - info.enc_id = EncodeId(info.id); - m->dynamic_info.export_libs.push_back(info); - } break; - default: - LOG_INFO(Core_Linker, "unsupported dynamic tag ..........: {:#018x}", dyn->d_tag); - } - } -} - -const ModuleInfo* Linker::FindModule(const Module& m, const std::string& id) { - const auto& import_modules = m.dynamic_info.import_modules; - int index = 0; - for (const auto& mod : import_modules) { - if (mod.enc_id.compare(id) == 0) { - return &import_modules.at(index); - } - index++; - } - const auto& export_modules = m.dynamic_info.export_modules; - index = 0; - for (const auto& mod : export_modules) { - if (mod.enc_id.compare(id) == 0) { - return &export_modules.at(index); - } - index++; - } - return nullptr; -} - -const LibraryInfo* Linker::FindLibrary(const Module& m, const std::string& id) { - const auto& import_libs = m.dynamic_info.import_libs; - int index = 0; - for (const auto& lib : import_libs) { - if (lib.enc_id.compare(id) == 0) { - return &import_libs.at(index); - } - index++; - } - const auto& export_libs = m.dynamic_info.export_libs; - index = 0; - for (const auto& lib : export_libs) { - if (lib.enc_id.compare(id) == 0) { - return &export_libs.at(index); - } - index++; - } - return nullptr; -} - -void Linker::LoadSymbols(Module* m) { - - const auto symbol_database = [this](Module* m, Loader::SymbolsResolver* symbol, - bool export_func) { - if (m->dynamic_info.symbol_table == nullptr || m->dynamic_info.str_table == nullptr || - m->dynamic_info.symbol_table_total_size == 0) { - LOG_INFO(Core_Linker, "Symbol table not found!"); - return; - } - for (auto* sym = m->dynamic_info.symbol_table; - reinterpret_cast(sym) < reinterpret_cast(m->dynamic_info.symbol_table) + - m->dynamic_info.symbol_table_total_size; - sym++) { - std::string id = std::string(m->dynamic_info.str_table + sym->st_name); - auto bind = sym->GetBind(); - auto type = sym->GetType(); - auto visibility = sym->GetVisibility(); - const auto ids = Common::SplitString(id, '#'); - if (ids.size() == 3) { - const auto* library = FindLibrary(*m, ids.at(1)); - const auto* module = FindModule(*m, ids.at(2)); - ASSERT_MSG(library && module, "Unable to find library and module"); - if ((bind == STB_GLOBAL || bind == STB_WEAK) && - (type == STT_FUN || type == STT_OBJECT) && - export_func == (sym->st_value != 0)) { - std::string nidName = ""; - auto aeronid = AeroLib::FindByNid(ids.at(0).c_str()); - if (aeronid != nullptr) { - nidName = aeronid->name; - } else { - nidName = "UNK"; - } - Loader::SymbolResolver sym_r{}; - sym_r.name = ids.at(0); - sym_r.nidName = nidName; - sym_r.library = library->name; - sym_r.library_version = library->version; - sym_r.module = module->name; - sym_r.module_version_major = module->version_major; - sym_r.module_version_minor = module->version_minor; - switch (type) { - case STT_NOTYPE: - sym_r.type = Loader::SymbolType::NoType; - break; - case STT_FUN: - sym_r.type = Loader::SymbolType::Function; - break; - case STT_OBJECT: - sym_r.type = Loader::SymbolType::Object; - break; - default: - sym_r.type = Loader::SymbolType::Unknown; - break; - } - symbol->AddSymbol(sym_r, - (export_func ? sym->st_value + m->base_virtual_addr : 0)); - } - } - } - }; - symbol_database(m, &m->export_sym, true); - symbol_database(m, &m->import_sym, false); -} - -void Linker::Relocate(Module* m) { - const auto relocate = [this](u32 idx, elf_relocation* rel, Module* m, bool isJmpRel) { - auto type = rel->GetType(); - auto symbol = rel->GetSymbol(); - auto addend = rel->rel_addend; - auto* symbolsTlb = m->dynamic_info.symbol_table; - auto* namesTlb = m->dynamic_info.str_table; - - u64 rel_value = 0; - u64 rel_base_virtual_addr = m->base_virtual_addr; - u64 rel_virtual_addr = m->base_virtual_addr + rel->rel_offset; - bool rel_isResolved = false; - Loader::SymbolType rel_sym_type = Loader::SymbolType::Unknown; - std::string rel_name; - - switch (type) { - case R_X86_64_RELATIVE: - rel_value = rel_base_virtual_addr + addend; - rel_isResolved = true; - break; - case R_X86_64_DTPMOD64: - rel_value = reinterpret_cast(m); - rel_isResolved = true; - rel_sym_type = Loader::SymbolType::Tls; - break; - case R_X86_64_GLOB_DAT: - case R_X86_64_JUMP_SLOT: - addend = 0; - case R_X86_64_64: { - auto sym = symbolsTlb[symbol]; - auto sym_bind = sym.GetBind(); - auto sym_type = sym.GetType(); - auto sym_visibility = sym.GetVisibility(); - u64 symbol_vitrual_addr = 0; - Loader::SymbolRecord symrec{}; - switch (sym_type) { - case STT_FUN: - rel_sym_type = Loader::SymbolType::Function; - break; - case STT_OBJECT: - rel_sym_type = Loader::SymbolType::Object; - break; - case STT_NOTYPE: - rel_sym_type = Loader::SymbolType::NoType; - break; - default: - ASSERT_MSG(0, "unknown symbol type {}", sym_type); - } - if (sym_visibility != 0) // should be zero log if else - { - LOG_INFO(Core_Linker, "symbol visilibity !=0"); - } - switch (sym_bind) { - case STB_LOCAL: - symbol_vitrual_addr = rel_base_virtual_addr + sym.st_value; - break; - case STB_GLOBAL: - case STB_WEAK: { - rel_name = namesTlb + sym.st_name; - Resolve(rel_name, rel_sym_type, m, &symrec); - symbol_vitrual_addr = symrec.virtual_address; - } break; - default: - ASSERT_MSG(0, "unknown bind type {}", sym_bind); - } - rel_isResolved = (symbol_vitrual_addr != 0); - rel_value = (rel_isResolved ? symbol_vitrual_addr + addend : 0); - rel_name = symrec.name; - } break; - default: - LOG_INFO(Core_Linker, "UNK type {:#010x} rel symbol : {:#010x}", type, symbol); - } - - if (rel_isResolved) { - VirtualMemory::memory_patch(rel_virtual_addr, rel_value); - } else { - LOG_INFO(Core_Linker, "function not patched! {}", rel_name); - } - }; - - u32 idx = 0; - for (auto* rel = m->dynamic_info.relocation_table; - reinterpret_cast(rel) < reinterpret_cast(m->dynamic_info.relocation_table) + - m->dynamic_info.relocation_table_size; - rel++, idx++) { - relocate(idx, rel, m, false); - } - idx = 0; - for (auto* rel = m->dynamic_info.jmp_relocation_table; - reinterpret_cast(rel) < reinterpret_cast(m->dynamic_info.jmp_relocation_table) + - m->dynamic_info.jmp_relocation_table_size; - rel++, idx++) { - relocate(idx, rel, m, true); - } -} - -template -bool contains(const std::vector& vecObj, const T& element) { - auto it = std::find(vecObj.begin(), vecObj.end(), element); - return it != vecObj.end(); -} - -Module* Linker::FindExportedModule(const ModuleInfo& module, const LibraryInfo& library) { - // std::scoped_lock lock{m_mutex}; - - for (auto& m : m_modules) { - const auto& export_libs = m->dynamic_info.export_libs; - const auto& export_modules = m->dynamic_info.export_modules; - - if (contains(export_libs, library) && contains(export_modules, module)) { - return m.get(); - } - } - return nullptr; -} - -void Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Module* m, - Loader::SymbolRecord* return_info) { - // std::scoped_lock lock{m_mutex}; - const auto ids = Common::SplitString(name, '#'); - if (ids.size() == 3) { - const auto* library = FindLibrary(*m, ids.at(1)); - const auto* module = FindModule(*m, ids.at(2)); - ASSERT_MSG(library && module, "Unable to find library and module"); - - Loader::SymbolResolver sr{}; - sr.name = ids.at(0); - sr.library = library->name; - sr.library_version = library->version; - sr.module = module->name; - sr.module_version_major = module->version_major; - sr.module_version_minor = module->version_minor; - sr.type = sym_type; - - const Loader::SymbolRecord* rec = nullptr; - - rec = m_hle_symbols.FindSymbol(sr); - if (rec == nullptr) { - // check if it an export function - if (auto* p = FindExportedModule(*module, *library); - p != nullptr && p->export_sym.GetSize() > 0) { - rec = p->export_sym.FindSymbol(sr); - } - } - if (rec != nullptr) { - *return_info = *rec; - } else { - auto aeronid = AeroLib::FindByNid(sr.name.c_str()); - if (aeronid) { - return_info->name = aeronid->name; - return_info->virtual_address = AeroLib::GetStub(aeronid->nid); - } else { - return_info->virtual_address = AeroLib::GetStub(sr.name.c_str()); - return_info->name = "Unknown !!!"; - } - LOG_ERROR(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name, - return_info->name, library->name, module->name); - } - } else { - return_info->virtual_address = 0; - return_info->name = name; - LOG_ERROR(Core_Linker, "Not Resolved {}", name); - } -} - -u64 Linker::GetProcParam() { - // std::scoped_lock lock{m_mutex}; - - for (auto& m : m_modules) { - if (!m->elf.IsSharedLib()) { - return m->proc_param_virtual_addr; - } - } - return 0; -} -using exit_func_t = PS4_SYSV_ABI void (*)(); -using entry_func_t = PS4_SYSV_ABI void (*)(EntryParams* params, exit_func_t atexit_func); -using module_ini_func_t = PS4_SYSV_ABI int (*)(size_t args, const void* argp, module_func_t func); - -static PS4_SYSV_ABI int run_module(uint64_t addr, size_t args, const void* argp, - module_func_t func) { - return reinterpret_cast(addr)(args, argp, func); -} - -int Linker::StartModule(Module* m, size_t args, const void* argp, module_func_t func) { - LOG_INFO(Core_Linker, "Module started : {}", m->file_name); - return run_module(m->dynamic_info.init_virtual_addr + m->base_virtual_addr, args, argp, func); -} - -void Linker::StartAllModules() { - std::scoped_lock lock{m_mutex}; - - for (auto& m : m_modules) { - if (m->elf.IsSharedLib()) { - StartModule(m.get(), 0, nullptr, nullptr); - } - } -} +using ExitFunc = PS4_SYSV_ABI void (*)(); static PS4_SYSV_ABI void ProgramExitFunc() { fmt::print("exit function called\n"); } -static void RunMainEntry(u64 addr, EntryParams* params, exit_func_t exit_func) { +static void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) { // reinterpret_cast(addr)(params, exit_func); // can't be used, stack has to have // a specific layout - asm volatile("andq $-16, %%rsp\n" // Align to 16 bytes "subq $8, %%rsp\n" // videoout_basic expects the stack to be misaligned @@ -672,42 +46,283 @@ static void RunMainEntry(u64 addr, EntryParams* params, exit_func_t exit_func) { : "rax", "rsi", "rdi"); } +Linker::Linker() = default; + +Linker::~Linker() = default; + void Linker::Execute() { if (Config::debugDump()) { DebugDump(); } - Common::SetCurrentThreadName("GAME_MainThread"); + // Calculate static TLS size. + for (const auto& module : m_modules) { + if (module->tls.image_size != 0) { + module->tls.modid = ++max_tls_index; + } + static_tls_size += module->tls.image_size; + module->tls.offset = static_tls_size; + } - Libraries::Kernel::pthreadInitSelfMainThread(); // Relocate all modules for (const auto& m : m_modules) { Relocate(m.get()); } - StartAllModules(); + + // Init primary thread. + Common::SetCurrentThreadName("GAME_MainThread"); + Libraries::Kernel::pthreadInitSelfMainThread(); + InitTlsForThread(true); + + // Start shared library modules + for (auto& m : m_modules) { + if (m->IsSharedLib()) { + m->Start(0, nullptr, nullptr); + } + } + + // Start main module. EntryParams p{}; p.argc = 1; - p.argv[0] = "eboot.bin"; // hmm should be ok? + p.argv[0] = "eboot.bin"; for (auto& m : m_modules) { - if (m->elf.IsSharedLib()) { - continue; + if (!m->IsSharedLib()) { + RunMainEntry(m->GetEntryAddress(), &p, ProgramExitFunc); } - if (m->tls.image_virtual_addr != 0) { - SetTLSStorage(m->tls.image_virtual_addr); - } - RunMainEntry(m->elf.GetElfEntry() + m->base_virtual_addr, &p, ProgramExitFunc); } } +s32 Linker::LoadModule(const std::filesystem::path& elf_name) { + std::scoped_lock lk{mutex}; + + if (!std::filesystem::exists(elf_name)) { + LOG_ERROR(Core_Linker, "Provided file {} does not exist", elf_name.string()); + return -1; + } + + auto module = std::make_unique(elf_name); + if (!module->IsValid()) { + LOG_ERROR(Core_Linker, "Provided file {} is not valid ELF file", elf_name.string()); + return -1; + } + + m_modules.emplace_back(std::move(module)); + return m_modules.size() - 1; +} + +void Linker::Relocate(Module* module) { + module->ForEachRelocation([&](elf_relocation* rel, bool isJmpRel) { + auto type = rel->GetType(); + auto symbol = rel->GetSymbol(); + auto addend = rel->rel_addend; + auto* symbol_table = module->dynamic_info.symbol_table; + auto* namesTlb = module->dynamic_info.str_table; + + const VAddr rel_base_virtual_addr = module->GetBaseAddress(); + const VAddr rel_virtual_addr = rel_base_virtual_addr + rel->rel_offset; + bool rel_is_resolved = false; + u64 rel_value = 0; + Loader::SymbolType rel_sym_type = Loader::SymbolType::Unknown; + std::string rel_name; + + switch (type) { + case R_X86_64_RELATIVE: + rel_value = rel_base_virtual_addr + addend; + rel_is_resolved = true; + break; + case R_X86_64_DTPMOD64: + rel_value = static_cast(module->tls.modid); + rel_is_resolved = true; + rel_sym_type = Loader::SymbolType::Tls; + break; + case R_X86_64_GLOB_DAT: + case R_X86_64_JUMP_SLOT: + addend = 0; + case R_X86_64_64: { + auto sym = symbol_table[symbol]; + auto sym_bind = sym.GetBind(); + auto sym_type = sym.GetType(); + auto sym_visibility = sym.GetVisibility(); + u64 symbol_vitrual_addr = 0; + Loader::SymbolRecord symrec{}; + switch (sym_type) { + case STT_FUN: + rel_sym_type = Loader::SymbolType::Function; + break; + case STT_OBJECT: + rel_sym_type = Loader::SymbolType::Object; + break; + case STT_NOTYPE: + rel_sym_type = Loader::SymbolType::NoType; + break; + default: + ASSERT_MSG(0, "unknown symbol type {}", sym_type); + } + + if (sym_visibility != 0) { + LOG_INFO(Core_Linker, "symbol visilibity !=0"); + } + + switch (sym_bind) { + case STB_LOCAL: + symbol_vitrual_addr = rel_base_virtual_addr + sym.st_value; + break; + case STB_GLOBAL: + case STB_WEAK: { + rel_name = namesTlb + sym.st_name; + Resolve(rel_name, rel_sym_type, module, &symrec); + symbol_vitrual_addr = symrec.virtual_address; + break; + } + default: + ASSERT_MSG(0, "unknown bind type {}", sym_bind); + } + rel_is_resolved = (symbol_vitrual_addr != 0); + rel_value = (rel_is_resolved ? symbol_vitrual_addr + addend : 0); + rel_name = symrec.name; + break; + } + default: + LOG_INFO(Core_Linker, "UNK type {:#010x} rel symbol : {:#010x}", type, symbol); + } + + if (rel_is_resolved) { + VirtualMemory::memory_patch(rel_virtual_addr, rel_value); + } else { + LOG_INFO(Core_Linker, "function not patched! {}", rel_name); + } + }); +} + +const Module* Linker::FindExportedModule(const ModuleInfo& module, const LibraryInfo& library) { + const auto it = std::ranges::find_if(m_modules, [&](const auto& m) { + return std::ranges::contains(m->GetExportLibs(), library) && + std::ranges::contains(m->GetExportModules(), module); + }); + return it == m_modules.end() ? nullptr : it->get(); +} + +void Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Module* m, + Loader::SymbolRecord* return_info) { + const auto ids = Common::SplitString(name, '#'); + if (ids.size() != 3) { + return_info->virtual_address = 0; + return_info->name = name; + LOG_ERROR(Core_Linker, "Not Resolved {}", name); + return; + } + + const LibraryInfo* library = m->FindLibrary(ids[1]); + const ModuleInfo* module = m->FindModule(ids[2]); + ASSERT_MSG(library && module, "Unable to find library and module"); + + Loader::SymbolResolver sr{}; + sr.name = ids.at(0); + sr.library = library->name; + sr.library_version = library->version; + sr.module = module->name; + sr.module_version_major = module->version_major; + sr.module_version_minor = module->version_minor; + sr.type = sym_type; + + const auto* record = m_hle_symbols.FindSymbol(sr); + if (!record) { + // Check if it an export function + const auto* p = FindExportedModule(*module, *library); + if (p && p->export_sym.GetSize() > 0) { + record = p->export_sym.FindSymbol(sr); + } + } + if (record) { + *return_info = *record; + return; + } + + const auto aeronid = AeroLib::FindByNid(sr.name.c_str()); + if (aeronid) { + return_info->name = aeronid->name; + return_info->virtual_address = AeroLib::GetStub(aeronid->nid); + } else { + return_info->virtual_address = AeroLib::GetStub(sr.name.c_str()); + return_info->name = "Unknown !!!"; + } + LOG_ERROR(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name, + return_info->name, library->name, module->name); +} + +void* Linker::TlsGetAddr(u64 module_index, u64 offset) { + std::scoped_lock lk{mutex}; + + DtvEntry* dtv_table = GetTcbBase()->tcb_dtv; + ASSERT_MSG(dtv_table[0].counter == dtv_generation_counter, + "Reallocation of DTV table is not supported"); + + void* module = (u8*)dtv_table[module_index + 1].pointer + offset; + ASSERT_MSG(module, "DTV allocation is not supported"); + return module; +} + +void Linker::InitTlsForThread(bool is_primary) { + static constexpr size_t TcbSize = 0x40; + static constexpr size_t TlsAllocAlign = 0x20; + const size_t total_tls_size = Common::AlignUp(static_tls_size, TlsAllocAlign) + TcbSize; + + // The kernel module has a few different paths for TLS allocation. + // For SDK < 1.7 it allocates both main and secondary thread blocks using libc mspace/malloc. + // In games compiled with newer SDK, the main thread gets mapped from flexible memory, + // with addr = 0, so system managed area. Here we will only implement the latter. + void* addr_out{}; + if (is_primary) { + const size_t tls_aligned = Common::AlignUp(total_tls_size, 16_KB); + const int ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory( + &addr_out, tls_aligned, 3, 0, "SceKernelPrimaryTcbTls"); + ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread"); + } else { + if (heap_api_func) { + addr_out = heap_api_func(total_tls_size); + } else { + addr_out = std::malloc(total_tls_size); + } + } + + // Initialize allocated memory and allocate DTV table. + const u32 num_dtvs = max_tls_index; + std::memset(addr_out, 0, total_tls_size); + DtvEntry* dtv_table = new DtvEntry[num_dtvs + 2]; + + // Initialize thread control block + u8* addr = reinterpret_cast(addr_out); + Tcb* tcb = reinterpret_cast(addr + static_tls_size); + tcb->tcb_self = tcb; + tcb->tcb_dtv = dtv_table; + + // Dtv[0] is the generation counter. libkernel puts their number into dtv[1] (why?) + dtv_table[0].counter = dtv_generation_counter; + dtv_table[1].counter = num_dtvs; + + // Copy init images to TLS thread blocks and map them to DTV slots. + for (const auto& module : m_modules) { + if (module->tls.image_size == 0) { + continue; + } + u8* dest = reinterpret_cast(addr + static_tls_size - module->tls.offset); + const u8* src = reinterpret_cast(module->tls.image_virtual_addr); + std::memcpy(dest, src, module->tls.init_image_size); + tcb->tcb_dtv[module->tls.modid + 1].pointer = dest; + } + + // Set pointer to FS base + SetTcbBase(tcb); +} + void Linker::DebugDump() { - std::scoped_lock lock{m_mutex}; const auto& log_dir = Common::FS::GetUserPath(Common::FS::PathType::LogDir); const std::filesystem::path debug(log_dir / "debugdump"); std::filesystem::create_directory(debug); for (const auto& m : m_modules) { // TODO make a folder with game id for being more unique? - const std::filesystem::path filepath(debug / m.get()->file_name); + const std::filesystem::path filepath(debug / m.get()->file.stem()); std::filesystem::create_directory(filepath); m.get()->import_sym.DebugDump(filepath / "imports.txt"); m.get()->export_sym.DebugDump(filepath / "exports.txt"); diff --git a/src/core/linker.h b/src/core/linker.h index ae730878..f5414ec6 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -5,11 +5,10 @@ #include #include -#include "core/loader/elf.h" -#include "core/loader/symbols_resolver.h" +#include "core/module.h" namespace Core { -using module_func_t = int (*)(size_t args, const void* argp); + struct DynamicModuleInfo; class Linker; @@ -19,136 +18,50 @@ struct EntryParams { const char* argv[3]; }; -struct ModuleInfo { - bool operator==(const ModuleInfo& other) const { - return version_major == other.version_major && version_minor == other.version_minor && - name == other.name; - } - std::string name; - union { - u64 value; - struct { - u32 name_offset; - u8 version_minor; - u8 version_major; - u16 id; - }; - }; - std::string enc_id; -}; - -struct LibraryInfo { - bool operator==(const LibraryInfo& other) const { - return version == other.version && name == other.name; - } - std::string name; - union { - u64 value; - struct { - u32 name_offset; - u16 version; - u16 id; - }; - }; - std::string enc_id; -}; - -struct PS4ThreadLocal { - u64 image_virtual_addr = 0; - u64 image_size = 0; - u64 handler_virtual_addr = 0; -}; - -struct DynamicModuleInfo { - void* hash_table = nullptr; - u64 hash_table_size = 0; - - char* str_table = nullptr; - u64 str_table_size = 0; - - elf_symbol* symbol_table = nullptr; - u64 symbol_table_total_size = 0; - u64 symbol_table_entries_size = 0; - - u64 init_virtual_addr = 0; - u64 fini_virtual_addr = 0; - u64 pltgot_virtual_addr = 0; - u64 init_array_virtual_addr = 0; - u64 fini_array_virtual_addr = 0; - u64 preinit_array_virtual_addr = 0; - u64 init_array_size = 0; - u64 fini_array_size = 0; - u64 preinit_array_size = 0; - - elf_relocation* jmp_relocation_table = nullptr; - u64 jmp_relocation_table_size = 0; - s64 jmp_relocation_type = 0; - - elf_relocation* relocation_table = nullptr; - u64 relocation_table_size = 0; - u64 relocation_table_entries_size = 0; - - u64 debug = 0; - u64 textrel = 0; - u64 flags = 0; - - std::vector needed; - std::vector import_modules; - std::vector export_modules; - std::vector import_libs; - std::vector export_libs; - - std::string filename; // Filename with absolute path -}; - -// This struct keeps neccesary info about loaded modules. Main executeable is included too as well -struct Module { - Loader::Elf elf; - u64 aligned_base_size = 0; - u64 base_virtual_addr = 0; - u64 proc_param_virtual_addr = 0; - - std::string file_name; - - std::vector m_dynamic; - std::vector m_dynamic_data; - DynamicModuleInfo dynamic_info{}; - - Loader::SymbolsResolver export_sym; - Loader::SymbolsResolver import_sym; - - PS4ThreadLocal tls; -}; +using HeapApiFunc = PS4_SYSV_ABI void* (*)(size_t); class Linker { public: - Linker(); - virtual ~Linker(); + explicit Linker(); + ~Linker(); - Module* LoadModule(const std::filesystem::path& elf_name); - void LoadModuleToMemory(Module* m); - void LoadDynamicInfo(Module* m); - void LoadSymbols(Module* m); - Loader::SymbolsResolver& getHLESymbols() { + Loader::SymbolsResolver& GetHLESymbols() { return m_hle_symbols; } - void Relocate(Module* m); - void Resolve(const std::string& name, Loader::SymbolType Symtype, Module* m, + + VAddr GetProcParam() const { + return m_modules[0]->GetProcParam(); + } + + Module* GetModule(s32 index) const { + return m_modules.at(index).get(); + } + + void SetHeapApiFunc(void* func) { + heap_api_func = *reinterpret_cast(func); + } + + void* TlsGetAddr(u64 module_index, u64 offset); + void InitTlsForThread(bool is_primary = false); + + s32 LoadModule(const std::filesystem::path& elf_name); + + void Relocate(Module* module); + void Resolve(const std::string& name, Loader::SymbolType type, Module* module, Loader::SymbolRecord* return_info); void Execute(); void DebugDump(); - u64 GetProcParam(); private: - const ModuleInfo* FindModule(const Module& m, const std::string& id); - const LibraryInfo* FindLibrary(const Module& program, const std::string& id); - Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l); - int StartModule(Module* m, size_t args, const void* argp, module_func_t func); - void StartAllModules(); + const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l); + std::mutex mutex; + u32 dtv_generation_counter{1}; + size_t static_tls_size{}; + size_t max_tls_index{}; + HeapApiFunc heap_api_func{}; std::vector> m_modules; Loader::SymbolsResolver m_hle_symbols{}; - std::mutex m_mutex; }; } // namespace Core diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 0b58880a..a4cc2522 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -445,7 +445,7 @@ std::string Elf::ElfHeaderStr() { return header; } -std::string Elf::ElfPheaderTypeStr(u32 type) { +std::string_view Elf::ElfPheaderTypeStr(u32 type) { switch (type) { case PT_NULL: return "Null"; diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h index b2d2ea59..026102b9 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h @@ -3,7 +3,6 @@ #pragma once -#include #include #include #include @@ -482,11 +481,15 @@ public: return m_elf_header.e_entry; } + [[nodiscard]] bool IsSharedLib() const { + return m_elf_header.e_type == ET_SCE_DYNAMIC; + } + std::string SElfHeaderStr(); std::string SELFSegHeader(u16 no); std::string ElfHeaderStr(); std::string ElfPHeaderStr(u16 no); - std::string ElfPheaderTypeStr(u32 type); + std::string_view ElfPheaderTypeStr(u32 type); std::string ElfPheaderFlagsStr(u32 flags); void LoadSegment(u64 virtual_addr, u64 file_offset, u64 size); diff --git a/src/core/loader/symbols_resolver.cpp b/src/core/loader/symbols_resolver.cpp index d573fc37..0b09a020 100644 --- a/src/core/loader/symbols_resolver.cpp +++ b/src/core/loader/symbols_resolver.cpp @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "common/io_file.h" -#include "common/logging/log.h" #include "common/string_util.h" #include "common/types.h" #include "core/aerolib/aerolib.h" @@ -11,9 +11,7 @@ namespace Core::Loader { void SymbolsResolver::AddSymbol(const SymbolResolver& s, u64 virtual_addr) { - SymbolRecord& r = m_symbols.emplace_back(); - r.name = GenerateName(s); - r.virtual_address = virtual_addr; + m_symbols.emplace_back(GenerateName(s), s.nidName, virtual_addr); } std::string SymbolsResolver::GenerateName(const SymbolResolver& s) { @@ -38,22 +36,13 @@ void SymbolsResolver::DebugDump(const std::filesystem::path& file_name) { Common::FS::FileType::TextFile}; for (const auto& symbol : m_symbols) { const auto ids = Common::SplitString(symbol.name, '#'); - std::string nidName = ""; - auto aeronid = AeroLib::FindByNid(ids.at(0).c_str()); - if (aeronid != nullptr) { - nidName = aeronid->name; - } else { - nidName = "UNK"; - } + const auto aeronid = AeroLib::FindByNid(ids.at(0).c_str()); + const auto nid_name = aeronid ? aeronid->name : "UNK"; f.WriteString( fmt::format("0x{:<20x} {:<16} {:<60} {:<30} {:<2} {:<30} {:<2} {:<2} {:<10}\n", - symbol.virtual_address, ids.at(0), nidName, ids.at(1), ids.at(2), ids.at(3), - ids.at(4), ids.at(5), ids.at(6))); + symbol.virtual_address, ids.at(0), nid_name, ids.at(1), ids.at(2), + ids.at(3), ids.at(4), ids.at(5), ids.at(6))); } } -int SymbolsResolver::GetSize() { - return m_symbols.size(); -} - } // namespace Core::Loader diff --git a/src/core/loader/symbols_resolver.h b/src/core/loader/symbols_resolver.h index e02e9e8b..80ea46f2 100644 --- a/src/core/loader/symbols_resolver.h +++ b/src/core/loader/symbols_resolver.h @@ -3,8 +3,9 @@ #pragma once +#include +#include #include -#include #include #include "common/types.h" @@ -20,6 +21,7 @@ enum class SymbolType { struct SymbolRecord { std::string name; + std::string nid_name; u64 virtual_address; }; @@ -42,6 +44,16 @@ public: void AddSymbol(const SymbolResolver& s, u64 virtual_addr); const SymbolRecord* FindSymbol(const SymbolResolver& s) const; + void DebugDump(const std::filesystem::path& file_name); + + std::span GetSymbols() const { + return m_symbols; + } + + size_t GetSize() const noexcept { + return m_symbols.size(); + } + static std::string GenerateName(const SymbolResolver& s); static std::string_view SymbolTypeToS(SymbolType sym_type) { @@ -59,9 +71,6 @@ public: } } - void DebugDump(const std::filesystem::path& file_name); - int GetSize(); - private: std::vector m_symbols; }; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 06fde132..8ecd311b 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -26,7 +26,7 @@ MemoryManager::~MemoryManager() = default; PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, int memory_type) { - PAddr free_addr = 0; + PAddr free_addr = search_start; // Iterate through allocated blocked and find the next free position for (const auto& block : allocations) { @@ -35,7 +35,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, } // Align free position - free_addr = Common::alignUp(free_addr, alignment); + free_addr = Common::AlignUp(free_addr, alignment); ASSERT(free_addr >= search_start && free_addr + size <= search_end); // Add the allocated region to the list and commit its pages. @@ -56,7 +56,7 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) { int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name, PAddr phys_addr, u64 alignment) { - VAddr mapped_addr = alignment > 0 ? Common::alignUp(virtual_addr, alignment) : virtual_addr; + VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr; SCOPE_EXIT { auto& new_vma = AddMapping(mapped_addr, size); new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); @@ -99,7 +99,7 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M } // Perform the mapping. - *out_addr = impl.Map(mapped_addr, size); + *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr); return ORBIS_OK; } diff --git a/src/core/memory.h b/src/core/memory.h index ab9006a4..7a623db7 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -107,7 +107,7 @@ public: int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name = "", - PAddr phys_addr = 0, u64 alignment = 0); + PAddr phys_addr = -1, u64 alignment = 0); void UnmapMemory(VAddr virtual_addr, size_t size); @@ -121,7 +121,7 @@ private: VMAHandle FindVMA(VAddr target) { // Return first the VMA with base >= target. const auto it = vma_map.lower_bound(target); - if (it->first == target) { + if (it != vma_map.end() && it->first == target) { return it; } return std::prev(it); diff --git a/src/core/module.cpp b/src/core/module.cpp new file mode 100644 index 00000000..102fbe1b --- /dev/null +++ b/src/core/module.cpp @@ -0,0 +1,419 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/aerolib/aerolib.h" +#include "core/module.h" +#include "core/tls.h" +#include "core/virtual_memory.h" + +namespace Core { + +using EntryFunc = PS4_SYSV_ABI int (*)(size_t args, const void* argp, void* param); + +static u64 LoadAddress = SYSTEM_RESERVED + CODE_BASE_OFFSET; +static constexpr u64 CODE_BASE_INCR = 0x010000000u; + +static u64 GetAlignedSize(const elf_program_header& phdr) { + return (phdr.p_align != 0 ? (phdr.p_memsz + (phdr.p_align - 1)) & ~(phdr.p_align - 1) + : phdr.p_memsz); +} + +static u64 CalculateBaseSize(const elf_header& ehdr, std::span phdr) { + u64 base_size = 0; + for (u16 i = 0; i < ehdr.e_phnum; i++) { + if (phdr[i].p_memsz != 0 && (phdr[i].p_type == PT_LOAD || phdr[i].p_type == PT_SCE_RELRO)) { + const u64 last_addr = phdr[i].p_vaddr + GetAlignedSize(phdr[i]); + base_size = std::max(last_addr, base_size); + } + } + return base_size; +} + +static std::string EncodeId(u64 nVal) { + std::string enc; + static constexpr std::string_view codes = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; + if (nVal < 0x40u) { + enc += codes[nVal]; + } else { + if (nVal < 0x1000u) { + enc += codes[static_cast(nVal >> 6u) & 0x3fu]; + enc += codes[nVal & 0x3fu]; + } else { + enc += codes[static_cast(nVal >> 12u) & 0x3fu]; + enc += codes[static_cast(nVal >> 6u) & 0x3fu]; + enc += codes[nVal & 0x3fu]; + } + } + return enc; +} + +Module::Module(const std::filesystem::path& file_) : file{file_} { + elf.Open(file); + if (elf.IsElfFile()) { + LoadModuleToMemory(); + LoadDynamicInfo(); + LoadSymbols(); + } +} + +Module::~Module() = default; + +void Module::Start(size_t args, const void* argp, void* param) { + LOG_INFO(Core_Linker, "Module started : {}", file.filename().string()); + const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); + reinterpret_cast(addr)(args, argp, param); +} + +void Module::LoadModuleToMemory() { + static constexpr size_t BlockAlign = 0x1000; + static constexpr u64 TrampolineSize = 8_MB; + + // Retrieve elf header and program header + const auto elf_header = elf.GetElfHeader(); + const auto elf_pheader = elf.GetProgramHeader(); + const u64 base_size = CalculateBaseSize(elf_header, elf_pheader); + aligned_base_size = Common::AlignUp(base_size, BlockAlign); + + // Map module segments (and possible TLS trampolines) + base_virtual_addr = VirtualMemory::memory_alloc(LoadAddress, aligned_base_size + TrampolineSize, + VirtualMemory::MemoryMode::ExecuteReadWrite); + LoadAddress += CODE_BASE_INCR * (1 + aligned_base_size / CODE_BASE_INCR); + + // Initialize trampoline generator. + void* trampoline_addr = std::bit_cast(base_virtual_addr + aligned_base_size); + Xbyak::CodeGenerator c(TrampolineSize, trampoline_addr); + + LOG_INFO(Core_Linker, "======== Load Module to Memory ========"); + LOG_INFO(Core_Linker, "base_virtual_addr ......: {:#018x}", base_virtual_addr); + LOG_INFO(Core_Linker, "base_size ..............: {:#018x}", base_size); + LOG_INFO(Core_Linker, "aligned_base_size ......: {:#018x}", aligned_base_size); + + for (u16 i = 0; i < elf_header.e_phnum; i++) { + const auto header_type = elf.ElfPheaderTypeStr(elf_pheader[i].p_type); + switch (elf_pheader[i].p_type) { + case PT_LOAD: + case PT_SCE_RELRO: { + if (elf_pheader[i].p_memsz == 0) { + LOG_ERROR(Core_Linker, "p_memsz==0 in type {}", header_type); + continue; + } + + const u64 segment_addr = elf_pheader[i].p_vaddr + base_virtual_addr; + const u64 segment_file_size = elf_pheader[i].p_filesz; + const u64 segment_memory_size = GetAlignedSize(elf_pheader[i]); + const auto segment_mode = elf.ElfPheaderFlagsStr(elf_pheader[i].p_flags); + LOG_INFO(Core_Linker, "program header = [{}] type = {}", i, header_type); + LOG_INFO(Core_Linker, "segment_addr ..........: {:#018x}", segment_addr); + LOG_INFO(Core_Linker, "segment_file_size .....: {}", segment_file_size); + LOG_INFO(Core_Linker, "segment_memory_size ...: {}", segment_memory_size); + LOG_INFO(Core_Linker, "segment_mode ..........: {}", segment_mode); + + elf.LoadSegment(segment_addr, elf_pheader[i].p_offset, segment_file_size); + if (elf_pheader[i].p_flags & PF_EXEC) { + PatchTLS(segment_addr, segment_memory_size, c); + } + break; + } + case PT_DYNAMIC: + if (elf_pheader[i].p_filesz != 0) { + m_dynamic.resize(elf_pheader[i].p_filesz); + const VAddr segment_addr = std::bit_cast(m_dynamic.data()); + elf.LoadSegment(segment_addr, elf_pheader[i].p_offset, elf_pheader[i].p_filesz); + } else { + LOG_ERROR(Core_Linker, "p_filesz==0 in type {}", header_type); + } + break; + case PT_SCE_DYNLIBDATA: + if (elf_pheader[i].p_filesz != 0) { + m_dynamic_data.resize(elf_pheader[i].p_filesz); + const VAddr segment_addr = std::bit_cast(m_dynamic_data.data()); + elf.LoadSegment(segment_addr, elf_pheader[i].p_offset, elf_pheader[i].p_filesz); + } else { + LOG_ERROR(Core_Linker, "p_filesz==0 in type {}", header_type); + } + break; + case PT_TLS: + tls.init_image_size = elf_pheader[i].p_filesz; + tls.align = elf_pheader[i].p_align; + tls.image_virtual_addr = elf_pheader[i].p_vaddr + base_virtual_addr; + tls.image_size = GetAlignedSize(elf_pheader[i]); + LOG_INFO(Core_Linker, "TLS virtual address = {:#x}", tls.image_virtual_addr); + LOG_INFO(Core_Linker, "TLS image size = {}", tls.image_size); + break; + case PT_SCE_PROCPARAM: + proc_param_virtual_addr = elf_pheader[i].p_vaddr + base_virtual_addr; + break; + default: + LOG_ERROR(Core_Linker, "Unimplemented type {}", header_type); + } + } + + const VAddr entry_addr = base_virtual_addr + elf.GetElfEntry(); + LOG_INFO(Core_Linker, "program entry addr ..........: {:#018x}", entry_addr); +} + +void Module::LoadDynamicInfo() { + for (const auto* dyn = reinterpret_cast(m_dynamic.data()); dyn->d_tag != DT_NULL; + dyn++) { + switch (dyn->d_tag) { + case DT_SCE_HASH: // Offset of the hash table. + dynamic_info.hash_table = + reinterpret_cast(m_dynamic_data.data() + dyn->d_un.d_ptr); + break; + case DT_SCE_HASHSZ: // Size of the hash table + dynamic_info.hash_table_size = dyn->d_un.d_val; + break; + case DT_SCE_STRTAB: // Offset of the string table. + dynamic_info.str_table = + reinterpret_cast(m_dynamic_data.data() + dyn->d_un.d_ptr); + break; + case DT_SCE_STRSZ: // Size of the string table. + dynamic_info.str_table_size = dyn->d_un.d_val; + break; + case DT_SCE_SYMTAB: // Offset of the symbol table. + dynamic_info.symbol_table = + reinterpret_cast(m_dynamic_data.data() + dyn->d_un.d_ptr); + break; + case DT_SCE_SYMTABSZ: // Size of the symbol table. + dynamic_info.symbol_table_total_size = dyn->d_un.d_val; + break; + case DT_INIT: + dynamic_info.init_virtual_addr = dyn->d_un.d_ptr; + break; + case DT_FINI: + dynamic_info.fini_virtual_addr = dyn->d_un.d_ptr; + break; + case DT_SCE_PLTGOT: // Offset of the global offset table. + dynamic_info.pltgot_virtual_addr = dyn->d_un.d_ptr; + break; + case DT_SCE_JMPREL: // Offset of the table containing jump slots. + dynamic_info.jmp_relocation_table = + reinterpret_cast(m_dynamic_data.data() + dyn->d_un.d_ptr); + break; + case DT_SCE_PLTRELSZ: // Size of the global offset table. + dynamic_info.jmp_relocation_table_size = dyn->d_un.d_val; + break; + case DT_SCE_PLTREL: // The type of relocations in the relocation table. Should be DT_RELA + dynamic_info.jmp_relocation_type = dyn->d_un.d_val; + if (dynamic_info.jmp_relocation_type != DT_RELA) { + LOG_WARNING(Core_Linker, "DT_SCE_PLTREL is NOT DT_RELA should check!"); + } + break; + case DT_SCE_RELA: // Offset of the relocation table. + dynamic_info.relocation_table = + reinterpret_cast(m_dynamic_data.data() + dyn->d_un.d_ptr); + break; + case DT_SCE_RELASZ: // Size of the relocation table. + dynamic_info.relocation_table_size = dyn->d_un.d_val; + break; + case DT_SCE_RELAENT: // The size of relocation table entries. + dynamic_info.relocation_table_entries_size = dyn->d_un.d_val; + if (dynamic_info.relocation_table_entries_size != 0x18) { + LOG_WARNING(Core_Linker, "DT_SCE_RELAENT is NOT 0x18 should check!"); + } + break; + case DT_INIT_ARRAY: // Address of the array of pointers to initialization functions + dynamic_info.init_array_virtual_addr = dyn->d_un.d_ptr; + break; + case DT_FINI_ARRAY: // Address of the array of pointers to termination functions + dynamic_info.fini_array_virtual_addr = dyn->d_un.d_ptr; + break; + case DT_INIT_ARRAYSZ: // Size in bytes of the array of initialization functions + dynamic_info.init_array_size = dyn->d_un.d_val; + break; + case DT_FINI_ARRAYSZ: // Size in bytes of the array of terminationfunctions + dynamic_info.fini_array_size = dyn->d_un.d_val; + break; + case DT_PREINIT_ARRAY: // Address of the array of pointers to pre - initialization functions + dynamic_info.preinit_array_virtual_addr = dyn->d_un.d_ptr; + break; + case DT_PREINIT_ARRAYSZ: // Size in bytes of the array of pre - initialization functions + dynamic_info.preinit_array_size = dyn->d_un.d_val; + break; + case DT_SCE_SYMENT: // The size of symbol table entries + dynamic_info.symbol_table_entries_size = dyn->d_un.d_val; + if (dynamic_info.symbol_table_entries_size != 0x18) { + LOG_WARNING(Core_Linker, "DT_SCE_SYMENT is NOT 0x18 should check!"); + } + break; + case DT_DEBUG: + dynamic_info.debug = dyn->d_un.d_val; + break; + case DT_TEXTREL: + dynamic_info.textrel = dyn->d_un.d_val; + break; + case DT_FLAGS: + dynamic_info.flags = dyn->d_un.d_val; + // This value should always be DF_TEXTREL (0x04) + if (dynamic_info.flags != 0x04) { + LOG_WARNING(Core_Linker, "DT_FLAGS is NOT 0x04 should check!"); + } + break; + case DT_NEEDED: + // Offset of the library string in the string table to be linked in. + // In theory this should already be filled from about just make a test case + if (dynamic_info.str_table) { + dynamic_info.needed.push_back(dynamic_info.str_table + dyn->d_un.d_val); + } else { + LOG_ERROR(Core_Linker, "DT_NEEDED str table is not loaded should check!"); + } + break; + case DT_SCE_NEEDED_MODULE: { + ModuleInfo& info = dynamic_info.import_modules.emplace_back(); + info.value = dyn->d_un.d_val; + info.name = dynamic_info.str_table + info.name_offset; + info.enc_id = EncodeId(info.id); + break; + } + case DT_SCE_IMPORT_LIB: { + LibraryInfo& info = dynamic_info.import_libs.emplace_back(); + info.value = dyn->d_un.d_val; + info.name = dynamic_info.str_table + info.name_offset; + info.enc_id = EncodeId(info.id); + break; + } + case DT_SCE_FINGERPRINT: + // The fingerprint is a 24 byte (0x18) size buffer that contains a unique identifier for + // the given app. How exactly this is generated isn't known, however it is not necessary + // to have a valid fingerprint. While an invalid fingerprint will cause a warning to be + // printed to the kernel log, the ELF will still load and run. + LOG_INFO(Core_Linker, "unsupported DT_SCE_FINGERPRINT value = ..........: {:#018x}", + dyn->d_un.d_val); + break; + case DT_SCE_IMPORT_LIB_ATTR: + // The upper 32-bits should contain the module index multiplied by 0x10000. The lower + // 32-bits should be a constant 0x9. + LOG_INFO(Core_Linker, "unsupported DT_SCE_IMPORT_LIB_ATTR value = ......: {:#018x}", + dyn->d_un.d_val); + break; + case DT_SCE_ORIGINAL_FILENAME: + dynamic_info.filename = dynamic_info.str_table + dyn->d_un.d_val; + break; + case DT_SCE_MODULE_INFO: { + ModuleInfo& info = dynamic_info.export_modules.emplace_back(); + info.value = dyn->d_un.d_val; + info.name = dynamic_info.str_table + info.name_offset; + info.enc_id = EncodeId(info.id); + break; + }; + case DT_SCE_MODULE_ATTR: + LOG_INFO(Core_Linker, "unsupported DT_SCE_MODULE_ATTR value = ..........: {:#018x}", + dyn->d_un.d_val); + break; + case DT_SCE_EXPORT_LIB: { + LibraryInfo& info = dynamic_info.export_libs.emplace_back(); + info.value = dyn->d_un.d_val; + info.name = dynamic_info.str_table + info.name_offset; + info.enc_id = EncodeId(info.id); + break; + } + default: + LOG_INFO(Core_Linker, "unsupported dynamic tag ..........: {:#018x}", dyn->d_tag); + } + } +} + +void Module::LoadSymbols() { + const auto symbol_database = [this](Loader::SymbolsResolver& symbol, bool export_func) { + if (!dynamic_info.symbol_table || !dynamic_info.str_table || + dynamic_info.symbol_table_total_size == 0) { + LOG_INFO(Core_Linker, "Symbol table not found!"); + return; + } + for (auto* sym = dynamic_info.symbol_table; + reinterpret_cast(sym) < reinterpret_cast(dynamic_info.symbol_table) + + dynamic_info.symbol_table_total_size; + sym++) { + const u8 bind = sym->GetBind(); + const u8 type = sym->GetType(); + const u8 visibility = sym->GetVisibility(); + const auto id = std::string(dynamic_info.str_table + sym->st_name); + const auto ids = Common::SplitString(id, '#'); + if (ids.size() != 3) { + continue; + } + + const auto* library = FindLibrary(ids[1]); + const auto* module = FindModule(ids[2]); + ASSERT_MSG(library && module, "Unable to find library and module"); + if ((bind != STB_GLOBAL && bind != STB_WEAK) || + (type != STT_FUN && type != STT_OBJECT) || export_func != (sym->st_value != 0)) { + continue; + } + + const auto aeronid = AeroLib::FindByNid(ids.at(0).c_str()); + const auto nid_name = aeronid ? aeronid->name : "UNK"; + + Loader::SymbolResolver sym_r{}; + sym_r.name = ids.at(0); + sym_r.nidName = nid_name; + sym_r.library = library->name; + sym_r.library_version = library->version; + sym_r.module = module->name; + sym_r.module_version_major = module->version_major; + sym_r.module_version_minor = module->version_minor; + switch (type) { + case STT_NOTYPE: + sym_r.type = Loader::SymbolType::NoType; + break; + case STT_FUN: + sym_r.type = Loader::SymbolType::Function; + break; + case STT_OBJECT: + sym_r.type = Loader::SymbolType::Object; + break; + default: + sym_r.type = Loader::SymbolType::Unknown; + break; + } + const VAddr sym_addr = export_func ? sym->st_value + base_virtual_addr : 0; + symbol.AddSymbol(sym_r, sym_addr); + } + }; + symbol_database(export_sym, true); + symbol_database(import_sym, false); +} + +const ModuleInfo* Module::FindModule(std::string_view id) { + const auto& import_modules = dynamic_info.import_modules; + for (u32 i = 0; const auto& mod : import_modules) { + if (mod.enc_id == id) { + return &import_modules[i]; + } + i++; + } + const auto& export_modules = dynamic_info.export_modules; + for (u32 i = 0; const auto& mod : export_modules) { + if (mod.enc_id == id) { + return &export_modules[i]; + } + i++; + } + return nullptr; +} + +const LibraryInfo* Module::FindLibrary(std::string_view id) { + const auto& import_libs = dynamic_info.import_libs; + for (u32 i = 0; const auto& lib : import_libs) { + if (lib.enc_id == id) { + return &import_libs[i]; + } + i++; + } + const auto& export_libs = dynamic_info.export_libs; + for (u32 i = 0; const auto& lib : export_libs) { + if (lib.enc_id == id) { + return &export_libs[i]; + } + i++; + } + return nullptr; +} + +} // namespace Core diff --git a/src/core/module.h b/src/core/module.h new file mode 100644 index 00000000..bb11439e --- /dev/null +++ b/src/core/module.h @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/types.h" +#include "core/loader/elf.h" +#include "core/loader/symbols_resolver.h" + +namespace Core { + +struct ModuleInfo { + bool operator==(const ModuleInfo& other) const { + return version_major == other.version_major && version_minor == other.version_minor && + name == other.name; + } + std::string name; + union { + u64 value; + struct { + u32 name_offset; + u8 version_minor; + u8 version_major; + u16 id; + }; + }; + std::string enc_id; +}; + +struct LibraryInfo { + bool operator==(const LibraryInfo& other) const { + return version == other.version && name == other.name; + } + std::string name; + union { + u64 value; + struct { + u32 name_offset; + u16 version; + u16 id; + }; + }; + std::string enc_id; +}; + +struct ThreadLocalImage { + u64 align; + u64 image_size; + u64 offset; + u32 modid; + VAddr image_virtual_addr; + u64 init_image_size; +}; + +struct DynamicModuleInfo { + void* hash_table = nullptr; + u64 hash_table_size = 0; + + char* str_table = nullptr; + u64 str_table_size = 0; + + elf_symbol* symbol_table = nullptr; + u64 symbol_table_total_size = 0; + u64 symbol_table_entries_size = 0; + + u64 init_virtual_addr = 0; + u64 fini_virtual_addr = 0; + u64 pltgot_virtual_addr = 0; + u64 init_array_virtual_addr = 0; + u64 fini_array_virtual_addr = 0; + u64 preinit_array_virtual_addr = 0; + u64 init_array_size = 0; + u64 fini_array_size = 0; + u64 preinit_array_size = 0; + + elf_relocation* jmp_relocation_table = nullptr; + u64 jmp_relocation_table_size = 0; + s64 jmp_relocation_type = 0; + + elf_relocation* relocation_table = nullptr; + u64 relocation_table_size = 0; + u64 relocation_table_entries_size = 0; + + u64 debug = 0; + u64 textrel = 0; + u64 flags = 0; + + std::vector needed; + std::vector import_modules; + std::vector export_modules; + std::vector import_libs; + std::vector export_libs; + + std::string filename; +}; + +using ModuleFunc = int (*)(size_t, const void*); + +class Module { +public: + explicit Module(const std::filesystem::path& file); + ~Module(); + + VAddr GetBaseAddress() const noexcept { + return base_virtual_addr; + } + + VAddr GetEntryAddress() const noexcept { + return base_virtual_addr + elf.GetElfEntry(); + } + + bool IsValid() const noexcept { + return base_virtual_addr != 0; + } + + bool IsSharedLib() const noexcept { + return elf.IsSharedLib(); + } + + void* FindByName(std::string_view name) { + const auto symbols = export_sym.GetSymbols(); + const auto it = std::ranges::find(symbols, name, &Loader::SymbolRecord::nid_name); + if (it != symbols.end()) { + return reinterpret_cast(it->virtual_address); + } + return nullptr; + } + + template + T GetProcParam() const noexcept { + return reinterpret_cast(proc_param_virtual_addr); + } + + std::span GetImportModules() const { + return dynamic_info.import_modules; + } + + std::span GetExportModules() const { + return dynamic_info.export_modules; + } + + std::span GetImportLibs() const { + return dynamic_info.import_libs; + } + + std::span GetExportLibs() const { + return dynamic_info.export_libs; + } + + void ForEachRelocation(auto&& func) { + for (u32 i = 0; i < dynamic_info.relocation_table_size / sizeof(elf_relocation); i++) { + func(&dynamic_info.relocation_table[i], false); + } + for (u32 i = 0; i < dynamic_info.jmp_relocation_table_size / sizeof(elf_relocation); i++) { + func(&dynamic_info.jmp_relocation_table[i], true); + } + } + + void Start(size_t args, const void* argp, void* param); + void LoadModuleToMemory(); + void LoadDynamicInfo(); + void LoadSymbols(); + + const ModuleInfo* FindModule(std::string_view id); + const LibraryInfo* FindLibrary(std::string_view id); + +public: + std::filesystem::path file; + Loader::Elf elf; + u64 aligned_base_size{}; + VAddr base_virtual_addr{}; + VAddr proc_param_virtual_addr{}; + DynamicModuleInfo dynamic_info{}; + std::vector m_dynamic; + std::vector m_dynamic_data; + Loader::SymbolsResolver export_sym; + Loader::SymbolsResolver import_sym; + ThreadLocalImage tls{}; +}; + +} // namespace Core diff --git a/src/core/tls.cpp b/src/core/tls.cpp index b945baef..15147788 100644 --- a/src/core/tls.cpp +++ b/src/core/tls.cpp @@ -44,13 +44,15 @@ constexpr static TLSPattern TlsPatterns[] = { #ifdef _WIN32 static DWORD slot = 0; -void SetTLSStorage(u64 image_address) { - // Guest apps will use both positive and negative offsets to the TLS pointer. - // User data at probably in negative offsets, while pthread data at positive offset. - const BOOL result = TlsSetValue(slot, reinterpret_cast(image_address)); +void SetTcbBase(void* image_address) { + const BOOL result = TlsSetValue(slot, image_address); ASSERT(result != 0); } +Tcb* GetTcbBase() { + return reinterpret_cast(TlsGetValue(slot)); +} + void PatchTLS(u64 segment_addr, u64 segment_size, Xbyak::CodeGenerator& c) { using namespace Xbyak::util; @@ -81,6 +83,7 @@ void PatchTLS(u64 segment_addr, u64 segment_size, Xbyak::CodeGenerator& c) { std::memcpy(&offset, code + tls_pattern.pattern_size, sizeof(u64)); LOG_INFO(Core_Linker, "PATTERN64 FOUND at {}, reg: {} offset: {:#x}", fmt::ptr(code), tls_pattern.target_reg, offset); + continue; } ASSERT(offset == 0); @@ -132,7 +135,11 @@ void PatchTLS(u64 segment_addr, u64 segment_size, Xbyak::CodeGenerator& c) { #else -void SetTLSStorage(u64 image_address) { +void SetTcbBase(void* image_address) { + UNREACHABLE_MSG("Thread local storage is unimplemented on posix platforms!"); +} + +Tcb* GetTcbBase() { UNREACHABLE_MSG("Thread local storage is unimplemented on posix platforms!"); } diff --git a/src/core/tls.h b/src/core/tls.h index e9825bf6..3f154420 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -11,8 +11,22 @@ class CodeGenerator; namespace Core { -/// Sets the data pointer that contains the TLS image. -void SetTLSStorage(u64 image_address); +union DtvEntry { + size_t counter; + void* pointer; +}; + +struct Tcb { + Tcb* tcb_self; + DtvEntry* tcb_dtv; + void* tcb_thread; +}; + +/// Sets the data pointer to the TCB block. +void SetTcbBase(void* image_address); + +/// Retrieves Tcb structure for the calling thread. +Tcb* GetTcbBase(); /// Patches any instructions that access guest TLS to use provided storage. void PatchTLS(u64 segment_addr, u64 segment_size, Xbyak::CodeGenerator& c); diff --git a/src/main.cpp b/src/main.cpp index 43196046..e72cb136 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,7 +76,7 @@ int main(int argc, char* argv[]) { } auto linker = Common::Singleton::Instance(); - Libraries::InitHLELibs(&linker->getHLESymbols()); + Libraries::InitHLELibs(&linker->GetHLESymbols()); linker->LoadModule(path); // check if we have system modules to load @@ -103,7 +103,7 @@ int main(int argc, char* argv[]) { } } if (!found) { - Libraries::LibC::libcSymbolsRegister(&linker->getHLESymbols()); + Libraries::LibC::libcSymbolsRegister(&linker->GetHLESymbols()); } std::thread mainthread([linker]() { linker->Execute(); }); Discord::RPC discordRPC; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e3b38ca4..37d6f72b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -85,7 +85,6 @@ void Rasterizer::Draw(bool is_indexed) { } void Rasterizer::DispatchDirect() { - compute_done = true; return; const auto cmdbuf = scheduler.CommandBuffer(); const auto& cs_program = liverpool->regs.cs_program; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 8f365f65..a1b6a5a6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -49,7 +49,6 @@ private: Core::MemoryManager* memory; PipelineCache pipeline_cache; StreamBuffer vertex_index_buffer; - bool compute_done{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index 0d9bedfe..cad4daea 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -92,14 +92,14 @@ StreamBuffer::~StreamBuffer() { std::tuple StreamBuffer::Map(u64 size, u64 alignment) { if (!is_coherent && type == BufferType::Stream) { - size = Common::alignUp(size, instance.NonCoherentAtomSize()); + size = Common::AlignUp(size, instance.NonCoherentAtomSize()); } ASSERT(size <= stream_buffer_size); mapped_size = size; if (alignment > 0) { - offset = Common::alignUp(offset, alignment); + offset = Common::AlignUp(offset, alignment); } bool invalidate{false}; @@ -124,7 +124,7 @@ std::tuple StreamBuffer::Map(u64 size, u64 alignment) { void StreamBuffer::Commit(u64 size) { if (!is_coherent && type == BufferType::Stream) { - size = Common::alignUp(size, instance.NonCoherentAtomSize()); + size = Common::AlignUp(size, instance.NonCoherentAtomSize()); } ASSERT_MSG(size <= mapped_size, "Reserved size {} is too small compared to {}", mapped_size, diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index d3c6b678..bf39963c 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -59,7 +59,7 @@ LONG WINAPI GuestFaultSignalHandler(EXCEPTION_POINTERS* pExp) noexcept { } #endif -static constexpr u64 StreamBufferSize = 128_MB; +static constexpr u64 StreamBufferSize = 512_MB; static constexpr u64 PageShift = 12; TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_)