sharpemu/tests/SharpEmu.Libs.Tests/Loader/RosettaVectorStorePatchTest...

127 lines
6.2 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using Iced.Intel;
using SharpEmu.Core.Loader;
using SharpEmu.Core.Memory;
using Xunit;
namespace SharpEmu.Libs.Tests.Loader;
public sealed class RosettaVectorStorePatchTests
{
[Theory]
[InlineData(new byte[] { 0xC4, 0xC1, 0x7C, 0x11, 0x86, 0x90, 4, 0, 0 }, true)]
[InlineData(new byte[] { 0xC5, 0xFD, 0x11, 0x07 }, true)]
[InlineData(new byte[] { 0xC5, 0xFE, 0x7F, 0x07 }, true)]
[InlineData(new byte[] { 0xC5, 0xFC, 0x10, 0x07 }, false)]
[InlineData(new byte[] { 0xC5, 0xF8, 0x11, 0x07 }, false)]
[InlineData(new byte[] { 0xC5, 0xFC, 0x29, 0x07 }, false)]
[InlineData(new byte[] { 0xC5, 0xFC, 0x11, 0xC1 }, false)]
public void SelectsOnlyUnaligned256BitMemoryStores(byte[] instructionBytes, bool requiresStoreSplit)
{
Assert.Equal(requiresStoreSplit, RosettaVectorStorePatch.RequiresStoreSplit(DecodeInstruction(instructionBytes)));
}
[Theory]
[InlineData(new byte[] { 0xC4, 0x41, 0x7C, 0x11, 0x66, 0xF0 })]
[InlineData(new byte[] { 0xC5, 0xFC, 0x11, 0x5C, 0xF3, 0x60 })]
[InlineData(new byte[] { 0xC5, 0xFC, 0x11, 0x05, 0x00, 0x10, 0, 0 })]
[InlineData(new byte[] { 0x67, 0xC5, 0xFC, 0x11, 0x07 })]
public void SplitStoresPreserveAddressAndSourceAfterRelocation(byte[] instructionBytes)
{
var originalInstruction = DecodeInstruction(instructionBytes);
var instructions = RosettaVectorStorePatch.SplitVectorStores([originalInstruction]);
var writer = new InstructionByteWriter();
Assert.True(BlockEncoder.TryEncode(64, new InstructionBlock(writer, instructions, 0x200000), out var error, out _), error);
var decoder = Decoder.Create(64, new ByteArrayCodeReader(writer.EncodedBytes.ToArray()));
decoder.IP = 0x200000;
var lowerStore = decoder.Decode();
var upperStore = decoder.Decode();
Assert.Equal(Code.VEX_Vmovups_xmmm128_xmm, lowerStore.Code);
Assert.Equal(Register.XMM0 + (originalInstruction.Op1Register - Register.YMM0), lowerStore.Op1Register);
Assert.Equal(Code.VEX_Vextractf128_xmmm128_ymm_imm8, upperStore.Code);
Assert.Equal(originalInstruction.Op1Register, upperStore.Op1Register);
Assert.Equal(1, upperStore.Immediate8);
Assert.Equal(originalInstruction.MemoryBase, lowerStore.MemoryBase);
Assert.Equal(originalInstruction.MemoryIndex, lowerStore.MemoryIndex);
Assert.Equal(originalInstruction.MemoryIndexScale, upperStore.MemoryIndexScale);
Assert.Equal(originalInstruction.MemoryDisplacement64, lowerStore.MemoryDisplacement64);
Assert.Equal(unchecked(originalInstruction.MemoryDisplacement64 + 16), upperStore.MemoryDisplacement64);
}
[Fact]
public unsafe void RosettaSplitsStoresWithoutRedZoneUseAndPreservesAllBytes()
{
if (!RosettaVectorStorePatch.IsRequired)
return;
using var memory = new PhysicalVirtualMemory();
const ulong imageSize = 0x10000;
var imageBase = memory.AllocateAt(0, imageSize);
// Load a vector, write it to the test address, and copy the source register.
// Clear the upper vector registers before the function returns.
byte[] function =
[
0xC5, 0xFE, 0x6F, 0x06,
0xC5, 0xFC, 0x11, 0x87, 0x90, 4, 0, 0,
0xC5, 0xFE, 0x7F, 0x02,
0xC5, 0xF8, 0x77, 0xC3,
];
Assert.True(memory.TryWrite(imageBase, function));
var exceptionFrameHeader = new byte[32];
exceptionFrameHeader[0] = 1;
exceptionFrameHeader[2] = 3;
BinaryPrimitives.WriteUInt32LittleEndian(exceptionFrameHeader.AsSpan(12), 1);
BinaryPrimitives.WriteUInt64LittleEndian(exceptionFrameHeader.AsSpan(16), imageBase);
Assert.True(memory.TryWrite(imageBase + 0x1000, exceptionFrameHeader));
ProgramHeader[] programHeaders =
[
CreateProgramHeader(ProgramHeaderType.Load, ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute, 0, (ulong)function.Length),
CreateProgramHeader(ProgramHeaderType.GnuEhFrame, ProgramHeaderFlags.Read, 0x1000, (ulong)exceptionFrameHeader.Length),
];
var result = GuestRedZonePatcher.Patch(memory, memory, programHeaders, imageBase, imageSize);
Assert.Equal(0, result.RedZoneFunctions);
Assert.Equal(2, result.VectorStoreCount);
Assert.Equal(2, result.PatchedSites);
Assert.Equal(0, result.FailedSites);
byte* source = stackalloc byte[32];
byte* copiedSource = stackalloc byte[32];
byte* destination = stackalloc byte[32];
for (int byteIndex = 0; byteIndex < 32; byteIndex++) source[byteIndex] = (byte)(byteIndex * 7 + 1);
((delegate* unmanaged<byte*, byte*, byte*, void>)imageBase)(destination - 0x490, source, copiedSource);
Assert.Equal(new ReadOnlySpan<byte>(source, 32).ToArray(), new ReadOnlySpan<byte>(destination, 32).ToArray());
Assert.Equal(new ReadOnlySpan<byte>(source, 32).ToArray(), new ReadOnlySpan<byte>(copiedSource, 32).ToArray());
}
private static ProgramHeader CreateProgramHeader(ProgramHeaderType type, ProgramHeaderFlags flags, ulong virtualAddress, ulong segmentSize)
{
Span<byte> bytes = stackalloc byte[56];
bytes.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(bytes, (uint)type);
BinaryPrimitives.WriteUInt32LittleEndian(bytes[4..], (uint)flags);
BinaryPrimitives.WriteUInt64LittleEndian(bytes[16..], virtualAddress);
BinaryPrimitives.WriteUInt64LittleEndian(bytes[32..], segmentSize);
BinaryPrimitives.WriteUInt64LittleEndian(bytes[40..], segmentSize);
return MemoryMarshal.Read<ProgramHeader>(bytes);
}
private static Instruction DecodeInstruction(byte[] instructionBytes)
{
var decoder = Decoder.Create(64, new ByteArrayCodeReader(instructionBytes));
decoder.IP = 0x100000;
return decoder.Decode();
}
private sealed class InstructionByteWriter : CodeWriter
{
public List<byte> EncodedBytes { get; } = [];
public override void WriteByte(byte value) => EncodedBytes.Add(value);
}
}