Merge pull request #13962 from LeandroLP23/fix/relinker-windows-df-symbolic

fix(relinker): convert DF_SYMBOLIC guest modules for Windows
This commit is contained in:
Dean authored and GitHub committed 2026-11-03 21:09:26 +13:00
commit 04ca53e25d
5 files changed
+107 -9

No files matched your search

+2
View File
@@ -845,6 +845,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $<TARGET_FILE:relinker>)
add_test(NAME guest_module_identity COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_identity.py $<TARGET_FILE:relinker>)
add_test(NAME guest_symbolic_modules COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_symbolic_modules.py $<TARGET_FILE:relinker>)
add_test(NAME linux_guest_zero_address_exports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_guest_zero_address_exports.py
$<TARGET_FILE:relinker> $<TARGET_FILE:guest_module_args_host>)
@@ -15,7 +15,6 @@ namespace Elfpatcher {
std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestImage& guest, Domain::GuestRuntime& runtime, const std::function<void(std::string_view)>& message) const {
using namespace Windows;
if (guest.Symbolic) throw Domain::RelinkerException("DF_SYMBOLIC guest modules are not supported by the Windows writer");
WindowsLoadImage image(guest.Bytes, guest.Headers, false);
std::vector<std::uint32_t> relocations;
std::vector<std::pair<std::uint64_t, std::uint32_t>> tlsModules;
@@ -68,7 +68,7 @@ int main() {
const std::array<std::pair<unsigned,std::uint64_t>,7> changes={{{4,0x22},{4,0x11},{5,0},{6,2},{8,0x28},{16,2},{6,Relinker::AbsoluteSection}}};
for(const auto& [field,value]:changes){auto b=Fixture();auto off=0x2400+48+field;if(field<6)b[off]=value;else if(field==6)Io::WriteU16(b,off,value);else Io::WriteU64(b,off,value);Reject([&]{Relinker::GuestImageReader().Read("generic.prx",b,true);},"Duplicate or empty export");}
auto good=Relinker::GuestImageReader().Read("generic.prx",Fixture(),true);
auto symbolic=good;symbolic.Symbolic=true;Domain::GuestRuntime symbolicRuntime;Reject([&]{Elfpatcher::GuestModuleWriter().WriteWindows(symbolic,symbolicRuntime);},"DF_SYMBOLIC");
auto symbolic=good;symbolic.Symbolic=true;Domain::GuestRuntime symbolicRuntime,ordinaryRuntime;Check(Elfpatcher::GuestModuleWriter().WriteWindows(symbolic,symbolicRuntime)==Elfpatcher::GuestModuleWriter().WriteWindows(good,ordinaryRuntime)&&symbolicRuntime.Imports.size()==ordinaryRuntime.Imports.size(),"DF_SYMBOLIC changed Windows binding");
for(unsigned kind=0;kind<10;++kind){auto g=good;auto& s=g.Symbols[2];switch(kind){case 0:s.Value+=8;break;case 1:s.Size+=1;break;case 2:s.Info=0x22;break;case 3:s.Info=0x11;break;case 4:s.Visibility=0;break;case 5:s.Section=2;break;case 6:g.Symbols[1].Info=s.Info=0x11;break;case 7:g.Symbols[1].Section=s.Section=Relinker::AbsoluteSection;break;case 8:g.Symbols[1].Info=s.Info=0x16;g.Dynamic.RelaData.clear();break;case 9:g.Symbols[1].Info=s.Info=0x10;break;}Check(!Relinker::IdenticalOrdinaryFunctionExport(g.Symbols[1],s),"nonordinary/conflicting tuple accepted");Domain::GuestRuntime r;Reject([&]{Elfpatcher::GuestModuleWriter().WriteWindows(g,r);},kind==8?"TLS":"Duplicate guest export");}
Check(Domain::DeclaredImportModule("same#A#B",{{1,"one"},{2,"two"}})=="one"&&Domain::DeclaredImportModule("same#A#C",{{1,"one"},{2,"two"}})=="two","module-qualified IDs merged");
auto zeroBucket=Fixture();Io::WriteU32(zeroBucket,0x2600,0);
@@ -98,7 +98,7 @@ def main():
prx.mkdir(parents=True)
provider = (case / standard) if standard else prx
provider.mkdir(exist_ok=True)
(provider / "provider.prx").write_bytes(module_with_symbol(True, symbolic=not windows))
(provider / "provider.prx").write_bytes(module_with_symbol(True, symbolic=True))
(prx / "consumer.prx").write_bytes(module_with_symbol(False))
(prx / "ignored.txt").write_text("not ELF")
(prx / "old.prx.guest.prx").write_bytes(guest_fixture(PLAIN_SITE))
@@ -227,12 +227,6 @@ def main():
assert result.returncode == 2 and "Ambiguous guest import" in result.stderr and "shared" in result.stderr, result.stderr
assert not output.exists() and not (case / "app0").exists(), output
case = work / "windows-symbolic"
(case / "prx").mkdir(parents=True)
(case / "prx" / "provider.prx").write_bytes(module_with_symbol(True, symbolic=True))
result, output = convert(case, True)
assert result.returncode == 2 and "DF_SYMBOLIC" in result.stderr, result.stderr
assert not output.exists(), output
print("Guest module directory integration tests passed")
@@ -0,0 +1,103 @@
from pathlib import Path
import os
import struct
import subprocess
import sys
from guest_import_fixtures import dynamic, fixture_directory
from test_guest_intel_trampolines import PLAIN_SITE, guest_fixture, main_fixture
ENTRY = 0x1010
SHARED = 0x1000
CALL_SLOTS = (0x23a0, 0x23a8, 0x23b0)
TLS_SLOT = 0x23b8
def module(value, tls_value, identity, entry_symbol, symbolic):
image = guest_fixture(PLAIN_SITE, symbolic=symbolic)
strings = b'\0shared#A#B\0' + entry_symbol.encode() + b'#A#B\0tls_value#A#B\0'
entry_name = strings.index(entry_symbol.encode())
tls_name = strings.index(b'tls_value')
identity_offset = len(strings)
strings += identity.encode() + b'\0'
assert len(strings) <= 0x40
image[0x800:0x800 + len(strings)] = strings
tags = [(5, 0x2200), (10, len(strings)), (6, 0x2280), (11, 24),
(4, 0x2240), (7, 0x2300), (8, 24 * (len(CALL_SLOTS) + 1)), (9, 24),
(0x61000043, (1 << 48) | (0x101 << 32) | identity_offset)]
if symbolic:
tags.append((30, 2))
dynamic(image, 0x600, 176, tags)
struct.pack_into('<H', image, 56, 4)
struct.pack_into('<IIQQQQQQ', image, 232, 7, 4, 0x980, 0x2380, 0x2380, 8, 8, 8)
struct.pack_into('<Q', image, 0x980, tls_value)
struct.pack_into('<IIIIIII', image, 0x840, 1, 4, 1, 0, 2, 3, 0)
shared = b'\xb8' + struct.pack('<I', value) + b'\xc3'
entry = bytearray(b'\x53')
for index, slot in enumerate(CALL_SLOTS):
call = ENTRY + len(entry)
entry += b'\xff\x15' + struct.pack('<i', slot - (call + 6))
entry += b'\x89\xc3' if index == 0 else b'\x01\xc3'
load = ENTRY + len(entry)
entry += b'\x48\x8b\x05' + struct.pack('<i', TLS_SLOT - (load + 7))
entry += b'\x64\x48\x8b\x80\0\0\0\0' + b'\x01\xd8\x5b\xc3'
image[0x400:0x400 + len(shared)] = shared
image[0x410:0x410 + len(entry)] = entry
struct.pack_into('<IBBHQQ', image, 0x898, 1, 0x12, 0, 1, SHARED, len(shared))
struct.pack_into('<IBBHQQ', image, 0x8b0, entry_name, 0x12, 0, 1, ENTRY, len(entry))
struct.pack_into('<IBBHQQ', image, 0x8c8, tls_name, 0x16, 0, 1, 0, 8)
for index, (slot, kind) in enumerate(zip(CALL_SLOTS, (6, 7, 1))):
struct.pack_into('<QQq', image, 0x900 + 24 * index, slot, (1 << 32) | kind, 0)
struct.pack_into('<QQq', image, 0x900 + 24 * len(CALL_SLOTS), TLS_SLOT, (3 << 32) | 18, 0)
return image
def executable(first, second):
image = main_fixture()
struct.pack_into('<I', image, 68, 7)
names = (b'first#A#B', b'second#A#C', first.encode() + b'.prx', second.encode() + b'.prx', first.encode(), second.encode())
strings = b'\0'
offsets = []
for name in names:
offsets.append(len(strings))
strings += name + b'\0'
assert len(strings) <= 0x80
image[0x4800:0x4800 + len(strings)] = strings
tags = [(5, 0x4800), (10, len(strings)), (6, 0x4880), (11, 24),
(4, 0x48d0), (7, 0x4900), (8, 48), (9, 24),
(1, offsets[2]), (1, offsets[3]),
(0x61000045, (1 << 48) | offsets[4]), (0x61000045, (2 << 48) | offsets[5])]
dynamic(image, 0x4600, 120, tags)
struct.pack_into('<IIIIII', image, 0x48d0, 1, 3, 1, 0, 2, 0)
for index in range(2):
struct.pack_into('<IBBHQQ', image, 0x4898 + 24 * index, offsets[index], 0x12, 0, 0, 0, 0)
struct.pack_into('<QQq', image, 0x4900 + 24 * index, 0x4a20 + 8 * index, ((index + 1) << 32) | 6, 0)
code = (b'\x53' + b'\xff\x15' + struct.pack('<i', 0x4a20 - 0x4007) + b'\x89\xc3'
+ b'\xff\x15' + struct.pack('<i', 0x4a28 - 0x400f) + b'\xc1\xe0\x04\x01\xd8\x5b\xc3')
image[0x4000:0x4000 + len(code)] = code
return image
def main():
relinker = Path(sys.argv[1]).resolve()
with fixture_directory('anyps5-symbolic-modules-') as directory:
work = Path(directory)
(work / 'sce_module').mkdir()
(work / 'sce_module' / 'libFirst.prx').write_bytes(module(1, 4, 'libFirst', 'first', symbolic=False))
(work / 'sce_module' / 'libSecond.prx').write_bytes(module(2, 8, 'libSecond', 'second', symbolic=True))
source = work / 'input.elf'
source.write_bytes(executable('libFirst', 'libSecond'))
output = work / 'output.exe'
result = subprocess.run([str(relinker), '--windows', str(source), str(output)], capture_output=True, text=True, timeout=30)
assert result.returncode == 0, (result.stdout, result.stderr)
modules = work / 'app0' / 'sce_module'
for name in ('libFirst', 'libSecond'):
assert (modules / (name + '.prx.guest.prx')).read_bytes().startswith(b'MZ'), name
if os.name == 'nt':
run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30)
assert run.returncode == (3 * 1 + 4) + (3 * 2 + 8) * 16, (run.returncode, run.stdout, run.stderr)
print('Guest symbolic module tests passed')
if __name__ == '__main__':
main()