fix(relinker): keep host prx references out of guest modules on Linux
On Linux every module resolves its references through one global scope, and the guest modules are listed ahead of the host prx. When a title bundles its own libc.prx, the host libc and the other host prx bound their references to their own NIDs to the guest libc, so the host libc built its standard streams on top of the guest _Stdout. The exports of guest modules, and the guest and executable imports that a guest module satisfies, now carry a #guest suffix. The title's imports still reach the guest modules first, and a host prx only sees host definitions, as on Windows. sceKernelDlsym also looks for the suffixed name.
This commit is contained in:
parent
bc24783e5b
commit
5fab03e4e5
|
|
@ -5,6 +5,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
|
|
@ -47,7 +48,14 @@ void* Symbol(Module& module, const char* name) {
|
|||
void* FindSymbol(Module& module, const char* name) {
|
||||
if (auto* symbol = Symbol(module, name)) return symbol;
|
||||
const auto nid = Nid::ComputeNid(name, "");
|
||||
#ifdef _WIN32
|
||||
return Symbol(module, nid.c_str());
|
||||
#else
|
||||
if (auto* symbol = Symbol(module, nid.c_str())) return symbol;
|
||||
constexpr char guestSuffix[] = "#guest";
|
||||
if (auto* symbol = Symbol(module, (std::string(name) + guestSuffix).c_str())) return symbol;
|
||||
return Symbol(module, (nid + guestSuffix).c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ int main(int argc, char** argv) {
|
|||
auto add = reinterpret_cast<Add>(dlsym_nid_postfix(module, "GuestModuleAdd"));
|
||||
Require(add && add(17, 25) == 42);
|
||||
Require(dlsym_nid_postfix(reinterpret_cast<void*>(-2), "GuestModuleAdd") == reinterpret_cast<void*>(add));
|
||||
#ifndef _WIN32
|
||||
auto mul = reinterpret_cast<Add>(dlsym_nid_postfix(module, "GuestModuleMul"));
|
||||
Require(mul && mul(6, 7) == 42);
|
||||
auto sub = reinterpret_cast<Add>(dlsym_nid_postfix(module, "GuestModuleSub"));
|
||||
Require(sub && sub(50, 8) == 42);
|
||||
#endif
|
||||
Require(dlsym_nid_postfix(module, "missing_symbol") == nullptr);
|
||||
std::thread other([] { Require(dlerror_nid_postfix() == nullptr); });
|
||||
other.join();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libc/include/general/ExportMacros.hpp"
|
||||
#ifdef _WIN32
|
||||
#define MODULE_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
|
|
@ -7,3 +8,13 @@
|
|||
extern "C" MODULE_EXPORT int APS5_VABI GuestModuleAdd_nid_postfix(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
#ifndef _WIN32
|
||||
extern "C" MODULE_EXPORT int APS5_VABI GuestModuleMul_nid_no_patch(int a, int b) {
|
||||
return a * b;
|
||||
}
|
||||
APS5_EXPORT("GuestModuleMul#guest", GuestModuleMul_nid_no_patch);
|
||||
extern "C" MODULE_EXPORT int APS5_VABI GuestModuleSub_nid_no_patch(int a, int b) {
|
||||
return a - b;
|
||||
}
|
||||
APS5_EXPORT("BOyBJaKwOa8#guest", GuestModuleSub_nid_no_patch);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -218,6 +218,8 @@ if(BUILD_TESTING)
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_module_directories COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_directories.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_symbol_names COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_symbol_names.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_test(NAME elf_header COMMAND ${Python3_EXECUTABLE}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
namespace Relinker {
|
||||
|
||||
inline constexpr char GuestSymbolSuffix[] = "#guest";
|
||||
inline constexpr char GuestModuleSuffix[] = ".guest.prx";
|
||||
|
||||
struct GuestSymbol {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
namespace Relinker {
|
||||
|
|
@ -100,6 +101,11 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
for (const auto provider : shared->second) providers += " " + images[provider].SourcePath.string();
|
||||
throw Domain::RelinkerException("Ambiguous guest import " + name + " in " + importer + ": exported by" + providers);
|
||||
};
|
||||
const auto rename = [](std::vector<std::uint8_t>& symbols, std::vector<std::uint8_t>& strings, std::size_t index, const std::string& name) {
|
||||
if (strings.size() > std::numeric_limits<std::uint32_t>::max()) throw Domain::RelinkerException("Guest string table too large");
|
||||
Io::WriteU32(symbols, index * 24, static_cast<std::uint32_t>(strings.size()));
|
||||
Io::AppendString(strings, name + GuestSymbolSuffix);
|
||||
};
|
||||
if (dynamic.DynSymData.size() % 24 != 0) throw Domain::RelinkerException("Invalid executable symbol table");
|
||||
for (std::size_t offset = 0; offset < dynamic.DynSymData.size(); offset += 24) {
|
||||
if (Io::ReadU16(dynamic.DynSymData, offset + 6) != 0) continue;
|
||||
|
|
@ -110,6 +116,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated executable symbol name");
|
||||
const std::string name(start, end);
|
||||
rejectSharedImport(name.substr(0, name.find('#')), inputPath.string());
|
||||
if (!windows && exports.contains(name)) rename(dynamic.DynSymData, dynamic.DynStrData, offset / 24, name);
|
||||
}
|
||||
std::vector<std::set<std::size_t>> dependencies(images.size());
|
||||
for (auto& image : images) image.UsePlatformTlsResolver = !exports.contains("vNe1w4diLCs");
|
||||
|
|
@ -130,6 +137,15 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
} else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name);
|
||||
}
|
||||
}
|
||||
if (!windows) {
|
||||
for (auto& image : images) {
|
||||
for (std::size_t index = 1; index < image.Symbols.size(); ++index) {
|
||||
const auto& symbol = image.Symbols[index];
|
||||
const bool exported = symbol.Section != 0 && (symbol.Info >> 4) != 0 && symbol.Visibility != 1 && symbol.Visibility != 2;
|
||||
if ((symbol.Section == 0 || exported) && exports.contains(symbol.Name)) rename(image.Dynamic.DynSymData, image.Dynamic.DynStrData, index, symbol.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<std::size_t> order;
|
||||
std::vector<unsigned char> states(images.size());
|
||||
const std::function<void(std::size_t)> visit = [&](std::size_t index) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
from pathlib import Path
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_guest_intel_trampolines import PLAIN_SITE, elf_loads, guest_fixture, main_fixture
|
||||
|
||||
DYNAMIC = 0x4800
|
||||
STRINGS = 0x4A00
|
||||
SYMBOLS = 0x4B00
|
||||
RELOCATIONS = 0x4C00
|
||||
GOT = 0x4E00
|
||||
|
||||
|
||||
def string_table(names):
|
||||
strings = b"\0"
|
||||
offsets = []
|
||||
for name in names:
|
||||
offsets.append(len(strings))
|
||||
strings += name.encode() + b"\0"
|
||||
return strings, offsets
|
||||
|
||||
|
||||
def guest_module(symbols):
|
||||
image = guest_fixture(PLAIN_SITE)
|
||||
strings, offsets = string_table(name for name, _ in symbols)
|
||||
assert len(strings) <= 0x40
|
||||
image[0x800:0x800 + len(strings)] = strings
|
||||
struct.pack_into("<Q", image, 0x618, len(strings))
|
||||
struct.pack_into("<Q", image, 0x628, 0x2280)
|
||||
struct.pack_into("<II", image, 0x840, 1, len(symbols) + 1)
|
||||
for index, (offset, (_, exported)) in enumerate(zip(offsets, symbols), 1):
|
||||
struct.pack_into("<IBBHQQ", image, 0x880 + index * 24, offset, 0x12, 0,
|
||||
1 if exported else 0, 0x1000 if exported else 0, 1 if exported else 0)
|
||||
return image
|
||||
|
||||
|
||||
def importing_executable(names):
|
||||
image = main_fixture()
|
||||
strings, offsets = string_table(names)
|
||||
image[STRINGS:STRINGS + len(strings)] = strings
|
||||
for index, offset in enumerate(offsets, 1):
|
||||
struct.pack_into("<IBBHQQ", image, SYMBOLS + index * 24, offset, 0x12, 0, 0, 0, 0)
|
||||
struct.pack_into("<QQq", image, RELOCATIONS + (index - 1) * 24, GOT + (index - 1) * 8, (index << 32) | 6, 0)
|
||||
tags = [(5, STRINGS), (10, len(strings)), (6, SYMBOLS), (11, 24),
|
||||
(7, RELOCATIONS), (8, len(names) * 24), (9, 24), (0, 0)]
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into("<qQ", image, DYNAMIC + index * 16, *tag)
|
||||
struct.pack_into("<QQQQQ", image, 120 + 8, DYNAMIC, DYNAMIC, DYNAMIC, len(tags) * 16, len(tags) * 16)
|
||||
return image
|
||||
|
||||
|
||||
def dynamic_tags(image):
|
||||
phoff, = struct.unpack_from("<Q", image, 32)
|
||||
phsize, phcount = struct.unpack_from("<HH", image, 54)
|
||||
dynamic = next(struct.unpack_from("<IIQQQQQQ", image, phoff + index * phsize)
|
||||
for index in range(phcount)
|
||||
if struct.unpack_from("<I", image, phoff + index * phsize)[0] == 2)
|
||||
tags = {}
|
||||
for position in range(dynamic[2], dynamic[2] + dynamic[5], 16):
|
||||
tag, value = struct.unpack_from("<qQ", image, position)
|
||||
if tag == 0:
|
||||
break
|
||||
tags.setdefault(tag, value)
|
||||
return tags
|
||||
|
||||
|
||||
def file_offset(image, address):
|
||||
for header in elf_loads(image):
|
||||
if header[3] <= address < header[3] + header[5]:
|
||||
return header[2] + address - header[3]
|
||||
raise AssertionError(f"Unmapped address: {address:#x}")
|
||||
|
||||
|
||||
def symbol_name(image, tags, index):
|
||||
strings = file_offset(image, tags[5])
|
||||
name, = struct.unpack_from("<I", image, file_offset(image, tags[6]) + index * 24)
|
||||
return image[strings + name:image.index(0, strings + name)].decode()
|
||||
|
||||
|
||||
def module_symbols(image):
|
||||
tags = dynamic_tags(image)
|
||||
count, = struct.unpack_from("<I", image, file_offset(image, tags[4]) + 4)
|
||||
return [symbol_name(image, tags, index) for index in range(1, count)]
|
||||
|
||||
|
||||
def imported_symbols(image):
|
||||
tags = dynamic_tags(image)
|
||||
names = set()
|
||||
for address, size in ((7, 8), (23, 2)):
|
||||
if address not in tags or tags.get(size, 0) == 0:
|
||||
continue
|
||||
table = file_offset(image, tags[address])
|
||||
for position in range(table, table + tags[size], 24):
|
||||
info, = struct.unpack_from("<Q", image, position + 8)
|
||||
if info >> 32:
|
||||
names.add(symbol_name(image, tags, info >> 32))
|
||||
return names
|
||||
|
||||
|
||||
def convert(relinker, case):
|
||||
(case / "sce_module").mkdir(parents=True)
|
||||
source = case / "input.elf"
|
||||
source.write_bytes(importing_executable(["AAAAAAAAAAA#A#A", "DDDDDDDDDDD#D#D"]))
|
||||
(case / "sce_module" / "libc.prx").write_bytes(
|
||||
guest_module([("AAAAAAAAAAA#A#A", True), ("BBBBBBBBBBB#B#B", False), ("CCCCCCCCCCC#C#C", False), ("EEEEEEEEEEE", True)]))
|
||||
(case / "sce_module" / "other.prx").write_bytes(guest_module([("BBBBBBBBBBB#B#B", True), ("EEEEEEEEEEE", True)]))
|
||||
output = case / "output.elf"
|
||||
result = subprocess.run([str(relinker), str(source), str(output)], capture_output=True, text=True, timeout=30)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
return output
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-guest-symbol-names-") as directory:
|
||||
work = Path(directory)
|
||||
|
||||
output = convert(relinker, work)
|
||||
modules = output.parent / "app0" / "sce_module"
|
||||
libc = module_symbols((modules / "libc.prx.guest.prx").read_bytes())
|
||||
assert libc == ["AAAAAAAAAAA#guest", "BBBBBBBBBBB#guest", "CCCCCCCCCCC", "EEEEEEEEEEE#guest"], libc
|
||||
other = module_symbols((modules / "other.prx.guest.prx").read_bytes())
|
||||
assert other == ["BBBBBBBBBBB#guest", "EEEEEEEEEEE#guest"], other
|
||||
imports = imported_symbols(output.read_bytes())
|
||||
assert imports == {"AAAAAAAAAAA#guest", "DDDDDDDDDDD"}, imports
|
||||
print("Guest symbol name tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue