fix(cpu): preserve TLS instruction boundaries after rel8 branches (#846)

This commit is contained in:
Foued Attar
2026-08-24 22:52:30 +02:00
committed by GitHub
parent 600fcde637
commit 51e5480049
2 changed files with 148 additions and 4 deletions
@@ -8,6 +8,7 @@ using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Iced.Intel;
using SharpEmu.Core.Cpu;
using SharpEmu.Core.Cpu.Debugging;
using SharpEmu.Core.Loader;
@@ -3350,10 +3351,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
return false;
}
// A bare 0xEB (JMP rel8) always owns a disp8 after it, so it can
// never be the last byte of a valid instruction. If it precedes our
// candidate, we're mid-instruction: reject and let the scan retry.
if (regionOffset >= 1 && source[-1] == 0xEB)
var region = new ReadOnlySpan<byte>(source - regionOffset, regionOffset + availableLength);
if (IsTlsLoadCandidateInsideShortJump(region, regionOffset))
{
return false;
}
@@ -3410,6 +3409,76 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
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)
{
uint flNewProtect = default(uint);