Merge pull request #6112 from ayam04/fix/relinker-guest-preinit

fix(relinker): run guest preinit arrays before the module initializer
This commit is contained in:
Dean authored and GitHub committed 2026-10-19 07:33:14 +13:00
commit 376c040294
13 files changed
+363 -4

No files matched your search

+4
View File
@@ -526,6 +526,9 @@ if(BUILD_TESTING)
add_test(NAME guest_deferred_dependencies COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_deferred_dependencies.py
$<TARGET_FILE:relinker> $<TARGET_FILE:guest_module_args_host>)
add_test(NAME guest_preinit COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_preinit.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 guest_module_identity COMMAND ${Python3_EXECUTABLE}
@@ -539,6 +542,7 @@ if(BUILD_TESTING)
add_asan_test_options(guest_module_args verify_asan_link_order=0)
add_asan_test_options(guest_deferred_dependencies verify_asan_link_order=0)
add_asan_test_options(guest_module_fini verify_asan_link_order=0)
add_asan_test_options(guest_preinit verify_asan_link_order=0)
add_test(NAME guest_needed_nested_modules COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_needed_nested_modules.py $<TARGET_FILE:relinker>)
set(windows_stdio_host_arguments)
@@ -22,6 +22,7 @@ struct GuestRuntime {
std::vector<GuestImport> Imports;
std::vector<std::uint32_t> InitArrayRvas;
std::vector<std::uint32_t> FiniArrayRvas;
std::vector<std::uint32_t> PreInitArrayRvas;
std::uint32_t InitRva = 0;
std::uint32_t FiniRva = 0;
bool UsePlatformTlsResolver = true;
@@ -50,6 +50,7 @@ struct MacOsImageInput {
std::uint32_t FiniRva = 0;
std::vector<std::uint32_t> InitArrayRvas;
std::vector<std::uint32_t> FiniArrayRvas;
std::vector<std::uint32_t> PreInitArrayRvas;
std::vector<std::uint32_t> TlsModuleSlots;
std::vector<MacOsTlsImport> TlsImports;
std::vector<MacOsTlsExport> TlsExports;
@@ -150,7 +150,31 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestIma
Io::AppendU32(bytes, static_cast<std::uint32_t>(distance));
return start;
};
const auto preinit = [&](std::uint64_t target) {
if (image.PreInitArray.empty()) return std::uint64_t{};
if (target == 0) throw Domain::RelinkerException("Guest preinit array has no initializer");
const auto start = address();
std::vector<std::uint8_t> stub{0x50};
for (const auto slot : image.PreInitArray) {
const auto load = static_cast<std::int64_t>(stub.size());
const auto distance = static_cast<std::int64_t>(slot) - static_cast<std::int64_t>(start + static_cast<std::uint64_t>(load) + 7);
if (distance < std::numeric_limits<std::int32_t>::min() || distance > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("Guest preinit exceeds relative branch range");
stub.insert(stub.end(), {0x48, 0x8b, 0x05});
Io::AppendU32(stub, static_cast<std::uint32_t>(static_cast<std::int32_t>(distance)));
stub.insert(stub.end(), {0x48, 0x85, 0xc0, 0x74, 0x08, 0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xff, 0xd0});
}
stub.push_back(0x58);
const auto distance = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(start + stub.size() + 5);
if (distance < std::numeric_limits<std::int32_t>::min() || distance > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("Guest preinit exceeds relative branch range");
stub.push_back(0xe9);
Io::AppendU32(stub, static_cast<std::uint32_t>(static_cast<std::int32_t>(distance)));
bytes.insert(bytes.end(), stub.begin(), stub.end());
return start;
};
const auto init = lifecycle(image.Init);
const auto preinitInit = preinit(init);
const auto fini = finalizer(image.Fini);
Io::AlignBuffer(bytes, 8);
const auto dynamicOffset = bytes.size();
@@ -177,7 +201,8 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestIma
}
if (!image.InitArray.empty()) { tag(25, image.InitArray.front()); tag(27, image.InitArray.size() * 8); }
if (!image.FiniArray.empty()) { tag(26, image.FiniArray.front()); tag(28, image.FiniArray.size() * 8); }
if (init != 0) tag(12, init);
const auto entry = preinitInit != 0 ? preinitInit : init;
if (entry != 0) tag(12, entry);
if (fini != 0) tag(13, fini);
tag(30, DF_BIND_NOW | (image.Symbolic ? DF_SYMBOLIC : 0));
tag(0, 0);
@@ -109,6 +109,7 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteMacOs(const Relinker::GuestIma
input.RunPaths = MachORunPaths(runPath, "@loader_path");
for (const auto slot : guest.InitArray) input.InitArrayRvas.push_back(image.GetRva(slot, 8));
for (const auto slot : guest.FiniArray) input.FiniArrayRvas.push_back(image.GetRva(slot, 8));
for (const auto slot : guest.PreInitArray) input.PreInitArrayRvas.push_back(image.GetRva(slot, 8));
input.InitRva = guest.Init == 0 ? 0 : image.GetRva(guest.Init);
input.FiniRva = guest.Fini == 0 ? 0 : image.GetRva(guest.Fini);
input.Trampolines = &guest.Trampolines;
@@ -534,7 +534,7 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
input.Binds.push_back({"___cxa_atexit", metaRva + static_cast<std::uint32_t>(MetaCxaAtexitSlot)});
}
const bool hasInit = hasTls || !input.TlsImports.empty() || input.InitRva != 0 || !input.InitArrayRvas.empty() || hasFini;
const bool hasInit = hasTls || !input.TlsImports.empty() || input.InitRva != 0 || !input.InitArrayRvas.empty() || !input.PreInitArrayRvas.empty() || hasFini;
std::vector<Section> dataSections{{"__meta", 0, metaSize, 0, 3}};
if (hasInit) {
const auto initStub = code.GetRva();
@@ -554,6 +554,7 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
}
code.Rip({0x48, 0x89, 0x05}, import.Rva);
}
for (const auto slot : input.PreInitArrayRvas) callSlot(slot);
if (input.InitRva != 0) {
code.Rip({0xff, 0x15}, metaRva + static_cast<std::uint32_t>(moduleArgsSlot));
code.Emit({0x48, 0x8b, 0x38, 0x48, 0x8b, 0x70, 0x08, 0x31, 0xd2});
@@ -1,6 +1,7 @@
#include <elfpatcher/general/GuestModuleWriter.hpp>
#include <elfpatcher/windows/WindowsImportBuilder.hpp>
#include <elfpatcher/windows/WindowsLoadImage.hpp>
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
#include <elfpatcher/windows/WindowsTlsBuilder.hpp>
#include <elfpatcher/windows/WindowsPeWriter.hpp>
#include <elfpatcher/windows/WindowsRelocationBuilder.hpp>
@@ -155,7 +156,37 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
for (const auto slot : guest.InitArray) Io::AppendU32(lifecycle.Data, image.GetRva(slot, 8));
if (!exports.emplace(Relinker::GuestInitializeExport, nextRva).second) throw Domain::RelinkerException("Duplicate guest export: " + std::string(Relinker::GuestInitializeExport));
nextRva = AlignRva(nextRva + lifecycle.Data.size());
sections.push_back(std::move(lifecycle));
if (guest.PreInitArray.empty()) sections.push_back(std::move(lifecycle));
else {
if (guest.Init == 0) throw Domain::RelinkerException("Guest preinit array has no initializer");
const auto thunkRva = nextRva;
const auto codeSize = static_cast<std::uint32_t>(40 + 23 * guest.PreInitArray.size());
const auto slotsRva = static_cast<std::uint32_t>(thunkRva + codeSize + (8 - codeSize % 8) % 8);
WindowsStubEmitter code(thunkRva);
code.Emit({0x41, 0x54, 0x41, 0x55, 0x41, 0x56});
code.Emit({0x49, 0x89, 0xfc, 0x49, 0x89, 0xf5, 0x49, 0x89, 0xd6});
for (std::size_t index = 0; index < guest.PreInitArray.size(); ++index) {
code.Rip({0x48, 0x8b, 0x05}, slotsRva + static_cast<std::uint32_t>(index) * 8);
code.Emit({0x48, 0x8b, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x08});
code.Emit({0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xff, 0xd0});
}
code.Emit({0x4c, 0x89, 0xe7, 0x4c, 0x89, 0xee, 0x4c, 0x89, 0xf2});
code.Rip({0x48, 0x8b, 0x05}, slotsRva + static_cast<std::uint32_t>(guest.PreInitArray.size()) * 8);
code.Emit({0xff, 0xd0});
code.Emit({0x41, 0x5e, 0x41, 0x5d, 0x41, 0x5c, 0xc3});
auto thunk = code.TakeBytes();
while (thunk.size() % 8 != 0) thunk.push_back(0x90);
for (const auto slot : guest.PreInitArray) {
relocations.push_back(CheckedRva(thunkRva + thunk.size()));
Io::AppendU64(thunk, ImageBase + image.GetRva(slot, 8));
}
relocations.push_back(CheckedRva(thunkRva + thunk.size()));
Io::AppendU64(thunk, ImageBase + image.GetRva(guest.Init, 1));
Io::WriteU32(lifecycle.Data, 0, thunkRva);
nextRva = AlignRva(nextRva + thunk.size());
sections.push_back(std::move(lifecycle));
sections.push_back({".ginitc", thunkRva, SectionRead | SectionExecute | 0x60u, std::move(thunk)});
}
}
if (exports.size() > 65535) throw Domain::RelinkerException("Too many guest PE exports");
PeSection exportSection{".edata", nextRva, SectionRead | 0x40u, std::vector<std::uint8_t>(40)};
@@ -202,6 +233,7 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
const auto entry = nextRva;
sections.push_back({".dllmain", entry, SectionRead | SectionExecute | 0x20u, {0xb8, 1, 0, 0, 0, 0xc3}});
for (const auto slot : guest.InitArray) runtime.InitArrayRvas.push_back(image.GetRva(slot, 8));
for (const auto slot : guest.PreInitArray) runtime.PreInitArrayRvas.push_back(image.GetRva(slot, 8));
for (const auto slot : guest.FiniArray) runtime.FiniArrayRvas.push_back(image.GetRva(slot, 8));
runtime.InitRva = guest.Init == 0 ? 0 : image.GetRva(guest.Init);
runtime.UnmatchedImportModules.assign(guest.Dynamic.UnmatchedImportModules.begin(), guest.Dynamic.UnmatchedImportModules.end());
@@ -31,6 +31,7 @@ void WindowsGuestStartup::Initialize(WindowsStubEmitter& code, const std::vector
for (std::size_t index = 0; index < modules.size(); ++index) {
if (modules[index].DeferInitialization) continue;
const auto handle = CheckedRva(handles + index * 8);
for (const auto slot : modules[index].PreInitArrayRvas) callLifecycle(code, handle, slot, true);
callLifecycle(code, handle, modules[index].InitRva);
for (const auto slot : modules[index].InitArrayRvas) callLifecycle(code, handle, slot, true);
}
@@ -47,6 +47,7 @@ struct GuestImage {
std::vector<GuestSymbol> Symbols;
std::vector<std::string> Dependencies;
Domain::SysVDynamicSection Dynamic;
std::vector<std::uint64_t> PreInitArray;
std::vector<std::uint64_t> InitArray;
std::vector<std::uint64_t> FiniArray;
std::uint64_t Init = 0;
@@ -358,6 +358,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
entries.push_back(address + offset);
}
};
readLifecycleArray(32, 33, image.PreInitArray);
readLifecycleArray(25, 27, image.InitArray);
readLifecycleArray(26, 28, image.FiniArray);
if (tags.contains(33) && tags.at(33) != 0 && image.Init == 0) fail("Guest PREINIT_ARRAY has no module initializer");
@@ -0,0 +1,290 @@
from pathlib import Path
import os
import platform
import shutil
import struct
import subprocess
import sys
import tempfile
from test_guest_symbol_names import dynamic_tags, file_offset, imported_symbols, module_symbols
from test_macos_output import MachO
from test_guest_intel_trampolines import main_fixture, pe_sections
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'
SHARED = 0x1000
PREINIT0 = 0x1010
PREINIT1 = 0x1030
INIT = 0x1050
RECORD_GOT = 0x2820
PREINIT_SLOTS = 0x2840
FLAG = 0x2820
IMAGE_BASE = 0x140000000
LOAD_RVA = 0x10000
CALL_SLOT = b'\x48\x8b\x05'
def elf_header(image, program_headers=4):
image[:16] = b'\x7fELF\x02\x01\x01' + bytes(9)
struct.pack_into('<HHIQQQIHHHHHH', image, 16,
3, 62, 1, 0, 64, 0, 0, 64, 56, program_headers, 0, 0, 0)
struct.pack_into('<IIQQQQQQ', image, 64, 1, 5, 0, 0, 0, 0x2000, 0x2000, 0x1000)
struct.pack_into('<IIQQQQQQ', image, 176, 0x6474e551, 6, 0, 0, 0, 0, 0, 16)
struct.pack_into('<IIQQQQQQ', image, 232, 1, 6, 0x2000, 0x2000, 0x2000, 0x1000, 0x1000, 0x1000)
def recording_module():
image = bytearray(0x3000)
elf_header(image)
strings = b'\0RecordArgs\0libc.prx\0shared\0'
tags = [(5, 0x2600), (10, len(strings)), (6, 0x2400), (11, 24), (4, 0x2500),
(7, 0x2700), (8, 72), (9, 24), (1, 12), (12, INIT), (32, PREINIT_SLOTS), (33, 16), (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, SHARED, 6)
struct.pack_into('<IIII', image, 0x2500, 1, 3, 0, 0)
struct.pack_into('<QQq', image, 0x2700, RECORD_GOT, (1 << 32) | 6, 0)
struct.pack_into('<QQq', image, 0x2718, PREINIT_SLOTS, 8, PREINIT0)
struct.pack_into('<QQq', image, 0x2730, PREINIT_SLOTS + 8, 8, PREINIT1)
image[SHARED:SHARED + 6] = b'\xb8\x2a\x00\x00\x00\xc3'
for address in (PREINIT0, PREINIT1):
code = b'\x48\x83\xec\x08\xff\x15' + struct.pack('<i', RECORD_GOT - (address + 10)) + b'\x48\x83\xc4\x08\xc3'
image[address:address + len(code)] = code
code = b'\x48\x83\xec\x08\xff\x15' + struct.pack('<i', RECORD_GOT - (INIT + 10))
code += b'\x48\x83\xc4\x08\xb8' + struct.pack('<I', RETURNED) + b'\xc3'
image[INIT:INIT + len(code)] = code
return image
def flag_module():
image = bytearray(0x3000)
elf_header(image)
strings = b'\0shared\0'
tags = [(5, 0x2600), (10, len(strings)), (6, 0x2400), (11, 24), (4, 0x2500),
(7, 0x2700), (8, 48), (9, 24), (12, INIT), (32, PREINIT_SLOTS), (33, 16), (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, 1, SHARED, 6)
struct.pack_into('<IIII', image, 0x2500, 1, 2, 0, 0)
struct.pack_into('<QQq', image, 0x2700, PREINIT_SLOTS, 8, PREINIT0)
struct.pack_into('<QQq', image, 0x2718, PREINIT_SLOTS + 8, 8, PREINIT1)
image[SHARED:SHARED + 6] = b'\xb8\x2a\x00\x00\x00\xc3'
for address in (PREINIT0, PREINIT1):
code = b'\xc7\x05' + struct.pack('<i', FLAG - (address + 10)) + b'\x01\x00\x00\x00\xc3'
image[address:address + len(code)] = code
code = b'\x8b\x05' + struct.pack('<i', FLAG - (INIT + 6))
code += b'\x83\xf8\x01\x74\x02\x0f\x0b\xb8\x2a\x00\x00\x00\xc3'
image[INIT:INIT + len(code)] = code
return image
def load_and_record(work, provider, module):
events = work / 'events.txt'
run = subprocess.run([sys.executable, __file__, '--load', str(provider), str(module)],
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)
lines = events.read_text().splitlines()
assert len(lines) == 4, lines
assert lines[0] == 'args=0000000000000000 argp=0000000000000000', lines
assert lines[1] == 'args=0000000000000000 argp=0000000000000000', lines
assert lines[2] == f'args={ARGS:016x} argp={ARGP:016x}', lines
assert lines[3] == f'result={RETURNED}', lines
def check_linux_stub(image, stub, slots):
offset = file_offset(image, stub)
assert image[offset] == 0x50, image[offset:offset + 48]
for index, slot in enumerate(slots):
base = offset + 1 + 20 * index
assert image[base:base + 3] == CALL_SLOT, image[base:base + 20]
displacement, = struct.unpack_from('<i', image, base + 3)
assert stub + 1 + 20 * index + 7 + displacement == slot, (stub, index, displacement)
assert image[base + 7:base + 20] == b'\x48\x85\xc0\x74\x08\x31\xff\x31\xf6\x31\xd2\xff\xd0', image[base + 7:base + 20]
tail = offset + 1 + 20 * len(slots)
assert image[tail] == 0x58, image[tail:tail + 7]
assert image[tail + 1] == 0xe9, image[tail:tail + 7]
displacement, = struct.unpack_from('<i', image, tail + 2)
initializer = stub + (tail - offset) + 6 + displacement
assert initializer != stub, initializer
entry = file_offset(image, initializer)
assert image[entry] == 0x50 and image[entry + 1:entry + 3] == b'\xff\x15', image[entry:entry + 8]
def check_macos_stub(data, slots):
machine = MachO(data)
def address_of(position):
for segment in machine.segments:
if segment['fileoff'] <= position < segment['fileoff'] + segment['filesize']:
return segment['vmaddr'] + position - segment['fileoff']
raise AssertionError(f'Unmapped file offset: {position:#x}')
targets = []
position = data.find(CALL_SLOT)
while position >= 0:
if data[position + 7:position + 20] == b'\x48\x85\xc0\x74\x08\x31\xff\x31\xf6\x31\xd2\xff\xd0':
displacement, = struct.unpack_from('<i', data, position + 3)
targets.append(address_of(position) + 7 + displacement)
position = data.find(CALL_SLOT, position + 1)
assert sorted(targets) == sorted(slots), targets
def check_pe_relocations(data, section, addresses):
_, raw_size, raw_offset, _ = section
found = set()
position = 0
while position + 8 <= raw_size:
page, size = struct.unpack_from('<II', data, raw_offset + position)
if size < 8:
break
for index in range(8, size, 2):
entry, = struct.unpack_from('<H', data, raw_offset + position + index)
if entry >> 12 == 10:
found.add(page + (entry & 0xfff))
position += size
assert set(addresses) <= found, (sorted(found), addresses)
def check_windows_deferred(data, slots):
sections = {name: (rva, raw_size, raw_offset, flags) for name, rva, raw_size, raw_offset, flags in pe_sections(data)}
ginit_rva, ginit_size, ginit_raw, _ = sections[b'.ginit']
ginitc_rva, ginitc_size, ginitc_raw, _ = sections[b'.ginitc']
entry, count = struct.unpack_from('<II', data, ginit_raw)
assert entry == ginitc_rva, (entry, ginitc_rva)
assert count == 0, count
code = data[ginitc_raw:ginitc_raw + ginitc_size]
assert code[:15] == b'\x41\x54\x41\x55\x41\x56\x49\x89\xfc\x49\x89\xf5\x49\x89\xd6', code[:15]
code_size = 40 + 23 * len(slots)
restore = 15 + 23 * len(slots)
assert code[restore:restore + 12] == b'\x4c\x89\xe7\x4c\x89\xee\x4c\x89\xf2\x48\x8b\x05', code[restore:restore + 16]
assert code[restore + 16:restore + 18] == b'\xff\xd0', code[restore:restore + 25]
assert code[restore + 18:restore + 25] == b'\x41\x5e\x41\x5d\x41\x5c\xc3', code[restore:restore + 25]
slots_offset = code_size + (8 - code_size % 8) % 8
slots_rva = ginitc_rva + slots_offset
for index, slot in enumerate(slots):
base = 15 + 23 * index
assert code[base:base + 3] == CALL_SLOT, code[base:base + 23]
assert code[base + 3 + 4:base + 3 + 4 + 4] == b'\x48\x8b\x00\x48', code[base:base + 23]
displacement, = struct.unpack_from('<i', code, base + 3)
assert ginitc_rva + base + 7 + displacement == slots_rva + 8 * index, (index, displacement)
value, = struct.unpack_from('<Q', code, slots_offset + 8 * index)
assert value == IMAGE_BASE + LOAD_RVA + slot, (value, slot)
initializer, = struct.unpack_from('<Q', code, slots_offset + 8 * len(slots))
assert initializer == IMAGE_BASE + LOAD_RVA + INIT, initializer
check_pe_relocations(data, sections[b'.reloc'], [slots_rva + 8 * index for index in range(len(slots) + 1)])
def check_windows_startup(data, slots):
sections = {name: (rva, raw_size, raw_offset, flags) for name, rva, raw_size, raw_offset, flags in pe_sections(data)}
_, raw_size, raw_offset, _ = sections[b'.entry']
code = data[raw_offset:raw_offset + raw_size]
def position_of(address):
marker = b'\xb9' + struct.pack('<I', address)
found = code.find(marker)
assert found >= 0, address
return found
initializer = position_of(LOAD_RVA + INIT)
for slot in slots:
assert position_of(LOAD_RVA + slot) < initializer, (slot, initializer)
def main():
if sys.argv[1] == '--load':
import ctypes
host = ctypes.CDLL(sys.argv[2], mode=ctypes.RTLD_GLOBAL if sys.platform == 'darwin' else ctypes.DEFAULT_MODE)
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()
linux = sys.platform == 'linux' and os.uname().machine == 'x86_64'
macos = sys.platform == 'darwin' and platform.machine() == 'x86_64'
with tempfile.TemporaryDirectory(prefix='anyps5-guest-preinit-') as directory:
work = Path(directory)
modules = work / 'sce_module'
modules.mkdir()
name = 'preinit.prx'
(modules / name).write_bytes(recording_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)
tags = dynamic_tags(image)
check_linux_stub(image, tags[12], [PREINIT_SLOTS, PREINIT_SLOTS + 8])
if linux:
libraries = work / 'libs'
libraries.mkdir()
shutil.copyfile(host, libraries / 'libc.prx')
shutil.copyfile(host, libraries / 'libkernel.prx')
load_and_record(work, libraries / 'libc.prx', converted)
mach = work / 'macos'
mach_modules = mach / 'sce_module'
mach_modules.mkdir(parents=True)
(mach_modules / name).write_bytes(recording_module())
mach_source = mach / 'input.elf'
mach_source.write_bytes(executable(name))
mach_result = subprocess.run([str(relinker), '--macos', '--skip-syscall-check', str(mach_source), str(mach / 'output')],
capture_output=True, text=True, timeout=30)
assert mach_result.returncode == 0, (mach_result.stdout, mach_result.stderr)
mach_converted = mach / 'app0' / 'sce_module' / (name + '.guest.prx')
check_macos_stub(mach_converted.read_bytes(), [IMAGE_BASE + LOAD_RVA + PREINIT_SLOTS, IMAGE_BASE + LOAD_RVA + PREINIT_SLOTS + 8])
windows = work / 'windows'
win_modules = windows / 'sce_module'
win_modules.mkdir(parents=True)
(win_modules / name).write_bytes(flag_module())
win_source = windows / 'input.elf'
win_source.write_bytes(executable(name))
win_result = subprocess.run([str(relinker), '--windows', '--skip-syscall-check', str(win_source), str(windows / 'output.exe')],
capture_output=True, text=True, timeout=30)
assert win_result.returncode == 0, (win_result.stdout, win_result.stderr)
win_converted = windows / 'app0' / 'sce_module' / (name + '.guest.prx')
check_windows_startup((windows / 'output.exe').read_bytes(), [PREINIT_SLOTS, PREINIT_SLOTS + 8])
deferred = work / 'deferred'
(deferred / 'sce_module').mkdir(parents=True)
plugins = deferred / 'Media' / 'Plugins'
plugins.mkdir(parents=True)
(plugins / name).write_bytes(flag_module())
deferred_source = deferred / 'input.elf'
deferred_source.write_bytes(main_fixture())
deferred_result = subprocess.run([str(relinker), '--windows', '--skip-syscall-check',
'--module-dir', 'Media/Plugins', str(deferred_source), str(deferred / 'output.exe')],
capture_output=True, text=True, timeout=30)
assert deferred_result.returncode == 0, (deferred_result.stdout, deferred_result.stderr)
deferred_converted = deferred / 'app0' / 'Media' / 'Plugins' / (name + '.guest.prx')
check_windows_deferred(deferred_converted.read_bytes(), [PREINIT_SLOTS, PREINIT_SLOTS + 8])
if os.name == 'nt':
run = subprocess.run([str(windows / 'output.exe')], capture_output=True, text=True, timeout=30)
assert run.returncode == 42, (run.returncode, run.stdout, run.stderr)
if linux or macos or os.name == 'nt':
print('Guest preinit tests passed')
else:
print('Guest preinit tests passed: native execution skipped')
if __name__ == '__main__':
main()
+1 -1
View File
@@ -47,7 +47,7 @@ flowchart LR
## Windows deferred module initialization
Runtime-loaded Windows modules export `__aps5_guest_initialize`: the entry RVA, the initializer count and the RVAs of initializer pointer slots. [DynamicLoader.cpp](../../core/libs/prx/libkernel/Module/src/DynamicLoader.cpp) consumes the first two words in the mapped image after initialization, so another reference to that image does not run it again. Unloading the image discards this state; a later load reads the original table from the file, including when Windows reuses the same base address. Native references outside the guest handle table retain the same initialized image.
Runtime-loaded Windows modules export `__aps5_guest_initialize`: the entry RVA, the initializer count and the RVAs of initializer pointer slots. [DynamicLoader.cpp](../../core/libs/prx/libkernel/Module/src/DynamicLoader.cpp) consumes the first two words in the mapped image after initialization, so another reference to that image does not run it again. Unloading the image discards this state; a later load reads the original table from the file, including when Windows reuses the same base address. Native references outside the guest handle table retain the same initialized image. A module with a preinit array exports a thunk as its entry RVA that runs the preinit entries before the module initializer.
While initialization runs, the count is `UINT32_MAX` and the entry word holds the initializing Windows thread ID. A different thread waits on the loader's condition variable; the owning thread can recursively open its module. Completion clears both words, restores the table's original page protection and wakes waiters. An exception leaves the count at `UINT32_MAX` with thread ID 0, so another load rejects that image until it is unloaded. The table's file format is unchanged and its initializer slots remain intact.
+1
View File
@@ -4,6 +4,7 @@
- [Executable guest imports](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - on Linux and macOS, a relocated executable import resolved to one guest provider requires that module and its dependencies at startup, even when it was discovered through `--module-dir` without a DT_NEEDED entry. Unreferenced symbols and no-op relocations do not require an otherwise deferred module. Console module-start ordering is unmeasured.
- Guest `DT_PREINIT_ARRAY` entries are run with `(0, NULL, NULL)` before the module initializer by the [Linux](../../../core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp), [macOS](../../../core/relinker/elfpatcher/src/macos/MacOsImage.cpp) and [Windows](../../../core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp) outputs, since `ld.so` runs a shared object's preinit array only for the main executable; what the console passes to these entries is not measured. A module whose preinit array is non-empty and whose initializer is absent is rejected, because the Linux stub that would run the entries exists only with one.
- [Runtime-loaded modules](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded.
- The executable file that [relinker](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens a console when launched, unless it is relinked with `--windows-gui`, which selects the GUI subsystem. A GUI build started from Explorer has no standard output or error handle, so everything it prints is lost, including the entry stub's errors and crash reports (#3282).
- [Guest import registries](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - declared host imports of bundled modules are recorded with their module name and relocation target, even when another guest module exports the same stripped NID. No-op relocations are omitted. Registries describe import bindings, not implementation correctness or runtime reachability.