Ryujinx.Tests: Add unicorn to test framework (#389)

* Ryujinx.Tests: Add unicorn to test framework

* CpuTestSimdArithmetic: Comment out inaccurate results
This commit is contained in:
Merry
2018-09-01 15:24:05 +01:00
committed by gdkchan
parent 42dc925c3d
commit 326777ca4a
31 changed files with 1345 additions and 1 deletions

View File

@ -0,0 +1,28 @@
using System;
namespace Ryujinx.Tests.Unicorn
{
public class IndexedProperty<TIndex, TValue>
{
readonly Action<TIndex, TValue> SetAction;
readonly Func<TIndex, TValue> GetFunc;
public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction)
{
this.GetFunc = getFunc;
this.SetAction = setAction;
}
public TValue this[TIndex i]
{
get
{
return GetFunc(i);
}
set
{
SetAction(i, value);
}
}
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace Ryujinx.Tests.Unicorn
{
public enum MemoryPermission
{
NONE = 0,
READ = 1,
WRITE = 2,
EXEC = 4,
ALL = 7,
}
}

View File

@ -0,0 +1,296 @@
using System;
namespace Ryujinx.Tests.Unicorn.Native
{
public enum ArmRegister
{
INVALID = 0,
X29,
X30,
NZCV,
SP,
WSP,
WZR,
XZR,
B0,
B1,
B2,
B3,
B4,
B5,
B6,
B7,
B8,
B9,
B10,
B11,
B12,
B13,
B14,
B15,
B16,
B17,
B18,
B19,
B20,
B21,
B22,
B23,
B24,
B25,
B26,
B27,
B28,
B29,
B30,
B31,
D0,
D1,
D2,
D3,
D4,
D5,
D6,
D7,
D8,
D9,
D10,
D11,
D12,
D13,
D14,
D15,
D16,
D17,
D18,
D19,
D20,
D21,
D22,
D23,
D24,
D25,
D26,
D27,
D28,
D29,
D30,
D31,
H0,
H1,
H2,
H3,
H4,
H5,
H6,
H7,
H8,
H9,
H10,
H11,
H12,
H13,
H14,
H15,
H16,
H17,
H18,
H19,
H20,
H21,
H22,
H23,
H24,
H25,
H26,
H27,
H28,
H29,
H30,
H31,
Q0,
Q1,
Q2,
Q3,
Q4,
Q5,
Q6,
Q7,
Q8,
Q9,
Q10,
Q11,
Q12,
Q13,
Q14,
Q15,
Q16,
Q17,
Q18,
Q19,
Q20,
Q21,
Q22,
Q23,
Q24,
Q25,
Q26,
Q27,
Q28,
Q29,
Q30,
Q31,
S0,
S1,
S2,
S3,
S4,
S5,
S6,
S7,
S8,
S9,
S10,
S11,
S12,
S13,
S14,
S15,
S16,
S17,
S18,
S19,
S20,
S21,
S22,
S23,
S24,
S25,
S26,
S27,
S28,
S29,
S30,
S31,
W0,
W1,
W2,
W3,
W4,
W5,
W6,
W7,
W8,
W9,
W10,
W11,
W12,
W13,
W14,
W15,
W16,
W17,
W18,
W19,
W20,
W21,
W22,
W23,
W24,
W25,
W26,
W27,
W28,
W29,
W30,
X0,
X1,
X2,
X3,
X4,
X5,
X6,
X7,
X8,
X9,
X10,
X11,
X12,
X13,
X14,
X15,
X16,
X17,
X18,
X19,
X20,
X21,
X22,
X23,
X24,
X25,
X26,
X27,
X28,
V0,
V1,
V2,
V3,
V4,
V5,
V6,
V7,
V8,
V9,
V10,
V11,
V12,
V13,
V14,
V15,
V16,
V17,
V18,
V19,
V20,
V21,
V22,
V23,
V24,
V25,
V26,
V27,
V28,
V29,
V30,
V31,
//> pseudo registers
PC, // program counter register
CPACR_EL1,
ESR,
//> thread registers
TPIDR_EL0,
TPIDRRO_EL0,
TPIDR_EL1,
PSTATE, // PSTATE pseudoregister
//> floating point control and status registers
FPCR,
FPSR,
ENDING, // <-- mark the end of the list of registers
//> alias registers
IP0 = X16,
IP1 = X17,
FP = X29,
LR = X30,
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Runtime.InteropServices;
using Ryujinx.Tests.Unicorn;
namespace Ryujinx.Tests.Unicorn.Native
{
public class Interface
{
public static void Checked(UnicornError error)
{
if (error != UnicornError.UC_ERR_OK)
{
throw new UnicornException(error);
}
}
public static void MarshalArrayOf<T>(IntPtr input, int length, out T[] output)
{
var size = Marshal.SizeOf(typeof(T));
output = new T[length];
for (int i = 0; i < length; i++)
{
IntPtr item = new IntPtr(input.ToInt64() + i * size);
output[i] = Marshal.PtrToStructure<T>(item);
}
}
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern uint uc_version(out uint major, out uint minor);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_open(uint arch, uint mode, out IntPtr uc);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_close(IntPtr uc);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr uc_strerror(UnicornError err);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_reg_write(IntPtr uc, int regid, byte[] value);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_reg_read(IntPtr uc, int regid, byte[] value);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_write(IntPtr uc, ulong address, byte[] bytes, ulong size);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_read(IntPtr uc, ulong address, byte[] bytes, ulong size);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_emu_start(IntPtr uc, ulong begin, ulong until, ulong timeout, ulong count);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_map(IntPtr uc, ulong address, ulong size, uint perms);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_unmap(IntPtr uc, ulong address, ulong size);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_protect(IntPtr uc, ulong address, ulong size, uint perms);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_regions(IntPtr uc, out IntPtr regions, out uint count);
}
}

View File

@ -0,0 +1,16 @@
using System;
namespace Ryujinx.Tests.Unicorn.Native
{
public enum UnicornArch
{
UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2)
UC_ARCH_ARM64, // ARM-64, also called AArch64
UC_ARCH_MIPS, // Mips architecture
UC_ARCH_X86, // X86 architecture (including x86 & x86-64)
UC_ARCH_PPC, // PowerPC architecture (currently unsupported)
UC_ARCH_SPARC, // Sparc architecture
UC_ARCH_M68K, // M68K architecture
UC_ARCH_MAX,
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Tests.Unicorn.Native
{
[StructLayout(LayoutKind.Sequential)]
public struct UnicornMemoryRegion
{
public UInt64 begin; // begin address of the region (inclusive)
public UInt64 end; // end address of the region (inclusive)
public UInt32 perms; // memory permissions of the region
}
}

View File

@ -0,0 +1,34 @@
using System;
namespace Ryujinx.Tests.Unicorn.Native
{
public enum UnicornMode
{
UC_MODE_LITTLE_ENDIAN = 0, // little-endian mode (default mode)
UC_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode
// arm / arm64
UC_MODE_ARM = 0, // ARM mode
UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2)
UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series (currently unsupported)
UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM (currently unsupported)
// mips
UC_MODE_MICRO = 1 << 4, // MicroMips mode (currently unsupported)
UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (currently unsupported)
UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (currently unsupported)
UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA
UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA
// x86 / x64
UC_MODE_16 = 1 << 1, // 16-bit mode
UC_MODE_32 = 1 << 2, // 32-bit mode
UC_MODE_64 = 1 << 3, // 64-bit mode
// ppc
UC_MODE_PPC32 = 1 << 2, // 32-bit mode (currently unsupported)
UC_MODE_PPC64 = 1 << 3, // 64-bit mode (currently unsupported)
UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode (currently unsupported)
// sparc
UC_MODE_SPARC32 = 1 << 2, // 32-bit mode
UC_MODE_SPARC64 = 1 << 3, // 64-bit mode
UC_MODE_V9 = 1 << 4, // SparcV9 mode (currently unsupported)
// m68k
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="System.Runtime.Intrinsics.Experimental" Version="4.5.0-rc1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,315 @@
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Ryujinx.Tests.Unicorn
{
public class UnicornAArch64
{
internal readonly IntPtr uc;
public IndexedProperty<int, ulong> X
{
get
{
return new IndexedProperty<int, ulong>(
(int i) => GetX(i),
(int i, ulong value) => SetX(i, value));
}
}
public IndexedProperty<int, Vector128<float>> Q
{
get
{
return new IndexedProperty<int, Vector128<float>>(
(int i) => GetQ(i),
(int i, Vector128<float> value) => SetQ(i, value));
}
}
public ulong LR
{
get { return GetRegister(Native.ArmRegister.LR); }
set { SetRegister(Native.ArmRegister.LR, value); }
}
public ulong SP
{
get { return GetRegister(Native.ArmRegister.SP); }
set { SetRegister(Native.ArmRegister.SP, value); }
}
public ulong PC
{
get { return GetRegister(Native.ArmRegister.PC); }
set { SetRegister(Native.ArmRegister.PC, value); }
}
public uint Pstate
{
get { return (uint)GetRegister(Native.ArmRegister.PSTATE); }
set { SetRegister(Native.ArmRegister.PSTATE, (uint)value); }
}
public int Fpcr
{
get { return (int)GetRegister(Native.ArmRegister.FPCR); }
set { SetRegister(Native.ArmRegister.FPCR, (uint)value); }
}
public int Fpsr
{
get { return (int)GetRegister(Native.ArmRegister.FPSR); }
set { SetRegister(Native.ArmRegister.FPSR, (uint)value); }
}
public bool OverflowFlag
{
get { return (Pstate & 0x10000000u) != 0; }
set { Pstate = (Pstate & ~0x10000000u) | (value ? 0x10000000u : 0u); }
}
public bool CarryFlag
{
get { return (Pstate & 0x20000000u) != 0; }
set { Pstate = (Pstate & ~0x20000000u) | (value ? 0x20000000u : 0u); }
}
public bool ZeroFlag
{
get { return (Pstate & 0x40000000u) != 0; }
set { Pstate = (Pstate & ~0x40000000u) | (value ? 0x40000000u : 0u); }
}
public bool NegativeFlag
{
get { return (Pstate & 0x80000000u) != 0; }
set { Pstate = (Pstate & ~0x80000000u) | (value ? 0x80000000u : 0u); }
}
public UnicornAArch64()
{
Native.Interface.Checked(Native.Interface.uc_open((uint)Native.UnicornArch.UC_ARCH_ARM64, (uint)Native.UnicornMode.UC_MODE_LITTLE_ENDIAN, out uc));
SetRegister(Native.ArmRegister.CPACR_EL1, 0x00300000);
}
~UnicornAArch64()
{
Native.Interface.Checked(Native.Interface.uc_close(uc));
}
public void RunForCount(ulong count)
{
Native.Interface.Checked(Native.Interface.uc_emu_start(uc, this.PC, 0xFFFFFFFFFFFFFFFFu, 0, count));
}
public void Step()
{
RunForCount(1);
}
internal static Native.ArmRegister[] X_registers = new Native.ArmRegister[31]
{
Native.ArmRegister.X0,
Native.ArmRegister.X1,
Native.ArmRegister.X2,
Native.ArmRegister.X3,
Native.ArmRegister.X4,
Native.ArmRegister.X5,
Native.ArmRegister.X6,
Native.ArmRegister.X7,
Native.ArmRegister.X8,
Native.ArmRegister.X9,
Native.ArmRegister.X10,
Native.ArmRegister.X11,
Native.ArmRegister.X12,
Native.ArmRegister.X13,
Native.ArmRegister.X14,
Native.ArmRegister.X15,
Native.ArmRegister.X16,
Native.ArmRegister.X17,
Native.ArmRegister.X18,
Native.ArmRegister.X19,
Native.ArmRegister.X20,
Native.ArmRegister.X21,
Native.ArmRegister.X22,
Native.ArmRegister.X23,
Native.ArmRegister.X24,
Native.ArmRegister.X25,
Native.ArmRegister.X26,
Native.ArmRegister.X27,
Native.ArmRegister.X28,
Native.ArmRegister.X29,
Native.ArmRegister.X30,
};
internal static Native.ArmRegister[] Q_registers = new Native.ArmRegister[32]
{
Native.ArmRegister.Q0,
Native.ArmRegister.Q1,
Native.ArmRegister.Q2,
Native.ArmRegister.Q3,
Native.ArmRegister.Q4,
Native.ArmRegister.Q5,
Native.ArmRegister.Q6,
Native.ArmRegister.Q7,
Native.ArmRegister.Q8,
Native.ArmRegister.Q9,
Native.ArmRegister.Q10,
Native.ArmRegister.Q11,
Native.ArmRegister.Q12,
Native.ArmRegister.Q13,
Native.ArmRegister.Q14,
Native.ArmRegister.Q15,
Native.ArmRegister.Q16,
Native.ArmRegister.Q17,
Native.ArmRegister.Q18,
Native.ArmRegister.Q19,
Native.ArmRegister.Q20,
Native.ArmRegister.Q21,
Native.ArmRegister.Q22,
Native.ArmRegister.Q23,
Native.ArmRegister.Q24,
Native.ArmRegister.Q25,
Native.ArmRegister.Q26,
Native.ArmRegister.Q27,
Native.ArmRegister.Q28,
Native.ArmRegister.Q29,
Native.ArmRegister.Q30,
Native.ArmRegister.Q31,
};
internal ulong GetRegister(Native.ArmRegister register)
{
byte[] value_bytes = new byte[8];
Native.Interface.Checked(Native.Interface.uc_reg_read(uc, (int)register, value_bytes));
return (ulong)BitConverter.ToInt64(value_bytes, 0);
}
internal void SetRegister(Native.ArmRegister register, ulong value)
{
byte[] value_bytes = BitConverter.GetBytes(value);
Native.Interface.Checked(Native.Interface.uc_reg_write(uc, (int)register, value_bytes));
}
internal Vector128<float> GetVector(Native.ArmRegister register)
{
byte[] value_bytes = new byte[16];
Native.Interface.Checked(Native.Interface.uc_reg_read(uc, (int)register, value_bytes));
unsafe
{
fixed (byte* p = &value_bytes[0])
{
return Sse.LoadVector128((float*)p);
}
}
}
internal void SetVector(Native.ArmRegister register, Vector128<float> value)
{
byte[] value_bytes = new byte[16];
unsafe
{
fixed (byte* p = &value_bytes[0])
{
Sse.Store((float*)p, value);
}
}
Native.Interface.Checked(Native.Interface.uc_reg_write(uc, (int)register, value_bytes));
}
public ulong GetX(int index)
{
Contract.Requires(index <= 30, "invalid register");
return GetRegister(X_registers[index]);
}
public void SetX(int index, ulong value)
{
Contract.Requires(index <= 30, "invalid register");
SetRegister(X_registers[index], value);
}
public Vector128<float> GetQ(int index)
{
Contract.Requires(index <= 31, "invalid vector");
return GetVector(Q_registers[index]);
}
public void SetQ(int index, Vector128<float> value)
{
Contract.Requires(index <= 31, "invalid vector");
SetVector(Q_registers[index], value);
}
public byte[] MemoryRead(ulong address, ulong size)
{
byte[] value = new byte[size];
Native.Interface.Checked(Native.Interface.uc_mem_read(uc, address, value, size));
return value;
}
public byte MemoryRead8 (ulong address) { return MemoryRead(address, 1)[0]; }
public UInt16 MemoryRead16(ulong address) { return (UInt16)BitConverter.ToInt16(MemoryRead(address, 2), 0); }
public UInt32 MemoryRead32(ulong address) { return (UInt32)BitConverter.ToInt32(MemoryRead(address, 4), 0); }
public UInt64 MemoryRead64(ulong address) { return (UInt64)BitConverter.ToInt64(MemoryRead(address, 8), 0); }
public void MemoryWrite(ulong address, byte[] value)
{
Native.Interface.Checked(Native.Interface.uc_mem_write(uc, address, value, (ulong)value.Length));
}
public void MemoryWrite8 (ulong address, byte value) { MemoryWrite(address, new byte[]{value}); }
public void MemoryWrite16(ulong address, Int16 value) { MemoryWrite(address, BitConverter.GetBytes(value)); }
public void MemoryWrite16(ulong address, UInt16 value) { MemoryWrite(address, BitConverter.GetBytes(value)); }
public void MemoryWrite32(ulong address, Int32 value) { MemoryWrite(address, BitConverter.GetBytes(value)); }
public void MemoryWrite32(ulong address, UInt32 value) { MemoryWrite(address, BitConverter.GetBytes(value)); }
public void MemoryWrite64(ulong address, Int64 value) { MemoryWrite(address, BitConverter.GetBytes(value)); }
public void MemoryWrite64(ulong address, UInt64 value) { MemoryWrite(address, BitConverter.GetBytes(value)); }
public void MemoryMap(ulong address, ulong size, MemoryPermission permissions)
{
Native.Interface.Checked(Native.Interface.uc_mem_map(uc, address, size, (uint)permissions));
}
public void MemoryUnmap(ulong address, ulong size)
{
Native.Interface.Checked(Native.Interface.uc_mem_unmap(uc, address, size));
}
public void MemoryProtect(ulong address, ulong size, MemoryPermission permissions)
{
Native.Interface.Checked(Native.Interface.uc_mem_protect(uc, address, size, (uint)permissions));
}
public void DumpMemoryInformation()
{
Native.Interface.Checked(Native.Interface.uc_mem_regions(uc, out IntPtr regions_raw, out uint length));
Native.Interface.MarshalArrayOf<Native.UnicornMemoryRegion>(regions_raw, (int)length, out var regions);
foreach (var region in regions)
{
Console.WriteLine("region: begin {0:X16} end {1:X16} perms {2:X8}", region.begin, region.end, region.perms);
}
}
public static bool IsAvailable()
{
try
{
Native.Interface.uc_version(out uint major, out uint minor);
return true;
}
catch (DllNotFoundException)
{
return false;
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace Ryujinx.Tests.Unicorn
{
public enum UnicornError
{
UC_ERR_OK = 0, // No error: everything was fine
UC_ERR_NOMEM, // Out-Of-Memory error: uc_open(), uc_emulate()
UC_ERR_ARCH, // Unsupported architecture: uc_open()
UC_ERR_HANDLE, // Invalid handle
UC_ERR_MODE, // Invalid/unsupported mode: uc_open()
UC_ERR_VERSION, // Unsupported version (bindings)
UC_ERR_READ_UNMAPPED, // Quit emulation due to READ on unmapped memory: uc_emu_start()
UC_ERR_WRITE_UNMAPPED, // Quit emulation due to WRITE on unmapped memory: uc_emu_start()
UC_ERR_FETCH_UNMAPPED, // Quit emulation due to FETCH on unmapped memory: uc_emu_start()
UC_ERR_HOOK, // Invalid hook type: uc_hook_add()
UC_ERR_INSN_INVALID, // Quit emulation due to invalid instruction: uc_emu_start()
UC_ERR_MAP, // Invalid memory mapping: uc_mem_map()
UC_ERR_WRITE_PROT, // Quit emulation due to UC_MEM_WRITE_PROT violation: uc_emu_start()
UC_ERR_READ_PROT, // Quit emulation due to UC_MEM_READ_PROT violation: uc_emu_start()
UC_ERR_FETCH_PROT, // Quit emulation due to UC_MEM_FETCH_PROT violation: uc_emu_start()
UC_ERR_ARG, // Inavalid argument provided to uc_xxx function (See specific function API)
UC_ERR_READ_UNALIGNED, // Unaligned read
UC_ERR_WRITE_UNALIGNED, // Unaligned write
UC_ERR_FETCH_UNALIGNED, // Unaligned fetch
UC_ERR_HOOK_EXIST, // hook for this event already existed
UC_ERR_RESOURCE, // Insufficient resource: uc_emu_start()
UC_ERR_EXCEPTION // Unhandled CPU exception
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Tests.Unicorn
{
public class UnicornException : Exception
{
public readonly UnicornError Error;
internal UnicornException(UnicornError error)
{
Error = error;
}
public override string Message
{
get
{
return Marshal.PtrToStringAnsi(Native.Interface.uc_strerror(Error));
}
}
}
}

View File

@ -0,0 +1,3 @@
The pre-compiled dynamic libraries in this directory are licenced under the GPLv2.
The source code for windows/unicorn.dll is available at: https://github.com/MerryMage/UnicornDotNet/tree/299451c02d9c810d2feca51f5e9cb6d8b2f38960

Binary file not shown.