Преглед изворни кода

Force all exclusive memory accesses to be ordered on AppleHv (#5898)

* Implement reprotect method on virtual memory manager (currently stubbed)

* Force all exclusive memory accesses to be ordered on AppleHv

* Format whitespace

* Fix test build

* Fix comment copy/paste

* Fix wrong bit for isLoad

* Update src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs

Co-authored-by: riperiperi <rhy3756547@hotmail.com>

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
gdkchan пре 2 година
родитељ
комит
815819767c

+ 62 - 0
src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace Ryujinx.Cpu.AppleHv
+{
+    static class HvCodePatcher
+    {
+        private const uint XMask = 0x3f808000u;
+        private const uint XValue = 0x8000000u;
+
+        private const uint ZrIndex = 31u;
+
+        public static void RewriteUnorderedExclusiveInstructions(Span<byte> code)
+        {
+            Span<uint> codeUint = MemoryMarshal.Cast<byte, uint>(code);
+            Span<Vector128<uint>> codeVector = MemoryMarshal.Cast<byte, Vector128<uint>>(code);
+
+            Vector128<uint> mask = Vector128.Create(XMask);
+            Vector128<uint> value = Vector128.Create(XValue);
+
+            for (int index = 0; index < codeVector.Length; index++)
+            {
+                Vector128<uint> v = codeVector[index];
+
+                if (Vector128.EqualsAny(Vector128.BitwiseAnd(v, mask), value))
+                {
+                    int baseIndex = index * 4;
+
+                    for (int instIndex = baseIndex; instIndex < baseIndex + 4; instIndex++)
+                    {
+                        ref uint inst = ref codeUint[instIndex];
+
+                        if ((inst & XMask) != XValue)
+                        {
+                            continue;
+                        }
+
+                        bool isPair = (inst & (1u << 21)) != 0;
+                        bool isLoad = (inst & (1u << 22)) != 0;
+
+                        uint rt2 = (inst >> 10) & 0x1fu;
+                        uint rs = (inst >> 16) & 0x1fu;
+
+                        if (isLoad && rs != ZrIndex)
+                        {
+                            continue;
+                        }
+
+                        if (!isPair && rt2 != ZrIndex)
+                        {
+                            continue;
+                        }
+
+                        // Set the ordered flag.
+                        inst |= 1u << 15;
+                    }
+                }
+            }
+        }
+    }
+}

+ 18 - 15
src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs

@@ -128,21 +128,6 @@ namespace Ryujinx.Cpu.AppleHv
             }
         }
 
-#pragma warning disable IDE0051 // Remove unused private member
-        /// <summary>
-        /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
-        /// </summary>
-        /// <param name="va">Virtual address of the range</param>
-        /// <param name="size">Size of the range in bytes</param>
-        private void AssertMapped(ulong va, ulong size)
-        {
-            if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
-            {
-                throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
-            }
-        }
-#pragma warning restore IDE0051
-
         /// <inheritdoc/>
         public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
         {
@@ -736,6 +721,24 @@ namespace Ryujinx.Cpu.AppleHv
             return (int)(vaSpan / PageSize);
         }
 
+        /// <inheritdoc/>
+        public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+        {
+            if (protection.HasFlag(MemoryPermission.Execute))
+            {
+                // Some applications use unordered exclusive memory access instructions
+                // where it is not valid to do so, leading to memory re-ordering that
+                // makes the code behave incorrectly on some CPUs.
+                // To work around this, we force all such accesses to be ordered.
+
+                using WritableRegion writableRegion = GetWritableRegion(va, (int)size);
+
+                HvCodePatcher.RewriteUnorderedExclusiveInstructions(writableRegion.Memory.Span);
+            }
+
+            // TODO
+        }
+
         /// <inheritdoc/>
         public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
         {

+ 5 - 16
src/Ryujinx.Cpu/Jit/MemoryManager.cs

@@ -575,22 +575,15 @@ namespace Ryujinx.Cpu.Jit
             }
         }
 
-#pragma warning disable IDE0051 // Remove unused private member
-        private ulong GetPhysicalAddress(ulong va)
+        private ulong GetPhysicalAddressInternal(ulong va)
         {
-            // We return -1L if the virtual address is invalid or unmapped.
-            if (!ValidateAddress(va) || !IsMapped(va))
-            {
-                return ulong.MaxValue;
-            }
-
-            return GetPhysicalAddressInternal(va);
+            return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
         }
-#pragma warning restore IDE0051
 
-        private ulong GetPhysicalAddressInternal(ulong va)
+        /// <inheritdoc/>
+        public void Reprotect(ulong va, ulong size, MemoryPermission protection)
         {
-            return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
+            // TODO
         }
 
         /// <inheritdoc/>
@@ -698,9 +691,5 @@ namespace Ryujinx.Cpu.Jit
         /// Disposes of resources used by the memory manager.
         /// </summary>
         protected override void Destroy() => _pageTable.Dispose();
-
-#pragma warning disable IDE0051 // Remove unused private member
-        private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
-#pragma warning restore IDE0051
     }
 }

+ 6 - 0
src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs

@@ -615,6 +615,12 @@ namespace Ryujinx.Cpu.Jit
             return (int)(vaSpan / PageSize);
         }
 
+        /// <inheritdoc/>
+        public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+        {
+            // TODO
+        }
+
         /// <inheritdoc/>
         public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
         {

+ 46 - 0
src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs

@@ -0,0 +1,46 @@
+using Ryujinx.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+    [Flags]
+    enum KMemoryPermission : uint
+    {
+        None = 0,
+        UserMask = Read | Write | Execute,
+        Mask = uint.MaxValue,
+
+        Read = 1 << 0,
+        Write = 1 << 1,
+        Execute = 1 << 2,
+        DontCare = 1 << 28,
+
+        ReadAndWrite = Read | Write,
+        ReadAndExecute = Read | Execute,
+    }
+
+    static class KMemoryPermissionExtensions
+    {
+        public static MemoryPermission Convert(this KMemoryPermission permission)
+        {
+            MemoryPermission output = MemoryPermission.None;
+
+            if (permission.HasFlag(KMemoryPermission.Read))
+            {
+                output = MemoryPermission.Read;
+            }
+
+            if (permission.HasFlag(KMemoryPermission.Write))
+            {
+                output |= MemoryPermission.Write;
+            }
+
+            if (permission.HasFlag(KMemoryPermission.Execute))
+            {
+                output |= MemoryPermission.Execute;
+            }
+
+            return output;
+        }
+    }
+}

+ 6 - 4
src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs

@@ -203,15 +203,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
         /// <inheritdoc/>
         protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
         {
-            // TODO.
+            _cpuMemory.Reprotect(address, pagesCount * PageSize, permission.Convert());
+
             return Result.Success;
         }
 
         /// <inheritdoc/>
-        protected override Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
+        protected override Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission)
         {
-            // TODO.
-            return Result.Success;
+            // TODO: Flush JIT cache.
+
+            return Reprotect(address, pagesCount, permission);
         }
 
         /// <inheritdoc/>

+ 3 - 3
src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs

@@ -1255,7 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
 
                         if ((oldPermission & KMemoryPermission.Execute) != 0)
                         {
-                            result = ReprotectWithAttributes(address, pagesCount, permission);
+                            result = ReprotectAndFlush(address, pagesCount, permission);
                         }
                         else
                         {
@@ -3036,13 +3036,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
         protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
 
         /// <summary>
-        /// Changes the permissions of a given virtual memory region.
+        /// Changes the permissions of a given virtual memory region, while also flushing the cache.
         /// </summary>
         /// <param name="address">Virtual address of the region to have the permission changes</param>
         /// <param name="pagesCount">Number of pages to have their permissions changed</param>
         /// <param name="permission">New permission</param>
         /// <returns>Result of the permission change operation</returns>
-        protected abstract Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission);
+        protected abstract Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission);
 
         /// <summary>
         /// Alerts the memory tracking that a given region has been read from or written to.

+ 0 - 20
src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs

@@ -1,20 +0,0 @@
-using System;
-
-namespace Ryujinx.HLE.HOS.Kernel.Memory
-{
-    [Flags]
-    enum KMemoryPermission : uint
-    {
-        None = 0,
-        UserMask = Read | Write | Execute,
-        Mask = uint.MaxValue,
-
-        Read = 1 << 0,
-        Write = 1 << 1,
-        Execute = 1 << 2,
-        DontCare = 1 << 28,
-
-        ReadAndWrite = Read | Write,
-        ReadAndExecute = Read | Execute,
-    }
-}

+ 5 - 0
src/Ryujinx.Memory/AddressSpaceManager.cs

@@ -455,6 +455,11 @@ namespace Ryujinx.Memory
             return _pageTable.Read(va) + (nuint)(va & PageMask);
         }
 
+        /// <inheritdoc/>
+        public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+        {
+        }
+
         /// <inheritdoc/>
         public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
         {

+ 14 - 0
src/Ryujinx.Memory/IVirtualMemoryManager.cs

@@ -104,6 +104,12 @@ namespace Ryujinx.Memory
         /// <returns>True if the data was changed, false otherwise</returns>
         bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data);
 
+        /// <summary>
+        /// Fills the specified memory region with the value specified in <paramref name="value"/>.
+        /// </summary>
+        /// <param name="va">Virtual address to fill the value into</param>
+        /// <param name="size">Size of the memory region to fill</param>
+        /// <param name="value">Value to fill with</param>
         void Fill(ulong va, ulong size, byte value)
         {
             const int MaxChunkSize = 1 << 24;
@@ -194,6 +200,14 @@ namespace Ryujinx.Memory
         /// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
         void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
 
+        /// <summary>
+        /// Reprotect a region of virtual memory for guest access.
+        /// </summary>
+        /// <param name="va">Virtual address base</param>
+        /// <param name="size">Size of the region to protect</param>
+        /// <param name="protection">Memory protection to set</param>
+        void Reprotect(ulong va, ulong size, MemoryPermission protection);
+
         /// <summary>
         /// Reprotect a region of virtual memory for tracking.
         /// </summary>

+ 5 - 0
src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs

@@ -102,6 +102,11 @@ namespace Ryujinx.Tests.Memory
             throw new NotImplementedException();
         }
 
+        public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+        {
+            throw new NotImplementedException();
+        }
+
         public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
         {
             OnProtect?.Invoke(va, size, protection);