diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index cee9b57f2..f498f966e 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -444,6 +444,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_input_preservation.py $) add_test(NAME input_file COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_input_file.py $) + add_test(NAME null_symbol_relocations COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_null_symbol_relocations.py $) add_test(NAME input_nonascii COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_input_nonascii.py $) add_test(NAME string_table_bounds COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index 615c7c774..40802530e 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -141,6 +141,7 @@ inline constexpr std::uint32_t R_X86_64_REX_GOTPCRELX = 42; inline constexpr std::uint8_t STB_GLOBAL = 1; inline constexpr std::uint8_t STB_WEAK = 2; +inline constexpr std::uint8_t STT_NOTYPE = 0; inline constexpr std::uint8_t STT_FUNC = 2; inline constexpr std::uint8_t STV_DEFAULT = 0; inline constexpr std::uint16_t SHN_UNDEF = 0; diff --git a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp index b1f71e3f3..9706bfd1b 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -38,11 +39,15 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom if (Io::ReadU16(dynamicSection.DynSymData, symbolOffset + 6) != 0) throw Domain::RelinkerException("Defined symbols require explicit relocation support", target); const auto name = ReadString(dynamicSection.DynStrData, Io::ReadU32(dynamicSection.DynSymData, symbolOffset)); - if (name.empty()) + if (name.empty() && dynamicSection.DynSymData[symbolOffset + 4] >> 4 != STB_WEAK) throw Domain::RelinkerException("Empty import symbol name", target); if (type != 1 && addend != 0) throw Domain::RelinkerException("GLOB_DAT/JUMP_SLOT relocation has a nonzero addend", target); image.RequireWritable(target, 8); + if (name.empty()) { + image.WritePointer(target, addend); + continue; + } image.WritePointer(target, 0); result.Imports.push_back({name, rva, addend}); if (const auto module = dynamicSection.ImportModules.find(target); module != dynamicSection.ImportModules.end()) diff --git a/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp b/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp index d3ad5fee4..b63960866 100644 --- a/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp +++ b/core/relinker/relinker/src/output/SysVDynamicSectionBuilder.cpp @@ -82,6 +82,11 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( return value.substr(0, hashPos); }; + auto symbolInfo = [](const NidReference& ref) -> std::uint8_t { + if (ref.Nid.empty()) return static_cast((STB_WEAK << 4) | STT_NOTYPE); + return static_cast((STB_GLOBAL << 4) | STT_FUNC); + }; + static constexpr std::size_t kRelaEntSize = 24; std::vector pltSlots(originalJmprelCount, nullptr); @@ -132,8 +137,7 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( const NidReference& ref = *slot; const std::uint32_t nameOff = _appendStr(result.DynStrData, stripHashSuffix(ref.Nid)); - const auto info = static_cast((STB_GLOBAL << 4) | STT_FUNC); - _appendElfSym(result.DynSymData, nameOff, info, STV_DEFAULT, 0, 0, 0); + _appendElfSym(result.DynSymData, nameOff, symbolInfo(ref), STV_DEFAULT, 0, 0, 0); const std::uint64_t relaInfo = (static_cast(symIdx) << 32) | R_X86_64_JUMP_SLOT; _appendRela(result.RelaPltData, ref.RelocationAddress, relaInfo, ref.Addend); @@ -143,8 +147,7 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( for (const NidReference* slotPtr : nonPltRefs) { const NidReference& ref = *slotPtr; const std::uint32_t nameOff = _appendStr(result.DynStrData, stripHashSuffix(ref.Nid)); - const auto info = static_cast((STB_GLOBAL << 4) | STT_FUNC); - _appendElfSym(result.DynSymData, nameOff, info, STV_DEFAULT, 0, 0, 0); + _appendElfSym(result.DynSymData, nameOff, symbolInfo(ref), STV_DEFAULT, 0, 0, 0); const std::uint64_t relaInfo = (static_cast(symIdx) << 32) | ref.RelocationTypeValue; _appendRela(result.RelaData, ref.RelocationAddress, relaInfo, ref.Addend); diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index d1ff243f4..64aa5b865 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -258,6 +258,10 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf std::memcpy(&nameOff, raw.data() + symOff, 4); const auto name = readCStr(nameOff); + if (name.empty() && (raw.size() - symOff < symEntSize + || std::any_of(raw.begin() + static_cast(symOff), raw.begin() + static_cast(symOff + symEntSize), + [](const std::uint8_t byte) { return byte != 0; }))) + throw RelinkerException("Unnamed import symbol is not a null symbol entry", symOff); nidRefs.push_back({name, Domain::ImportModule(name, importModules, neededLibraries), relType, pos, rOffset, rAddend}); } }; @@ -426,6 +430,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf std::vector entries; entries.reserve(nidRefs.size()); for (const auto& ref : nidRefs) { + if (ref.Nid.empty()) continue; CallRegistryEntry entry; entry.Nid = ref.Nid; entry.Library = ref.Library; diff --git a/core/relinker/relinker/tests/test_null_symbol_relocations.py b/core/relinker/relinker/tests/test_null_symbol_relocations.py new file mode 100644 index 000000000..23aa8c8d4 --- /dev/null +++ b/core/relinker/relinker/tests/test_null_symbol_relocations.py @@ -0,0 +1,87 @@ +import json +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_dynamic_segment import fixture +from test_linux_dynamic_strings import dynamic_strings, file_offset +from test_linux_load_alignment import loads + + +GLOB_DAT_SLOT = 0x308 +ABSOLUTE_SLOT = 0x310 +ADDEND = 0x1234 +CHECK_SLOTS = bytes.fromhex( + "488b0501010000" "4885c0" "751a" + "488b0dfd000000" "4881f934120000" "750a" + "b82a000000c3" "cccccccc" "b807000000c3") +TARGETS = (("linux", [], b"\x7fELF"), ("windows", ["--windows"], b"MZ"), ("macos", ["--macos"], b"\xcf\xfa\xed\xfe")) + + +def image_with(symbol=b""): + image = fixture() + relocations = [(GLOB_DAT_SLOT, (1 << 32) | 6, 0), (ABSOLUTE_SLOT, (1 << 32) | 1, ADDEND)] + struct.pack_into("> 32) * 24) + imports.append((target, info & 0xffffffff, addend, strings[name:strings.index(b"\0", name)], bind_type, section, value, size)) + return sorted(imports) + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-null-symbol-") as directory: + work = Path(directory) + source = work / "null-entry.elf" + source.write_bytes(image_with()) + for target, options, magic in TARGETS: + for level in (0, 1): + output = work / f"null-entry-{target}-{level}.out" + result = subprocess.run([str(relinker), "--skip-sce-module", "--registry", f"unused-filter={level}", + *options, str(source), str(output)], capture_output=True, text=True, timeout=20) + assert result.returncode == 0, (target, level, result.stdout, result.stderr) + data = output.read_bytes() + assert data.startswith(magic), target + registry = json.loads(output.with_suffix(".registry.json").read_text(encoding="utf-8")) + assert registry == [], (target, level, registry) + if target == "linux": + assert linux_imports(data) == [(GLOB_DAT_SLOT, 6, 0, b"", 0x20, 0, 0, 0), + (ABSOLUTE_SLOT, 1, ADDEND, b"", 0x20, 0, 0, 0)], level + if target == "windows" and os.name == "nt": + run = subprocess.run([str(output)], capture_output=True, timeout=20) + assert run.returncode == 42, (level, run.returncode, run.stderr) + source = work / "named-unnamed.elf" + source.write_bytes(image_with(struct.pack("