From 0970972f0d22c99a2bc0f51df44b79531292e18f Mon Sep 17 00:00:00 2001 From: gdk Date: Mon, 3 Jul 2023 19:28:05 -0300 Subject: [PATCH] Add NCE code --- .../UI/ViewModels/SettingsViewModel.cs | 2 +- src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs | 5 + src/Ryujinx.Cpu/AppleHv/HvEngine.cs | 2 +- src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 7 +- src/Ryujinx.Cpu/DiskCacheLoadState.cs | 15 + src/Ryujinx.Cpu/ICpuContext.cs | 2 + src/Ryujinx.Cpu/ICpuEngine.cs | 4 +- src/Ryujinx.Cpu/ICpuMemoryManager.cs | 19 + src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 5 + src/Ryujinx.Cpu/Jit/JitEngine.cs | 2 +- src/Ryujinx.Cpu/Jit/MemoryManager.cs | 7 +- .../Jit/MemoryManagerHostMapped.cs | 7 +- src/Ryujinx.Cpu/MemoryEhMeilleure.cs | 14 + src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs | 848 ++++++++++++++++++ src/Ryujinx.Cpu/Nce/NceAsmTable.cs | 409 +++++++++ src/Ryujinx.Cpu/Nce/NceCpuContext.cs | 99 ++ src/Ryujinx.Cpu/Nce/NceEngine.cs | 19 + src/Ryujinx.Cpu/Nce/NceExecutionContext.cs | 131 +++ src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs | 13 + src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs | 24 + src/Ryujinx.Cpu/Nce/NceNativeContext.cs | 43 + src/Ryujinx.Cpu/Nce/NceNativeInterface.cs | 55 ++ src/Ryujinx.Cpu/Nce/NcePatcher.cs | 293 ++++++ src/Ryujinx.Cpu/Nce/NceThreadPal.cs | 40 + src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs | 20 + src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs | 37 + src/Ryujinx.Cpu/Nce/NceThreadTable.cs | 97 ++ src/Ryujinx.Cpu/Nce/nce.S | 378 ++++++++ src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs | 4 +- .../Signal/UnixSignalHandlerRegistration.cs | 17 +- src/Ryujinx.HLE/HOS/ArmProcessContext.cs | 14 +- .../HOS/ArmProcessContextFactory.cs | 26 +- .../HOS/Kernel/Memory/KPageTableBase.cs | 17 +- .../HOS/Kernel/Process/IProcessContext.cs | 2 + .../HOS/Kernel/Process/KProcess.cs | 13 +- .../HOS/Kernel/Process/ProcessContext.cs | 5 + .../Loaders/Processes/ProcessLoaderHelper.cs | 28 +- src/Ryujinx.Memory/AddressSpaceManager.cs | 5 + src/Ryujinx.Memory/IVirtualMemoryManager.cs | 8 + src/Ryujinx.Memory/MemoryManagementUnix.cs | 6 +- 40 files changed, 2702 insertions(+), 40 deletions(-) create mode 100644 src/Ryujinx.Cpu/DiskCacheLoadState.cs create mode 100644 src/Ryujinx.Cpu/ICpuMemoryManager.cs create mode 100644 src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceAsmTable.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceCpuContext.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceEngine.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceNativeContext.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceNativeInterface.cs create mode 100644 src/Ryujinx.Cpu/Nce/NcePatcher.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceThreadPal.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs create mode 100644 src/Ryujinx.Cpu/Nce/NceThreadTable.cs create mode 100644 src/Ryujinx.Cpu/Nce/nce.S diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs index 604e34067..bac5c6c15 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs @@ -115,7 +115,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS(); - public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + public bool IsHypervisorAvailable => RuntimeInformation.ProcessArchitecture == Architecture.Arm64; public bool DirectoryChanged { diff --git a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs index 99e4c0479..7c8067189 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs @@ -32,6 +32,11 @@ namespace Ryujinx.Cpu.AppleHv { } + /// + public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize) + { + } + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) { return new DummyDiskCacheLoadState(); diff --git a/src/Ryujinx.Cpu/AppleHv/HvEngine.cs b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs index c3c1a4484..1a50f8d19 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvEngine.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Cpu.AppleHv } /// - public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit) + public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit) { return new HvCpuContext(_tickSource, memoryManager, for64Bit); } diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 2f9743ab4..41e5ec7de 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -16,7 +16,7 @@ namespace Ryujinx.Cpu.AppleHv /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// [SupportedOSPlatform("macos")] - public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public class HvMemoryManager : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { public const int PageBits = 12; public const int PageSize = 1 << PageBits; @@ -182,6 +182,11 @@ namespace Ryujinx.Cpu.AppleHv } } + /// + public void Reprotect(ulong va, ulong size, MemoryPermission permission) + { + } + /// public T Read(ulong va) where T : unmanaged { diff --git a/src/Ryujinx.Cpu/DiskCacheLoadState.cs b/src/Ryujinx.Cpu/DiskCacheLoadState.cs new file mode 100644 index 000000000..930bd95a1 --- /dev/null +++ b/src/Ryujinx.Cpu/DiskCacheLoadState.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.Cpu.Jit +{ + class DiskCacheLoadState : IDiskCacheLoadState + { + /// + public event Action StateChanged; + + /// + public void Cancel() + { + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/ICpuContext.cs b/src/Ryujinx.Cpu/ICpuContext.cs index edcebdfc4..011f6ed28 100644 --- a/src/Ryujinx.Cpu/ICpuContext.cs +++ b/src/Ryujinx.Cpu/ICpuContext.cs @@ -38,6 +38,8 @@ namespace Ryujinx.Cpu /// Size of the region to be invalidated void InvalidateCacheRegion(ulong address, ulong size); + void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize); + /// /// Loads cached code from disk for a given application. /// diff --git a/src/Ryujinx.Cpu/ICpuEngine.cs b/src/Ryujinx.Cpu/ICpuEngine.cs index b53b23a8c..e23c78750 100644 --- a/src/Ryujinx.Cpu/ICpuEngine.cs +++ b/src/Ryujinx.Cpu/ICpuEngine.cs @@ -1,5 +1,3 @@ -using ARMeilleure.Memory; - namespace Ryujinx.Cpu { /// @@ -13,6 +11,6 @@ namespace Ryujinx.Cpu /// Memory manager for the address space of the context /// Indicates if the context will be used to run 64-bit or 32-bit Arm code /// CPU context - ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit); + ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit); } } diff --git a/src/Ryujinx.Cpu/ICpuMemoryManager.cs b/src/Ryujinx.Cpu/ICpuMemoryManager.cs new file mode 100644 index 000000000..e0927c5f4 --- /dev/null +++ b/src/Ryujinx.Cpu/ICpuMemoryManager.cs @@ -0,0 +1,19 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; + +namespace Ryujinx.Cpu +{ + /// + /// CPU memory manager interface. + /// + public interface ICpuMemoryManager : IMemoryManager + { + /// + /// Reprotects a previously mapped range of virtual memory. + /// + /// Virtual address of the range to be reprotected + /// Size of the range to be reprotected + /// New protection of the memory range + void Reprotect(ulong va, ulong size, MemoryPermission permission); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs index dce0490a4..0adb6af74 100644 --- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -46,6 +46,11 @@ namespace Ryujinx.Cpu.Jit _translator.InvalidateJitCacheRegion(address, size); } + /// + public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize) + { + } + /// public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) { diff --git a/src/Ryujinx.Cpu/Jit/JitEngine.cs b/src/Ryujinx.Cpu/Jit/JitEngine.cs index deebb8b9e..ad991e5a5 100644 --- a/src/Ryujinx.Cpu/Jit/JitEngine.cs +++ b/src/Ryujinx.Cpu/Jit/JitEngine.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.Jit } /// - public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit) + public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit) { return new JitCpuContext(_tickSource, memoryManager, for64Bit); } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index b9a547025..fca12870c 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager. /// - public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManager : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { public const int PageBits = 12; public const int PageSize = 1 << PageBits; @@ -126,6 +126,11 @@ namespace Ryujinx.Cpu.Jit } } + /// + public void Reprotect(ulong va, ulong size, MemoryPermission permission) + { + } + /// public T Read(ulong va) where T : unmanaged { diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index 2b315e841..f6adb93a1 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.Jit /// /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// - public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + public sealed class MemoryManagerHostMapped : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { public const int PageBits = 12; public const int PageSize = 1 << PageBits; @@ -194,6 +194,11 @@ namespace Ryujinx.Cpu.Jit } } + /// + public void Reprotect(ulong va, ulong size, MemoryPermission permission) + { + } + /// public T Read(ulong va) where T : unmanaged { diff --git a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs index f3a5b056b..74f62bc13 100644 --- a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs +++ b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs @@ -46,6 +46,20 @@ namespace Ryujinx.Cpu } } + public MemoryEhMeilleure(ulong asSize, MemoryTracking tracking) + { + _baseAddress = 0UL; + ulong endAddress = asSize; + + _trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent); + bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); + + if (!added) + { + throw new InvalidOperationException("Number of allowed tracked regions exceeded."); + } + } + public void Dispose() { GC.SuppressFinalize(this); diff --git a/src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs b/src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs new file mode 100644 index 000000000..e7c757247 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/MemoryManagerNative.cs @@ -0,0 +1,848 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Cpu.Nce +{ + /// + /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. + /// + public sealed class MemoryManagerNative : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. + public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. + + private enum HostMappedPtBits : ulong + { + Unmapped = 0, + Mapped, + WriteTracked, + ReadWriteTracked, + + MappedReplicated = 0x5555555555555555, + WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, + ReadWriteTrackedReplicated = ulong.MaxValue + } + + private readonly InvalidAccessHandler _invalidAccessHandler; + + private readonly MemoryBlock _addressSpace; + private readonly MemoryBlock _addressSpaceMirror; + private readonly ulong _addressSpaceSize; + + private readonly MemoryBlock _backingMemory; + private readonly PageTable _pageTable; + + private readonly MemoryEhMeilleure _memoryEh; + + private readonly ulong[] _pageBitmap; + + /// + public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; + + public int AddressSpaceBits { get; } + + public IntPtr PageTablePointer => IntPtr.Zero; + + public ulong ReservedSize => (ulong)_addressSpace.Pointer.ToInt64(); + + public MemoryManagerType Type => MemoryManagerType.HostMappedUnsafe; + + public MemoryTracking Tracking { get; } + + public event Action UnmapEvent; + + /// + /// Creates a new instance of the host mapped memory manager. + /// + /// Address space instance to use + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + /// Optional function to handle invalid memory accesses + public MemoryManagerNative( + AddressSpace addressSpace, + MemoryBlock backingMemory, + ulong addressSpaceSize, + InvalidAccessHandler invalidAccessHandler = null) + { + _backingMemory = backingMemory; + _pageTable = new PageTable(); + _invalidAccessHandler = invalidAccessHandler; + _addressSpaceSize = addressSpaceSize; + + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < addressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + + _pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; + + _addressSpace = addressSpace.Base; + _addressSpaceMirror = addressSpace.Mirror; + + Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler); + _memoryEh = new MemoryEhMeilleure(asSize, Tracking); + } + + /// + /// Checks if the virtual address is part of the addressable space. + /// + /// Virtual address + /// True if the virtual address is part of the addressable space + private bool ValidateAddress(ulong va) + { + return va < _addressSpaceSize; + } + + /// + /// Checks if the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the combination of virtual address and size is part of the addressable space + private bool ValidateAddressAndSize(ulong va, ulong size) + { + ulong endVa = va + size; + return endVa >= va && endVa >= size && endVa <= _addressSpaceSize; + } + + /// + /// Ensures the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// Throw when the memory region specified outside the addressable space + private void AssertValidAddressAndSize(ulong va, ulong size) + { + if (!ValidateAddressAndSize(va, size)) + { + throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); + } + } + + /// + /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped. + /// + /// Virtual address of the range + /// Size of the range in bytes + 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}"); + } + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + _addressSpace.MapView(_backingMemory, pa, AddressToOffset(va), size); + _addressSpaceMirror.MapView(_backingMemory, pa, AddressToOffset(va), size); + AddMapping(va, size); + PtMap(va, pa, size); + + Tracking.Map(va, size); + } + + /// + public void MapForeign(ulong va, nuint hostPointer, ulong size) + { + throw new NotSupportedException(); + } + + /// + public void Unmap(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + UnmapEvent?.Invoke(va, size); + Tracking.Unmap(va, size); + + RemoveMapping(va, size); + PtUnmap(va, size); + _addressSpace.UnmapView(_backingMemory, AddressToOffset(va), size); + _addressSpaceMirror.UnmapView(_backingMemory, AddressToOffset(va), size); + } + + private void PtMap(ulong va, ulong pa, ulong size) + { + while (size != 0) + { + _pageTable.Map(va, pa); + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + private void PtUnmap(ulong va, ulong size) + { + while (size != 0) + { + _pageTable.Unmap(va); + + va += PageSize; + size -= PageSize; + } + } + + /// + public void Reprotect(ulong va, ulong size, MemoryPermission permission) + { + _addressSpace.Reprotect(AddressToOffset(va), size, permission); + } + + /// + public T Read(ulong va) where T : unmanaged + { + try + { + AssertMapped(va, (ulong)Unsafe.SizeOf()); + + return _addressSpaceMirror.Read(AddressToOffset(va)); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + /// + public T ReadTracked(ulong va) where T : unmanaged + { + try + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); + + return Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + /// + public void Read(ulong va, Span data) + { + try + { + AssertMapped(va, (ulong)data.Length); + + _addressSpaceMirror.Read(AddressToOffset(va), data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + + /// + public void Write(ulong va, T value) where T : unmanaged + { + try + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), write: true); + + _addressSpaceMirror.Write(AddressToOffset(va), value); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + /// + public void Write(ulong va, ReadOnlySpan data) + { + try + { + SignalMemoryTracking(va, (ulong)data.Length, write: true); + + _addressSpaceMirror.Write(AddressToOffset(va), data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + /// + public void WriteUntracked(ulong va, ReadOnlySpan data) + { + try + { + AssertMapped(va, (ulong)data.Length); + + _addressSpaceMirror.Write(AddressToOffset(va), data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + /// + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + try + { + SignalMemoryTracking(va, (ulong)data.Length, false); + + Span target = _addressSpaceMirror.GetSpan(AddressToOffset(va), data.Length); + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return true; + } + } + + /// + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, write: false); + } + else + { + AssertMapped(va, (ulong)size); + } + + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + return _addressSpaceMirror.GetSpan(AddressToOffset(va), size); + } + + /// + public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + else + { + AssertMapped(va, (ulong)size); + } + + return _addressSpaceMirror.GetWritableRegion(AddressToOffset(va), size); + } + + /// + public ref T GetRef(ulong va) where T : unmanaged + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); + + return ref _addressSpaceMirror.GetRef(AddressToOffset(va)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsMapped(ulong va) + { + return ValidateAddress(va) && IsMappedImpl(va); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsMappedImpl(ulong va) + { + ulong page = va >> PageBits; + + int bit = (int)((page & 31) << 1); + + int pageIndex = (int)(page >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + + return ((pte >> bit) & 3) != 0; + } + + /// + public bool IsRangeMapped(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return IsRangeMappedImpl(va, size); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) + { + startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); + endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); + + pageIndex = (int)(pageStart >> PageToPteShift); + pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); + } + + private bool IsRangeMappedImpl(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + + if (pages == 1) + { + return IsMappedImpl(va); + } + + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + // Check if either bit in each 2 bit page entry is set. + // OR the block with itself shifted down by 1, and check the first bit of each entry. + + ulong mask = BlockMappedMask & startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte = Volatile.Read(ref pageRef); + + pte |= pte >> 1; + if ((pte & mask) != mask) + { + return false; + } + + mask = BlockMappedMask; + } + + return true; + } + + /// + public IEnumerable GetHostRegions(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return Enumerable.Repeat(new HostMemoryRange((nuint)(ulong)_addressSpaceMirror.GetPointer(AddressToOffset(va), size), size), 1); + } + + /// + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressChecked(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressChecked(va + PageSize); + + if (GetPhysicalAddressChecked(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return regions; + } + + private ulong GetPhysicalAddressChecked(ulong va) + { + if (!IsMapped(va)) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + } + + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return _pageTable.Read(va) + (va & PageMask); + } + + /// + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + AssertValidAddressAndSize(va, size); + + if (precise) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId); + return; + } + + // Software table, used for managed memory tracking. + + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked); + + int bit = (int)((pageStart & 31) << 1); + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + ulong state = ((pte >> bit) & 3); + + if (state >= tag) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + return; + } + else if (state == 0) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte = Volatile.Read(ref pageRef); + ulong mappedMask = mask & BlockMappedMask; + + ulong mappedPte = pte | (pte >> 1); + if ((mappedPte & mappedMask) != mappedMask) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + + pte &= mask; + if ((pte & anyTrackingTag) != 0) // Search for any tracking. + { + // Writes trigger any tracking. + // Only trigger tracking from reads if both bits are set on any page. + if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + break; + } + } + + mask = ulong.MaxValue; + } + } + } + + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + { + // Protection is inverted on software pages, since the default value is 0. + protection = (~protection) & MemoryPermission.ReadAndWrite; + + int pages = GetPagesCount(va, size, out va); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)HostMappedPtBits.Mapped, + MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked, + _ => (ulong)HostMappedPtBits.ReadWriteTracked, + }; + + int bit = (int)((pageStart & 31) << 1); + + ulong tagMask = 3UL << bit; + ulong invTagMask = ~tagMask; + + ulong tag = protTag << bit; + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated, + MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated, + _ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated, + }; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Change the protection of all 2 bit entries that are mapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask &= mask; // Only update mapped pages within the given range. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); + + mask = ulong.MaxValue; + } + } + + protection = protection switch + { + MemoryPermission.None => MemoryPermission.ReadAndWrite, + MemoryPermission.Write => MemoryPermission.Read, + _ => MemoryPermission.None + }; + + _addressSpace.Reprotect(AddressToOffset(va), size, protection, false); + } + + /// + public RegionHandle BeginTracking(ulong address, ulong size, int id) + { + return Tracking.BeginTracking(address, size, id); + } + + /// + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id) + { + return Tracking.BeginGranularTracking(address, size, handles, granularity, id); + } + + /// + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + return Tracking.BeginSmartGranularTracking(address, size, granularity, id); + } + + /// + /// Adds the given address mapping to the page table. + /// + /// Virtual memory address + /// Size to be mapped + private void AddMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Map all 2-bit entries that are unmapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); + + mask = ulong.MaxValue; + } + } + + /// + /// Removes the given address mapping from the page table. + /// + /// Virtual memory address + /// Size to be unmapped + private void RemoveMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + startMask = ~startMask; + endMask = ~endMask; + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask |= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); + + mask = 0; + } + } + + private ulong AddressToOffset(ulong address) + { + if (address < ReservedSize) + { + throw new ArgumentException($"Invalid address 0x{address:x16}"); + } + + return address - ReservedSize; + } + + /// + /// Disposes of resources used by the memory manager. + /// + protected override void Destroy() + { + _addressSpace.Dispose(); + _addressSpaceMirror.Dispose(); + _memoryEh.Dispose(); + } + + private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + } +} diff --git a/src/Ryujinx.Cpu/Nce/NceAsmTable.cs b/src/Ryujinx.Cpu/Nce/NceAsmTable.cs new file mode 100644 index 000000000..72e2d9780 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceAsmTable.cs @@ -0,0 +1,409 @@ +using System; + +namespace Ryujinx.Cpu.Nce +{ + static class NceAsmTable + { + public static uint[] GetTpidrEl0Code = new uint[] + { + GetMrsTpidrEl0(0), // mrs x0, tpidr_el0 + 0xd65f03c0u, // ret + }; + + public static uint[] ThreadStartCode = new uint[] + { + 0xa9ae53f3u, // stp x19, x20, [sp, #-288]! + 0xa9015bf5u, // stp x21, x22, [sp, #16] + 0xa90263f7u, // stp x23, x24, [sp, #32] + 0xa9036bf9u, // stp x25, x26, [sp, #48] + 0xa90473fbu, // stp x27, x28, [sp, #64] + 0xa9057bfdu, // stp x29, x30, [sp, #80] + 0x6d0627e8u, // stp d8, d9, [sp, #96] + 0x6d072feau, // stp d10, d11, [sp, #112] + 0x6d0837ecu, // stp d12, d13, [sp, #128] + 0x6d093feeu, // stp d14, d15, [sp, #144] + 0x6d0a47f0u, // stp d16, d17, [sp, #160] + 0x6d0b4ff2u, // stp d18, d19, [sp, #176] + 0x6d0c57f4u, // stp d20, d21, [sp, #192] + 0x6d0d5ff6u, // stp d22, d23, [sp, #208] + 0x6d0e67f8u, // stp d24, d25, [sp, #224] + 0x6d0f6ffau, // stp d26, d27, [sp, #240] + 0x6d1077fcu, // stp d28, d29, [sp, #256] + 0x6d117ffeu, // stp d30, d31, [sp, #272] + 0xb9031c1fu, // str wzr, [x0, #796] + 0x910003e1u, // mov x1, sp + 0xf9019001u, // str x1, [x0, #800] + 0xa9410c02u, // ldp x2, x3, [x0, #16] + 0xa9421404u, // ldp x4, x5, [x0, #32] + 0xa9431c06u, // ldp x6, x7, [x0, #48] + 0xa9442408u, // ldp x8, x9, [x0, #64] + 0xa9452c0au, // ldp x10, x11, [x0, #80] + 0xa946340cu, // ldp x12, x13, [x0, #96] + 0xa9473c0eu, // ldp x14, x15, [x0, #112] + 0xa9484410u, // ldp x16, x17, [x0, #128] + 0xa9494c12u, // ldp x18, x19, [x0, #144] + 0xa94a5414u, // ldp x20, x21, [x0, #160] + 0xa94b5c16u, // ldp x22, x23, [x0, #176] + 0xa94c6418u, // ldp x24, x25, [x0, #192] + 0xa94d6c1au, // ldp x26, x27, [x0, #208] + 0xa94e741cu, // ldp x28, x29, [x0, #224] + 0xad480400u, // ldp q0, q1, [x0, #256] + 0xad490c02u, // ldp q2, q3, [x0, #288] + 0xad4a1404u, // ldp q4, q5, [x0, #320] + 0xad4b1c06u, // ldp q6, q7, [x0, #352] + 0xad4c2408u, // ldp q8, q9, [x0, #384] + 0xad4d2c0au, // ldp q10, q11, [x0, #416] + 0xad4e340cu, // ldp q12, q13, [x0, #448] + 0xad4f3c0eu, // ldp q14, q15, [x0, #480] + 0xad504410u, // ldp q16, q17, [x0, #512] + 0xad514c12u, // ldp q18, q19, [x0, #544] + 0xad525414u, // ldp q20, q21, [x0, #576] + 0xad535c16u, // ldp q22, q23, [x0, #608] + 0xad546418u, // ldp q24, q25, [x0, #640] + 0xad556c1au, // ldp q26, q27, [x0, #672] + 0xad56741cu, // ldp q28, q29, [x0, #704] + 0xad577c1eu, // ldp q30, q31, [x0, #736] + 0xa94f041eu, // ldp x30, x1, [x0, #240] + 0x9100003fu, // mov sp, x1 + 0xa9400400u, // ldp x0, x1, [x0] + 0xd61f03c0u, // br x30 + }; + + public static uint[] ExceptionHandlerEntryCode = new uint[] + { + 0xa9bc53f3u, // stp x19, x20, [sp, #-64]! + 0xa9015bf5u, // stp x21, x22, [sp, #16] + 0xa90263f7u, // stp x23, x24, [sp, #32] + 0xf9001bf9u, // str x25, [sp, #48] + 0xaa0003f3u, // mov x19, x0 + 0xaa0103f4u, // mov x20, x1 + 0xaa0203f5u, // mov x21, x2 + 0x910003f6u, // mov x22, sp + 0xaa1e03f7u, // mov x23, x30 + 0xd2800018u, // mov x24, #0x0 + 0xf2a00018u, // movk x24, #0x0, lsl #16 + 0xf2c00018u, // movk x24, #0x0, lsl #32 + 0xf2e00018u, // movk x24, #0x0, lsl #48 + 0xf85f8319u, // ldur x25, [x24, #-8] + 0x8b191319u, // add x25, x24, x25, lsl #4 + GetMrsTpidrEl0(1), // mrs x1, tpidr_el0 + 0xeb19031fu, // cmp x24, x25 + 0x540000a0u, // b.eq 13c + 0xf8410702u, // ldr x2, [x24], #16 + 0xeb02003fu, // cmp x1, x2 + 0x54000080u, // b.eq 144 + 0x17fffffbu, // b 124 + 0xd2800018u, // mov x24, #0x0 + 0x14000002u, // b 148 + 0xf85f8318u, // ldur x24, [x24, #-8] + 0xb4000438u, // cbz x24, 1cc + 0xf9419300u, // ldr x0, [x24, #800] + 0x9100001fu, // mov sp, x0 + 0x7100027fu, // cmp w19, #0x0 + 0x54000180u, // b.eq 188 + 0x52800020u, // mov w0, #0x1 + 0xb9031f00u, // str w0, [x24, #796] + 0xaa1303e0u, // mov x0, x19 + 0xaa1403e1u, // mov x1, x20 + 0xaa1503e2u, // mov x2, x21 + 0xd2800008u, // mov x8, #0x0 + 0xf2a00008u, // movk x8, #0x0, lsl #16 + 0xf2c00008u, // movk x8, #0x0, lsl #32 + 0xf2e00008u, // movk x8, #0x0, lsl #48 + 0xd63f0100u, // blr x8 + 0x1400000au, // b 1ac + 0xb9431f00u, // ldr w0, [x24, #796] + 0x35000120u, // cbnz w0, 1b0 + 0x52800020u, // mov w0, #0x1 + 0xb9031f00u, // str w0, [x24, #796] + 0xd2800000u, // mov x0, #0x0 + 0xf2a00000u, // movk x0, #0x0, lsl #16 + 0xf2c00000u, // movk x0, #0x0, lsl #32 + 0xf2e00000u, // movk x0, #0x0, lsl #48 + 0xd63f0000u, // blr x0 + 0xb9031f1fu, // str wzr, [x24, #796] + 0x910002dfu, // mov sp, x22 + 0xaa1703feu, // mov x30, x23 + 0xa9415bf5u, // ldp x21, x22, [sp, #16] + 0xa94263f7u, // ldp x23, x24, [sp, #32] + 0xa9436bf9u, // ldp x25, x26, [sp, #48] + 0xa8c453f3u, // ldp x19, x20, [sp], #64 + 0xd65f03c0u, // ret + 0xaa1303e0u, // mov x0, x19 + 0xaa1403e1u, // mov x1, x20 + 0xaa1503e2u, // mov x2, x21 + 0x910002dfu, // mov sp, x22 + 0xa9415bf5u, // ldp x21, x22, [sp, #16] + 0xa94263f7u, // ldp x23, x24, [sp, #32] + 0xf9401bf9u, // ldr x25, [sp, #48] + 0xa8c453f3u, // ldp x19, x20, [sp], #64 + 0xd2800003u, // mov x3, #0x0 + 0xf2a00003u, // movk x3, #0x0, lsl #16 + 0xf2c00003u, // movk x3, #0x0, lsl #32 + 0xf2e00003u, // movk x3, #0x0, lsl #48 + 0xd61f0060u, // br x3 + }; + + public static uint[] SvcPatchCode = new uint[] + { + 0xa9be53f3u, // stp x19, x20, [sp, #-32]! + 0xf9000bf5u, // str x21, [sp, #16] + 0xd2800013u, // mov x19, #0x0 + 0xf2a00013u, // movk x19, #0x0, lsl #16 + 0xf2c00013u, // movk x19, #0x0, lsl #32 + 0xf2e00013u, // movk x19, #0x0, lsl #48 + GetMrsTpidrEl0(20), // mrs x20, tpidr_el0 + 0xf8410675u, // ldr x21, [x19], #16 + 0xeb15029fu, // cmp x20, x21 + 0x54000040u, // b.eq 22c + 0x17fffffdu, // b 21c + 0xf85f8273u, // ldur x19, [x19, #-8] + 0xa9000660u, // stp x0, x1, [x19] + 0xa9010e62u, // stp x2, x3, [x19, #16] + 0xa9021664u, // stp x4, x5, [x19, #32] + 0xa9031e66u, // stp x6, x7, [x19, #48] + 0xa9042668u, // stp x8, x9, [x19, #64] + 0xa9052e6au, // stp x10, x11, [x19, #80] + 0xa906366cu, // stp x12, x13, [x19, #96] + 0xa9073e6eu, // stp x14, x15, [x19, #112] + 0xa9084670u, // stp x16, x17, [x19, #128] + 0xf9400bf5u, // ldr x21, [sp, #16] + 0xa8c253e0u, // ldp x0, x20, [sp], #32 + 0xa9090272u, // stp x18, x0, [x19, #144] + 0xa90a5674u, // stp x20, x21, [x19, #160] + 0xa90b5e76u, // stp x22, x23, [x19, #176] + 0xa90c6678u, // stp x24, x25, [x19, #192] + 0xa90d6e7au, // stp x26, x27, [x19, #208] + 0xa90e767cu, // stp x28, x29, [x19, #224] + 0x910003e0u, // mov x0, sp + 0xa90f027eu, // stp x30, x0, [x19, #240] + 0xad080660u, // stp q0, q1, [x19, #256] + 0xad090e62u, // stp q2, q3, [x19, #288] + 0xad0a1664u, // stp q4, q5, [x19, #320] + 0xad0b1e66u, // stp q6, q7, [x19, #352] + 0xad0c2668u, // stp q8, q9, [x19, #384] + 0xad0d2e6au, // stp q10, q11, [x19, #416] + 0xad0e366cu, // stp q12, q13, [x19, #448] + 0xad0f3e6eu, // stp q14, q15, [x19, #480] + 0xad104670u, // stp q16, q17, [x19, #512] + 0xad114e72u, // stp q18, q19, [x19, #544] + 0xad125674u, // stp q20, q21, [x19, #576] + 0xad135e76u, // stp q22, q23, [x19, #608] + 0xad146678u, // stp q24, q25, [x19, #640] + 0xad156e7au, // stp q26, q27, [x19, #672] + 0xad16767cu, // stp q28, q29, [x19, #704] + 0xad177e7eu, // stp q30, q31, [x19, #736] + 0xf9419260u, // ldr x0, [x19, #800] + 0x9100001fu, // mov sp, x0 + 0x52800020u, // mov w0, #0x1 + 0xb9031e60u, // str w0, [x19, #796] + 0x52800000u, // mov w0, #0x0 + 0xf941aa68u, // ldr x8, [x19, #848] + 0xd63f0100u, // blr x8 + 0x35000280u, // cbnz w0, 328 + 0x6d517ffeu, // ldp d30, d31, [sp, #272] + 0x6d5077fcu, // ldp d28, d29, [sp, #256] + 0x6d4f6ffau, // ldp d26, d27, [sp, #240] + 0x6d4e67f8u, // ldp d24, d25, [sp, #224] + 0x6d4d5ff6u, // ldp d22, d23, [sp, #208] + 0x6d4c57f4u, // ldp d20, d21, [sp, #192] + 0x6d4b4ff2u, // ldp d18, d19, [sp, #176] + 0x6d4a47f0u, // ldp d16, d17, [sp, #160] + 0x6d493feeu, // ldp d14, d15, [sp, #144] + 0x6d4837ecu, // ldp d12, d13, [sp, #128] + 0x6d472feau, // ldp d10, d11, [sp, #112] + 0x6d4627e8u, // ldp d8, d9, [sp, #96] + 0xa9457bfdu, // ldp x29, x30, [sp, #80] + 0xa94473fbu, // ldp x27, x28, [sp, #64] + 0xa9436bf9u, // ldp x25, x26, [sp, #48] + 0xa94263f7u, // ldp x23, x24, [sp, #32] + 0xa9415bf5u, // ldp x21, x22, [sp, #16] + 0xa8d253f3u, // ldp x19, x20, [sp], #288 + 0xd65f03c0u, // ret + 0xb9031e7fu, // str wzr, [x19, #796] + 0xa94f027eu, // ldp x30, x0, [x19, #240] + 0x9100001fu, // mov sp, x0 + 0xa9400660u, // ldp x0, x1, [x19] + 0xa9410e62u, // ldp x2, x3, [x19, #16] + 0xa9421664u, // ldp x4, x5, [x19, #32] + 0xa9431e66u, // ldp x6, x7, [x19, #48] + 0xa9442668u, // ldp x8, x9, [x19, #64] + 0xa9452e6au, // ldp x10, x11, [x19, #80] + 0xa946366cu, // ldp x12, x13, [x19, #96] + 0xa9473e6eu, // ldp x14, x15, [x19, #112] + 0xa9484670u, // ldp x16, x17, [x19, #128] + 0xf9404a72u, // ldr x18, [x19, #144] + 0xa94a5674u, // ldp x20, x21, [x19, #160] + 0xa94b5e76u, // ldp x22, x23, [x19, #176] + 0xa94c6678u, // ldp x24, x25, [x19, #192] + 0xa94d6e7au, // ldp x26, x27, [x19, #208] + 0xa94e767cu, // ldp x28, x29, [x19, #224] + 0xad480660u, // ldp q0, q1, [x19, #256] + 0xad490e62u, // ldp q2, q3, [x19, #288] + 0xad4a1664u, // ldp q4, q5, [x19, #320] + 0xad4b1e66u, // ldp q6, q7, [x19, #352] + 0xad4c2668u, // ldp q8, q9, [x19, #384] + 0xad4d2e6au, // ldp q10, q11, [x19, #416] + 0xad4e366cu, // ldp q12, q13, [x19, #448] + 0xad4f3e6eu, // ldp q14, q15, [x19, #480] + 0xad504670u, // ldp q16, q17, [x19, #512] + 0xad514e72u, // ldp q18, q19, [x19, #544] + 0xad525674u, // ldp q20, q21, [x19, #576] + 0xad535e76u, // ldp q22, q23, [x19, #608] + 0xad546678u, // ldp q24, q25, [x19, #640] + 0xad556e7au, // ldp q26, q27, [x19, #672] + 0xad56767cu, // ldp q28, q29, [x19, #704] + 0xad577e7eu, // ldp q30, q31, [x19, #736] + 0xf9404e73u, // ldr x19, [x19, #152] + 0x14000000u, // b 3b4 + }; + + public static uint[] MrsTpidrroEl0PatchCode = new uint[] + { + 0xa9be4fffu, // stp xzr, x19, [sp, #-32]! + 0xa90157f4u, // stp x20, x21, [sp, #16] + 0xd2800013u, // mov x19, #0x0 + 0xf2a00013u, // movk x19, #0x0, lsl #16 + 0xf2c00013u, // movk x19, #0x0, lsl #32 + 0xf2e00013u, // movk x19, #0x0, lsl #48 + GetMrsTpidrEl0(20), // mrs x20, tpidr_el0 + 0xf8410675u, // ldr x21, [x19], #16 + 0xeb15029fu, // cmp x20, x21 + 0x54000040u, // b.eq 3e4 + 0x17fffffdu, // b 3d4 + 0xf85f8273u, // ldur x19, [x19, #-8] + 0xf9418673u, // ldr x19, [x19, #776] + 0xf90003f3u, // str x19, [sp] + 0xa94157f4u, // ldp x20, x21, [sp, #16] + 0xf94007f3u, // ldr x19, [sp, #8] + 0xf84207e0u, // ldr x0, [sp], #32 + 0x14000000u, // b 3fc + }; + + public static uint[] MrsTpidrEl0PatchCode = new uint[] + { + 0xa9be4fffu, // stp xzr, x19, [sp, #-32]! + 0xa90157f4u, // stp x20, x21, [sp, #16] + 0xd2800013u, // mov x19, #0x0 + 0xf2a00013u, // movk x19, #0x0, lsl #16 + 0xf2c00013u, // movk x19, #0x0, lsl #32 + 0xf2e00013u, // movk x19, #0x0, lsl #48 + GetMrsTpidrEl0(20), // mrs x20, tpidr_el0 + 0xf8410675u, // ldr x21, [x19], #16 + 0xeb15029fu, // cmp x20, x21 + 0x54000040u, // b.eq 42c + 0x17fffffdu, // b 41c + 0xf85f8273u, // ldur x19, [x19, #-8] + 0xf9418273u, // ldr x19, [x19, #768] + 0xf90003f3u, // str x19, [sp] + 0xa94157f4u, // ldp x20, x21, [sp, #16] + 0xf94007f3u, // ldr x19, [sp, #8] + 0xf84207e0u, // ldr x0, [sp], #32 + 0x14000000u, // b 444 + }; + + public static uint[] MrsCtrEl0PatchCode = new uint[] + { + 0xa9be4fffu, // stp xzr, x19, [sp, #-32]! + 0xa90157f4u, // stp x20, x21, [sp, #16] + 0xd2800013u, // mov x19, #0x0 + 0xf2a00013u, // movk x19, #0x0, lsl #16 + 0xf2c00013u, // movk x19, #0x0, lsl #32 + 0xf2e00013u, // movk x19, #0x0, lsl #48 + GetMrsTpidrEl0(20), // mrs x20, tpidr_el0 + 0xf8410675u, // ldr x21, [x19], #16 + 0xeb15029fu, // cmp x20, x21 + 0x54000040u, // b.eq 474 + 0x17fffffdu, // b 464 + 0xf85f8273u, // ldur x19, [x19, #-8] + 0xf9419e73u, // ldr x19, [x19, #824] + 0xf90003f3u, // str x19, [sp] + 0xa94157f4u, // ldp x20, x21, [sp, #16] + 0xf94007f3u, // ldr x19, [sp, #8] + 0xf84207e0u, // ldr x0, [sp], #32 + 0x14000000u, // b 48c + }; + + public static uint[] MsrTpidrEl0PatchCode = new uint[] + { + 0xa9be03f3u, // stp x19, x0, [sp, #-32]! + 0xa90157f4u, // stp x20, x21, [sp, #16] + 0xd2800013u, // mov x19, #0x0 + 0xf2a00013u, // movk x19, #0x0, lsl #16 + 0xf2c00013u, // movk x19, #0x0, lsl #32 + 0xf2e00013u, // movk x19, #0x0, lsl #48 + GetMrsTpidrEl0(20), // mrs x20, tpidr_el0 + 0xf8410675u, // ldr x21, [x19], #16 + 0xeb15029fu, // cmp x20, x21 + 0x54000040u, // b.eq 4bc + 0x17fffffdu, // b 4ac + 0xf85f8273u, // ldur x19, [x19, #-8] + 0xf94007f4u, // ldr x20, [sp, #8] + 0xf9018274u, // str x20, [x19, #768] + 0xa94157f4u, // ldp x20, x21, [sp, #16] + 0xf84207f3u, // ldr x19, [sp], #32 + 0x14000000u, // b 4d0 + }; + + public static uint[] MrsCntpctEl0PatchCode = new uint[] + { + 0xa9b407e0u, // stp x0, x1, [sp, #-192]! + 0xa9010fe2u, // stp x2, x3, [sp, #16] + 0xa90217e4u, // stp x4, x5, [sp, #32] + 0xa9031fe6u, // stp x6, x7, [sp, #48] + 0xa90427e8u, // stp x8, x9, [sp, #64] + 0xa9052feau, // stp x10, x11, [sp, #80] + 0xa90637ecu, // stp x12, x13, [sp, #96] + 0xa9073feeu, // stp x14, x15, [sp, #112] + 0xa90847f0u, // stp x16, x17, [sp, #128] + 0xa9094ff2u, // stp x18, x19, [sp, #144] + 0xa90a57f4u, // stp x20, x21, [sp, #160] + 0xf9005ffeu, // str x30, [sp, #184] + 0xd2800013u, // mov x19, #0x0 + 0xf2a00013u, // movk x19, #0x0, lsl #16 + 0xf2c00013u, // movk x19, #0x0, lsl #32 + 0xf2e00013u, // movk x19, #0x0, lsl #48 + GetMrsTpidrEl0(20), // mrs x20, tpidr_el0 + 0xf8410675u, // ldr x21, [x19], #16 + 0xeb15029fu, // cmp x20, x21 + 0x54000040u, // b.eq 528 + 0x17fffffdu, // b 518 + 0xf85f8273u, // ldur x19, [x19, #-8] + 0x52800020u, // mov w0, #0x1 + 0xb9031e60u, // str w0, [x19, #796] + 0xd2800000u, // mov x0, #0x0 + 0xf2a00000u, // movk x0, #0x0, lsl #16 + 0xf2c00000u, // movk x0, #0x0, lsl #32 + 0xf2e00000u, // movk x0, #0x0, lsl #48 + 0xd63f0000u, // blr x0 + 0xb9031e7fu, // str wzr, [x19, #796] + 0xf9005be0u, // str x0, [sp, #176] + 0xf9405ffeu, // ldr x30, [sp, #184] + 0xa94a57f4u, // ldp x20, x21, [sp, #160] + 0xa9494ff2u, // ldp x18, x19, [sp, #144] + 0xa94847f0u, // ldp x16, x17, [sp, #128] + 0xa9473feeu, // ldp x14, x15, [sp, #112] + 0xa94637ecu, // ldp x12, x13, [sp, #96] + 0xa9452feau, // ldp x10, x11, [sp, #80] + 0xa94427e8u, // ldp x8, x9, [sp, #64] + 0xa9431fe6u, // ldp x6, x7, [sp, #48] + 0xa94217e4u, // ldp x4, x5, [sp, #32] + 0xa9410fe2u, // ldp x2, x3, [sp, #16] + 0xa8cb07e0u, // ldp x0, x1, [sp], #176 + 0xf84107e0u, // ldr x0, [sp], #16 + 0x14000000u, // b 584 + }; + + private static uint GetMrsTpidrEl0(uint rd) + { + if (OperatingSystem.IsMacOS()) + { + return 0xd53bd060u | rd; // TPIDRRO + } + else + { + return 0xd53bd040u | rd; // TPIDR + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceCpuContext.cs b/src/Ryujinx.Cpu/Nce/NceCpuContext.cs new file mode 100644 index 000000000..04a97a696 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceCpuContext.cs @@ -0,0 +1,99 @@ +using ARMeilleure.Memory; +using ARMeilleure.Signal; +using Ryujinx.Cpu.Jit; +using Ryujinx.Common; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Nce +{ + class NceCpuContext : ICpuContext + { + private delegate void ThreadStart(IntPtr nativeContextPtr); + private delegate IntPtr GetTpidrEl0(); + private static MemoryBlock _codeBlock; + private static ThreadStart _threadStart; + private static GetTpidrEl0 _getTpidrEl0; + + private readonly ITickSource _tickSource; + private readonly ICpuMemoryManager _memoryManager; + + static NceCpuContext() + { + ulong threadStartCodeSize = (ulong)NceAsmTable.ThreadStartCode.Length * 4; + ulong enEntryCodeOffset = threadStartCodeSize; + ulong ehEntryCodeSize = (ulong)NceAsmTable.ExceptionHandlerEntryCode.Length * 4; + ulong getTpidrEl0CodeOffset = threadStartCodeSize + ehEntryCodeSize; + ulong getTpidrEl0CodeSize = (ulong)NceAsmTable.GetTpidrEl0Code.Length * 4; + + ulong size = BitUtils.AlignUp(threadStartCodeSize + ehEntryCodeSize + getTpidrEl0CodeSize, 0x1000UL); + + MemoryBlock codeBlock = new MemoryBlock(size); + + codeBlock.Write(0, MemoryMarshal.Cast(NceAsmTable.ThreadStartCode.AsSpan())); + codeBlock.Write(getTpidrEl0CodeOffset, MemoryMarshal.Cast(NceAsmTable.GetTpidrEl0Code.AsSpan())); + + NativeSignalHandler.Initialize(new JitMemoryAllocator()); + + NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize(), (IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr) => + { + uint[] ehEntryCode = NcePatcher.GenerateExceptionHandlerEntry(oldSignalHandlerSegfaultPtr, signalHandlerPtr); + codeBlock.Write(enEntryCodeOffset, MemoryMarshal.Cast(ehEntryCode.AsSpan())); + codeBlock.Reprotect(0, size, MemoryPermission.ReadAndExecute, true); + return codeBlock.GetPointer(enEntryCodeOffset, ehEntryCodeSize); + }, NceThreadPal.UnixSuspendSignal); + + _threadStart = Marshal.GetDelegateForFunctionPointer(codeBlock.GetPointer(0, threadStartCodeSize)); + _getTpidrEl0 = Marshal.GetDelegateForFunctionPointer(codeBlock.GetPointer(getTpidrEl0CodeOffset, getTpidrEl0CodeSize)); + _codeBlock = codeBlock; + } + + public NceCpuContext(ITickSource tickSource, ICpuMemoryManager memory, bool for64Bit) + { + _tickSource = tickSource; + _memoryManager = memory; + } + + /// + public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks) + { + return new NceExecutionContext(exceptionCallbacks); + } + + /// + public void Execute(IExecutionContext context, ulong address) + { + NceExecutionContext nec = (NceExecutionContext)context; + NceNativeInterface.RegisterThread(nec, _tickSource); + int tableIndex = NceThreadTable.Register(_getTpidrEl0(), nec.NativeContextPtr); + + nec.SetStartAddress(address); + _threadStart(nec.NativeContextPtr); + + NceThreadTable.Unregister(tableIndex); + } + + /// + public void InvalidateCacheRegion(ulong address, ulong size) + { + } + + /// + public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize) + { + NcePatcher.Patch(_memoryManager, textAddress, textSize, patchRegionAddress, patchRegionSize); + } + + /// + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + { + return new DiskCacheLoadState(); + } + + /// + public void PrepareCodeRange(ulong address, ulong size) + { + } + } +} diff --git a/src/Ryujinx.Cpu/Nce/NceEngine.cs b/src/Ryujinx.Cpu/Nce/NceEngine.cs new file mode 100644 index 000000000..17c7ed0f7 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceEngine.cs @@ -0,0 +1,19 @@ +using ARMeilleure.Memory; + +namespace Ryujinx.Cpu.Nce +{ + public class NceEngine : ICpuEngine + { + public ITickSource TickSource{ get; } + + public NceEngine(ITickSource tickSource) + { + TickSource = tickSource; + } + + public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit) + { + return new NceCpuContext(TickSource, memoryManager, for64Bit); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceExecutionContext.cs b/src/Ryujinx.Cpu/Nce/NceExecutionContext.cs new file mode 100644 index 000000000..8746cac33 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceExecutionContext.cs @@ -0,0 +1,131 @@ +using ARMeilleure.State; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Nce +{ + class NceExecutionContext : IExecutionContext + { + private readonly NceNativeContext _context; + private readonly ExceptionCallbacks _exceptionCallbacks; + + internal IntPtr NativeContextPtr => _context.BasePtr; + + public ulong Pc => 0UL; + + public long TpidrEl0 + { + get => (long)_context.GetStorage().TpidrEl0; + set => _context.GetStorage().TpidrEl0 = (ulong)value; + } + + public long TpidrroEl0 + { + get => (long)_context.GetStorage().TpidrroEl0; + set => _context.GetStorage().TpidrroEl0 = (ulong)value; + } + + public uint Pstate + { + get => _context.GetStorage().Pstate; + set => _context.GetStorage().Pstate = value; + } + + public uint Fpcr + { + get => _context.GetStorage().Fpcr; + set => _context.GetStorage().Fpcr = value; + } + + public uint Fpsr + { + get => _context.GetStorage().Fpsr; + set => _context.GetStorage().Fpsr = value; + } + + public bool IsAarch32 + { + get => false; + set + { + if (value) + { + throw new NotSupportedException(); + } + } + } + + public bool Running { get; private set; } + + private delegate bool SupervisorCallHandler(int imm); + private SupervisorCallHandler _svcHandler; + + public NceExecutionContext(ExceptionCallbacks exceptionCallbacks) + { + _svcHandler = OnSupervisorCall; + IntPtr svcHandlerPtr = Marshal.GetFunctionPointerForDelegate(_svcHandler); + + _context = new NceNativeContext(); + + ref var storage = ref _context.GetStorage(); + storage.SvcCallHandler = svcHandlerPtr; + storage.InManaged = 1u; + storage.CtrEl0 = 0x8444c004; // TODO: Get value from host CPU instead of using guest one? + + Running = true; + _exceptionCallbacks = exceptionCallbacks; + } + + public ulong GetX(int index) => _context.GetStorage().X[index]; + public void SetX(int index, ulong value) => _context.GetStorage().X[index] = value; + + public V128 GetV(int index) => _context.GetStorage().V[index]; + public void SetV(int index, V128 value) => _context.GetStorage().V[index] = value; + + // TODO + public bool GetPstateFlag(PState flag) => false; + public void SetPstateFlag(PState flag, bool value) { } + + // TODO + public bool GetFPstateFlag(FPState flag) => false; + public void SetFPstateFlag(FPState flag, bool value) { } + + public void SetStartAddress(ulong address) + { + ref var storage = ref _context.GetStorage(); + storage.X[30] = address; + storage.HostThreadHandle = NceThreadPal.GetCurrentThreadHandle(); + } + + public bool OnSupervisorCall(int imm) + { + _exceptionCallbacks.SupervisorCallback?.Invoke(this, 0UL, imm); + return Running; + } + + public bool OnInterrupt() + { + _exceptionCallbacks.InterruptCallback?.Invoke(this); + return Running; + } + + public void RequestInterrupt() + { + IntPtr threadHandle = _context.GetStorage().HostThreadHandle; + if (threadHandle != IntPtr.Zero) + { + NceThreadPal.SuspendThread(threadHandle); + } + } + + public void StopRunning() + { + Running = false; + } + + public void Dispose() + { + _context.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs b/src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs new file mode 100644 index 000000000..34badd7ab --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceMemoryAllocator.cs @@ -0,0 +1,13 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; + +namespace Ryujinx.Cpu.Nce +{ + class NceMemoryAllocator : IJitMemoryAllocator + { + public IJitMemoryBlock Allocate(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.None); + public IJitMemoryBlock Reserve(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.Reserve); + + public ulong GetPageSize() => MemoryBlock.GetPageSize(); + } +} diff --git a/src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs b/src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs new file mode 100644 index 000000000..a8f0b8e0e --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceMemoryBlock.cs @@ -0,0 +1,24 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Cpu.Nce +{ + class NceMemoryBlock : IJitMemoryBlock + { + private readonly MemoryBlock _impl; + + public IntPtr Pointer => _impl.Pointer; + + public NceMemoryBlock(ulong size, MemoryAllocationFlags flags) + { + _impl = new MemoryBlock(size, flags); + } + + public void Commit(ulong offset, ulong size) => _impl.Commit(offset, size); + public void MapAsRx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndExecute); + public void MapAsRwx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadWriteExecute); + + public void Dispose() => _impl.Dispose(); + } +} diff --git a/src/Ryujinx.Cpu/Nce/NceNativeContext.cs b/src/Ryujinx.Cpu/Nce/NceNativeContext.cs new file mode 100644 index 000000000..4db41f347 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceNativeContext.cs @@ -0,0 +1,43 @@ +using ARMeilleure.State; +using Ryujinx.Common.Memory; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Cpu.Nce +{ + class NceNativeContext : IDisposable + { + public struct NativeCtxStorage + { + public Array32 X; + public Array32 V; + public ulong TpidrEl0; // 0x300 + public ulong TpidrroEl0; // 0x308 + public uint Pstate; // 0x310 + public uint Fpcr; // 0x314 + public uint Fpsr; // 0x318 + public uint InManaged; // 0x31C + public ulong HostSp; // 0x320 + public IntPtr HostThreadHandle; // 0x328 + public ulong HostX30; // 0x330 + public ulong CtrEl0; // 0x338 + public ulong Reserved340; // 0x340 + public ulong Reserved348; // 0x348 + public IntPtr SvcCallHandler; // 0x350 + } + + private readonly MemoryBlock _block; + + public IntPtr BasePtr => _block.Pointer; + + public NceNativeContext() + { + _block = new MemoryBlock((ulong)Unsafe.SizeOf()); + } + + public unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef((void*)_block.Pointer); + + public void Dispose() => _block.Dispose(); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceNativeInterface.cs b/src/Ryujinx.Cpu/Nce/NceNativeInterface.cs new file mode 100644 index 000000000..843af8b5c --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceNativeInterface.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Nce +{ + static class NceNativeInterface + { + private delegate ulong GetTickCounterDelegate(); + private delegate bool SuspendThreadHandlerDelegate(); + private static GetTickCounterDelegate _getTickCounter; + private static SuspendThreadHandlerDelegate _suspendThreadHandler; + private static IntPtr _getTickCounterPtr; + private static IntPtr _suspendThreadHandlerPtr; + + [ThreadStatic] + private static NceExecutionContext _context; + + [ThreadStatic] + private static ITickSource _tickSource; + + static NceNativeInterface() + { + _getTickCounter = GetTickCounter; + _suspendThreadHandler = SuspendThreadHandler; + _getTickCounterPtr = Marshal.GetFunctionPointerForDelegate(_getTickCounter); + _suspendThreadHandlerPtr = Marshal.GetFunctionPointerForDelegate(_suspendThreadHandler); + } + + public static void RegisterThread(NceExecutionContext context, ITickSource tickSource) + { + _context = context; + _tickSource = tickSource; + } + + public static ulong GetTickCounter() + { + return _tickSource.Counter; + } + + public static bool SuspendThreadHandler() + { + return _context.OnInterrupt(); + } + + public static IntPtr GetTickCounterAccessFunctionPointer() + { + return _getTickCounterPtr; + } + + public static IntPtr GetSuspendThreadHandlerFunctionPointer() + { + return _suspendThreadHandlerPtr; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NcePatcher.cs b/src/Ryujinx.Cpu/Nce/NcePatcher.cs new file mode 100644 index 000000000..ee2c57578 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NcePatcher.cs @@ -0,0 +1,293 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Nce +{ + static class NcePatcher + { + private struct Context + { + public readonly ICpuMemoryManager MemoryManager; + public ulong PatchRegionAddress; + public ulong PatchRegionSize; + + public Context(ICpuMemoryManager memoryManager, ulong patchRegionAddress, ulong patchRegionSize) + { + MemoryManager = memoryManager; + PatchRegionAddress = patchRegionAddress; + PatchRegionSize = patchRegionSize; + } + + public ulong GetPatchWriteAddress(int length) + { + ulong byteLength = (ulong)length * 4; + + if (PatchRegionSize < byteLength) + { + throw new Exception("No enough space for patch."); + } + + ulong address = PatchRegionAddress; + PatchRegionAddress += byteLength; + PatchRegionSize -= byteLength; + + return address; + } + } + + public static void Patch( + ICpuMemoryManager memoryManager, + ulong textAddress, + ulong textSize, + ulong patchRegionAddress, + ulong patchRegionSize) + { + Context context = new Context(memoryManager, patchRegionAddress, patchRegionSize); + + ulong address = textAddress; + while (address < textAddress + textSize) + { + uint inst = memoryManager.Read(address); + + if ((inst & ~(0xffffu << 5)) == 0xd4000001u) // svc #0 + { + uint svcId = (ushort)(inst >> 5); + PatchInstruction(memoryManager, address, WriteSvcPatch(ref context, address, svcId)); + Logger.Debug?.Print(LogClass.Cpu, $"Patched SVC #{svcId} at 0x{address:X}."); + } + else if ((inst & ~0x1f) == 0xd53bd060) // mrs x0, tpidrro_el0 + { + uint rd = inst & 0x1f; + PatchInstruction(memoryManager, address, WriteMrsTpidrroEl0Patch(ref context, address, rd)); + Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidrro_el0 at 0x{address:X}."); + } + else if ((inst & ~0x1f) == 0xd53bd040) // mrs x0, tpidr_el0 + { + uint rd = inst & 0x1f; + PatchInstruction(memoryManager, address, WriteMrsTpidrEl0Patch(ref context, address, rd)); + Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidr_el0 at 0x{address:X}."); + } + else if ((inst & ~0x1f) == 0xd53b0020 && OperatingSystem.IsMacOS()) // mrs x0, ctr_el0 + { + uint rd = inst & 0x1f; + PatchInstruction(memoryManager, address, WriteMrsCtrEl0Patch(ref context, address, rd)); + Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, ctr_el0 at 0x{address:X}."); + } + else if ((inst & ~0x1f) == 0xd51bd040) // msr tpidr_el0, x0 + { + uint rd = inst & 0x1f; + PatchInstruction(memoryManager, address, WriteMsrTpidrEl0Patch(ref context, address, rd)); + Logger.Debug?.Print(LogClass.Cpu, $"Patched MSR tpidr_el0, x{rd} at 0x{address:X}."); + } + else if ((inst & ~0x1f) == 0xd53be020) // mrs x0, cntpct_el0 + { + uint rd = inst & 0x1f; + PatchInstruction(memoryManager, address, WriteMrsCntpctEl0Patch(ref context, address, rd)); + Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, cntpct_el0 at 0x{address:X}."); + } + + address += 4; + } + + ulong patchRegionConsumed = BitUtils.AlignUp(context.PatchRegionAddress - patchRegionAddress, 0x1000UL); + if (patchRegionConsumed != 0) + { + memoryManager.Reprotect(patchRegionAddress, patchRegionConsumed, MemoryPermission.ReadAndExecute); + } + } + + private static void PatchInstruction(ICpuMemoryManager memoryManager, ulong instructionAddress, ulong targetAddress) + { + memoryManager.Write(instructionAddress, 0x14000000u | GetImm26(instructionAddress, targetAddress)); + } + + private static ulong WriteSvcPatch(ref Context context, ulong svcAddress, uint svcId) + { + uint[] code = GetCopy(NceAsmTable.SvcPatchCode); + + int movIndex = Array.IndexOf(code, 0xd2800013u); + + WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer); + + int mov2Index = Array.IndexOf(code, 0x52800000u, movIndex + 1); + + ulong targetAddress = context.GetPatchWriteAddress(code.Length); + + code[mov2Index] |= svcId << 5; + code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, svcAddress + 4); + + WriteCode(context.MemoryManager, targetAddress, code); + + return targetAddress; + } + + private static ulong WriteMrsTpidrroEl0Patch(ref Context context, ulong mrsAddress, uint rd) + { + uint[] code = GetCopy(NceAsmTable.MrsTpidrroEl0PatchCode); + + int movIndex = Array.IndexOf(code, 0xd2800013u); + + WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer); + + ulong targetAddress = context.GetPatchWriteAddress(code.Length); + + code[code.Length - 2] |= rd; + code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4); + + WriteCode(context.MemoryManager, targetAddress, code); + + return targetAddress; + } + + private static ulong WriteMrsTpidrEl0Patch(ref Context context, ulong mrsAddress, uint rd) + { + uint[] code = GetCopy(NceAsmTable.MrsTpidrEl0PatchCode); + + int movIndex = Array.IndexOf(code, 0xd2800013u); + + WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer); + + ulong targetAddress = context.GetPatchWriteAddress(code.Length); + + code[code.Length - 2] |= rd; + code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4); + + WriteCode(context.MemoryManager, targetAddress, code); + + return targetAddress; + } + + private static ulong WriteMrsCtrEl0Patch(ref Context context, ulong mrsAddress, uint rd) + { + uint[] code = GetCopy(NceAsmTable.MrsCtrEl0PatchCode); + + int movIndex = Array.IndexOf(code, 0xd2800013u); + + WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer); + + ulong targetAddress = context.GetPatchWriteAddress(code.Length); + + code[code.Length - 2] |= rd; + code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4); + + WriteCode(context.MemoryManager, targetAddress, code); + + return targetAddress; + } + + private static ulong WriteMsrTpidrEl0Patch(ref Context context, ulong msrAddress, uint rd) + { + uint r2 = rd == 0 ? 1u : 0u; + + uint[] code = GetCopy(NceAsmTable.MsrTpidrEl0PatchCode); + + code[0] |= rd << 10; + + int movIndex = Array.IndexOf(code, 0xd2800013u); + + WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer); + + ulong targetAddress = context.GetPatchWriteAddress(code.Length); + + code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, msrAddress + 4); + + WriteCode(context.MemoryManager, targetAddress, code); + + return targetAddress; + } + + private static ulong WriteMrsCntpctEl0Patch(ref Context context, ulong mrsAddress, uint rd) + { + uint[] code = GetCopy(NceAsmTable.MrsCntpctEl0PatchCode); + + int movIndex = Array.IndexOf(code, 0xd2800013u); + + WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer); + + int mov2Index = Array.IndexOf(code, 0xD2800000u, movIndex + 1); + + WriteTickCounterAccessFunctionPointer(code, mov2Index); + + ulong targetAddress = context.GetPatchWriteAddress(code.Length); + + code[code.Length - 2] |= rd; + code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4); + + WriteCode(context.MemoryManager, targetAddress, code); + + return targetAddress; + } + + public static uint[] GenerateExceptionHandlerEntry(IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr) + { + uint[] code = GetCopy(NceAsmTable.ExceptionHandlerEntryCode); + + int movIndex = Array.IndexOf(code, 0xd2800018u); + + WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer); + + int mov2Index = Array.IndexOf(code, 0xd2800008u, movIndex + 1); + + WritePointer(code, mov2Index, (ulong)signalHandlerPtr); + + int mov3Index = Array.IndexOf(code, 0xd2800000u, mov2Index + 1); + + WritePointer(code, mov3Index, (ulong)NceNativeInterface.GetSuspendThreadHandlerFunctionPointer()); + + int mov4Index = Array.IndexOf(code, 0xd2800003u, mov3Index + 1); + + WritePointer(code, mov4Index, (ulong)oldSignalHandlerSegfaultPtr); + + int cmpIndex = Array.IndexOf(code, 0x7100027fu); + + code[cmpIndex] |= (uint)NceThreadPal.UnixSuspendSignal << 10; + + return code; + } + + private static void WriteTickCounterAccessFunctionPointer(uint[] code, int movIndex) + { + WritePointer(code, movIndex, (ulong)NceNativeInterface.GetTickCounterAccessFunctionPointer()); + } + + private static void WritePointer(uint[] code, int movIndex, ulong ptr) + { + code[movIndex] |= (uint)(ushort)ptr << 5; + code[movIndex + 1] |= (uint)(ushort)(ptr >> 16) << 5; + code[movIndex + 2] |= (uint)(ushort)(ptr >> 32) << 5; + code[movIndex + 3] |= (uint)(ushort)(ptr >> 48) << 5; + } + + private static uint GetImm26(ulong sourceAddress, ulong targetAddress) + { + long offset = (long)(targetAddress - sourceAddress); + long offsetTrunc = (offset >> 2) & 0x3FFFFFF; + + if ((offsetTrunc << 38) >> 36 != offset) + { + throw new Exception($"Offset out of range: 0x{sourceAddress:X} -> 0x{targetAddress:X} (0x{offset:X})"); + } + + return (uint)offsetTrunc; + } + + private static uint[] GetCopy(uint[] code) + { + uint[] codeCopy = new uint[code.Length]; + code.CopyTo(codeCopy, 0); + + return codeCopy; + } + + private static void WriteCode(ICpuMemoryManager memoryManager, ulong address, uint[] code) + { + for (int i = 0; i < code.Length; i++) + { + memoryManager.Write(address + (ulong)i * sizeof(uint), code[i]); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceThreadPal.cs b/src/Ryujinx.Cpu/Nce/NceThreadPal.cs new file mode 100644 index 000000000..b59507995 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceThreadPal.cs @@ -0,0 +1,40 @@ +using System; + +namespace Ryujinx.Cpu.Nce +{ + static class NceThreadPal + { + private const int SigUsr2Linux = 12; + private const int SigUsr2MacOS = 31; + + public static int UnixSuspendSignal => OperatingSystem.IsMacOS() ? SigUsr2MacOS : SigUsr2Linux; + + public static IntPtr GetCurrentThreadHandle() + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsAndroid()) + { + return NceThreadPalUnix.GetCurrentThreadHandle(); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void SuspendThread(IntPtr handle) + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + NceThreadPalUnix.SuspendThread(handle); + } + else if (OperatingSystem.IsAndroid()) + { + NceThreadPalAndroid.SuspendThread(handle); + } + else + { + throw new PlatformNotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs b/src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs new file mode 100644 index 000000000..65bfd1bfb --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceThreadPalAndroid.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Nce +{ + static class NceThreadPalAndroid + { + [DllImport("libc", SetLastError = true)] + private static extern int pthread_kill(IntPtr thread, int sig); + + public static void SuspendThread(IntPtr handle) + { + int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal); + if (result != 0) + { + throw new Exception($"Thread kill returned error 0x{result:X}."); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs b/src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs new file mode 100644 index 000000000..9f4877615 --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceThreadPalUnix.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Nce +{ + static class NceThreadPalUnix + { + [DllImport("libc", SetLastError = true)] + private static extern IntPtr pthread_self(); + + [DllImport("libc", SetLastError = true)] + private static extern int pthread_threadid_np(IntPtr arg0, out ulong tid); + + [DllImport("libpthread", SetLastError = true)] + private static extern int pthread_kill(IntPtr thread, int sig); + + public static IntPtr GetCurrentThreadHandle() + { + return pthread_self(); + } + + public static ulong GetCurrentThreadId() + { + pthread_threadid_np(IntPtr.Zero, out ulong tid); + return tid; + } + + public static void SuspendThread(IntPtr handle) + { + int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal); + if (result != 0) + { + throw new Exception($"Thread kill returned error 0x{result:X}."); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/NceThreadTable.cs b/src/Ryujinx.Cpu/Nce/NceThreadTable.cs new file mode 100644 index 000000000..8fe39b7bd --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/NceThreadTable.cs @@ -0,0 +1,97 @@ +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Cpu.Nce +{ + static class NceThreadTable + { + private const int MaxThreads = 4096; + + private struct Entry + { + public IntPtr ThreadId; + public IntPtr NativeContextPtr; + + public Entry(IntPtr threadId, IntPtr nativeContextPtr) + { + ThreadId = threadId; + NativeContextPtr = nativeContextPtr; + } + } + + private static MemoryBlock _block; + + public static IntPtr EntriesPointer => _block.Pointer + 8; + + static NceThreadTable() + { + _block = new MemoryBlock((ulong)Unsafe.SizeOf() * MaxThreads + 8UL); + _block.Write(0UL, 0UL); + } + + public static int Register(IntPtr threadId, IntPtr nativeContextPtr) + { + Span entries = GetStorage(); + + lock (_block) + { + ref ulong currentThreadCount = ref GetThreadsCount(); + + for (int i = 0; i < MaxThreads; i++) + { + if (entries[i].ThreadId == IntPtr.Zero) + { + entries[i] = new Entry(threadId, nativeContextPtr); + + if (currentThreadCount < (ulong)i + 1) + { + currentThreadCount = (ulong)i + 1; + } + + return i; + } + } + } + + throw new Exception($"Number of active threads exceeds limit of {MaxThreads}."); + } + + public static void Unregister(int tableIndex) + { + Span entries = GetStorage(); + + lock (_block) + { + if (entries[tableIndex].ThreadId != IntPtr.Zero) + { + entries[tableIndex] = default; + + ulong currentThreadCount = GetThreadsCount(); + + for (int i = (int)currentThreadCount - 1; i >= 0; i--) + { + if (entries[i].ThreadId != IntPtr.Zero) + { + break; + } + + currentThreadCount = (ulong)i; + } + + GetThreadsCount() = currentThreadCount; + } + } + } + + private static ref ulong GetThreadsCount() + { + return ref _block.GetRef(0UL); + } + + private static unsafe Span GetStorage() + { + return new Span((void*)_block.GetPointer(8UL, (ulong)Unsafe.SizeOf() * MaxThreads), MaxThreads); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Cpu/Nce/nce.S b/src/Ryujinx.Cpu/Nce/nce.S new file mode 100644 index 000000000..bf60676af --- /dev/null +++ b/src/Ryujinx.Cpu/Nce/nce.S @@ -0,0 +1,378 @@ +.text + +.macro longmov0 reg +mov \reg, #0 +movk \reg, #0, lsl #16 +movk \reg, #0, lsl #32 +movk \reg, #0, lsl #48 +.endm + +// r1 = EntriesPointer +// r2 = current_thread_id_local +// r3 = expected_thread_id +// r4 = ThreadsCount_local +.macro loadctxptr_reg r1, r2, r3 +longmov0 \r1 +mrs \r2, tpidr_el0 +1: +ldr \r3, [\r1], #16 +cmp \r2, \r3 +beq 2f +b 1b +2: +ldr \r1, [\r1, #-8] +.endm + +.macro loadctxptr +loadctxptr_reg x19, x20, x21 +.endm + +.global GetTpidrEl0Code +GetTpidrEl0Code: +mrs x0, tpidr_el0 +ret + +.global ThreadStartCode +ThreadStartCode: +stp x19, x20, [sp, #-0x120]! +stp x21, x22, [sp, #0x10] +stp x23, x24, [sp, #0x20] +stp x25, x26, [sp, #0x30] +stp x27, x28, [sp, #0x40] +stp x29, x30, [sp, #0x50] +stp d8, d9, [sp, #0x60] +stp d10, d11, [sp, #0x70] +stp d12, d13, [sp, #0x80] +stp d14, d15, [sp, #0x90] +stp d16, d17, [sp, #0xA0] +stp d18, d19, [sp, #0xB0] +stp d20, d21, [sp, #0xC0] +stp d22, d23, [sp, #0xD0] +stp d24, d25, [sp, #0xE0] +stp d26, d27, [sp, #0xF0] +stp d28, d29, [sp, #0x100] +stp d30, d31, [sp, #0x110] +str wzr, [x0, #0x31C] +mov x1, sp +str x1, [x0, #0x320] +ldp x2, x3, [x0, #0x10] +ldp x4, x5, [x0, #0x20] +ldp x6, x7, [x0, #0x30] +ldp x8, x9, [x0, #0x40] +ldp x10, x11, [x0, #0x50] +ldp x12, x13, [x0, #0x60] +ldp x14, x15, [x0, #0x70] +ldp x16, x17, [x0, #0x80] +ldp x18, x19, [x0, #0x90] +ldp x20, x21, [x0, #0xA0] +ldp x22, x23, [x0, #0xB0] +ldp x24, x25, [x0, #0xC0] +ldp x26, x27, [x0, #0xD0] +ldp x28, x29, [x0, #0xE0] +ldp q0, q1, [x0, #0x100] +ldp q2, q3, [x0, #0x120] +ldp q4, q5, [x0, #0x140] +ldp q6, q7, [x0, #0x160] +ldp q8, q9, [x0, #0x180] +ldp q10, q11, [x0, #0x1A0] +ldp q12, q13, [x0, #0x1C0] +ldp q14, q15, [x0, #0x1E0] +ldp q16, q17, [x0, #0x200] +ldp q18, q19, [x0, #0x220] +ldp q20, q21, [x0, #0x240] +ldp q22, q23, [x0, #0x260] +ldp q24, q25, [x0, #0x280] +ldp q26, q27, [x0, #0x2A0] +ldp q28, q29, [x0, #0x2C0] +ldp q30, q31, [x0, #0x2E0] +ldp x30, x1, [x0, #0xF0] +mov sp, x1 +ldp x0, x1, [x0, #0x0] +br x30 + +// Inputs +// r1 = EntriesPointer +// r2 = current_thread_id_local +// r3 = expected_thread_id +// r4 = EntriesPointerEnd + +// Outputs +// r1 = EntryPointer or 0x0 on not found +.macro loadctxptr_safe_reg r1, r2, r3, r4 +longmov0 \r1 +ldr \r4, [\r1, #-8] +add \r4, \r1, \r4, lsl #4 +mrs \r2, tpidr_el0 +1: +cmp \r1, \r4 +beq 2f +ldr \r3, [\r1], #16 +cmp \r2, \r3 +beq 3f +b 1b +2: +mov \r1, 0x0 +b 4f +3: +ldr \r1, [\r1, #-8] +4: +.endm + +.global ExceptionHandlerEntryCode +ExceptionHandlerEntryCode: +stp x19, x20, [sp, #-0x40]! +stp x21, x22, [sp, #0x10] +stp x23, x24, [sp, #0x20] +str x25, [sp, #0x30] +// signo +mov x19, x0 +// siginfo_t *si +mov x20, x1 +// void *thread_id +mov x21, x2 +mov x22, sp +mov x23, x30 +// x24 = EntriesPointer +// x1 = si +// x2 = thread_id +loadctxptr_safe_reg x24, x1, x2, x25 +cbz x24, 4f +ldr x0, [x24, 0x320] +mov sp, x0 +cmp w19, #0 +beq 1f +mov w0, #1 +str w0, [x24, 0x31C] +mov x0, x19 +mov x1, x20 +mov x2, x21 +mov x8, #0 +movk x8, #0, lsl #16 +movk x8, #0, lsl #32 +movk x8, #0, lsl #48 +blr x8 +b 2f +1: +ldr w0, [x24, 0x31C] +cbnz w0, 3f +mov w0, #1 +str w0, [x24, 0x31C] +mov x0, #0 +movk x0, #0, lsl #16 +movk x0, #0, lsl #32 +movk x0, #0, lsl #48 +blr x0 +2: +str wzr, [x24, 0x31C] +3: +mov sp, x22 +mov x30, x23 +ldp x21, x22, [sp, #0x10] +ldp x23, x24, [sp, #0x20] +ldp x25, x26, [sp, #0x30] +ldp x19, x20, [sp], #0x40 +ret +4: +// ThreadId is invalid, forward to other handler. +mov x0, x19 +mov x1, x20 +mov x2, x21 +mov sp, x22 +ldp x21, x22, [sp, #0x10] +ldp x23, x24, [sp, #0x20] +ldr x25, [sp, #0x30] +ldp x19, x20, [sp], #0x40 +longmov0 x3 +br x3 + +.global SvcPatchCode +SvcPatchCode: + +stp x19, x20, [sp, #-0x20]! +str x21, [sp, #0x10] +loadctxptr +stp x0, x1, [x19, #0x0] +stp x2, x3, [x19, #0x10] +stp x4, x5, [x19, #0x20] +stp x6, x7, [x19, #0x30] +stp x8, x9, [x19, #0x40] +stp x10, x11, [x19, #0x50] +stp x12, x13, [x19, #0x60] +stp x14, x15, [x19, #0x70] +stp x16, x17, [x19, #0x80] +ldr x21, [sp, #0x10] +ldp x0, x20, [sp], #0x20 +stp x18, x0, [x19, #0x90] +stp x20, x21, [x19, #0xA0] +stp x22, x23, [x19, #0xB0] +stp x24, x25, [x19, #0xC0] +stp x26, x27, [x19, #0xD0] +stp x28, x29, [x19, #0xE0] +mov x0, sp +stp x30, x0, [x19, #0xF0] +stp q0, q1, [x19, #0x100] +stp q2, q3, [x19, #0x120] +stp q4, q5, [x19, #0x140] +stp q6, q7, [x19, #0x160] +stp q8, q9, [x19, #0x180] +stp q10, q11, [x19, #0x1A0] +stp q12, q13, [x19, #0x1C0] +stp q14, q15, [x19, #0x1E0] +stp q16, q17, [x19, #0x200] +stp q18, q19, [x19, #0x220] +stp q20, q21, [x19, #0x240] +stp q22, q23, [x19, #0x260] +stp q24, q25, [x19, #0x280] +stp q26, q27, [x19, #0x2A0] +stp q28, q29, [x19, #0x2C0] +stp q30, q31, [x19, #0x2E0] +ldr x0, [x19, #0x320] +mov sp, x0 +mov w0, #1 +str w0, [x19, #0x31C] +mov w0, #0 +ldr x8, [x19, #0x350] +blr x8 +cbnz w0, 1f +ldp d30, d31, [sp, #0x110] +ldp d28, d29, [sp, #0x100] +ldp d26, d27, [sp, #0xF0] +ldp d24, d25, [sp, #0xE0] +ldp d22, d23, [sp, #0xD0] +ldp d20, d21, [sp, #0xC0] +ldp d18, d19, [sp, #0xB0] +ldp d16, d17, [sp, #0xA0] +ldp d14, d15, [sp, #0x90] +ldp d12, d13, [sp, #0x80] +ldp d10, d11, [sp, #0x70] +ldp d8, d9, [sp, #0x60] +ldp x29, x30, [sp, #0x50] +ldp x27, x28, [sp, #0x40] +ldp x25, x26, [sp, #0x30] +ldp x23, x24, [sp, #0x20] +ldp x21, x22, [sp, #0x10] +ldp x19, x20, [sp], #0x120 +ret +1: +str wzr, [x19, #0x31C] +ldp x30, x0, [x19, #0xF0] +mov sp, x0 +ldp x0, x1, [x19, #0x0] +ldp x2, x3, [x19, #0x10] +ldp x4, x5, [x19, #0x20] +ldp x6, x7, [x19, #0x30] +ldp x8, x9, [x19, #0x40] +ldp x10, x11, [x19, #0x50] +ldp x12, x13, [x19, #0x60] +ldp x14, x15, [x19, #0x70] +ldp x16, x17, [x19, #0x80] +ldr x18, [x19, #0x90] +ldp x20, x21, [x19, #0xA0] +ldp x22, x23, [x19, #0xB0] +ldp x24, x25, [x19, #0xC0] +ldp x26, x27, [x19, #0xD0] +ldp x28, x29, [x19, #0xE0] +ldp q0, q1, [x19, #0x100] +ldp q2, q3, [x19, #0x120] +ldp q4, q5, [x19, #0x140] +ldp q6, q7, [x19, #0x160] +ldp q8, q9, [x19, #0x180] +ldp q10, q11, [x19, #0x1A0] +ldp q12, q13, [x19, #0x1C0] +ldp q14, q15, [x19, #0x1E0] +ldp q16, q17, [x19, #0x200] +ldp q18, q19, [x19, #0x220] +ldp q20, q21, [x19, #0x240] +ldp q22, q23, [x19, #0x260] +ldp q24, q25, [x19, #0x280] +ldp q26, q27, [x19, #0x2A0] +ldp q28, q29, [x19, #0x2C0] +ldp q30, q31, [x19, #0x2E0] +ldr x19, [x19, #0x98] +b #0 + +.global MrsTpidrroEl0PatchCode +MrsTpidrroEl0PatchCode: +stp xzr, x19, [sp, #-0x20]! +stp x20, x21, [sp, #0x10] +loadctxptr +ldr x19, [x19, #0x308] +str x19, [sp] +ldp x20, x21, [sp, #0x10] +ldr x19, [sp, #8] +ldr x0, [sp], #0x20 +b #0 + +.global MrsTpidrEl0PatchCode +MrsTpidrEl0PatchCode: +stp xzr, x19, [sp, #-0x20]! +stp x20, x21, [sp, #0x10] +loadctxptr +ldr x19, [x19, #0x300] +str x19, [sp] +ldp x20, x21, [sp, #0x10] +ldr x19, [sp, #8] +ldr x0, [sp], #0x20 +b #0 + +.global MrsCtrEl0PatchCode +MrsCtrEl0PatchCode: +stp xzr, x19, [sp, #-0x20]! +stp x20, x21, [sp, #0x10] +loadctxptr +ldr x19, [x19, #0x338] +str x19, [sp] +ldp x20, x21, [sp, #0x10] +ldr x19, [sp, #8] +ldr x0, [sp], #0x20 +b #0 + +.global MsrTpidrEl0PatchCode +MsrTpidrEl0PatchCode: +stp x19, x0, [sp, #-0x20]! +stp x20, x21, [sp, #0x10] +loadctxptr +ldr x20, [sp, #8] +str x20, [x19, #0x300] +ldp x20, x21, [sp, #0x10] +ldr x19, [sp], #0x20 +b #0 + +.global MrsCntpctEl0PatchCode +MrsCntpctEl0PatchCode: +stp x0, x1, [sp, #-0xC0]! +stp x2, x3, [sp, #0x10] +stp x4, x5, [sp, #0x20] +stp x6, x7, [sp, #0x30] +stp x8, x9, [sp, #0x40] +stp x10, x11, [sp, #0x50] +stp x12, x13, [sp, #0x60] +stp x14, x15, [sp, #0x70] +stp x16, x17, [sp, #0x80] +stp x18, x19, [sp, #0x90] +stp x20, x21, [sp, #0xA0] +str x30, [sp, #0xB8] +loadctxptr +mov w0, #1 +str w0, [x19, #0x31C] +mov x0, #0 +movk x0, #0, lsl #16 +movk x0, #0, lsl #32 +movk x0, #0, lsl #48 +blr x0 +str wzr, [x19, #0x31C] +str x0, [sp, #0xB0] +ldr x30, [sp, #0xB8] +ldp x20, x21, [sp, #0xA0] +ldp x18, x19, [sp, #0x90] +ldp x16, x17, [sp, #0x80] +ldp x14, x15, [sp, #0x70] +ldp x12, x13, [sp, #0x60] +ldp x10, x11, [sp, #0x50] +ldp x8, x9, [sp, #0x40] +ldp x6, x7, [sp, #0x30] +ldp x4, x5, [sp, #0x20] +ldp x2, x3, [sp, #0x10] +ldp x0, x1, [sp], #0xB0 +ldr x0, [sp], #0x10 +b #0 diff --git a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs index ad6e688cc..c37fd870e 100644 --- a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs +++ b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs @@ -70,7 +70,7 @@ namespace Ryujinx.Cpu.Signal config = new SignalHandlerConfig(); } - public static void InitializeSignalHandler(ulong pageSize, Func customSignalHandlerFactory = null) + public static void InitializeSignalHandler(ulong pageSize, Func customSignalHandlerFactory = null, int userSignal = -1) { if (_initialized) { @@ -103,7 +103,7 @@ namespace Ryujinx.Cpu.Signal _signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr); } - var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr); + var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr, userSignal); config.UnixOldSigaction = (nuint)(ulong)old.sa_handler; config.UnixOldSigaction3Arg = old.sa_flags & 4; diff --git a/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs index e659b236e..80d280508 100644 --- a/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs +++ b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs @@ -82,7 +82,7 @@ namespace Ryujinx.Cpu.Signal return old; } - public static SigAction RegisterExceptionHandler(IntPtr action) + public static SigAction RegisterExceptionHandler(IntPtr action, int userSignal = -1) { int result; SigAction old; @@ -150,6 +150,21 @@ namespace Ryujinx.Cpu.Signal } } + if (userSignal != -1) + { + result = sigaction(userSignal, ref sig, out SigAction oldu); + + if (oldu.sa_handler != IntPtr.Zero) + { + throw new InvalidOperationException($"SIG{userSignal} is already in use."); + } + + if (result != 0) + { + throw new InvalidOperationException($"Could not register SIG{userSignal} sigaction. Error: {result}"); + } + } + return old; } diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs index fde489ab7..1735fcaa4 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs @@ -16,13 +16,15 @@ namespace Ryujinx.HLE.HOS ulong codeSize); } - class ArmProcessContext : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager + class ArmProcessContext : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, ICpuMemoryManager { private readonly ulong _pid; private readonly GpuContext _gpuContext; private readonly ICpuContext _cpuContext; private T _memoryManager; + public ulong ReservedSize { get; } + public IVirtualMemoryManager AddressSpace => _memoryManager; public ulong AddressSpaceSize { get; } @@ -33,7 +35,8 @@ namespace Ryujinx.HLE.HOS GpuContext gpuContext, T memoryManager, ulong addressSpaceSize, - bool for64Bit) + bool for64Bit, + ulong reservedSize = 0UL) { if (memoryManager is IRefCounted rc) { @@ -46,8 +49,8 @@ namespace Ryujinx.HLE.HOS _gpuContext = gpuContext; _cpuContext = cpuEngine.CreateCpuContext(memoryManager, for64Bit); _memoryManager = memoryManager; - AddressSpaceSize = addressSpaceSize; + ReservedSize = reservedSize; } public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks) @@ -78,6 +81,11 @@ namespace Ryujinx.HLE.HOS _cpuContext.InvalidateCacheRegion(address, size); } + public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize) + { + _cpuContext.PatchCodeForNce(textAddress, textSize, patchRegionAddress, patchRegionSize); + } + public void Dispose() { if (_memoryManager is IRefCounted rc) diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 06b8fd345..fc137f0b5 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -4,6 +4,7 @@ using Ryujinx.Cpu; using Ryujinx.Cpu.AppleHv; using Ryujinx.Cpu.Jit; using Ryujinx.Cpu.LightningJit; +using Ryujinx.Cpu.Nce; using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Process; @@ -46,14 +47,31 @@ namespace Ryujinx.HLE.HOS public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit) { IArmProcessContext processContext; + AddressSpace addressSpace = null; bool isArm64Host = RuntimeInformation.ProcessArchitecture == Architecture.Arm64; if (OperatingSystem.IsMacOS() && isArm64Host && for64Bit && context.Device.Configuration.UseHypervisor) { - var cpuEngine = new HvEngine(_tickSource); - var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler); - processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit); + if (OperatingSystem.IsMacOS()) + { + var cpuEngine = new HvEngine(_tickSource); + var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit); + } + else + { + if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace)) + { + throw new Exception("Address space creation failed"); + } + + Logger.Info?.Print(LogClass.Cpu, $"NCE Base AS Address: 0x{addressSpace.Base.Pointer.ToInt64():X} Size: 0x{addressSpace.AddressSpaceSize:X}"); + + var cpuEngine = new NceEngine(_tickSource); + var memoryManager = new MemoryManagerNative(addressSpace, context.Memory, addressSpaceSize, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpace.AddressSpaceSize, for64Bit, memoryManager.ReservedSize); + } } else { @@ -70,8 +88,6 @@ namespace Ryujinx.HLE.HOS ? new LightningJitEngine(_tickSource) : new JitEngine(_tickSource); - AddressSpace addressSpace = null; - if (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) { if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace)) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs index 3a9f64bef..fd226987d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -107,6 +107,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryRegion memRegion, ulong address, ulong size, + ulong reservedSize, KMemoryBlockSlabManager slabManager) { if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits) @@ -128,6 +129,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory memRegion, address, size, + reservedSize, slabManager); if (result != Result.Success) @@ -155,6 +157,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory MemoryRegion memRegion, ulong address, ulong size, + ulong reservedSize, KMemoryBlockSlabManager slabManager) { ulong endAddr = address + size; @@ -178,7 +181,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory CodeRegionStart = 0x200000; codeRegionSize = 0x3fe00000; AslrRegionStart = 0x200000; - AslrRegionEnd = AslrRegionStart + 0xffe00000; + AslrRegionEnd = 0x100000000; stackAndTlsIoStart = 0x200000; stackAndTlsIoEnd = 0x40000000; break; @@ -191,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory CodeRegionStart = 0x8000000; codeRegionSize = 0x78000000; AslrRegionStart = 0x8000000; - AslrRegionEnd = AslrRegionStart + 0xff8000000; + AslrRegionEnd = 0x1000000000; stackAndTlsIoStart = 0x8000000; stackAndTlsIoEnd = 0x80000000; break; @@ -204,7 +207,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory CodeRegionStart = 0x200000; codeRegionSize = 0x3fe00000; AslrRegionStart = 0x200000; - AslrRegionEnd = AslrRegionStart + 0xffe00000; + AslrRegionEnd = 0x100000000; stackAndTlsIoStart = 0x200000; stackAndTlsIoEnd = 0x40000000; break; @@ -222,8 +225,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart; stackAndTlsIoStart = 0; stackAndTlsIoEnd = 0; - AslrRegionStart = 0x8000000; - addrSpaceEnd = 1UL << addressSpaceWidth; + AslrRegionStart = reservedSize + 0x8000000; + addrSpaceEnd = reservedSize + (1UL << addressSpaceWidth); AslrRegionEnd = addrSpaceEnd; } else @@ -234,8 +237,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory tlsIoRegion.Size = 0x1000000000; CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment); codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart; - AslrRegionStart = 0x8000000; - AslrRegionEnd = AslrRegionStart + 0x7ff8000000; + AslrRegionStart = reservedSize + 0x8000000; + AslrRegionEnd = 0x8000000000; stackAndTlsIoStart = 0; stackAndTlsIoEnd = 0; } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs index ac36b781b..9918c5d73 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs @@ -7,11 +7,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process interface IProcessContext : IDisposable { IVirtualMemoryManager AddressSpace { get; } + ulong ReservedSize { get; } ulong AddressSpaceSize { get; } IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks); void Execute(IExecutionContext context, ulong codeAddress); void InvalidateCacheRegion(ulong address, ulong size); + void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize); } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 6008548be..be3a6047d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -139,7 +139,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr); - ulong codeAddress = creationInfo.CodeAddress; + ulong codeAddress = creationInfo.CodeAddress + Context.ReservedSize; ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize; @@ -154,6 +154,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process memRegion, codeAddress, codeSize, + Context.ReservedSize, slabManager); if (result != Result.Success) @@ -189,7 +190,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process KResourceLimit resourceLimit, MemoryRegion memRegion, IProcessContextFactory contextFactory, - ThreadStart customThreadStart = null) + ThreadStart customThreadStart = null, + ulong entrypointOffset = 0UL) { ResourceLimit = resourceLimit; _memRegion = memRegion; @@ -247,7 +249,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr); - ulong codeAddress = creationInfo.CodeAddress; + ulong codeAddress = creationInfo.CodeAddress + Context.ReservedSize; ulong codeSize = codePagesCount * KPageTableBase.PageSize; @@ -258,6 +260,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process memRegion, codeAddress, codeSize, + Context.ReservedSize, slabManager); if (result != Result.Success) @@ -303,6 +306,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process CleanUpForError(); } + _entrypoint += entrypointOffset; + return result; } @@ -347,7 +352,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process Flags = creationInfo.Flags; TitleId = creationInfo.TitleId; - _entrypoint = creationInfo.CodeAddress; + _entrypoint = creationInfo.CodeAddress + Context.ReservedSize; _imageSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize; switch (Flags & ProcessCreationFlags.AddressSpaceMask) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs index b4ae6ec4e..d91cef4fa 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs @@ -7,6 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process class ProcessContext : IProcessContext { public IVirtualMemoryManager AddressSpace { get; } + public ulong ReservedSize => 0UL; public ulong AddressSpaceSize { get; } @@ -30,6 +31,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { } + public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize) + { + } + public void Dispose() { } diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index a6a1d87e0..7440fb9a4 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -133,6 +133,8 @@ namespace Ryujinx.HLE.Loaders.Processes return resultCode; } + private const int ReservedPatchSize = 0x100000; + public static bool LoadKip(KernelContext context, KipExecutable kip) { uint endOffset = kip.DataOffset + (uint)kip.Data.Length; @@ -197,7 +199,9 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } - result = LoadIntoMemory(process, kip, codeBaseAddress); + // TODO: Support NCE of KIPs too. + result = LoadIntoMemory(process, kip, codeBaseAddress, 0UL); + if (result != Result.Success) { Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); @@ -257,6 +261,7 @@ namespace Ryujinx.HLE.Loaders.Processes _ => "", }).ToUpper()); + ulong[] nsoPatch = new ulong[executables.Length]; ulong[] nsoBase = new ulong[executables.Length]; for (int index = 0; index < executables.Length; index++) @@ -281,6 +286,10 @@ namespace Ryujinx.HLE.Loaders.Processes nsoSize = BitUtils.AlignUp(nsoSize, KPageTableBase.PageSize); + nsoPatch[index] = codeStart + codeSize; + + codeSize += ReservedPatchSize; + nsoBase[index] = codeStart + codeSize; codeSize += nsoSize; @@ -304,7 +313,7 @@ namespace Ryujinx.HLE.Loaders.Processes programId, codeStart, codePagesCount, - (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication, + (ProcessCreationFlags)meta.Flags, 0, personalMmHeapPagesCount); @@ -381,7 +390,8 @@ namespace Ryujinx.HLE.Loaders.Processes MemoryMarshal.Cast(npdm.KernelCapabilityData), resourceLimit, memoryRegion, - processContextFactory); + processContextFactory, + entrypointOffset: ReservedPatchSize); if (result != Result.Success) { @@ -392,9 +402,13 @@ namespace Ryujinx.HLE.Loaders.Processes for (int index = 0; index < executables.Length; index++) { - Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); + ulong nsoPatchAddress = process.Context.ReservedSize + nsoPatch[index]; + ulong nsoBaseAddress = process.Context.ReservedSize + nsoBase[index]; + + Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBaseAddress:x16}..."); + + result = LoadIntoMemory(process, executables[index], nsoBaseAddress, nsoPatchAddress); - result = LoadIntoMemory(process, executables[index], nsoBase[index]); if (result != Result.Success) { Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); @@ -433,7 +447,7 @@ namespace Ryujinx.HLE.Loaders.Processes device.System.State.DesiredTitleLanguage); } - public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) + private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress, ulong patchAddress) { ulong textStart = baseAddress + image.TextOffset; ulong roStart = baseAddress + image.RoOffset; @@ -453,6 +467,8 @@ namespace Ryujinx.HLE.Loaders.Processes process.CpuMemory.Fill(bssStart, image.BssSize, 0); + process.Context.PatchCodeForNce(textStart, (ulong)image.Text.Length, patchAddress, ReservedPatchSize); + Result SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission) { if (size == 0) diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index 021d33663..a6620b4f7 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -96,6 +96,11 @@ namespace Ryujinx.Memory } } + /// + public void Reprotect(ulong va, ulong size, MemoryPermission permission) + { + } + /// public T Read(ulong va) where T : unmanaged { diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 9cf3663cf..edfeb79ad 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -44,6 +44,14 @@ namespace Ryujinx.Memory /// Size of the range to be unmapped void Unmap(ulong va, ulong size); + /// + /// Reprotects a previously mapped range of virtual memory. + /// + /// Virtual address of the range to be reprotected + /// Size of the range to be reprotected + /// New protection of the memory range + void Reprotect(ulong va, ulong size, MemoryPermission permission); + /// /// Reads data from CPU mapped memory. /// diff --git a/src/Ryujinx.Memory/MemoryManagementUnix.cs b/src/Ryujinx.Memory/MemoryManagementUnix.cs index 516df5a4b..ac008e697 100644 --- a/src/Ryujinx.Memory/MemoryManagementUnix.cs +++ b/src/Ryujinx.Memory/MemoryManagementUnix.cs @@ -16,15 +16,15 @@ namespace Ryujinx.Memory public static IntPtr Allocate(ulong size, bool forJit) { - return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit); + return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit, false); } public static IntPtr Reserve(ulong size, bool forJit) { - return AllocateInternal(size, MmapProts.PROT_NONE, forJit); + return AllocateInternal(size, MmapProts.PROT_NONE, forJit, false); } - private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared = false) + private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared) { MmapFlags flags = MmapFlags.MAP_ANONYMOUS;