diff options
Diffstat (limited to 'src/Ryujinx.Cpu/Signal')
-rw-r--r-- | src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs | 179 | ||||
-rw-r--r-- | src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs | 83 | ||||
-rw-r--r-- | src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs | 24 |
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; + } + } +} |