aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Cpu/Signal
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Cpu/Signal')
-rw-r--r--src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs179
-rw-r--r--src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs83
-rw-r--r--src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs24
3 files changed, 286 insertions, 0 deletions
diff --git a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs
new file mode 100644
index 00000000..5a9d92cc
--- /dev/null
+++ b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs
@@ -0,0 +1,179 @@
+using ARMeilleure.Signal;
+using Ryujinx.Common;
+using Ryujinx.Memory;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Signal
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct SignalHandlerRange
+ {
+ public int IsActive;
+ public nuint RangeAddress;
+ public nuint RangeEndAddress;
+ public IntPtr ActionPointer;
+ }
+
+ [InlineArray(NativeSignalHandlerGenerator.MaxTrackedRanges)]
+ struct SignalHandlerRangeArray
+ {
+ public SignalHandlerRange Range0;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct SignalHandlerConfig
+ {
+ /// <summary>
+ /// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct.
+ /// </summary>
+ public int StructAddressOffset;
+
+ /// <summary>
+ /// The byte offset of the write flag in the SigInfo or ExceptionRecord struct.
+ /// </summary>
+ public int StructWriteOffset;
+
+ /// <summary>
+ /// The sigaction handler that was registered before this one. (unix only)
+ /// </summary>
+ public nuint UnixOldSigaction;
+
+ /// <summary>
+ /// The type of the previous sigaction. True for the 3 argument variant. (unix only)
+ /// </summary>
+ public int UnixOldSigaction3Arg;
+
+ /// <summary>
+ /// Fixed size array of tracked ranges.
+ /// </summary>
+ public SignalHandlerRangeArray Ranges;
+ }
+
+ static class NativeSignalHandler
+ {
+ private static readonly IntPtr _handlerConfig;
+ private static IntPtr _signalHandlerPtr;
+
+ private static MemoryBlock _codeBlock;
+
+ private static readonly object _lock = new();
+ private static bool _initialized;
+
+ static NativeSignalHandler()
+ {
+ _handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
+ ref SignalHandlerConfig config = ref GetConfigRef();
+
+ config = new SignalHandlerConfig();
+ }
+
+ public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
+ {
+ if (_initialized)
+ {
+ return;
+ }
+
+ lock (_lock)
+ {
+ if (_initialized)
+ {
+ return;
+ }
+
+ int rangeStructSize = Unsafe.SizeOf<SignalHandlerRange>();
+
+ ref SignalHandlerConfig config = ref GetConfigRef();
+
+ if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+ {
+ _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateUnixSignalHandler(_handlerConfig, rangeStructSize, pageSize));
+
+ if (customSignalHandlerFactory != null)
+ {
+ _signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr);
+ }
+
+ var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
+
+ config.UnixOldSigaction = (nuint)(ulong)old.sa_handler;
+ config.UnixOldSigaction3Arg = old.sa_flags & 4;
+ }
+ else
+ {
+ config.StructAddressOffset = 40; // ExceptionInformation1
+ config.StructWriteOffset = 32; // ExceptionInformation0
+
+ _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateWindowsSignalHandler(_handlerConfig, rangeStructSize, pageSize));
+
+ if (customSignalHandlerFactory != null)
+ {
+ _signalHandlerPtr = customSignalHandlerFactory(IntPtr.Zero, _signalHandlerPtr);
+ }
+
+ WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
+ }
+
+ _initialized = true;
+ }
+ }
+
+ private static IntPtr MapCode(ReadOnlySpan<byte> code)
+ {
+ Debug.Assert(_codeBlock == null);
+
+ ulong codeSizeAligned = BitUtils.AlignUp((ulong)code.Length, MemoryBlock.GetPageSize());
+
+ _codeBlock = new MemoryBlock(codeSizeAligned);
+ _codeBlock.Write(0, code);
+ _codeBlock.Reprotect(0, codeSizeAligned, MemoryPermission.ReadAndExecute);
+
+ return _codeBlock.Pointer;
+ }
+
+ private static unsafe ref SignalHandlerConfig GetConfigRef()
+ {
+ return ref Unsafe.AsRef<SignalHandlerConfig>((void*)_handlerConfig);
+ }
+
+ public static bool AddTrackedRegion(nuint address, nuint endAddress, IntPtr action)
+ {
+ Span<SignalHandlerRange> ranges = GetConfigRef().Ranges;
+
+ for (int i = 0; i < NativeSignalHandlerGenerator.MaxTrackedRanges; i++)
+ {
+ if (ranges[i].IsActive == 0)
+ {
+ ranges[i].RangeAddress = address;
+ ranges[i].RangeEndAddress = endAddress;
+ ranges[i].ActionPointer = action;
+ ranges[i].IsActive = 1;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool RemoveTrackedRegion(nuint address)
+ {
+ Span<SignalHandlerRange> ranges = GetConfigRef().Ranges;
+
+ for (int i = 0; i < NativeSignalHandlerGenerator.MaxTrackedRanges; i++)
+ {
+ if (ranges[i].IsActive == 1 && ranges[i].RangeAddress == address)
+ {
+ ranges[i].IsActive = 0;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs
new file mode 100644
index 00000000..e88a6c0f
--- /dev/null
+++ b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Signal
+{
+ static partial class UnixSignalHandlerRegistration
+ {
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public unsafe struct SigSet
+ {
+ fixed long sa_mask[16];
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SigAction
+ {
+ public IntPtr sa_handler;
+ public SigSet sa_mask;
+ public int sa_flags;
+ public IntPtr sa_restorer;
+ }
+
+ private const int SIGSEGV = 11;
+ private const int SIGBUS = 10;
+ private const int SA_SIGINFO = 0x00000004;
+
+ [LibraryImport("libc", SetLastError = true)]
+ private static partial int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction);
+
+ [LibraryImport("libc", SetLastError = true)]
+ private static partial int sigaction(int signum, IntPtr sigAction, out SigAction oldAction);
+
+ [LibraryImport("libc", SetLastError = true)]
+ private static partial int sigemptyset(ref SigSet set);
+
+ public static SigAction GetSegfaultExceptionHandler()
+ {
+ int result = sigaction(SIGSEGV, IntPtr.Zero, out SigAction old);
+
+ if (result != 0)
+ {
+ throw new InvalidOperationException($"Could not get SIGSEGV sigaction. Error: {result}");
+ }
+
+ return old;
+ }
+
+ public static SigAction RegisterExceptionHandler(IntPtr action)
+ {
+ SigAction sig = new()
+ {
+ sa_handler = action,
+ sa_flags = SA_SIGINFO,
+ };
+
+ sigemptyset(ref sig.sa_mask);
+
+ int result = sigaction(SIGSEGV, ref sig, out SigAction old);
+
+ if (result != 0)
+ {
+ throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}");
+ }
+
+ if (OperatingSystem.IsMacOS())
+ {
+ result = sigaction(SIGBUS, ref sig, out _);
+
+ if (result != 0)
+ {
+ throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}");
+ }
+ }
+
+ return old;
+ }
+
+ public static bool RestoreExceptionHandler(SigAction oldAction)
+ {
+ return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0);
+ }
+ }
+}
diff --git a/src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs b/src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs
new file mode 100644
index 00000000..1fbce0f7
--- /dev/null
+++ b/src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu.Signal
+{
+ static partial class WindowsSignalHandlerRegistration
+ {
+ [LibraryImport("kernel32.dll")]
+ private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler);
+
+ [LibraryImport("kernel32.dll")]
+ private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle);
+
+ public static IntPtr RegisterExceptionHandler(IntPtr action)
+ {
+ return AddVectoredExceptionHandler(1, action);
+ }
+
+ public static bool RemoveExceptionHandler(IntPtr handle)
+ {
+ return RemoveVectoredExceptionHandler(handle) != 0;
+ }
+ }
+}