fix(relinker): identify unsupported Windows TLS instructions

The unsupported-instruction error now lists the instruction bytes, and the relink failure names the input file, the main executable or the guest module, on an extra Input: line.
This commit is contained in:
QDarkRex 2026-10-04 19:09:10 +07:00
parent 2cf901bcff
commit 47f0bf93ef
6 changed files with 80 additions and 4 deletions

View File

@ -191,6 +191,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $<TARGET_FILE:relinker>)
add_test(NAME tls_function_coverage COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $<TARGET_FILE:relinker>)
add_test(NAME tls_diagnostics COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_diagnostics.py $<TARGET_FILE:relinker>)
add_test(NAME linux_load_alignment COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_alignment.py $<TARGET_FILE:relinker>)
add_test(NAME executable_segments COMMAND ${Python3_EXECUTABLE}

View File

@ -82,6 +82,7 @@ struct DynamicTag {
struct RelinkerException : std::runtime_error {
FileByteOffset FailureOffset;
std::string InputPath;
explicit RelinkerException(const std::string& message, const FileByteOffset failureOffset = 0)
: std::runtime_error(message), FailureOffset(failureOffset) {}

View File

@ -80,7 +80,12 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
auto nextRva = image.GetEndRva();
std::array<PeDirectory, 16> directories{};
std::uint32_t tlsIndex = 0;
directories[9] = WindowsTlsBuilder().Build(guest.Bytes, guest.Headers, image, sections, relocations, nextRva, &tlsIndex);
try {
directories[9] = WindowsTlsBuilder().Build(guest.Bytes, guest.Headers, image, sections, relocations, nextRva, &tlsIndex);
} catch (Domain::RelinkerException& error) {
error.InputPath = guest.SourcePath.string();
throw;
}
WindowsTrampolineBuilder().Build(guest.Trampolines, image, sections, nextRva);
for (const auto& [target, rva] : tlsModules) {
if (tlsIndex == 0) throw Domain::RelinkerException("Guest TLS relocation has no TLS block", target);

View File

@ -6,8 +6,10 @@
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <bit>
#include <iomanip>
#include <limits>
#include <set>
#include <sstream>
#include <string>
namespace Elfpatcher::Windows {
@ -83,8 +85,14 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector<std::uint8_t>& source, co
const auto loadRegister = info.Length - position == 7 ? static_cast<std::uint8_t>(((bytes[position + 1] >> 3) & 7) | ((info.RexPrefix & 4) << 1)) : std::uint8_t{4};
const bool loadValue = supportedPrefixes && (info.RexPrefix == 0x48 || info.RexPrefix == 0x4c) && loadRegister != 4 && bytes[position] == 0x8b && (bytes[position + 1] & 0xc7) == 0x04 && bytes[position + 2] == 0x25;
const bool storeImmediate = supportedPrefixes && !hasOperandSizePrefix && (info.RexPrefix == 0 || info.RexPrefix == 0x40) && info.Length - position == 11 && bytes[position] == 0xc7 && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0x28;
if (!loadValue && !storeImmediate)
throw Domain::RelinkerException("Unsupported Windows guest TLS instruction", header.Offset + offset);
if (!loadValue && !storeImmediate) {
std::ostringstream message;
message << "Unsupported Windows guest TLS instruction (bytes:" << std::hex << std::setfill('0');
for (std::size_t index = 0; index < info.Length; ++index)
message << ' ' << std::setw(2) << static_cast<unsigned int>(bytes[index]);
message << ')';
throw Domain::RelinkerException(message.str(), header.Offset + offset);
}
accesses.push_back({rva, header.Offset + offset, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0, storeImmediate ? std::uint8_t{0} : loadRegister, storeImmediate ? 0 : Io::ReadU32(source, header.Offset + offset + position + 3)});
}
}

View File

@ -117,7 +117,13 @@ int main(const int argc, char* argv[]) {
);
}
const auto executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics, trampolines);
std::vector<std::uint8_t> executableBytes;
try {
executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics, trampolines);
} catch (Domain::RelinkerException& error) {
error.InputPath = args.inputPath;
throw;
}
for (const auto& artifact : guestArtifacts) {
std::filesystem::create_directories(artifact.Path.parent_path());
fileWriter.Write(artifact.Path.string(), artifact.Bytes);
@ -141,6 +147,7 @@ int main(const int argc, char* argv[]) {
std::cerr << "FAIL: " << e.what();
if (e.FailureOffset != 0) std::cerr << " (offset 0x" << std::hex << e.FailureOffset << ")";
std::cerr << "\n";
if (!e.InputPath.empty()) std::cerr << "Input: " << e.InputPath << '\n';
return 2;
} catch (const Codegen::CodegenException& e) {
std::cerr << "FAIL: " << e.what();

View File

@ -0,0 +1,53 @@
from pathlib import Path
import subprocess
import sys
import tempfile
from test_guest_intel_trampolines import main_fixture
from test_guest_tls import guest_with_tls
from test_tls_function_coverage import TLS_LOAD, make_image
TLS_INSTRUCTION = bytes.fromhex("64 8b 04 25 28 00 00 00")
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-tls-diagnostics-") as directory:
work = Path(directory) / "input with spaces"
work.mkdir()
source = work / "eboot.elf"
output = work / "output.exe"
def check(input_path, offset, options):
result = subprocess.run([str(relinker), "--windows", *options, str(source), str(output)],
capture_output=True, text=True, timeout=30)
assert result.returncode == 2, result
assert "Unsupported Windows guest TLS instruction" in result.stderr, result.stderr
assert f"(bytes: {TLS_INSTRUCTION.hex(' ')})" in result.stderr, result.stderr
assert f"(offset {offset:#x})" in result.stderr, result.stderr
assert f"Input: {input_path}\n" in result.stderr, result.stderr
assert not output.exists(), output
assert not list(work.rglob("*.guest.prx")), work
image = make_image("register", "unwind")
image[0x1240:0x1240 + len(TLS_LOAD)] = TLS_INSTRUCTION + b"\x90" * (len(TLS_LOAD) - len(TLS_INSTRUCTION))
source.write_bytes(image)
check(source, 0x1240, ["--skip-sce-module"])
source.write_bytes(main_fixture())
for name in ("sce_module", "sce_modules"):
module_dir = work / name
module_dir.mkdir()
module = module_dir / "guest with spaces.prx"
image = guest_with_tls(True)
image[0x400:0x400 + len(TLS_INSTRUCTION) + 1] = TLS_INSTRUCTION + b"\xc3"
module.write_bytes(image)
check(module, 0x400, [])
module.unlink()
module_dir.rmdir()
print("TLS diagnostics integration tests passed")
if __name__ == "__main__":
main()