diff --git a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
index 18e27e5a74..0c47be61a3 100644
--- a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
+++ b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs
@@ -326,7 +326,7 @@ namespace ARMeilleure.Instructions
}
while (bit < context.Memory.AddressSpaceBits);
- context.BranchIfTrue(lblSlowPath, context.ICompareLess(pte, Const(0L)));
+ context.BranchIfTrue(lblSlowPath, context.ICompareLessOrEqual(pte, Const(0L)));
Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, PageMask));
diff --git a/Ryujinx.Cpu/InvalidAccessHandler.cs b/Ryujinx.Cpu/InvalidAccessHandler.cs
new file mode 100644
index 0000000000..0d3d387d83
--- /dev/null
+++ b/Ryujinx.Cpu/InvalidAccessHandler.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Cpu
+{
+ ///
+ /// Function that handles a invalid memory access from the emulated CPU.
+ ///
+ /// Virtual address of the invalid region that is being accessed
+ /// True if the invalid access should be ignored, false otherwise
+ public delegate bool InvalidAccessHandler(ulong va);
+}
diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs
index 211a8c0d1d..3135750874 100644
--- a/Ryujinx.Cpu/MemoryManager.cs
+++ b/Ryujinx.Cpu/MemoryManager.cs
@@ -18,6 +18,11 @@ namespace Ryujinx.Cpu
private const int PteSize = 8;
+ private readonly InvalidAccessHandler _invalidAccessHandler;
+
+ ///
+ /// Address space width in bits.
+ ///
public int AddressSpaceBits { get; }
private readonly ulong _addressSpaceSize;
@@ -25,6 +30,9 @@ namespace Ryujinx.Cpu
private readonly MemoryBlock _backingMemory;
private readonly MemoryBlock _pageTable;
+ ///
+ /// Page table base pointer.
+ ///
public IntPtr PageTablePointer => _pageTable.Pointer;
///
@@ -32,8 +40,11 @@ namespace Ryujinx.Cpu
///
/// Physical backing memory where virtual memory will be mapped to
/// Size of the address space
- public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize)
+ /// Optional function to handle invalid memory accesses
+ public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
{
+ _invalidAccessHandler = invalidAccessHandler;
+
ulong asSize = PageSize;
int asBits = PageBits;
@@ -92,6 +103,7 @@ namespace Ryujinx.Cpu
/// Type of the data being read
/// Virtual address of the data in memory
/// The data
+ /// Throw for unhandled invalid or unmapped memory accesses
public T Read(ulong va) where T : unmanaged
{
return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0];
@@ -102,6 +114,7 @@ namespace Ryujinx.Cpu
///
/// Virtual address of the data in memory
/// Span to store the data being read into
+ /// Throw for unhandled invalid or unmapped memory accesses
public void Read(ulong va, Span data)
{
ReadImpl(va, data);
@@ -113,6 +126,7 @@ namespace Ryujinx.Cpu
/// Type of the data being written
/// Virtual address to write the data into
/// Data to be written
+ /// Throw for unhandled invalid or unmapped memory accesses
public void Write(ulong va, T value) where T : unmanaged
{
Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)));
@@ -123,6 +137,7 @@ namespace Ryujinx.Cpu
///
/// Virtual address to write the data into
/// Data to be written
+ /// Throw for unhandled invalid or unmapped memory accesses
public void Write(ulong va, ReadOnlySpan data)
{
if (data.Length == 0)
@@ -130,34 +145,44 @@ namespace Ryujinx.Cpu
return;
}
- MarkRegionAsModified(va, (ulong)data.Length);
-
- if (IsContiguous(va, data.Length))
+ try
{
- data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
- }
- else
- {
- int offset = 0, size;
+ MarkRegionAsModified(va, (ulong)data.Length);
- if ((va & PageMask) != 0)
+ if (IsContiguousAndMapped(va, data.Length))
{
- ulong pa = GetPhysicalAddressInternal(va);
-
- size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
-
- data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
-
- offset += size;
+ data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
}
-
- for (; offset < data.Length; offset += size)
+ else
{
- ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
+ int offset = 0, size;
- size = Math.Min(data.Length - offset, PageSize);
+ if ((va & PageMask) != 0)
+ {
+ ulong pa = GetPhysicalAddressInternal(va);
- data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
+ size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
+
+ data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
+
+ offset += size;
+ }
+
+ for (; offset < data.Length; offset += size)
+ {
+ ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
+
+ size = Math.Min(data.Length - offset, PageSize);
+
+ data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
+ }
+ }
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
}
}
}
@@ -172,6 +197,7 @@ namespace Ryujinx.Cpu
/// Virtual address of the data
/// Size of the data
/// A read-only span of the data
+ /// Throw for unhandled invalid or unmapped memory accesses
public ReadOnlySpan GetSpan(ulong va, int size)
{
if (size == 0)
@@ -179,7 +205,7 @@ namespace Ryujinx.Cpu
return ReadOnlySpan.Empty;
}
- if (IsContiguous(va, size))
+ if (IsContiguousAndMapped(va, size))
{
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
}
@@ -204,6 +230,7 @@ namespace Ryujinx.Cpu
/// Virtual address of the data
/// Size of the data
/// A writable region of memory containing the data
+ /// Throw for unhandled invalid or unmapped memory accesses
public WritableRegion GetWritableRegion(ulong va, int size)
{
if (size == 0)
@@ -211,7 +238,7 @@ namespace Ryujinx.Cpu
return new WritableRegion(null, va, Memory.Empty);
}
- if (IsContiguous(va, size))
+ if (IsContiguousAndMapped(va, size))
{
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
}
@@ -234,6 +261,7 @@ namespace Ryujinx.Cpu
/// Type of the data to get the reference
/// Virtual address of the data
/// A reference to the data in memory
+ /// Throw if the specified memory region is not contiguous in physical memory
public ref T GetRef(ulong va) where T : unmanaged
{
if (!IsContiguous(va, Unsafe.SizeOf()))
@@ -256,6 +284,9 @@ namespace Ryujinx.Cpu
return ref _backingMemory.GetRef(GetPhysicalAddressInternal(va));
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsContiguous(ulong va, int size)
{
@@ -295,26 +326,36 @@ namespace Ryujinx.Cpu
return;
}
- int offset = 0, size;
-
- if ((va & PageMask) != 0)
+ try
{
- ulong pa = GetPhysicalAddressInternal(va);
+ int offset = 0, size;
- size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
+ if ((va & PageMask) != 0)
+ {
+ ulong pa = GetPhysicalAddressInternal(va);
- _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
+ size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
- offset += size;
+ _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
+
+ offset += size;
+ }
+
+ for (; offset < data.Length; offset += size)
+ {
+ ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
+
+ size = Math.Min(data.Length - offset, PageSize);
+
+ _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
+ }
}
-
- for (; offset < data.Length; offset += size)
+ catch (InvalidMemoryRegionException)
{
- ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
-
- size = Math.Min(data.Length - offset, PageSize);
-
- _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
}
}
@@ -416,6 +457,7 @@ namespace Ryujinx.Cpu
///
/// Virtual address to check
/// True if the address is mapped, false otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va)
{
if (!ValidateAddress(va))
diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index 0fa228370c..5d00469413 100644
--- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -1,5 +1,6 @@
using ARMeilleure.State;
using Ryujinx.Common;
+using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Common;
@@ -1071,18 +1072,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return result;
}
- public void StopAllThreads()
- {
- lock (_threadingLock)
- {
- foreach (KThread thread in _threads)
- {
- KernelContext.Scheduler.ExitThread(thread);
- KernelContext.Scheduler.CoreManager.Set(thread.HostThread);
- }
- }
- }
-
private void InitializeMemoryManager(AddressSpaceType addrSpaceType, MemoryRegion memRegion)
{
int addrSpaceBits = addrSpaceType switch
@@ -1094,7 +1083,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_ => throw new ArgumentException(nameof(addrSpaceType))
};
- CpuMemory = new MemoryManager(KernelContext.Memory, 1UL << addrSpaceBits);
+ CpuMemory = new MemoryManager(KernelContext.Memory, 1UL << addrSpaceBits, InvalidAccessHandler);
CpuContext = new CpuContext(CpuMemory);
// TODO: This should eventually be removed.
@@ -1104,13 +1093,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
MemoryManager = new KMemoryManager(KernelContext, CpuMemory);
}
- public void PrintCurrentThreadStackTrace()
+ private bool InvalidAccessHandler(ulong va)
{
- KernelContext.Scheduler.GetCurrentThread().PrintGuestStackTrace();
+ KernelContext.Scheduler.GetCurrentThreadOrNull()?.PrintGuestStackTrace();
+
+ Logger.PrintError(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}.");
+
+ return false;
}
private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e)
{
+ KernelContext.Scheduler.GetCurrentThreadOrNull()?.PrintGuestStackTrace();
+
throw new UndefinedInstructionException(e.Address, e.OpCode);
}
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
index e07477ae1a..8499551394 100644
--- a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
@@ -198,6 +198,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
public KThread GetCurrentThread()
+ {
+ return GetCurrentThreadOrNull() ?? GetDummyThread();
+ }
+
+ public KThread GetCurrentThreadOrNull()
{
lock (CoreContexts)
{
@@ -210,9 +215,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
}
- return GetDummyThread();
-
- throw new InvalidOperationException("Current thread is not scheduled!");
+ return null;
}
private KThread _dummyThread;
diff --git a/Ryujinx.Memory/InvalidMemoryRegionException.cs b/Ryujinx.Memory/InvalidMemoryRegionException.cs
new file mode 100644
index 0000000000..9fc938c61b
--- /dev/null
+++ b/Ryujinx.Memory/InvalidMemoryRegionException.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Ryujinx.Memory
+{
+ public class InvalidMemoryRegionException : Exception
+ {
+ public InvalidMemoryRegionException() : base("Attempted to access a invalid memory region.")
+ {
+ }
+
+ public InvalidMemoryRegionException(string message) : base(message)
+ {
+ }
+
+ public InvalidMemoryRegionException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/Ryujinx.Memory/MemoryBlock.cs b/Ryujinx.Memory/MemoryBlock.cs
index 065e071349..198e275b4f 100644
--- a/Ryujinx.Memory/MemoryBlock.cs
+++ b/Ryujinx.Memory/MemoryBlock.cs
@@ -25,7 +25,7 @@ namespace Ryujinx.Memory
/// Initializes a new instance of the memory block class.
///
/// Size of the memory block
- /// Flags that control memory block memory allocation
+ /// Flags that controls memory block memory allocation
/// Throw when there's no enough memory to allocate the requested size
/// Throw when the current platform is not supported
public MemoryBlock(ulong size, MemoryAllocationFlags flags = MemoryAllocationFlags.None)
@@ -50,7 +50,7 @@ namespace Ryujinx.Memory
/// Size of the range to be committed
/// True if the operation was successful, false otherwise
/// Throw when the memory block has already been disposed
- /// Throw when either or are out of range
+ /// Throw when either or are out of range
public bool Commit(ulong offset, ulong size)
{
return MemoryManagement.Commit(GetPointerInternal(offset, size), size);
@@ -63,7 +63,7 @@ namespace Ryujinx.Memory
/// Size of the range to be reprotected
/// New memory permissions
/// Throw when the memory block has already been disposed
- /// Throw when either or are out of range
+ /// Throw when either or are out of range
/// Throw when is invalid
public void Reprotect(ulong offset, ulong size, MemoryPermission permission)
{
@@ -76,7 +76,7 @@ namespace Ryujinx.Memory
/// Starting offset of the range being read
/// Span where the bytes being read will be copied to
/// Throw when the memory block has already been disposed
- /// Throw when the memory region specified for the the data is out of range
+ /// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Read(ulong offset, Span data)
{
@@ -90,7 +90,7 @@ namespace Ryujinx.Memory
/// Offset where the data is located
/// Data at the specified address
/// Throw when the memory block has already been disposed
- /// Throw when the memory region specified for the the data is out of range
+ /// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Read(ulong offset) where T : unmanaged
{
@@ -103,7 +103,7 @@ namespace Ryujinx.Memory
/// Starting offset of the range being written
/// Span where the bytes being written will be copied from
/// Throw when the memory block has already been disposed
- /// Throw when the memory region specified for the the data is out of range
+ /// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong offset, ReadOnlySpan data)
{
@@ -117,7 +117,7 @@ namespace Ryujinx.Memory
/// Offset to write the data into
/// Data to be written
/// Throw when the memory block has already been disposed
- /// Throw when the memory region specified for the the data is out of range
+ /// Throw when the memory region specified for the the data is out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong offset, T data) where T : unmanaged
{
@@ -131,7 +131,7 @@ namespace Ryujinx.Memory
/// Source offset to read the data from
/// Size of the copy in bytes
/// Throw when the memory block has already been disposed
- /// Throw when , or is out of range
+ /// Throw when , or is out of range
public void Copy(ulong dstOffset, ulong srcOffset, ulong size)
{
const int MaxChunkSize = 1 << 30;
@@ -150,7 +150,7 @@ namespace Ryujinx.Memory
/// Offset of the region to fill with zeros
/// Size in bytes of the region to fill
/// Throw when the memory block has already been disposed
- /// Throw when either or are out of range
+ /// Throw when either or are out of range
public void ZeroFill(ulong offset, ulong size)
{
const int MaxChunkSize = 1 << 30;
@@ -170,7 +170,7 @@ namespace Ryujinx.Memory
/// Offset of the memory region
/// A reference to the given memory region data
/// Throw when the memory block has already been disposed
- /// Throw when either or are out of range
+ /// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ref T GetRef(ulong offset) where T : unmanaged
{
@@ -187,7 +187,7 @@ namespace Ryujinx.Memory
if (endOffset > Size || endOffset < offset)
{
- ThrowArgumentOutOfRange();
+ ThrowInvalidMemoryRegionException();
}
return ref Unsafe.AsRef((void*)PtrAddr(ptr, offset));
@@ -200,7 +200,7 @@ namespace Ryujinx.Memory
/// Size in bytes of the region
/// The pointer to the memory region
/// Throw when the memory block has already been disposed
- /// Throw when either or are out of range
+ /// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IntPtr GetPointer(ulong offset, int size) => GetPointerInternal(offset, (ulong)size);
@@ -218,20 +218,20 @@ namespace Ryujinx.Memory
if (endOffset > Size || endOffset < offset)
{
- ThrowArgumentOutOfRange();
+ ThrowInvalidMemoryRegionException();
}
return PtrAddr(ptr, offset);
}
///
- /// Gets the of a given memory block region.
+ /// Gets the of a given memory block region.
///
/// Start offset of the memory region
/// Size in bytes of the region
/// Span of the memory region
/// Throw when the memory block has already been disposed
- /// Throw when either or are out of range
+ /// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe Span GetSpan(ulong offset, int size)
{
@@ -239,13 +239,13 @@ namespace Ryujinx.Memory
}
///
- /// Gets the of a given memory block region.
+ /// Gets the of a given memory block region.
///
/// Start offset of the memory region
/// Size in bytes of the region
/// Memory of the memory region
/// Throw when the memory block has already been disposed
- /// Throw when either or are out of range
+ /// Throw when either or are out of range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe Memory GetMemory(ulong offset, int size)
{
@@ -285,6 +285,6 @@ namespace Ryujinx.Memory
}
private void ThrowObjectDisposed() => throw new ObjectDisposedException(nameof(MemoryBlock));
- private void ThrowArgumentOutOfRange() => throw new ArgumentOutOfRangeException();
+ private void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
}
}