From 9986eb0fa976eda2f64c90c91e34a039f18e991a Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 17 Feb 2019 00:01:49 -0300 Subject: [PATCH] Support Linux and OSX on MemoryAlloc and CompareExchange128, some cleanup --- ChocolArm64/ChocolArm64.csproj | 1 + ChocolArm64/Instructions/InstEmitMemoryEx.cs | 26 ++----- ChocolArm64/Memory/CompareExchange128.cs | 61 ++++++++++++---- ChocolArm64/Memory/MemoryAlloc.cs | 34 ++++++++- ChocolArm64/Memory/MemoryAllocUnix.cs | 70 +++++++++++++++++++ .../Memory/MemoryProtectionException.cs | 10 +++ 6 files changed, 163 insertions(+), 39 deletions(-) create mode 100644 ChocolArm64/Memory/MemoryAllocUnix.cs create mode 100644 ChocolArm64/Memory/MemoryProtectionException.cs diff --git a/ChocolArm64/ChocolArm64.csproj b/ChocolArm64/ChocolArm64.csproj index 1156e361f5..0b4051b051 100644 --- a/ChocolArm64/ChocolArm64.csproj +++ b/ChocolArm64/ChocolArm64.csproj @@ -14,6 +14,7 @@ + diff --git a/ChocolArm64/Instructions/InstEmitMemoryEx.cs b/ChocolArm64/Instructions/InstEmitMemoryEx.cs index cd0e8f4804..215fcffdd5 100644 --- a/ChocolArm64/Instructions/InstEmitMemoryEx.cs +++ b/ChocolArm64/Instructions/InstEmitMemoryEx.cs @@ -93,9 +93,9 @@ namespace ChocolArm64.Instructions if (pair) { - //Exclusive loads should be atomic, for pairwise loads, the need to - //read all the data at once. for a 32-bits pairwise load, we do a - //simple 64-bits load, for 128-bits load, we need to call a special + //Exclusive loads should be atomic. For pairwise loads, we need to + //read all the data at once. For a 32-bits pairwise load, we do a + //simple 64-bits load, for a 128-bits load, we need to call a special //method to read 128-bits atomically. if (op.Size == 2) { @@ -169,7 +169,7 @@ namespace ChocolArm64.Instructions } else { - //8, 16, 32 or 64-bits load, or 64-bits pair load. + //8, 16, 32 or 64-bits (non-pairwise) load. context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdtmp(); @@ -181,24 +181,6 @@ namespace ChocolArm64.Instructions } context.EmitStintzr(op.Rt); - - if (pair) - { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); - context.EmitLdtmp(); - context.EmitLdc_I8(1 << op.Size); - - context.Emit(OpCodes.Add); - - EmitReadZxCall(context, op.Size); - - if (exclusive) - { - WriteExclusiveValue(nameof(CpuThreadState.ExclusiveValueHigh)); - } - - context.EmitStintzr(op.Rt2); - } } } diff --git a/ChocolArm64/Memory/CompareExchange128.cs b/ChocolArm64/Memory/CompareExchange128.cs index 3ac14c6f13..5e564ac006 100644 --- a/ChocolArm64/Memory/CompareExchange128.cs +++ b/ChocolArm64/Memory/CompareExchange128.cs @@ -23,22 +23,53 @@ namespace ChocolArm64.Memory static CompareExchange128() { - byte[] interlockedCompareExchange128Code = new byte[] + //TODO: Also check if cmpxchg16b is supported on cpu flags. + if (RuntimeInformation.OSArchitecture != Architecture.X64) { - 0x53, // push rbx - 0x49, 0x8B, 0x00, // mov rax, [r8] - 0x49, 0x8B, 0x19, // mov rbx, [r9] - 0x49, 0x89, 0xCA, // mov r10, rcx - 0x49, 0x89, 0xD3, // mov r11, rdx - 0x49, 0x8B, 0x49, 0x08, // mov rcx, [r9+8] - 0x49, 0x8B, 0x50, 0x08, // mov rdx, [r8+8] - 0xF0, 0x49, 0x0F, 0xC7, 0x0B, // lock cmpxchg16b [r11] - 0x49, 0x89, 0x02, // mov [r10], rax - 0x4C, 0x89, 0xD0, // mov rax, r10 - 0x49, 0x89, 0x52, 0x08, // mov [r10+8], rdx - 0x5B, // pop rbx - 0xC3 // ret - }; + throw new PlatformNotSupportedException(); + } + + byte[] interlockedCompareExchange128Code; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + interlockedCompareExchange128Code = new byte[] + { + 0x53, // push rbx + 0x49, 0x8B, 0x00, // mov rax, [r8] + 0x49, 0x8B, 0x19, // mov rbx, [r9] + 0x49, 0x89, 0xCA, // mov r10, rcx + 0x49, 0x89, 0xD3, // mov r11, rdx + 0x49, 0x8B, 0x49, 0x08, // mov rcx, [r9+8] + 0x49, 0x8B, 0x50, 0x08, // mov rdx, [r8+8] + 0xF0, 0x49, 0x0F, 0xC7, 0x0B, // lock cmpxchg16b [r11] + 0x49, 0x89, 0x02, // mov [r10], rax + 0x4C, 0x89, 0xD0, // mov rax, r10 + 0x49, 0x89, 0x52, 0x08, // mov [r10+8], rdx + 0x5B, // pop rbx + 0xC3 // ret + }; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + interlockedCompareExchange128Code = new byte[] + { + 0x53, // push %rbx + 0x49, 0x89, 0xd1, // mov %rdx,%r9 + 0x48, 0x89, 0xcb, // mov %rcx,%rbx + 0x48, 0x89, 0xf0, // mov %rsi,%rax + 0x4c, 0x89, 0xca, // mov %r9,%rdx + 0x4c, 0x89, 0xc1, // mov %r8,%rcx + 0xf0, 0x48, 0x0f, 0xc7, 0x0f, // lock cmpxchg16b (%rdi) + 0x5b, // pop %rbx + 0xc3 // retq + }; + } + else + { + throw new PlatformNotSupportedException(); + } ulong codeLength = (ulong)interlockedCompareExchange128Code.Length; diff --git a/ChocolArm64/Memory/MemoryAlloc.cs b/ChocolArm64/Memory/MemoryAlloc.cs index 243eedc197..a24299cd70 100644 --- a/ChocolArm64/Memory/MemoryAlloc.cs +++ b/ChocolArm64/Memory/MemoryAlloc.cs @@ -16,6 +16,11 @@ namespace ChocolArm64.Memory return MemoryAllocWindows.Allocate(sizeNint); } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MemoryAllocUnix.Allocate(size); + } else { throw new PlatformNotSupportedException(); @@ -30,24 +35,41 @@ namespace ChocolArm64.Memory return MemoryAllocWindows.AllocateWriteTracked(sizeNint); } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MemoryAllocUnix.Allocate(size); + } else { throw new PlatformNotSupportedException(); } } - public static bool Reprotect(IntPtr address, ulong size, MemoryProtection permission) + public static void Reprotect(IntPtr address, ulong size, MemoryProtection permission) { + bool result; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { IntPtr sizeNint = new IntPtr((long)size); - return MemoryAllocWindows.Reprotect(address, sizeNint, permission); + result = MemoryAllocWindows.Reprotect(address, sizeNint, permission); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + result = MemoryAllocUnix.Reprotect(address, size, permission); } else { throw new PlatformNotSupportedException(); } + + if (!result) + { + throw new MemoryProtectionException(permission); + } } public static bool Free(IntPtr address) @@ -56,6 +78,11 @@ namespace ChocolArm64.Memory { return MemoryAllocWindows.Free(address); } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MemoryAllocUnix.Free(address); + } else { throw new PlatformNotSupportedException(); @@ -69,6 +96,9 @@ namespace ChocolArm64.Memory IntPtr[] addresses, out ulong count) { + //This is only supported on windows, but returning + //false (failed) is also valid for platforms without + //write tracking support on the OS. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return MemoryAllocWindows.GetModifiedPages(address, size, addresses, out count); diff --git a/ChocolArm64/Memory/MemoryAllocUnix.cs b/ChocolArm64/Memory/MemoryAllocUnix.cs new file mode 100644 index 0000000000..857c1c5042 --- /dev/null +++ b/ChocolArm64/Memory/MemoryAllocUnix.cs @@ -0,0 +1,70 @@ +using Mono.Unix.Native; +using System; + +namespace ChocolArm64.Memory +{ + static class MemoryAllocUnix + { + public static IntPtr Allocate(ulong size) + { + ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE); + + const MmapProts prot = MmapProts.PROT_READ | MmapProts.PROT_WRITE; + + const MmapFlags flags = MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS; + + IntPtr ptr = Syscall.mmap(IntPtr.Zero, size + pageSize, prot, flags, -1, 0); + + if (ptr == IntPtr.Zero) + { + throw new OutOfMemoryException(); + } + + unsafe + { + ptr = new IntPtr(ptr.ToInt64() + (long)pageSize); + + *((ulong*)ptr - 1) = size; + } + + return ptr; + } + + public static bool Reprotect(IntPtr address, ulong size, Memory.MemoryProtection protection) + { + MmapProts prot = GetProtection(protection); + + return Syscall.mprotect(address, size, prot) == 0; + } + + private static MmapProts GetProtection(Memory.MemoryProtection protection) + { + switch (protection) + { + case Memory.MemoryProtection.None: return MmapProts.PROT_NONE; + case Memory.MemoryProtection.Read: return MmapProts.PROT_READ; + case Memory.MemoryProtection.ReadAndWrite: return MmapProts.PROT_READ | MmapProts.PROT_WRITE; + case Memory.MemoryProtection.ReadAndExecute: return MmapProts.PROT_READ | MmapProts.PROT_EXEC; + case Memory.MemoryProtection.Execute: return MmapProts.PROT_EXEC; + + default: throw new ArgumentException($"Invalid permission \"{protection}\"."); + } + } + + public static bool Free(IntPtr address) + { + ulong pageSize = (ulong)Syscall.sysconf(SysconfName._SC_PAGESIZE); + + ulong size; + + unsafe + { + size = *((ulong*)address - 1); + + address = new IntPtr(address.ToInt64() - (long)pageSize); + } + + return Syscall.munmap(address, size + pageSize) == 0; + } + } +} \ No newline at end of file diff --git a/ChocolArm64/Memory/MemoryProtectionException.cs b/ChocolArm64/Memory/MemoryProtectionException.cs new file mode 100644 index 0000000000..3d2cebad33 --- /dev/null +++ b/ChocolArm64/Memory/MemoryProtectionException.cs @@ -0,0 +1,10 @@ +using System; + +namespace ChocolArm64.Memory +{ + class MemoryProtectionException : Exception + { + public MemoryProtectionException(MemoryProtection protection) : + base($"Failed to set memory protection to \"{protection}\".") { } + } +} \ No newline at end of file