mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-30 13:24:19 +08:00
fix(cpu): preserve TLS instruction boundaries after rel8 branches (#846)
This commit is contained in:
@@ -8,6 +8,7 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Iced.Intel;
|
||||||
using SharpEmu.Core.Cpu;
|
using SharpEmu.Core.Cpu;
|
||||||
using SharpEmu.Core.Cpu.Debugging;
|
using SharpEmu.Core.Cpu.Debugging;
|
||||||
using SharpEmu.Core.Loader;
|
using SharpEmu.Core.Loader;
|
||||||
@@ -3350,10 +3351,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A bare 0xEB (JMP rel8) always owns a disp8 after it, so it can
|
var region = new ReadOnlySpan<byte>(source - regionOffset, regionOffset + availableLength);
|
||||||
// never be the last byte of a valid instruction. If it precedes our
|
if (IsTlsLoadCandidateInsideShortJump(region, regionOffset))
|
||||||
// candidate, we're mid-instruction: reject and let the scan retry.
|
|
||||||
if (regionOffset >= 1 && source[-1] == 0xEB)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3410,6 +3409,76 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
return PatchTlsLoadInstruction(address, instructionLength, destinationRegister);
|
return PatchTlsLoadInstruction(address, instructionLength, destinationRegister);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool IsTlsLoadCandidateInsideShortJump(ReadOnlySpan<byte> region, int candidateOffset)
|
||||||
|
{
|
||||||
|
if ((uint)candidateOffset >= (uint)region.Length ||
|
||||||
|
candidateOffset < 1 ||
|
||||||
|
region[candidateOffset - 1] != 0xEB)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept EB when it is an aligned rel8 operand.
|
||||||
|
if (IsRel8ControlFlowInstructionEndingAtCandidate(region, candidateOffset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRel8ControlFlowInstructionEndingAtCandidate(
|
||||||
|
ReadOnlySpan<byte> region,
|
||||||
|
int candidateOffset)
|
||||||
|
{
|
||||||
|
if (candidateOffset < 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var branchOffset = candidateOffset - 2;
|
||||||
|
var opcode = region[branchOffset];
|
||||||
|
if (!((opcode >= 0x70 && opcode <= 0x7F) ||
|
||||||
|
opcode is >= 0xE0 and <= 0xE3 ||
|
||||||
|
opcode == 0xEB))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var branchTarget = candidateOffset + (sbyte)region[candidateOffset - 1];
|
||||||
|
if (branchTarget < 0 || branchTarget >= branchOffset)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require an aligned instruction stream.
|
||||||
|
var decoder = Decoder.Create(
|
||||||
|
64,
|
||||||
|
new ByteArrayCodeReader(region[branchTarget..candidateOffset].ToArray()));
|
||||||
|
decoder.IP = (ulong)branchTarget;
|
||||||
|
while (decoder.IP < (ulong)candidateOffset)
|
||||||
|
{
|
||||||
|
var instructionOffset = (int)decoder.IP;
|
||||||
|
decoder.Decode(out var instruction);
|
||||||
|
if (instruction.Code == Code.INVALID || instruction.Length <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instructionOffset == branchOffset)
|
||||||
|
{
|
||||||
|
return instruction.Length == 2 && decoder.IP == (ulong)candidateOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder.IP > (ulong)branchOffset)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe bool PatchTlsLoadInstruction(nint address, int instructionLength, int destinationRegister)
|
private unsafe bool PatchTlsLoadInstruction(nint address, int instructionLength, int destinationRegister)
|
||||||
{
|
{
|
||||||
uint flNewProtect = default(uint);
|
uint flNewProtect = default(uint);
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.Core.Cpu.Native;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests.Cpu;
|
||||||
|
|
||||||
|
public sealed class TlsLoadPatchBoundaryTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RejectsGtaShortJumpDisplacementAsTlsPrefix()
|
||||||
|
{
|
||||||
|
byte[] code =
|
||||||
|
[
|
||||||
|
0x90,
|
||||||
|
0xEB, 0x66,
|
||||||
|
0x66, 0x64, 0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
Assert.True(DirectExecutionBackend.IsTlsLoadCandidateInsideShortJump(code, candidateOffset: 2));
|
||||||
|
Assert.False(DirectExecutionBackend.IsTlsLoadCandidateInsideShortJump(code, candidateOffset: 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void KeepsDreamingSarahTlsInstructionAfterBackwardJnz()
|
||||||
|
{
|
||||||
|
byte[] code =
|
||||||
|
[
|
||||||
|
0x48, 0x8B, 0x1C, 0xD0,
|
||||||
|
0x4C, 0x39, 0x2B,
|
||||||
|
0x0F, 0x84, 0x10, 0x01, 0x00, 0x00,
|
||||||
|
0x48, 0xFF, 0xC2,
|
||||||
|
0x48, 0x39, 0xD1,
|
||||||
|
0x75, 0xEB,
|
||||||
|
0x66, 0x66, 0x66, 0x64, 0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
Assert.False(DirectExecutionBackend.IsTlsLoadCandidateInsideShortJump(code, candidateOffset: 21));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0x70)]
|
||||||
|
[InlineData(0x7F)]
|
||||||
|
[InlineData(0xE0)]
|
||||||
|
[InlineData(0xE3)]
|
||||||
|
[InlineData(0xEB)]
|
||||||
|
public void KeepsTlsInstructionAfterRel8ControlFlow(byte opcode)
|
||||||
|
{
|
||||||
|
byte[] code =
|
||||||
|
[
|
||||||
|
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||||
|
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||||
|
opcode, 0xEB,
|
||||||
|
0x66, 0x64, 0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
Assert.False(DirectExecutionBackend.IsTlsLoadCandidateInsideShortJump(code, candidateOffset: 21));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Rel8OpcodeByteInsidePreviousInstructionDoesNotBypassGuard()
|
||||||
|
{
|
||||||
|
byte[] code =
|
||||||
|
[
|
||||||
|
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||||
|
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||||
|
0x6A, 0x75,
|
||||||
|
0xEB, 0x66,
|
||||||
|
0x66, 0x64, 0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
Assert.True(DirectExecutionBackend.IsTlsLoadCandidateInsideShortJump(code, candidateOffset: 21));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user