Merge pull request #1102 from jeffrey12cali/fix/module-args-on-linux
fix(libkernel): pass module args and argp to runtime-loaded modules on Linux
This commit is contained in:
commit
ff8961dfbc
|
|
@ -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<KernelModule>(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<KernelModule>(SCE_KERNEL_ERROR_ENOENT);
|
||||
return static_cast<KernelModule>(reinterpret_cast<intptr_t>(handle));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,6 +239,13 @@ if(BUILD_TESTING)
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_registry.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $<TARGET_FILE:relinker>)
|
||||
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
|
||||
$<TARGET_FILE:relinker> $<TARGET_FILE:guest_module_args_host>)
|
||||
add_test(NAME guest_absolute_symbols COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME windows_import_modules COMMAND ${Python3_EXECUTABLE}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,19 @@ std::vector<std::uint8_t> 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<std::uint32_t>::max()) throw Domain::RelinkerException("Guest string table too large");
|
||||
const auto offset = static_cast<std::uint32_t>(strings.size());
|
||||
|
|
@ -46,6 +57,30 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestIma
|
|||
std::vector<std::uint64_t> needed;
|
||||
for (const auto& dependency : dependencies) needed.push_back(addString(dependency));
|
||||
if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2"));
|
||||
const auto 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<std::uint8_t> 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<std::uint32_t>(image.Symbols.size());
|
||||
const auto count = static_cast<std::uint32_t>(symbols.size() / 24);
|
||||
const auto bucketCount = std::max<std::uint32_t>(count, 1);
|
||||
std::vector<std::uint32_t> buckets(bucketCount), chains(count);
|
||||
for (std::uint32_t index = count; index-- > 1;) {
|
||||
|
|
@ -72,20 +107,49 @@ std::vector<std::uint8_t> 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<std::int64_t>(target) - static_cast<std::int64_t>(next);
|
||||
if (distance < std::numeric_limits<std::int32_t>::min() || distance > std::numeric_limits<std::int32_t>::max())
|
||||
throw Domain::RelinkerException("Guest initializer exceeds relative branch range");
|
||||
return static_cast<std::uint32_t>(static_cast<std::int32_t>(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<std::int64_t>(target) - static_cast<std::int64_t>(address() + 4);
|
||||
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max()) throw Domain::RelinkerException("Guest initializer exceeds relative branch range");
|
||||
Io::AppendU32(bytes, static_cast<std::uint32_t>(displacement));
|
||||
std::vector<std::uint8_t> 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<std::int64_t>(target) - static_cast<std::int64_t>(start + 5);
|
||||
if (distance < std::numeric_limits<std::int32_t>::min() || distance > std::numeric_limits<std::int32_t>::max())
|
||||
throw Domain::RelinkerException("Guest finaliser exceeds relative branch range");
|
||||
bytes.push_back(0xe9);
|
||||
Io::AppendU32(bytes, static_cast<std::uint32_t>(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<std::uint8_t> 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()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
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<std::size_t>(reinterpret_cast<std::uintptr_t>(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);
|
||||
}
|
||||
|
|
@ -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('<HHIQQQIHHHHHH', image, 16,
|
||||
3, 62, 1, 0, 64, 0, 0, 64, 56, 3, 0, 0, 0)
|
||||
struct.pack_into('<IIQQQQQQ', image, 64, 1, 7, 0, 0, 0, len(image), len(image), 0x1000)
|
||||
struct.pack_into('<IIQQQQQQ', image, 176, 0x6474e551, 6, 0, 0, 0, 0, 0, 16)
|
||||
strings = b'\0RecordArgs\0libc.prx\0shared\0'
|
||||
tags = [(5, 0x2600), (10, len(strings)), (6, 0x2400), (11, 24), (4, 0x2500),
|
||||
(7, 0x2700), (8, 24), (9, 24), (1, 12), (12, 0x1010), (0, 0)]
|
||||
struct.pack_into('<IIQQQQQQ', image, 120, 2, 6, 0x2000, 0x2000, 0x2000,
|
||||
len(tags) * 16, len(tags) * 16, 8)
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into('<qQ', image, 0x2000 + index * 16, *tag)
|
||||
image[0x2600:0x2600 + len(strings)] = strings
|
||||
struct.pack_into('<IBBHQQ', image, 0x2418, 1, 0x12, 0, 0, 0, 0)
|
||||
struct.pack_into('<IBBHQQ', image, 0x2430, 21, 0x12, 0, 1, 0x1000, 6)
|
||||
struct.pack_into('<II', image, 0x2500, 1, 3)
|
||||
struct.pack_into('<QQq', image, 0x2700, 0x2820, (1 << 32) | 6, 0)
|
||||
image[0x1000:0x1006] = b'\xb8\x2a\0\0\0\xc3'
|
||||
code = b'\x48\x83\xec\x08\xff\x15' + struct.pack('<i', 0x2820 - 0x101a)
|
||||
code += b'\x48\x83\xc4\x08\xb8' + struct.pack('<I', RETURNED) + b'\xc3'
|
||||
image[0x1010:0x1010 + len(code)] = code
|
||||
return image
|
||||
|
||||
|
||||
def no_relocation_module():
|
||||
image = args_module()
|
||||
for position in range(0x2000, 0x2200, 16):
|
||||
tag, _ = struct.unpack_from('<qQ', image, position)
|
||||
if tag == 0:
|
||||
break
|
||||
if tag == 8:
|
||||
struct.pack_into('<Q', image, position + 8, 0)
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
if sys.argv[1] == '--load':
|
||||
import ctypes
|
||||
host = ctypes.CDLL(sys.argv[2])
|
||||
host.SetPendingArgs.argtypes = [ctypes.c_size_t, ctypes.c_void_p]
|
||||
host.SetPendingArgs(ARGS, ARGP)
|
||||
ctypes.CDLL(sys.argv[3])
|
||||
return
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
host = Path(sys.argv[2]).resolve()
|
||||
native = sys.platform == 'linux' and os.uname().machine == 'x86_64'
|
||||
with tempfile.TemporaryDirectory(prefix='anyps5-guest-args-') as directory:
|
||||
work = Path(directory)
|
||||
modules = work / 'sce_module'
|
||||
modules.mkdir()
|
||||
name = 'args.prx'
|
||||
(modules / name).write_bytes(args_module())
|
||||
source = work / 'input.elf'
|
||||
source.write_bytes(executable(name))
|
||||
output = work / 'output.elf'
|
||||
result = subprocess.run([str(relinker), '--skip-syscall-check', str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
converted = output.parent / 'app0' / 'sce_module' / (name + '.guest.prx')
|
||||
image = converted.read_bytes()
|
||||
assert GETTER in imported_symbols(image), imported_symbols(image)
|
||||
assert SETTER in imported_symbols(image), imported_symbols(image)
|
||||
assert 'shared#guest' in module_symbols(image), module_symbols(image)
|
||||
bare = work / 'bare'
|
||||
bare_modules = bare / 'sce_module'
|
||||
bare_modules.mkdir(parents=True)
|
||||
(bare_modules / name).write_bytes(no_relocation_module())
|
||||
bare_source = bare / 'input.elf'
|
||||
bare_source.write_bytes(executable(name))
|
||||
bare_result = subprocess.run([str(relinker), '--skip-syscall-check', str(bare_source), str(bare / 'output.elf')],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert bare_result.returncode == 0, (bare_result.stdout, bare_result.stderr)
|
||||
bare_image = (bare / 'app0' / 'sce_module' / (name + '.guest.prx')).read_bytes()
|
||||
bare_imports = imported_symbols(bare_image)
|
||||
assert GETTER in bare_imports, bare_imports
|
||||
assert SETTER in bare_imports, bare_imports
|
||||
assert dynamic_tags(bare_image).get(8) == 48, dynamic_tags(bare_image)
|
||||
if not native:
|
||||
print('Guest module argument tests passed: native execution skipped')
|
||||
return
|
||||
libraries = work / 'libs'
|
||||
libraries.mkdir()
|
||||
shutil.copyfile(host, libraries / 'libc.prx')
|
||||
events = work / 'events.txt'
|
||||
run = subprocess.run([sys.executable, __file__, '--load', str(libraries / 'libc.prx'), str(converted)],
|
||||
env={**os.environ, 'ANYPS5_GUEST_MODULE_ARGS': str(events)},
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert run.returncode == 0, (run.returncode, run.stdout, run.stderr)
|
||||
recorded = events.read_text()
|
||||
assert f'args={ARGS:016x} argp={ARGP:016x}' in recorded, recorded
|
||||
assert f'result={RETURNED}' in recorded, recorded
|
||||
print('Guest module argument tests passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in New Issue