diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 84ded5ebcd..f41f133525 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -556,6 +556,9 @@ if(BUILD_TESTING) endforeach() add_test(NAME tls_diagnostics COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_diagnostics.py $) + add_test(NAME ignored_string_segments COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_ignored_string_segments.py $) + set_tests_properties(ignored_string_segments PROPERTIES TIMEOUT 30) add_test(NAME call_zero_padding COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_call_zero_padding.py $) set_tests_properties(call_zero_padding PROPERTIES TIMEOUT 60) diff --git a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp index 78936363b4..809af1503d 100644 --- a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp +++ b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp @@ -627,6 +627,9 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction( if ((!twoByteOpcode && op == OneByteLea) || (twoByteOpcode && op == TwoByteNopModRm)) info.HasMemoryOperand = false; + if (!twoByteOpcode && (op == 0x6c || op == 0x6d || op == 0xaa || op == 0xab || op == 0xae || op == 0xaf)) + info.SegmentPrefix = 0; + info.FlowKind = ControlFlowKind::Sequential; return info; } diff --git a/core/relinker/codegen/tests/DecoderLengthTests.cpp b/core/relinker/codegen/tests/DecoderLengthTests.cpp index 95c85168d7..a3f0ade694 100644 --- a/core/relinker/codegen/tests/DecoderLengthTests.cpp +++ b/core/relinker/codegen/tests/DecoderLengthTests.cpp @@ -113,6 +113,15 @@ int main() { const auto bytes = parse(testCase.Bytes); require(!decoder.DecodeInstruction(bytes.data(), bytes.size()).TruncatesBranchTarget, "Branch target of " + std::string(testCase.Bytes) + " was reported as truncated"); } + for (const auto segment : {0x64, 0x65}) { + for (const auto opcode : {0x6c, 0x6d, 0xaa, 0xab, 0xae, 0xaf, 0x6e, 0x6f, 0xa4, 0xa5, 0xa6, 0xa7, 0xac, 0xad}) { + const std::vector bytes{static_cast(segment), static_cast(opcode)}; + const auto info = decoder.DecodeInstruction(bytes.data(), bytes.size()); + const bool fixedEs = opcode == 0x6c || opcode == 0x6d || opcode == 0xaa || opcode == 0xab || opcode == 0xae || opcode == 0xaf; + require(info.HasMemoryOperand, "A string instruction lost its implicit memory operand"); + require(info.SegmentPrefix == (fixedEs ? 0 : segment), "Wrong effective segment for a string instruction"); + } + } const auto threeDNow = parse("0f0f0d11223344a6"); const auto threeDNowInfo = decoder.DecodeInstruction(threeDNow.data(), threeDNow.size()); require(threeDNowInfo.HasModRm && threeDNowInfo.ModRmByte == 0x0d, "3DNow! ModRM was not reported"); diff --git a/core/relinker/relinker/tests/test_ignored_string_segments.py b/core/relinker/relinker/tests/test_ignored_string_segments.py new file mode 100644 index 0000000000..25fb10cbb3 --- /dev/null +++ b/core/relinker/relinker/tests/test_ignored_string_segments.py @@ -0,0 +1,38 @@ +from pathlib import Path +import subprocess +import sys +import tempfile + +from test_macos_output import MachO +from test_tls_function_coverage import make_image, pe_bytes_at + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-string-segments-") as directory: + work = Path(directory) + for target in ("windows", "macos"): + for segment in (0x64, 0x65): + for opcode in (0x6c, 0x6d, 0xaa, 0xab, 0xae, 0xaf): + for prefixes in (b"", b"\x66", b"\xf3\x67", b"\xf2\x48"): + instruction = bytes([segment]) + prefixes + bytes([opcode]) + body = instruction + b"\xc3" + name = target + "-" + instruction.hex() + source, output = work / (name + ".elf"), work / name + source.write_bytes(make_image("register", "unwind", body=body)) + result = subprocess.run([str(relinker), "--" + target, "--skip-sce-module", str(source), str(output)], + capture_output=True, text=True, timeout=20) + assert result.returncode == 0, (name, result.stdout, result.stderr) + data = output.read_bytes() + if target == "windows": + actual = pe_bytes_at(data, 0x11240, len(body)) + else: + segment_info = next(s for s in MachO(data).segments if s["vmaddr"] <= 0x140011240 < s["vmaddr"] + s["filesize"]) + offset = segment_info["fileoff"] + 0x140011240 - segment_info["vmaddr"] + actual = data[offset:offset + len(body)] + assert actual == body, (name, actual.hex(), body.hex()) + print("Ignored string segment prefixes preserved on Windows and macOS") + + +if __name__ == "__main__": + main()