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:
parent
2cf901bcff
commit
47f0bf93ef
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue