From b689f4962556711ee1e0841c1cdc8c7e90b3289d Mon Sep 17 00:00:00 2001 From: David Date: Thu, 15 Oct 2026 10:46:32 +0400 Subject: [PATCH 1/3] fix(relinker): skip prefixed decodings inside an earlier instruction in the macOS TLS scan A function range that begins inside an instruction makes the code analysis collect a second decoding from the middle of it. The Windows TLS rewriter skips a non-FS segment-prefixed decoding there (90dcd6256); the macOS scan did not, and refused the module. OpenSSL's assembly keeps its "CRYPTOGAMS by " string in the code segment, where the "en" of "openssl" decodes as a GS-prefixed instruction, so 24 catalog titles could not be converted for macOS: "Unsupported macOS guest TLS instruction (bytes: 65 6e)". The scan now skips such decodings as the Windows rewriter does. --- core/relinker/elfpatcher/src/macos/MacOsImage.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 838d0d6c8b..196084a66e 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -218,16 +218,19 @@ TlsScan ScanTls(const std::vector& source, const std::vector(rva) + static_cast(info.Length) + info.BranchDisp; if (target >= 0 && target <= std::numeric_limits::max()) scan.BranchTargets.insert(static_cast(target)); } - if (info.SegmentPrefix == 0) continue; + if (info.SegmentPrefix == 0 || (insidePrevious && info.SegmentPrefix != 0x64)) continue; const auto position = info.OpcodeOffset; bool hasOperandSizePrefix = false; bool supportedPrefixes = info.SegmentPrefix == 0x64; From 2328902c91799693357966b5e29730f9806558ab Mon Sep 17 00:00:00 2001 From: David Date: Thu, 15 Oct 2026 10:47:32 +0400 Subject: [PATCH 2/3] test(relinker): convert a function that begins inside an instruction for macOS macos_tls_overlap converts the Windows coverage group's function-begins-inside-instruction fixture with --macos: a symbol starts inside a movabs whose immediate holds a GS-prefixed decoding. It converts, and the movabs is unchanged in the output. --- core/relinker/CMakeLists.txt | 3 ++ .../relinker/tests/test_macos_tls_overlap.py | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 core/relinker/relinker/tests/test_macos_tls_overlap.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 4660de2490..3b91a8c40a 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -489,6 +489,9 @@ if(BUILD_TESTING) set_tests_properties(needed_libraries PROPERTIES TIMEOUT 30) add_test(NAME macos_output COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_output.py $) + add_test(NAME macos_tls_overlap COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_tls_overlap.py $) + set_tests_properties(macos_tls_overlap PROPERTIES TIMEOUT 60) add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp) diff --git a/core/relinker/relinker/tests/test_macos_tls_overlap.py b/core/relinker/relinker/tests/test_macos_tls_overlap.py new file mode 100644 index 0000000000..c124c7c6f2 --- /dev/null +++ b/core/relinker/relinker/tests/test_macos_tls_overlap.py @@ -0,0 +1,41 @@ +"""Check the macOS TLS scan against decodings that start inside another instruction.""" +from pathlib import Path +import subprocess +import sys +import tempfile + +from test_macos_output import MachO +from test_tls_function_coverage import TLS_LOAD, add_alias, make_image + + +def convert(relinker, directory, name, image): + source = Path(directory) / f"{name}.elf" + output = Path(directory) / name + source.write_bytes(bytes(image)) + result = subprocess.run([str(relinker), "--macos", "--skip-sce-module", str(source), str(output)], + capture_output=True, text=True, timeout=60) + return result, output + + +def check_split_function(relinker, directory): + immediate = bytes.fromhex("48 b9 00 00 00 65 2e 62 69 6e") + body = TLS_LOAD + bytes.fromhex("eb 00 8b 40 f0") + immediate + b"\xc3" * 8 + image = add_alias(make_image("register", "symbol", 0x55, body), 0x0c, 0x1256) + result, output = convert(relinker, directory, "function-begins-inside-instruction", image) + if result.returncode != 0: + raise SystemExit(f"function-begins-inside-instruction: relinker failed: {result.stdout}{result.stderr}") + data = output.read_bytes() + code = b"".join(data[lo:hi] for lo, hi in MachO(data).executable_ranges()) + if immediate not in code: + raise SystemExit("function-begins-inside-instruction: the instruction around the GS-prefixed decoding changed") + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-macos-tls-overlap-") as directory: + check_split_function(relinker, directory) + print("macOS TLS overlap checks passed") + + +if __name__ == "__main__": + main() From be5dfd623771a142096cbaee7b928b268efe8e1c Mon Sep 17 00:00:00 2001 From: David Date: Thu, 15 Oct 2026 10:48:07 +0400 Subject: [PATCH 3/3] test(relinker): keep refusing a GS access at an instruction start for macOS Only a segment-prefixed decoding inside an earlier instruction is skipped; a GS load that begins an instruction is still refused. --- core/relinker/relinker/tests/test_macos_tls_overlap.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/relinker/relinker/tests/test_macos_tls_overlap.py b/core/relinker/relinker/tests/test_macos_tls_overlap.py index c124c7c6f2..0eaeb6396d 100644 --- a/core/relinker/relinker/tests/test_macos_tls_overlap.py +++ b/core/relinker/relinker/tests/test_macos_tls_overlap.py @@ -30,10 +30,19 @@ def check_split_function(relinker, directory): raise SystemExit("function-begins-inside-instruction: the instruction around the GS-prefixed decoding changed") +def check_gs_access_refused(relinker, directory): + image = make_image("register", "unwind", body=bytes.fromhex("65 48 8b 04 25 28 00 00 00") + b"\xc3") + result, output = convert(relinker, directory, "gs-access", image) + message = result.stdout + result.stderr + if result.returncode != 2 or "Unsupported macOS guest TLS instruction" not in message or output.exists(): + raise SystemExit(f"gs-access: a GS access at an instruction start was not refused: {message}") + + def main(): relinker = Path(sys.argv[1]).resolve() with tempfile.TemporaryDirectory(prefix="anyps5-macos-tls-overlap-") as directory: check_split_function(relinker, directory) + check_gs_access_refused(relinker, directory) print("macOS TLS overlap checks passed")