diff --git a/core/libs/prx/libkernel/Module/src/Module.cpp b/core/libs/prx/libkernel/Module/src/Module.cpp index 886503c99..6636e0dfc 100644 --- a/core/libs/prx/libkernel/Module/src/Module.cpp +++ b/core/libs/prx/libkernel/Module/src/Module.cpp @@ -153,14 +153,41 @@ int APS5_VABI sceKernelGetModuleInfoForUnwind(uint64_t addr, int flags, ModuleIn #endif } +namespace { + +struct PendingModuleArgs { + std::size_t args = 0; + const void* argp = nullptr; +}; +thread_local PendingModuleArgs pendingModuleArgs; +thread_local int pendingModuleInitResult = 0; + +} + +extern "C" { + +const void* __aps5_get_pending_module_args_nid_no_patch() { + return &pendingModuleArgs; +} + +void __aps5_set_module_init_result_nid_no_patch(int result) { + pendingModuleInitResult = result; +} + +} + KernelModule APS5_VABI sceKernelLoadStartModule(const char* module_file_name, size_t args, const void* argp, uint32_t flags, const KernelLoadModuleOpt* opt, int* res) { - (void)args; - (void)argp; (void)flags; (void)opt; if (res) *res = 0; if (!module_file_name) return static_cast(SCE_KERNEL_ERROR_EFAULT); + pendingModuleArgs = {args, argp}; + pendingModuleInitResult = 0; void* handle = dlopen_nid_postfix(module_file_name, kRtldNow); + const int started = pendingModuleInitResult; + pendingModuleArgs = {}; + pendingModuleInitResult = 0; + if (res) *res = started; if (!handle) return static_cast(SCE_KERNEL_ERROR_ENOENT); return static_cast(reinterpret_cast(handle)); } diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index d93145995..6ed3c7b25 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -239,6 +239,13 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_registry.py $) add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) + add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp) + target_include_directories(guest_module_args_host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../libs) + set_target_properties(guest_module_args_host PROPERTIES + PREFIX "" SUFFIX ".prx" WINDOWS_EXPORT_ALL_SYMBOLS ON) + add_test(NAME guest_module_args COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_args.py + $ $) add_test(NAME guest_absolute_symbols COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $) add_test(NAME windows_import_modules COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp index 9653c8b9f..834c21021 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp @@ -26,8 +26,19 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma const auto extraOffset = bytes.size(); const auto extraAddress = Io::AlignUp64(end, 0x4000); const auto address = [&] { return extraAddress + bytes.size() - extraOffset; }; + const bool passesModuleArgs = image.Init != 0; + std::uint64_t argsSlot = 0; + std::uint64_t resultSlot = 0; + if (passesModuleArgs) { + Io::AlignBuffer(bytes, 8); + argsSlot = address(); + bytes.insert(bytes.end(), 8, 0); + resultSlot = address(); + bytes.insert(bytes.end(), 8, 0); + } auto strings = image.Dynamic.DynStrData; auto symbols = image.Dynamic.DynSymData; + constexpr std::uint64_t kGlobDat = 6; const auto addString = [&](const std::string& value) { if (strings.size() > std::numeric_limits::max()) throw Domain::RelinkerException("Guest string table too large"); const auto offset = static_cast(strings.size()); @@ -46,6 +57,30 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma std::vector needed; for (const auto& dependency : dependencies) needed.push_back(addString(dependency)); if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2")); + const auto addUndefinedImport = [&](const std::string& name) { + const auto nameOffset = addString(name); + const auto entry = symbols.size(); + symbols.resize(entry + 24, 0); + Io::WriteU32(symbols, entry, nameOffset); + symbols[entry + 4] = 0x12; + Io::WriteU16(symbols, entry + 6, 0); + return entry / 24; + }; + auto relaData = image.Dynamic.RelaData; + std::uint64_t argsImport = 0; + std::uint64_t resultImport = 0; + const auto addGlobDat = [&](std::uint64_t slot, std::uint64_t symbol) { + const auto entry = relaData.size(); + relaData.resize(entry + 24, 0); + Io::WriteU64(relaData, entry, slot); + Io::WriteU64(relaData, entry + 8, (symbol << 32) | kGlobDat); + }; + if (passesModuleArgs) { + argsImport = addUndefinedImport("__aps5_get_pending_module_args_nid_no_patch"); + resultImport = addUndefinedImport("__aps5_set_module_init_result_nid_no_patch"); + addGlobDat(argsSlot, argsImport); + addGlobDat(resultSlot, resultImport); + } const auto soname = addString(image.OutputName); const auto search = addString(runPath); const auto strAddress = address(); @@ -54,7 +89,7 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma const auto symAddress = address(); bytes.insert(bytes.end(), symbols.begin(), symbols.end()); const auto hashAddress = address(); - const auto count = static_cast(image.Symbols.size()); + const auto count = static_cast(symbols.size() / 24); const auto bucketCount = std::max(count, 1); std::vector buckets(bucketCount), chains(count); for (std::uint32_t index = count; index-- > 1;) { @@ -72,20 +107,49 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma for (const auto chain : chains) Io::AppendU32(bytes, chain); Io::AlignBuffer(bytes, 8); const auto relaAddress = address(); - bytes.insert(bytes.end(), image.Dynamic.RelaData.begin(), image.Dynamic.RelaData.end()); + bytes.insert(bytes.end(), relaData.begin(), relaData.end()); const auto pltAddress = address(); bytes.insert(bytes.end(), image.Dynamic.RelaPltData.begin(), image.Dynamic.RelaPltData.end()); + constexpr std::size_t kArgsDisplacement = 3, kTargetDisplacement = 0x11, kResultDisplacement = 0x19; + constexpr std::size_t kStubSize = 0x1f; + const auto stubDisplacement = [](std::uint64_t target, std::uint64_t next) { + const auto distance = static_cast(target) - static_cast(next); + if (distance < std::numeric_limits::min() || distance > std::numeric_limits::max()) + throw Domain::RelinkerException("Guest initializer exceeds relative branch range"); + return static_cast(static_cast(distance)); + }; const auto lifecycle = [&](std::uint64_t target) { if (target == 0) return std::uint64_t{}; const auto start = address(); - bytes.insert(bytes.end(), {0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xe9}); - const auto displacement = static_cast(target) - static_cast(address() + 4); - if (displacement < std::numeric_limits::min() || displacement > std::numeric_limits::max()) throw Domain::RelinkerException("Guest initializer exceeds relative branch range"); - Io::AppendU32(bytes, static_cast(displacement)); + std::vector stub(kStubSize, 0xc3); + stub[0x00] = 0x50; + stub[0x01] = 0xff; stub[0x02] = 0x15; + Io::WriteU32(stub, kArgsDisplacement, stubDisplacement(argsSlot, start + 7)); + stub[0x07] = 0x48; stub[0x08] = 0x8b; stub[0x09] = 0x38; + stub[0x0a] = 0x48; stub[0x0b] = 0x8b; stub[0x0c] = 0x70; stub[0x0d] = 0x08; + stub[0x0e] = 0x31; stub[0x0f] = 0xd2; + stub[0x10] = 0xe8; + Io::WriteU32(stub, kTargetDisplacement, stubDisplacement(target, start + 0x15)); + stub[0x15] = 0x89; stub[0x16] = 0xc7; + stub[0x17] = 0xff; stub[0x18] = 0x15; + Io::WriteU32(stub, kResultDisplacement, stubDisplacement(resultSlot, start + 0x1d)); + stub[0x1d] = 0x58; + stub[0x1e] = 0xc3; + bytes.insert(bytes.end(), stub.begin(), stub.end()); + return start; + }; + const auto callThrough = [&](std::uint64_t target) { + if (target == 0) return std::uint64_t{}; + const auto start = address(); + const auto distance = static_cast(target) - static_cast(start + 5); + if (distance < std::numeric_limits::min() || distance > std::numeric_limits::max()) + throw Domain::RelinkerException("Guest finaliser exceeds relative branch range"); + bytes.push_back(0xe9); + Io::AppendU32(bytes, static_cast(distance)); return start; }; const auto init = lifecycle(image.Init); - const auto fini = lifecycle(image.Fini); + const auto fini = callThrough(image.Fini); Io::AlignBuffer(bytes, 8); const auto dynamicOffset = bytes.size(); const auto dynamicAddress = address(); @@ -98,9 +162,9 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma tag(10, strings.size()); tag(6, symAddress); tag(11, 24); - if (!image.Dynamic.RelaData.empty()) { + if (!relaData.empty()) { tag(7, relaAddress); - tag(8, image.Dynamic.RelaData.size()); + tag(8, relaData.size()); tag(9, 24); } if (!image.Dynamic.RelaPltData.empty()) { diff --git a/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp b/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp new file mode 100644 index 000000000..708d39dc7 --- /dev/null +++ b/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp @@ -0,0 +1,56 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace { + +struct Pending { + std::size_t args = 0; + const void* argp = nullptr; +} pending; + +[[noreturn]] void Fail(const char* message) noexcept { + std::fprintf(stderr, "Guest module argument fixture: %s\n", message); + std::fflush(stderr); + std::_Exit(EXIT_FAILURE); +} + +FILE* Events() { + const auto* path = std::getenv("ANYPS5_GUEST_MODULE_ARGS"); + if (!path) Fail("Missing ANYPS5_GUEST_MODULE_ARGS"); + auto* file = std::fopen(path, "ab"); + if (!file) Fail("Cannot open argument event file"); + return file; +} + +void Write(const char* text) { + auto* file = Events(); + const auto written = std::fputs(text, file); + const auto closed = std::fclose(file); + if (written == EOF || closed != 0) Fail("Cannot write argument event"); +} + +} + +extern "C" void APS5_VABI RecordArgs(std::size_t args, const void* argp) { + char line[80]; + std::snprintf(line, sizeof(line), "args=%016zx argp=%016zx\n", args, + static_cast(reinterpret_cast(argp))); + Write(line); +} + +extern "C" void SetPendingArgs(std::size_t args, const void* argp) { + pending.args = args; + pending.argp = argp; +} + +extern "C" const void* __aps5_get_pending_module_args_nid_no_patch() { + return &pending; +} + +extern "C" void __aps5_set_module_init_result_nid_no_patch(int result) { + char line[40]; + std::snprintf(line, sizeof(line), "result=%d\n", result); + Write(line); +} diff --git a/core/relinker/relinker/tests/test_guest_module_args.py b/core/relinker/relinker/tests/test_guest_module_args.py new file mode 100644 index 000000000..54ec3622a --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_module_args.py @@ -0,0 +1,116 @@ +from pathlib import Path +import os +import shutil +import struct +import subprocess +import sys +import tempfile + +from test_guest_symbol_names import dynamic_tags, imported_symbols, module_symbols +from test_windows_import_modules import executable + +ARGS = 0x1122334455667788 +ARGP = 0x99AABBCCDDEEFF00 +RETURNED = 0x5A17 +GETTER = '__aps5_get_pending_module_args_nid_no_patch' +SETTER = '__aps5_set_module_init_result_nid_no_patch' + + +def args_module(): + image = bytearray(0x3000) + image[:16] = b'\x7fELF\x02\x01\x01' + bytes(9) + struct.pack_into('