// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SharpEmu.Core.Cpu.Emulation; using SharpEmu.Core.Cpu.Native; using SharpEmu.HLE; using Xunit; namespace SharpEmu.Libs.Tests.Cpu; /// /// Coverage for the SSE4a EXTRQ/INSERTQ fault recovery through the POSIX signal bridge on /// Linux. Each test fabricates the exact frame the kernel hands the SIGILL handler - gregs /// whose RIP points at a real EXTRQ/INSERTQ encoding in probe-visible host memory, plus an /// FXSAVE image carrying the XMM registers - and drives the production entry point /// (TryHandlePosixFault) over it. The bridge must capture the XMM state into the CONTEXT /// scratch buffer, the recovery must decode and emulate the instruction, and the write-back /// must land the result in the FXSAVE image and advance RIP, because that is precisely what /// sigreturn restores on a live fault. /// public sealed unsafe class Sse4aPosixSignalRecoveryTests { private const int PosixSigIll = 4; private const int LinuxUcontextGregsOffset = 40; private const int LinuxGregsRipOffset = 16 * 8; private const int LinuxGregsFpstateOffset = 184; private const int FxsaveXmm0Offset = 160; private const int FxsaveXmm1Offset = 176; private static readonly MethodInfo TryHandlePosixFault = typeof(DirectExecutionBackend).GetMethod( "TryHandlePosixFault", BindingFlags.Static | BindingFlags.NonPublic)!; private static readonly FieldInfo PosixSignalBackend = typeof(DirectExecutionBackend).GetField( "_posixSignalBackend", BindingFlags.Static | BindingFlags.NonPublic)!; private static readonly FieldInfo EmulatedCounter = typeof(DirectExecutionBackend).GetField( "_sse4aInstructionsEmulated", BindingFlags.Static | BindingFlags.NonPublic)!; private static readonly FieldInfo XmmBridgedFlag = typeof(DirectExecutionBackend).GetField( "_posixXmmContextBridged", BindingFlags.Static | BindingFlags.NonPublic)!; private static readonly MethodInfo TryRecoverAmdCompat = typeof(DirectExecutionBackend).GetMethod( "TryRecoverAmdCompatInstruction", BindingFlags.Instance | BindingFlags.NonPublic)!; [Fact] public void ExtrqSigillRoundTripsXmmThroughTheBridge() { if (!OperatingSystem.IsLinux() || RuntimeInformation.ProcessArchitecture != Architecture.X64) { return; } // extrq xmm0, 0x10, 0x08 var code = AllocateProbeVisibleCode([0x66, 0x0F, 0x78, 0xC0, 0x10, 0x08]); try { const ulong value = 0x1234_5678_9ABC_DEF0UL; var frame = new FakeSignalFrame((ulong)code); frame.SetXmmLow(FxsaveXmm0Offset, value); var emulatedBefore = (long)EmulatedCounter.GetValue(null)!; Assert.True(frame.Dispatch()); Assert.Equal( Sse4aBitFieldEmulator.ExtractBitField(value, length: 0x10, index: 0x08), frame.XmmLow(FxsaveXmm0Offset)); Assert.Equal(0UL, frame.XmmHigh(FxsaveXmm0Offset)); Assert.Equal((ulong)code + 6, frame.Rip); Assert.True((long)EmulatedCounter.GetValue(null)! > emulatedBefore); } finally { FreeProbeVisibleCode(code); } } [Fact] public void InsertqSigillReadsSourceXmmThroughTheBridge() { if (!OperatingSystem.IsLinux() || RuntimeInformation.ProcessArchitecture != Architecture.X64) { return; } // insertq xmm0, xmm1, 0x10, 0x08 var code = AllocateProbeVisibleCode([0xF2, 0x0F, 0x78, 0xC1, 0x10, 0x08]); try { const ulong destination = 0x1111_2222_3333_4444UL; const ulong source = 0xAAAA_BBBB_CCCC_DDDDUL; var frame = new FakeSignalFrame((ulong)code); frame.SetXmmLow(FxsaveXmm0Offset, destination); frame.SetXmmLow(FxsaveXmm1Offset, source); Assert.True(frame.Dispatch()); Assert.Equal( Sse4aBitFieldEmulator.InsertBitField(destination, source, length: 0x10, index: 0x08), frame.XmmLow(FxsaveXmm0Offset)); Assert.Equal((ulong)code + 6, frame.Rip); } finally { FreeProbeVisibleCode(code); } } [Fact] public void RecoveryDeclinesWhenNoXmmStateWasBridged() { if (!OperatingSystem.IsLinux() || RuntimeInformation.ProcessArchitecture != Architecture.X64) { return; } // extrq xmm0, 0x10, 0x08 - valid and recoverable, but without bridged XMM state // (fpstate missing from the frame) the recovery must decline rather than emulate // over the zeroed scratch bytes. Drive the recovery entry directly: earlier tests // on this thread leave the thread-static bridge flag set, so clear it the way a // fpstate-less capture would. var code = AllocateProbeVisibleCode([0x66, 0x0F, 0x78, 0xC0, 0x10, 0x08]); try { XmmBridgedFlag.SetValue(null, false); var backend = RuntimeHelpers.GetUninitializedObject(typeof(DirectExecutionBackend)); var contextRecord = stackalloc byte[0x4D0]; var recovered = (bool)TryRecoverAmdCompat.Invoke( backend, [Pointer.Box(contextRecord, typeof(void*)), (ulong)code])!; Assert.False(recovered); } finally { FreeProbeVisibleCode(code); } } /// /// The Linux x86-64 signal frame as TryHandlePosixFault consumes it: a ucontext whose /// mcontext gregs sit at +40 (kernel sigcontext layout) with the fpstate pointer at /// gregs+184 aiming at a 512-byte FXSAVE image. /// private sealed class FakeSignalFrame { private readonly byte[] _ucontext = new byte[512]; private readonly byte[] _fpstate = new byte[512]; private readonly bool _wireFpstate; public FakeSignalFrame(ulong rip, bool wireFpstate = true) { _wireFpstate = wireFpstate; fixed (byte* ucontext = _ucontext) { *(ulong*)(ucontext + LinuxUcontextGregsOffset + LinuxGregsRipOffset) = rip; } } public ulong Rip { get { fixed (byte* ucontext = _ucontext) { return *(ulong*)(ucontext + LinuxUcontextGregsOffset + LinuxGregsRipOffset); } } } public void SetXmmLow(int fxsaveOffset, ulong value) { fixed (byte* fpstate = _fpstate) { *(ulong*)(fpstate + fxsaveOffset) = value; } } public ulong XmmLow(int fxsaveOffset) { fixed (byte* fpstate = _fpstate) { return *(ulong*)(fpstate + fxsaveOffset); } } public ulong XmmHigh(int fxsaveOffset) { fixed (byte* fpstate = _fpstate) { return *(ulong*)(fpstate + fxsaveOffset + 8); } } public bool Dispatch() { EnsureBridgeBackend(); fixed (byte* ucontext = _ucontext) fixed (byte* fpstate = _fpstate) { if (_wireFpstate) { *(byte**)(ucontext + LinuxUcontextGregsOffset + LinuxGregsFpstateOffset) = fpstate; } return (bool)TryHandlePosixFault.Invoke( null, [PosixSigIll, (nint)0, (nint)ucontext])!; } } } /// /// TryHandlePosixFault only runs the recovery chain when a backend instance is /// registered. The tests do not need any of the constructor's state (and must not run /// it: it installs process-wide signal handlers), so register an uninitialized /// instance - the SIGILL recovery path only touches static state. /// private static void EnsureBridgeBackend() { if (PosixSignalBackend.GetValue(null) == null) { PosixSignalBackend.SetValue( null, RuntimeHelpers.GetUninitializedObject(typeof(DirectExecutionBackend))); } } /// /// The instruction bytes must live in memory the fault-time page probe /// (TryReadHostBytes -> VirtualQuery) can see; on POSIX that is HostMemory's shadow /// region table, the same allocator guest code pages come from. A raw libc mmap or a /// pinned managed array would be invisible and the recovery would decline before /// decoding. /// private static nint AllocateProbeVisibleCode(ReadOnlySpan instructions) { var size = checked((nuint)Environment.SystemPageSize); var mapping = (nint)HostMemory.Alloc( null, size, HostMemory.MEM_COMMIT | HostMemory.MEM_RESERVE, HostMemory.PAGE_READWRITE); Assert.NotEqual((nint)0, mapping); instructions.CopyTo(new Span((void*)mapping, checked((int)size))); return mapping; } private static void FreeProbeVisibleCode(nint mapping) { Assert.True(HostMemory.Free((void*)mapping, 0, HostMemory.MEM_RELEASE)); } }