diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 1936a6b42..c04c4883a 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -191,6 +191,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) add_test(NAME tls_function_coverage COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $) + add_test(NAME tls_diagnostics COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_diagnostics.py $) add_test(NAME linux_load_alignment COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_alignment.py $) add_test(NAME executable_segments COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/domain/include/domain/Types.hpp b/core/relinker/domain/include/domain/Types.hpp index 40bd960ce..d9caa6a41 100644 --- a/core/relinker/domain/include/domain/Types.hpp +++ b/core/relinker/domain/include/domain/Types.hpp @@ -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) {} diff --git a/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp index 42a6e723f..a05d32ab4 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp @@ -80,7 +80,12 @@ std::vector GuestModuleWriter::WriteWindows(const Relinker::GuestI auto nextRva = image.GetEndRva(); std::array 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); diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index 3d111335a..7a7b94fd4 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include #include +#include #include namespace Elfpatcher::Windows { @@ -83,8 +85,14 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co const auto loadRegister = info.Length - position == 7 ? static_cast(((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(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)}); } } diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 27bebcfec..05b13295a 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -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 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(); diff --git a/core/relinker/relinker/tests/test_tls_diagnostics.py b/core/relinker/relinker/tests/test_tls_diagnostics.py new file mode 100644 index 000000000..51c9614bb --- /dev/null +++ b/core/relinker/relinker/tests/test_tls_diagnostics.py @@ -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()