From 39a3ba83299c55f27c94e4eb91b2d93f06c42a7c Mon Sep 17 00:00:00 2001 From: merry Date: Tue, 8 Feb 2022 18:57:32 +0000 Subject: [PATCH 01/65] IDebuggableProcess: Create and expose interface to enable implementation of debugging tools # Conflicts: # src/ARMeilleure/State/DebugState.cs # src/ARMeilleure/State/ExecutionContext.cs TODO: Fix comments. I also tried implementing AppleHV primitives here (it's actually broken). --- src/ARMeilleure/State/DebugState.cs | 9 ++++ src/ARMeilleure/State/ExecutionContext.cs | 38 ++++++++++++++ src/Ryujinx.Cpu/AppleHv/DebugState.cs | 9 ++++ src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 9 ++++ .../AppleHv/HvExecutionContextShadow.cs | 22 ++++++++ .../AppleHv/HvExecutionContextVcpu.cs | 50 ++++++++++++++++++- .../AppleHv/IHvExecutionContext.cs | 8 +++ src/Ryujinx.Cpu/IExecutionContext.cs | 5 ++ src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 9 ++++ .../Debugger/IDebuggableProcess.cs | 12 +++++ src/Ryujinx.HLE/HOS/Horizon.cs | 8 +++ .../HOS/Kernel/Process/KProcess.cs | 29 ++++++++++- .../Kernel/Process/ProcessExecutionContext.cs | 13 +++++ 13 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 src/ARMeilleure/State/DebugState.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/DebugState.cs create mode 100644 src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs diff --git a/src/ARMeilleure/State/DebugState.cs b/src/ARMeilleure/State/DebugState.cs new file mode 100644 index 000000000..dd05ddb61 --- /dev/null +++ b/src/ARMeilleure/State/DebugState.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.State +{ + enum DebugState + { + Running, + Stopping, + Stopped, + } +} diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs index ce10a591c..2d57df725 100644 --- a/src/ARMeilleure/State/ExecutionContext.cs +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -1,5 +1,8 @@ using ARMeilleure.Memory; using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; namespace ARMeilleure.State { @@ -96,6 +99,14 @@ namespace ARMeilleure.State private readonly ExceptionCallback _supervisorCallback; private readonly ExceptionCallback _undefinedCallback; + internal int _debugState = (int)DebugState.Running; + internal int _shouldStep = 0; + internal ManualResetEvent _debugHalt = new ManualResetEvent(true); + internal Barrier _stepBarrier = new Barrier(2); + + // This is only valid while debugging is enabled. + public ulong DebugPc; + public ExecutionContext( IJitMemoryAllocator allocator, ICounter counter, @@ -145,6 +156,33 @@ namespace ARMeilleure.State _interrupted = true; } + public void DebugStop() + { + _debugHalt.Reset(); + Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, (int)DebugState.Running); + } + + public bool DebugStep() + { + if (_debugState != (int)DebugState.Stopped) + { + return false; + } + + _shouldStep = 1; + _debugHalt.Set(); + _stepBarrier.SignalAndWait(); + _debugHalt.Reset(); + _stepBarrier.SignalAndWait(); + + return true; + } + + public void DebugContinue() + { + _debugHalt.Set(); + } + internal void OnBreak(ulong address, int imm) { _breakCallback?.Invoke(this, address, imm); diff --git a/src/Ryujinx.Cpu/AppleHv/DebugState.cs b/src/Ryujinx.Cpu/AppleHv/DebugState.cs new file mode 100644 index 000000000..5a5f2ba03 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/DebugState.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Cpu.AppleHv +{ + enum DebugState + { + Running, + Stopping, + Stopped, + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index fc2b76d15..8ce358898 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -127,6 +127,15 @@ namespace Ryujinx.Cpu.AppleHv return Interlocked.Exchange(ref _interruptRequested, 0) != 0; } + /// + public void DebugStop() => _impl.DebugStop(); + + /// + public bool DebugStep() => _impl.DebugStep(); + + /// + public void DebugContinue() => _impl.DebugContinue(); + /// public void StopRunning() { diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs index 6ce8e1800..b4e96b561 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs @@ -46,5 +46,27 @@ namespace Ryujinx.Cpu.AppleHv { _v[index] = value; } + + public void RequestInterrupt() + { + } + + public void DebugStop() + { + } + + public bool DebugStep() + { + return false; + } + + public void DebugContinue() + { + } + + public bool GetAndClearInterruptRequested() + { + return false; + } } } diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index bb232940d..a55a2aba2 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -2,11 +2,10 @@ using ARMeilleure.State; using Ryujinx.Memory; using System; using System.Runtime.InteropServices; -using System.Runtime.Versioning; +using System.Threading; namespace Ryujinx.Cpu.AppleHv { - [SupportedOSPlatform("macos")] class HvExecutionContextVcpu : IHvExecutionContext { private static readonly MemoryBlock _setSimdFpRegFuncMem; @@ -14,6 +13,14 @@ namespace Ryujinx.Cpu.AppleHv private static readonly SetSimdFpReg _setSimdFpReg; private static readonly IntPtr _setSimdFpRegNativePtr; + internal int _debugState = (int)DebugState.Running; + internal int _shouldStep = 0; + internal ManualResetEvent _debugHalt = new ManualResetEvent(true); + internal Barrier _stepBarrier = new Barrier(2); + + // This is only valid while debugging is enabled. + public ulong DebugPc; + static HvExecutionContextVcpu() { // .NET does not support passing vectors by value, so we need to pass a pointer and use a native @@ -136,6 +143,7 @@ namespace Ryujinx.Cpu.AppleHv } private readonly ulong _vcpu; + private int _interruptRequested; public HvExecutionContextVcpu(ulong vcpu) { @@ -181,8 +189,46 @@ namespace Ryujinx.Cpu.AppleHv public void RequestInterrupt() { + if (Interlocked.Exchange(ref _interruptRequested, 1) == 0) + { + ulong vcpu = _vcpu; + HvApi.hv_vcpus_exit(ref vcpu, 1); + } + } + + public void DebugStop() + { + _debugHalt.Reset(); + Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, (int)DebugState.Running); ulong vcpu = _vcpu; HvApi.hv_vcpus_exit(ref vcpu, 1); } + + public bool DebugStep() + { + if (_debugState != (int)DebugState.Stopped) + { + return false; + } + + _shouldStep = 1; + _debugHalt.Set(); + _stepBarrier.SignalAndWait(); + _debugHalt.Reset(); + _stepBarrier.SignalAndWait(); + + return true; + } + + public void DebugContinue() + { + _debugHalt.Set(); + HvApi.hv_vcpu_run(_vcpu); + } + + public bool GetAndClearInterruptRequested() + { + return Interlocked.Exchange(ref _interruptRequested, 0) != 0; + } } } diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs index 54b73acc6..b385cd78e 100644 --- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs @@ -39,5 +39,13 @@ namespace Ryujinx.Cpu.AppleHv SetV(i, context.GetV(i)); } } + + void RequestInterrupt(); + bool GetAndClearInterruptRequested(); + + // TODO: comments + void DebugStop(); + bool DebugStep(); + void DebugContinue(); } } diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index c38210800..ad07a2766 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -108,5 +108,10 @@ namespace Ryujinx.Cpu /// If you only need to pause the thread temporarily, use instead. /// void StopRunning(); + + // TODO: comments + void DebugStop(); + bool DebugStep(); + void DebugContinue(); } } diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs index f15486e68..a3224c366 100644 --- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs @@ -109,6 +109,15 @@ namespace Ryujinx.Cpu.Jit _impl.RequestInterrupt(); } + /// + public void DebugStop() => _impl.DebugStop(); + + /// + public bool DebugStep() => _impl.DebugStep(); + + /// + public void DebugContinue() => _impl.DebugContinue(); + /// public void StopRunning() { diff --git a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs new file mode 100644 index 000000000..c86d4acd5 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs @@ -0,0 +1,12 @@ +using Ryujinx.Memory; + +namespace Ryujinx.HLE.Debugger +{ + public interface IDebuggableProcess + { + public void DebugStopAllThreads(); + public ulong[] DebugGetThreadUids(); + public Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid); + public IVirtualMemoryManager CpuMemory { get; } + } +} diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 64b08e309..69086c0bc 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -473,5 +473,13 @@ namespace Ryujinx.HLE.HOS } IsPaused = pause; } + + public Debugger.IDebuggableProcess DebugGetApplicationProcess() + { + lock (KernelContext.Processes) + { + return KernelContext.Processes.Values.Where(x => x.IsApplication).First(); + } + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 422f03c64..0c0004be5 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -14,7 +14,7 @@ using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Process { - class KProcess : KSynchronizationObject + class KProcess : KSynchronizationObject, Debugger.IDebuggableProcess { public const uint KernelVersionMajor = 10; public const uint KernelVersionMinor = 4; @@ -1175,5 +1175,32 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { return Capabilities.IsSvcPermitted(svcId); } + + public void DebugStopAllThreads() + { + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + thread.Context.DebugStop(); + } + } + } + + public ulong[] DebugGetThreadUids() + { + lock (_threadingLock) + { + return _threads.Select(x => x.ThreadUid).ToArray(); + } + } + + public Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid) + { + lock (_threadingLock) + { + return _threads.Where(x => x.ThreadUid == threadUid).FirstOrDefault()?.Context; + } + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs index b8118fbb4..6050f88f2 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs @@ -31,6 +31,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { } + public void DebugStop() + { + } + + public bool DebugStep() + { + return false; + } + + public void DebugContinue() + { + } + public void StopRunning() { Running = false; From 569d01823ed846f51e2669840055ccc56a725eb1 Mon Sep 17 00:00:00 2001 From: merry Date: Tue, 8 Feb 2022 19:00:47 +0000 Subject: [PATCH 02/65] ARMeilleure: Add debuggable CPU loop # Conflicts: # src/ARMeilleure/Optimizations.cs # src/ARMeilleure/Translation/ArmEmitterContext.cs --- src/ARMeilleure/Optimizations.cs | 1 + src/ARMeilleure/Translation/Translator.cs | 35 +++++++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs index 8fe478e47..3cc8acc8d 100644 --- a/src/ARMeilleure/Optimizations.cs +++ b/src/ARMeilleure/Optimizations.cs @@ -9,6 +9,7 @@ namespace ARMeilleure public static bool AllowLcqInFunctionTable { get; set; } = true; public static bool UseUnmanagedDispatchLoop { get; set; } = true; + public static bool EnableDebugging { get; set; } = false; public static bool UseAdvSimdIfAvailable { get; set; } = true; public static bool UseArm64AesIfAvailable { get; set; } = true; diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 014b12035..410444c78 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -140,7 +140,33 @@ namespace ARMeilleure.Translation NativeInterface.RegisterThread(context, Memory, this); - if (Optimizations.UseUnmanagedDispatchLoop) + if (Optimizations.EnableDebugging) + { + context.DebugPc = address; + do + { + context.DebugPc = ExecuteSingle(context, context.DebugPc); + + while (context._debugState != (int)DebugState.Running) + { + Interlocked.CompareExchange(ref context._debugState, (int)DebugState.Stopped, (int)DebugState.Stopping); + context._debugHalt.WaitOne(); + if (Interlocked.CompareExchange(ref context._shouldStep, 0, 1) == 1) + { + context.DebugPc = Step(context, context.DebugPc); + + context._stepBarrier.SignalAndWait(); + context._stepBarrier.SignalAndWait(); + } + else + { + Interlocked.CompareExchange(ref context._debugState, (int)DebugState.Running, (int)DebugState.Stopped); + } + } + } + while (context.Running && context.DebugPc != 0); + } + else if (Optimizations.UseUnmanagedDispatchLoop) { Stubs.DispatchLoop(context.NativeContextPtr, address); } @@ -249,7 +275,7 @@ namespace ARMeilleure.Translation Stubs, address, highCq, - _ptc.State != PtcState.Disabled, + _ptc.State != PtcState.Disabled && !Optimizations.EnableDebugging, mode: Aarch32Mode.User); Logger.StartPass(PassName.Decoding); @@ -382,9 +408,8 @@ namespace ARMeilleure.Translation if (block.Exit) { - // Left option here as it may be useful if we need to return to managed rather than tail call in - // future. (eg. for debug) - bool useReturns = false; + // Return to managed rather than tail call. + bool useReturns = Optimizations.EnableDebugging; InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns); } From 20bdea45fa7bd069537f094a8f8628e42ec537ad Mon Sep 17 00:00:00 2001 From: merry Date: Tue, 8 Feb 2022 19:01:19 +0000 Subject: [PATCH 03/65] Logging: Add GdbStub LogClass # Conflicts: # src/Ryujinx.Common/Logging/LogClass.cs --- src/Ryujinx.Common/Logging/LogClass.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs index 1b404a06a..0662bf0e4 100644 --- a/src/Ryujinx.Common/Logging/LogClass.cs +++ b/src/Ryujinx.Common/Logging/LogClass.cs @@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging Cpu, Emulation, FFmpeg, + GdbStub, Font, Gpu, Hid, From 2a17f1314deca952fa800dfcd56fddb483ffea36 Mon Sep 17 00:00:00 2001 From: merry Date: Tue, 8 Feb 2022 19:49:54 +0000 Subject: [PATCH 04/65] Configuration: Add GDB stub related configuration # Conflicts: # Ryujinx/Configuration/ConfigurationState.cs # src/Ryujinx.HLE/HLEConfiguration.cs # src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs # src/Ryujinx/Ui/MainWindow.cs # src/Ryujinx/Ui/Windows/SettingsWindow.cs # src/Ryujinx/Ui/Windows/SettingsWindow.glade --- src/Ryujinx.Gtk3/UI/MainWindow.cs | 22 ++- src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs | 10 +- .../UI/Windows/SettingsWindow.glade | 137 ++++++++++++++++++ src/Ryujinx.HLE/HLEConfiguration.cs | 16 +- .../Configuration/ConfigurationFileFormat.cs | 12 +- .../Configuration/ConfigurationState.cs | 45 ++++++ 6 files changed, 238 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs index 66c0afae0..fe3414fa1 100644 --- a/src/Ryujinx.Gtk3/UI/MainWindow.cs +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -677,7 +677,9 @@ namespace Ryujinx.UI ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.UseHypervisor, ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, - ConfigurationState.Instance.Multiplayer.Mode); + ConfigurationState.Instance.Multiplayer.Mode, + ConfigurationState.Instance.Debug.EnableGdbStub, + ConfigurationState.Instance.Debug.GdbStubPort); _emulationContext = new HLE.Switch(configuration); } @@ -790,6 +792,24 @@ namespace Ryujinx.UI shadersDumpWarningDialog.Dispose(); } + + if (ConfigurationState.Instance.Debug.EnableGdbStub.Value) + { + MessageDialog gdbStubWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) + { + Title = "Ryujinx - Warning", + Text = "You have the GDB stub enabled, which is designed to be used by developers only.", + SecondaryText = "For optimal performance, it's recommended to disable the GDB stub. Would you like to disable the GDB stub now?" + }; + + if (gdbStubWarningDialog.Run() == (int)ResponseType.Yes) + { + ConfigurationState.Instance.Debug.EnableGdbStub.Value = false; + SaveConfig(); + } + + gdbStubWarningDialog.Dispose(); + } } private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle) diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs index dc467c0f2..a71d1428e 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs @@ -117,7 +117,8 @@ namespace Ryujinx.UI.Windows [GUI] ToggleButton _configureController7; [GUI] ToggleButton _configureController8; [GUI] ToggleButton _configureControllerH; - + [GUI] ToggleButton _gdbStubToggle; + [GUI] Adjustment _gdbStubPortSpinAdjustment; #pragma warning restore CS0649, IDE0044 public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } @@ -316,6 +317,11 @@ namespace Ryujinx.UI.Windows _custThemeToggle.Click(); } + if (ConfigurationState.Instance.Debug.EnableGdbStub) + { + _gdbStubToggle.Click(); + } + // Custom EntryCompletion Columns. If added to glade, need to override more signals ListStore tzList = new(typeof(string), typeof(string), typeof(string)); _systemTimeZoneCompletion.Model = tzList; @@ -375,6 +381,8 @@ namespace Ryujinx.UI.Windows _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; _systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset; + _gdbStubPortSpinAdjustment.Value = ConfigurationState.Instance.Debug.GdbStubPort; + _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); _gameDirsBoxStore = new ListStore(typeof(string)); _gameDirsBox.Model = _gameDirsBoxStore; diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade index f0dbd6b63..22ccfe861 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade @@ -46,6 +46,12 @@ True True + + 1 + 65535 + 1 + 5 + False Ryujinx - Settings @@ -3146,6 +3152,137 @@ False + + + True + False + 5 + 10 + 5 + vertical + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + Debug (WARNING: For Developer Use Only) + + + + + + False + True + 0 + + + + + True + False + start + 10 + 10 + vertical + + + Enable GDB Stub + True + True + False + Enables or disables GDB stub (for developer use only) + start + 5 + 5 + True + + + False + True + 0 + + + + + True + False + + + True + False + Specifies which TCP port for the GDB stub to listen on. Possible values are 1-65535. + GDB Stub Port + + + False + True + 5 + 0 + + + + + True + True + Specifies which TCP port for the GDB stub to listen on. Possible values are 1-65535. + 55555 + number + _gdbStubPortSpinAdjustment + True + + + True + True + 1 + + + + + False + True + 5 + 9 + + + + + True + True + 1 + + + + + False + True + 5 + 0 + + + + + 6 + + + + + True + False + Debug + + + 6 + False + + diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index 955fee4b5..f6026e7b8 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -169,6 +169,16 @@ namespace Ryujinx.HLE /// public Action RefreshInputConfig { internal get; set; } + /// + /// Enables gdbstub to allow for debugging of the guest . + /// + public bool EnableGdbStub { get; internal set; } + + /// + /// A TCP port to use to expose a gdbstub for a debugger to connect to. + /// + public ushort GdbStubPort { get; internal set; } + public HLEConfiguration(VirtualFileSystem virtualFileSystem, LibHacHorizonManager libHacHorizonManager, ContentManager contentManager, @@ -194,7 +204,9 @@ namespace Ryujinx.HLE float audioVolume, bool useHypervisor, string multiplayerLanInterfaceId, - MultiplayerMode multiplayerMode) + MultiplayerMode multiplayerMode, + bool enableGdbStub, + ushort gdbStubPort) { VirtualFileSystem = virtualFileSystem; LibHacHorizonManager = libHacHorizonManager; @@ -222,6 +234,8 @@ namespace Ryujinx.HLE UseHypervisor = useHypervisor; MultiplayerLanInterfaceId = multiplayerLanInterfaceId; MultiplayerMode = multiplayerMode; + EnableGdbStub = enableGdbStub; + GdbStubPort = gdbStubPort; } } } diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index af3ad0a1d..d041bf63e 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -386,6 +386,16 @@ namespace Ryujinx.UI.Common.Configuration /// public bool UseHypervisor { get; set; } + /// + /// Enables or disables the GDB stub + /// + public bool EnableGdbStub { get; set; } + + /// + /// Which TCP port should the GDB stub listen on + /// + public ushort GdbStubPort { get; set; } + /// /// Loads a configuration file from disk /// @@ -416,4 +426,4 @@ namespace Ryujinx.UI.Common.Configuration JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); } } -} +} \ No newline at end of file diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 8420dc5d9..d5ee41e64 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -577,6 +577,30 @@ namespace Ryujinx.UI.Common.Configuration } } + /// + /// Debug configuration section + /// + public class DebugSection + { + /// + /// Enables or disables the GDB stub + /// + public ReactiveObject EnableGdbStub { get; private set; } + + /// + /// Which TCP port should the GDB stub listen on + /// + public ReactiveObject GdbStubPort { get; private set; } + + public DebugSection() + { + EnableGdbStub = new ReactiveObject(); + EnableGdbStub.Event += static (sender, e) => LogValueChange(e, nameof(EnableGdbStub)); + GdbStubPort = new ReactiveObject(); + GdbStubPort.Event += static (sender, e) => LogValueChange(e, nameof(GdbStubPort)); + } + } + /// /// The default configuration instance /// @@ -607,6 +631,11 @@ namespace Ryujinx.UI.Common.Configuration /// public HidSection Hid { get; private set; } + /// + /// The Debug + /// + public DebugSection Debug { get; private set; } + /// /// The Multiplayer section /// @@ -649,6 +678,7 @@ namespace Ryujinx.UI.Common.Configuration System = new SystemSection(); Graphics = new GraphicsSection(); Hid = new HidSection(); + Debug = new DebugSection(); Multiplayer = new MultiplayerSection(); EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); @@ -766,6 +796,8 @@ namespace Ryujinx.UI.Common.Configuration PreferredGpu = Graphics.PreferredGpu, MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId, MultiplayerMode = Multiplayer.Mode, + EnableGdbStub = Debug.EnableGdbStub, + GdbStubPort = Debug.GdbStubPort, }; return configurationFile; @@ -923,6 +955,8 @@ namespace Ryujinx.UI.Common.Configuration }, }, }; + Debug.EnableGdbStub.Value = false; + Debug.GdbStubPort.Value = 55555; } public void Load(ConfigurationFileFormat configurationFileFormat, string configurationFilePath) @@ -1437,6 +1471,15 @@ namespace Ryujinx.UI.Common.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 48) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 38."); + + configurationFileFormat.EnableGdbStub = false; + configurationFileFormat.GdbStubPort = 55555; + + configurationFileUpdated = true; + } if (configurationFileFormat.Version < 48) { @@ -1564,6 +1607,8 @@ namespace Ryujinx.UI.Common.Configuration Hid.EnableMouse.Value = configurationFileFormat.EnableMouse; Hid.Hotkeys.Value = configurationFileFormat.Hotkeys; Hid.InputConfig.Value = configurationFileFormat.InputConfig; + Debug.EnableGdbStub.Value = configurationFileFormat.EnableGdbStub; + Debug.GdbStubPort.Value = configurationFileFormat.GdbStubPort; if (Hid.InputConfig.Value == null) { From 6edc00ec9c11bff40a66d800a80769ce769c72a3 Mon Sep 17 00:00:00 2001 From: merry Date: Tue, 8 Feb 2022 19:50:26 +0000 Subject: [PATCH 05/65] Debugger: Initial implementation # Conflicts: # src/Ryujinx.HLE/Debugger/Debugger.cs # src/Ryujinx.HLE/Debugger/GdbSignal.cs # src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs # src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs # src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs # src/Ryujinx.HLE/Debugger/Message/IMessage.cs # src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs # src/Ryujinx.HLE/Debugger/StringStream.cs # src/Ryujinx.HLE/Switch.cs --- src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 7 + .../AppleHv/HvExecutionContextShadow.cs | 2 + .../AppleHv/HvExecutionContextVcpu.cs | 2 +- .../AppleHv/IHvExecutionContext.cs | 2 + src/Ryujinx.Cpu/IExecutionContext.cs | 2 + src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 7 + src/Ryujinx.HLE/Debugger/Debugger.cs | 410 ++++++++++++++++++ src/Ryujinx.HLE/Debugger/GdbSignal.cs | 15 + .../Debugger/Message/AbortMessage.cs | 6 + .../Debugger/Message/BreakInMessage.cs | 6 + .../Debugger/Message/CommandMessage.cs | 12 + src/Ryujinx.HLE/Debugger/Message/IMessage.cs | 6 + .../Debugger/Message/SendNackMessage.cs | 6 + src/Ryujinx.HLE/Debugger/StringStream.cs | 68 +++ .../Kernel/Process/ProcessExecutionContext.cs | 2 + src/Ryujinx.HLE/Switch.cs | 3 + src/Ryujinx.Headless.SDL2/Options.cs | 8 + src/Ryujinx.Headless.SDL2/Program.cs | 4 +- src/Ryujinx/AppHost.cs | 4 +- 19 files changed, 569 insertions(+), 3 deletions(-) create mode 100644 src/Ryujinx.HLE/Debugger/Debugger.cs create mode 100644 src/Ryujinx.HLE/Debugger/GdbSignal.cs create mode 100644 src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs create mode 100644 src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs create mode 100644 src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs create mode 100644 src/Ryujinx.HLE/Debugger/Message/IMessage.cs create mode 100644 src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs create mode 100644 src/Ryujinx.HLE/Debugger/StringStream.cs diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index 8ce358898..63b4b2edc 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -136,6 +136,13 @@ namespace Ryujinx.Cpu.AppleHv /// public void DebugContinue() => _impl.DebugContinue(); + /// + public ulong DebugPc + { + get => _impl.DebugPc; + set => _impl.DebugPc = value; + } + /// public void StopRunning() { diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs index b4e96b561..b9cc08fb2 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs @@ -21,6 +21,8 @@ namespace Ryujinx.Cpu.AppleHv private readonly ulong[] _x; private readonly V128[] _v; + public ulong DebugPc { get; set; } + public HvExecutionContextShadow() { _x = new ulong[32]; diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index a55a2aba2..c581698b9 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -19,7 +19,7 @@ namespace Ryujinx.Cpu.AppleHv internal Barrier _stepBarrier = new Barrier(2); // This is only valid while debugging is enabled. - public ulong DebugPc; + public ulong DebugPc { get; set; } static HvExecutionContextVcpu() { diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs index b385cd78e..f12ba9dfc 100644 --- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs @@ -47,5 +47,7 @@ namespace Ryujinx.Cpu.AppleHv void DebugStop(); bool DebugStep(); void DebugContinue(); + + ulong DebugPc { get; set; } } } diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index ad07a2766..927ba120d 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -113,5 +113,7 @@ namespace Ryujinx.Cpu void DebugStop(); bool DebugStep(); void DebugContinue(); + + ulong DebugPc { get; set; } } } diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs index a3224c366..fda9e54cb 100644 --- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs @@ -118,6 +118,13 @@ namespace Ryujinx.Cpu.Jit /// public void DebugContinue() => _impl.DebugContinue(); + /// + public ulong DebugPc + { + get => _impl.DebugPc; + set => _impl.DebugPc = value; + } + /// public void StopRunning() { diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs new file mode 100644 index 000000000..534938e66 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -0,0 +1,410 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.Debugger +{ + public class Debugger : IDisposable + { + internal Switch Device { get; private set; } + + public ushort GdbStubPort { get; private set; } + + private TcpListener ListenerSocket; + private Socket ClientSocket = null; + private NetworkStream ReadStream = null; + private NetworkStream WriteStream = null; + private BlockingCollection Messages = new BlockingCollection(1); + private Thread SocketThread; + private Thread HandlerThread; + + private ulong cThread; + private ulong gThread; + + public Debugger(Switch device, ushort port) + { + Device = device; + GdbStubPort = port; + + ARMeilleure.Optimizations.EnableDebugging = true; + + SocketThread = new Thread(SocketReaderThreadMain); + HandlerThread = new Thread(HandlerThreadMain); + SocketThread.Start(); + HandlerThread.Start(); + } + + private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads(); + private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids(); + private Ryujinx.Cpu.IExecutionContext GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThreadContext(threadUid); + private Ryujinx.Cpu.IExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); + private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; + + const int GdbRegisterCount = 34; + + private int GdbRegisterHexSize(int gdbRegId) + { + switch (gdbRegId) + { + case >= 0 and <= 31: + return 16; + case 32: + return 16; + case 33: + return 8; + default: + throw new ArgumentException(); + } + } + + private string GdbReadRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId) + { + switch (gdbRegId) + { + case >= 0 and <= 31: + return $"{state.GetX(gdbRegId):x16}"; + case 32: + return $"{state.DebugPc:x16}"; + case 33: + return $"{state.Pstate:x8}"; + default: + throw new ArgumentException(); + } + } + + private void GdbWriteRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId, ulong value) + { + switch (gdbRegId) + { + case >= 0 and <= 31: + state.SetX(gdbRegId, value); + return; + case 32: + state.DebugPc = value; + return; + case 33: + state.Pstate = (uint)value; + return; + default: + throw new ArgumentException(); + } + } + + private void HandlerThreadMain() + { + while (true) + { + switch (Messages.Take()) + { + case AbortMessage _: + return; + + case BreakInMessage _: + Logger.Notice.Print(LogClass.GdbStub, "Break-in requested"); + // TODO + break; + + case SendNackMessage _: + WriteStream.WriteByte((byte)'-'); + break; + + case CommandMessage { Command: var cmd }: + Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}"); + WriteStream.WriteByte((byte)'+'); + ProcessCommand(cmd); + break; + } + } + } + + private void ProcessCommand(string cmd) + { + StringStream ss = new StringStream(cmd); + + switch (ss.ReadChar()) + { + case '!': + if (!ss.IsEmpty()) + { + goto default; + } + // Enable extended mode + Reply("OK"); + break; + case '?': + if (!ss.IsEmpty()) + { + goto default; + } + CommandQuery(); + break; + case 'c': + CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex()); + break; + case 'D': + if (!ss.IsEmpty()) + { + goto default; + } + CommandDetach(); + break; + case 'g': + if (!ss.IsEmpty()) + { + goto default; + } + CommandReadGeneralRegisters(); + break; + case 'G': + CommandWriteGeneralRegisters(ss); + break; + case 'H': + { + char op = ss.ReadChar(); + ulong threadId = ss.ReadRemainingAsHex(); + CommandSetThread(op, threadId); + break; + } + case 'k': + Logger.Notice.Print(LogClass.GdbStub, "Kill request received"); + Reply(""); + break; + case 'm': + { + ulong addr = ss.ReadUntilAsHex(','); + ulong len = ss.ReadRemainingAsHex(); + CommandReadMemory(addr, len); + break; + } + case 'M': + { + ulong addr = ss.ReadUntilAsHex(','); + ulong len = ss.ReadUntilAsHex(':'); + CommandWriteMemory(addr, len, ss); + break; + } + case 'p': + { + ulong gdbRegId = ss.ReadRemainingAsHex(); + CommandReadGeneralRegister((int)gdbRegId); + break; + } + case 'P': + { + ulong gdbRegId = ss.ReadUntilAsHex('='); + ulong value = ss.ReadRemainingAsHex(); + CommandWriteGeneralRegister((int)gdbRegId, value); + break; + } + default: + Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); + Reply(""); + break; + } + } + + void CommandQuery() + { + // GDB is performing initial contact. Stop everything. + HaltApplication(); + gThread = cThread = GetThreadIds().First(); + Reply($"T05thread:{cThread:x}"); + } + + void CommandContinue(ulong? newPc) + { + if (newPc.HasValue) + { + GetThread(cThread).DebugPc = newPc.Value; + } + + foreach (var thread in GetThreads()) + { + thread.DebugContinue(); + } + } + + void CommandDetach() + { + // TODO: Remove all breakpoints + CommandContinue(null); + } + + void CommandReadGeneralRegisters() + { + var ctx = GetThread(gThread); + string registers = ""; + for (int i = 0; i < GdbRegisterCount; i++) + { + registers += GdbReadRegister(ctx, i); + } + Reply(registers); + } + + void CommandWriteGeneralRegisters(StringStream ss) + { + var ctx = GetThread(gThread); + for (int i = 0; i < GdbRegisterCount; i++) + { + GdbWriteRegister(ctx, i, ss.ReadLengthAsHex(GdbRegisterHexSize(i))); + } + Reply(ss.IsEmpty() ? "OK" : "E99"); + } + + void CommandSetThread(char op, ulong threadId) + { + switch (op) + { + case 'c': + cThread = threadId; + Reply("OK"); + return; + case 'g': + gThread = threadId; + Reply("OK"); + return; + default: + Reply("E99"); + return; + } + } + + void CommandReadMemory(ulong addr, ulong len) + { + var data = new byte[len]; + GetMemory().Read(addr, data); + Reply(string.Join("", data.Select(x => $"{x:x2}"))); + } + + void CommandWriteMemory(ulong addr, ulong len, StringStream ss) + { + var data = new byte[len]; + for (ulong i = 0; i < len; i++) + { + data[i] = (byte)ss.ReadLengthAsHex(2); + } + GetMemory().Write(addr, data); + } + + void CommandReadGeneralRegister(int gdbRegId) + { + var ctx = GetThread(gThread); + Reply(GdbReadRegister(ctx, gdbRegId)); + } + + void CommandWriteGeneralRegister(int gdbRegId, ulong value) + { + var ctx = GetThread(gThread); + GdbWriteRegister(ctx, gdbRegId, value); + Reply("OK"); + } + + private void Reply(string cmd) + { + WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}")); + } + + private void SocketReaderThreadMain() + { + restartListen: + try + { + var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort); + ListenerSocket = new TcpListener(endpoint); + ListenerSocket.Start(); + Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client"); + + ClientSocket = ListenerSocket.AcceptSocket(); + ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read); + WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write); + Logger.Notice.Print(LogClass.GdbStub, "GDB client connected"); + + while (true) + { + switch (ReadStream.ReadByte()) + { + case -1: + goto eof; + case '+': + continue; + case '-': + Logger.Notice.Print(LogClass.GdbStub, "NACK received!"); + continue; + case '\x03': + Messages.Add(new BreakInMessage()); + break; + case '$': + string cmd = ""; + while (true) + { + int x = ReadStream.ReadByte(); + if (x == -1) + goto eof; + if (x == '#') + break; + cmd += (char)x; + } + + string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}"; + // Debug.Assert(checksum == $"{CalculateChecksum(cmd):x2}"); + + Messages.Add(new CommandMessage(cmd)); + break; + } + } + + eof: + Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); + goto restartListen; + } + catch (Exception) + { + Logger.Notice.Print(LogClass.GdbStub, "GDB stub socket closed"); + return; + } + } + + private byte CalculateChecksum(string cmd) + { + byte checksum = 0; + foreach (char x in cmd) + { + unchecked + { + checksum += (byte)x; + } + } + return checksum; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (HandlerThread.IsAlive) + { + Messages.Add(new AbortMessage()); + } + ListenerSocket.Stop(); + ClientSocket?.Shutdown(SocketShutdown.Both); + ClientSocket?.Close(); + ReadStream?.Close(); + WriteStream?.Close(); + SocketThread.Join(); + HandlerThread.Join(); + } + } + } +} diff --git a/src/Ryujinx.HLE/Debugger/GdbSignal.cs b/src/Ryujinx.HLE/Debugger/GdbSignal.cs new file mode 100644 index 000000000..687194ef3 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/GdbSignal.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.Debugger +{ + enum GdbSignal + { + Zero = 0, + Int = 2, + Quit = 3, + Trap = 5, + Abort = 6, + Alarm = 14, + IO = 23, + XCPU = 24, + Unknown = 143 + } +} diff --git a/src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs b/src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs new file mode 100644 index 000000000..2c0d6bb79 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.HLE.Debugger +{ + struct AbortMessage : IMessage + { + } +} diff --git a/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs b/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs new file mode 100644 index 000000000..24cfb0b4a --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.HLE.Debugger +{ + struct BreakInMessage : IMessage + { + } +} diff --git a/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs b/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs new file mode 100644 index 000000000..72320532f --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.Debugger +{ + struct CommandMessage : IMessage + { + public string Command; + + public CommandMessage(string cmd) + { + Command = cmd; + } + } +} diff --git a/src/Ryujinx.HLE/Debugger/Message/IMessage.cs b/src/Ryujinx.HLE/Debugger/Message/IMessage.cs new file mode 100644 index 000000000..d530d00d3 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Message/IMessage.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.HLE.Debugger +{ + interface IMessage + { + } +} diff --git a/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs b/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs new file mode 100644 index 000000000..f599ee91c --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.HLE.Debugger +{ + struct SendNackMessage : IMessage + { + } +} diff --git a/src/Ryujinx.HLE/Debugger/StringStream.cs b/src/Ryujinx.HLE/Debugger/StringStream.cs new file mode 100644 index 000000000..b654f6f54 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/StringStream.cs @@ -0,0 +1,68 @@ +using System.Globalization; + +namespace Ryujinx.HLE.Debugger +{ + class StringStream + { + private readonly string Data; + private int Position; + + public StringStream(string s) + { + Data = s; + } + + public char ReadChar() + { + return Data[Position++]; + } + + public string ReadUntil(char needle) + { + int needlePos = Data.IndexOf(needle, Position); + + if (needlePos == -1) + { + needlePos = Data.Length; + } + + string result = Data.Substring(Position, needlePos - Position); + Position = needlePos + 1; + return result; + } + + public string ReadLength(int len) + { + string result = Data.Substring(Position, len); + Position += len; + return result; + } + + public string ReadRemaining() + { + string result = Data.Substring(Position); + Position = Data.Length; + return result; + } + + public ulong ReadRemainingAsHex() + { + return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber); + } + + public ulong ReadUntilAsHex(char needle) + { + return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber); + } + + public ulong ReadLengthAsHex(int len) + { + return ulong.Parse(ReadLength(len), NumberStyles.HexNumber); + } + + public bool IsEmpty() + { + return Position >= Data.Length; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs index 6050f88f2..f9718280e 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs @@ -21,6 +21,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private readonly ulong[] _x = new ulong[32]; + public ulong DebugPc { get; set; } + public ulong GetX(int index) => _x[index]; public void SetX(int index, ulong value) => _x[index] = value; diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 9dfc69892..89bf7b975 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -26,6 +26,7 @@ namespace Ryujinx.HLE public Hid Hid { get; } public TamperMachine TamperMachine { get; } public IHostUIHandler UIHandler { get; } + public Debugger.Debugger Debugger { get; } public bool EnableDeviceVsync { get; set; } = true; @@ -53,6 +54,7 @@ namespace Ryujinx.HLE Statistics = new PerformanceStatistics(); Hid = new Hid(this, System.HidStorage); Processes = new ProcessLoader(this); + Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, configuration.GdbStubPort) : null; TamperMachine = new TamperMachine(); System.InitializeServices(); @@ -154,6 +156,7 @@ namespace Ryujinx.HLE AudioDeviceDriver.Dispose(); FileSystem.Dispose(); Memory.Dispose(); + Debugger.Dispose(); } } } diff --git a/src/Ryujinx.Headless.SDL2/Options.cs b/src/Ryujinx.Headless.SDL2/Options.cs index ea2063758..5891319c7 100644 --- a/src/Ryujinx.Headless.SDL2/Options.cs +++ b/src/Ryujinx.Headless.SDL2/Options.cs @@ -229,5 +229,13 @@ namespace Ryujinx.Headless.SDL2 [Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)] public string InputPath { get; set; } + + // Debugging + + [Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enable the GDB stub.")] + public bool EnableGdbStub { get; set; } + + [Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "GDB stub port.")] + public ushort GdbStubPort { get; set; } } } diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index 85aff6712..d6cf7d6d6 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -571,7 +571,9 @@ namespace Ryujinx.Headless.SDL2 options.AudioVolume, options.UseHypervisor ?? true, options.MultiplayerLanInterfaceId, - Common.Configuration.Multiplayer.MultiplayerMode.Disabled); + Common.Configuration.Multiplayer.MultiplayerMode.Disabled, + options.EnableGdbStub, + options.GdbStubPort); return new Switch(configuration); } diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 0db8ef414..65ef63926 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -872,7 +872,9 @@ namespace Ryujinx.Ava ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.UseHypervisor, ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, - ConfigurationState.Instance.Multiplayer.Mode); + ConfigurationState.Instance.Multiplayer.Mode, + ConfigurationState.Instance.Debug.EnableGdbStub.Value, + ConfigurationState.Instance.Debug.GdbStubPort.Value); Device = new Switch(configuration); } From deccf05e60ca4f70f23cfcfcbaa51337f5ffa698 Mon Sep 17 00:00:00 2001 From: merry Date: Tue, 8 Feb 2022 20:05:23 +0000 Subject: [PATCH 06/65] SDL2: Add GDB stub options to SDL2 frontend # Conflicts: # src/Ryujinx.Headless.SDL2/Program.cs --- src/Ryujinx.Headless.SDL2/Options.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Ryujinx.Headless.SDL2/Options.cs b/src/Ryujinx.Headless.SDL2/Options.cs index 5891319c7..e76515efc 100644 --- a/src/Ryujinx.Headless.SDL2/Options.cs +++ b/src/Ryujinx.Headless.SDL2/Options.cs @@ -225,17 +225,17 @@ namespace Ryujinx.Headless.SDL2 [Option("ignore-missing-services", Required = false, Default = false, HelpText = "Enable ignoring missing services.")] public bool IgnoreMissingServices { get; set; } + // Debug + + [Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")] + public bool EnableGdbStub { get; set; } + + [Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")] + public ushort GdbStubPort { get; set; } + // Values [Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)] public string InputPath { get; set; } - - // Debugging - - [Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enable the GDB stub.")] - public bool EnableGdbStub { get; set; } - - [Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "GDB stub port.")] - public ushort GdbStubPort { get; set; } } } From 1bb8f6381c8bdd2ea30594ae9f49525943553091 Mon Sep 17 00:00:00 2001 From: merry Date: Wed, 9 Feb 2022 16:16:45 +0000 Subject: [PATCH 07/65] Debugger: Add replies for qGDBServerVersion, qHostInfo, qProcessInfo, qfThreadInfo, qsThreadInfo --- src/Ryujinx.HLE/Debugger/Debugger.cs | 45 ++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 534938e66..9bc3c2aa6 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -1,4 +1,5 @@ -using Ryujinx.Common.Logging; +using Ryujinx.Common; +using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; using System.Collections.Concurrent; @@ -133,7 +134,7 @@ namespace Ryujinx.HLE.Debugger case '!': if (!ss.IsEmpty()) { - goto default; + goto unknownCommand; } // Enable extended mode Reply("OK"); @@ -141,7 +142,7 @@ namespace Ryujinx.HLE.Debugger case '?': if (!ss.IsEmpty()) { - goto default; + goto unknownCommand; } CommandQuery(); break; @@ -151,14 +152,14 @@ namespace Ryujinx.HLE.Debugger case 'D': if (!ss.IsEmpty()) { - goto default; + goto unknownCommand; } CommandDetach(); break; case 'g': if (!ss.IsEmpty()) { - goto default; + goto unknownCommand; } CommandReadGeneralRegisters(); break; @@ -203,6 +204,28 @@ namespace Ryujinx.HLE.Debugger CommandWriteGeneralRegister((int)gdbRegId, value); break; } + case 'q': + switch (ss.ReadUntil(':')) + { + case "GDBServerVersion": + Reply($"name:Ryujinx;version:{ReleaseInformations.GetVersion()};"); + break; + case "HostInfo": + Reply($"triple:{ToHex("aarch64-none-elf")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};"); + break; + case "ProcessInfo": + Reply("pid:1;cputype:100000c;cpusubtype:0;ostype:unknown;vendor:none;endian:little;ptrsize:8;"); + break; + case "fThreadInfo": + Reply($"m {string.Join(",", GetThreadIds().Select(x => $"{x:x}"))}"); + break; + case "sThreadInfo": + Reply("l"); + break; + default: + goto unknownCommand; + } + break; default: Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); Reply(""); @@ -280,7 +303,7 @@ namespace Ryujinx.HLE.Debugger { var data = new byte[len]; GetMemory().Read(addr, data); - Reply(string.Join("", data.Select(x => $"{x:x2}"))); + Reply(ToHex(data)); } void CommandWriteMemory(ulong addr, ulong len, StringStream ss) @@ -384,6 +407,16 @@ namespace Ryujinx.HLE.Debugger return checksum; } + private string ToHex(byte[] bytes) + { + return string.Join("", bytes.Select(x => $"{x:x2}")); + } + + private string ToHex(string str) + { + return ToHex(Encoding.ASCII.GetBytes(str)); + } + public void Dispose() { Dispose(true); From a1de4f1b5b9df46474e7357b70d00435321cd8a2 Mon Sep 17 00:00:00 2001 From: merry Date: Wed, 9 Feb 2022 21:29:53 +0000 Subject: [PATCH 08/65] Debugger: Do more stuff # Conflicts: # src/Ryujinx.HLE/Debugger/Debugger.cs # src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml # src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml # src/Ryujinx.HLE/Debugger/GdbXml/target.xml # src/Ryujinx.HLE/Debugger/RegisterInformation.cs --- src/Ryujinx.HLE/Debugger/Debugger.cs | 184 +++++++++++++----- .../Debugger/GdbXml/aarch64-core.xml | 93 +++++++++ .../Debugger/GdbXml/aarch64-fpu.xml | 159 +++++++++++++++ src/Ryujinx.HLE/Debugger/GdbXml/target.xml | 14 ++ .../Debugger/RegisterInformation.cs | 25 +++ src/Ryujinx.HLE/Debugger/StringStream.cs | 36 +++- src/Ryujinx.HLE/Ryujinx.HLE.csproj | 6 + 7 files changed, 470 insertions(+), 47 deletions(-) create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml create mode 100644 src/Ryujinx.HLE/Debugger/GdbXml/target.xml create mode 100644 src/Ryujinx.HLE/Debugger/RegisterInformation.cs diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 9bc3c2aa6..ce83b366d 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -3,7 +3,6 @@ using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; using System.Collections.Concurrent; -using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; @@ -70,31 +69,31 @@ namespace Ryujinx.HLE.Debugger switch (gdbRegId) { case >= 0 and <= 31: - return $"{state.GetX(gdbRegId):x16}"; + return ToHex(BitConverter.GetBytes(state.GetX(gdbRegId))); case 32: - return $"{state.DebugPc:x16}"; + return ToHex(BitConverter.GetBytes(state.DebugPc)); case 33: - return $"{state.Pstate:x8}"; + return ToHex(BitConverter.GetBytes(state.Pstate)); default: - throw new ArgumentException(); + return null; } } - private void GdbWriteRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId, ulong value) + private bool GdbWriteRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId, ulong value) { switch (gdbRegId) { case >= 0 and <= 31: state.SetX(gdbRegId, value); - return; + return true; case 32: state.DebugPc = value; - return; + return true; case 33: state.Pstate = (uint)value; - return; + return true; default: - throw new ArgumentException(); + return false; } } @@ -137,7 +136,7 @@ namespace Ryujinx.HLE.Debugger goto unknownCommand; } // Enable extended mode - Reply("OK"); + ReplyOK(); break; case '?': if (!ss.IsEmpty()) @@ -205,29 +204,74 @@ namespace Ryujinx.HLE.Debugger break; } case 'q': - switch (ss.ReadUntil(':')) + if (ss.ConsumeRemaining("GDBServerVersion")) { - case "GDBServerVersion": - Reply($"name:Ryujinx;version:{ReleaseInformations.GetVersion()};"); - break; - case "HostInfo": - Reply($"triple:{ToHex("aarch64-none-elf")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};"); - break; - case "ProcessInfo": - Reply("pid:1;cputype:100000c;cpusubtype:0;ostype:unknown;vendor:none;endian:little;ptrsize:8;"); - break; - case "fThreadInfo": - Reply($"m {string.Join(",", GetThreadIds().Select(x => $"{x:x}"))}"); - break; - case "sThreadInfo": - Reply("l"); - break; - default: - goto unknownCommand; + Reply($"name:Ryujinx;version:{ReleaseInformation.GetVersion()};"); + break; } - break; + if (ss.ConsumeRemaining("HostInfo")) + { + Reply($"triple:{ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};"); + break; + } + if (ss.ConsumeRemaining("ProcessInfo")) + { + Reply($"pid:1;cputype:100000c;cpusubtype:0;triple:{ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;"); + break; + } + if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported")) + { + Reply("PacketSize=10000,qXfer:features:read+"); + break; + } + if (ss.ConsumeRemaining("fThreadInfo")) + { + Reply($"m{string.Join(",", GetThreadIds().Select(x => $"{x:x}"))}"); + break; + } + if (ss.ConsumeRemaining("sThreadInfo")) + { + Reply("l"); + break; + } + if (ss.ConsumePrefix("Xfer:features:read:")) + { + string feature = ss.ReadUntil(':'); + ulong addr = ss.ReadUntilAsHex(','); + ulong len = ss.ReadRemainingAsHex(); + + string data; + if (RegisterInformation.Features.TryGetValue(feature, out data)) + { + if (addr >= (ulong)data.Length) + { + Reply("l"); + break; + } + + if (len >= (ulong)data.Length - addr) + { + Reply("l" + data.Substring((int)addr)); + break; + } + else + { + Reply("m" + data.Substring((int)addr, (int)len)); + break; + } + } + else + { + Reply("E00"); // Invalid annex + break; + } + } + goto unknownCommand; + case 'Q': + goto unknownCommand; default: - Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); + unknownCommand: + // Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); Reply(""); break; } @@ -276,9 +320,16 @@ namespace Ryujinx.HLE.Debugger var ctx = GetThread(gThread); for (int i = 0; i < GdbRegisterCount; i++) { - GdbWriteRegister(ctx, i, ss.ReadLengthAsHex(GdbRegisterHexSize(i))); + GdbWriteRegister(ctx, i, ss.ReadLengthAsLEHex(GdbRegisterHexSize(i))); + } + if (ss.IsEmpty()) + { + ReplyOK(); + } + else + { + ReplyError(); } - Reply(ss.IsEmpty() ? "OK" : "E99"); } void CommandSetThread(char op, ulong threadId) @@ -287,53 +338,93 @@ namespace Ryujinx.HLE.Debugger { case 'c': cThread = threadId; - Reply("OK"); + ReplyOK(); return; case 'g': gThread = threadId; - Reply("OK"); + ReplyOK(); return; default: - Reply("E99"); + ReplyError(); return; } } void CommandReadMemory(ulong addr, ulong len) { - var data = new byte[len]; - GetMemory().Read(addr, data); - Reply(ToHex(data)); + try + { + var data = new byte[len]; + GetMemory().Read(addr, data); + Reply(ToHex(data)); + } + catch (InvalidMemoryRegionException) + { + ReplyError(); + } } void CommandWriteMemory(ulong addr, ulong len, StringStream ss) { - var data = new byte[len]; - for (ulong i = 0; i < len; i++) + try { - data[i] = (byte)ss.ReadLengthAsHex(2); + var data = new byte[len]; + for (ulong i = 0; i < len; i++) + { + data[i] = (byte)ss.ReadLengthAsHex(2); + } + GetMemory().Write(addr, data); + ReplyOK(); + } + catch (InvalidMemoryRegionException) + { + ReplyError(); } - GetMemory().Write(addr, data); } void CommandReadGeneralRegister(int gdbRegId) { var ctx = GetThread(gThread); - Reply(GdbReadRegister(ctx, gdbRegId)); + string result = GdbReadRegister(ctx, gdbRegId); + if (result != null) + { + Reply(result); + } + else + { + ReplyError(); + } } void CommandWriteGeneralRegister(int gdbRegId, ulong value) { var ctx = GetThread(gThread); - GdbWriteRegister(ctx, gdbRegId, value); - Reply("OK"); + if (GdbWriteRegister(ctx, gdbRegId, value)) + { + ReplyOK(); + } + else + { + ReplyError(); + } } private void Reply(string cmd) { + Logger.Notice.Print(LogClass.GdbStub, $"Reply: {cmd}"); WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}")); } + private void ReplyOK() + { + Reply("OK"); + } + + private void ReplyError() + { + Reply("E01"); + } + private void SocketReaderThreadMain() { restartListen: @@ -345,6 +436,7 @@ namespace Ryujinx.HLE.Debugger Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client"); ClientSocket = ListenerSocket.AcceptSocket(); + ClientSocket.NoDelay = true; ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read); WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write); Logger.Notice.Print(LogClass.GdbStub, "GDB client connected"); diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml new file mode 100644 index 000000000..22f2d865e --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml new file mode 100644 index 000000000..9e453016c --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.HLE/Debugger/GdbXml/target.xml b/src/Ryujinx.HLE/Debugger/GdbXml/target.xml new file mode 100644 index 000000000..d83c066e7 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/GdbXml/target.xml @@ -0,0 +1,14 @@ + + + + + + aarch64 + + + diff --git a/src/Ryujinx.HLE/Debugger/RegisterInformation.cs b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs new file mode 100644 index 000000000..1ad70dff7 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.Debugger +{ + class RegisterInformation + { + public static readonly Dictionary Features = new() + { + { "target.xml", GetEmbeddedResourceContent("target.xml") }, + { "aarch64-core.xml", GetEmbeddedResourceContent("aarch64-core.xml") }, + { "aarch64-fpu.xml", GetEmbeddedResourceContent("aarch64-fpu.xml") }, + }; + + private static string GetEmbeddedResourceContent(string resourceName) + { + Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.GdbXml." + resourceName); + StreamReader reader = new StreamReader(stream); + string result = reader.ReadToEnd(); + reader.Dispose(); + stream.Dispose(); + return result; + } + } +} diff --git a/src/Ryujinx.HLE/Debugger/StringStream.cs b/src/Ryujinx.HLE/Debugger/StringStream.cs index b654f6f54..3197d2e55 100644 --- a/src/Ryujinx.HLE/Debugger/StringStream.cs +++ b/src/Ryujinx.HLE/Debugger/StringStream.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.Diagnostics; +using System.Globalization; namespace Ryujinx.HLE.Debugger { @@ -60,6 +61,39 @@ namespace Ryujinx.HLE.Debugger return ulong.Parse(ReadLength(len), NumberStyles.HexNumber); } + public ulong ReadLengthAsLEHex(int len) + { + Debug.Assert(len % 2 == 0); + + ulong result = 0; + int pos = 0; + while (pos < len) + { + result += ReadLengthAsHex(2) << (4 * pos); + pos += 2; + } + return result; + } + public bool ConsumePrefix(string prefix) + { + if (Data.Substring(Position).StartsWith(prefix)) + { + Position += prefix.Length; + return true; + } + return false; + } + + public bool ConsumeRemaining(string match) + { + if (Data.Substring(Position) == match) + { + Position += match.Length; + return true; + } + return false; + } + public bool IsEmpty() { return Position >= Data.Length; diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj index 0fcf9e4b5..fa6a0a579 100644 --- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -30,6 +30,9 @@ + + + @@ -39,6 +42,9 @@ + + + From 1b9753d42aa22141871d046cde3c78adf42d162f Mon Sep 17 00:00:00 2001 From: merry Date: Sun, 13 Feb 2022 13:59:58 +0000 Subject: [PATCH 09/65] Debugger: Fixups from testing with GDB # Conflicts: # src/Ryujinx.HLE/Debugger/Debugger.cs --- src/Ryujinx.HLE/Debugger/Debugger.cs | 149 +++++++++++++++++++---- src/Ryujinx.HLE/Debugger/StringStream.cs | 9 +- 2 files changed, 136 insertions(+), 22 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index ce83b366d..95099053f 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -25,8 +25,8 @@ namespace Ryujinx.HLE.Debugger private Thread SocketThread; private Thread HandlerThread; - private ulong cThread; - private ulong gThread; + private ulong? cThread; + private ulong? gThread; public Debugger(Switch device, ushort port) { @@ -45,9 +45,10 @@ namespace Ryujinx.HLE.Debugger private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids(); private Ryujinx.Cpu.IExecutionContext GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThreadContext(threadUid); private Ryujinx.Cpu.IExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); + private ulong? GetThreadUid(Ryujinx.Cpu.IExecutionContext thread) => GetThreadIds().Where(x => GetThread(x) == thread).First(); private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; - const int GdbRegisterCount = 34; + const int GdbRegisterCount = 68; private int GdbRegisterHexSize(int gdbRegId) { @@ -59,6 +60,12 @@ namespace Ryujinx.HLE.Debugger return 16; case 33: return 8; + case >= 34 and <= 65: + return 32; + case 66: + return 8; + case 67: + return 8; default: throw new ArgumentException(); } @@ -74,6 +81,12 @@ namespace Ryujinx.HLE.Debugger return ToHex(BitConverter.GetBytes(state.DebugPc)); case 33: return ToHex(BitConverter.GetBytes(state.Pstate)); + case >= 34 and <= 65: + return ToHex(state.GetV(gdbRegId - 34).ToArray()); + case 66: + return ToHex(BitConverter.GetBytes((uint)state.Fpsr)); + case 67: + return ToHex(BitConverter.GetBytes((uint)state.Fpcr)); default: return null; } @@ -108,15 +121,15 @@ namespace Ryujinx.HLE.Debugger case BreakInMessage _: Logger.Notice.Print(LogClass.GdbStub, "Break-in requested"); - // TODO + CommandQuery(); break; case SendNackMessage _: WriteStream.WriteByte((byte)'-'); break; - case CommandMessage { Command: var cmd }: - Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}"); + case CommandMessage {Command: var cmd}: + Logger.Notice.Print(LogClass.GdbStub, $"Received Command: {cmd}"); WriteStream.WriteByte((byte)'+'); ProcessCommand(cmd); break; @@ -135,6 +148,7 @@ namespace Ryujinx.HLE.Debugger { goto unknownCommand; } + // Enable extended mode ReplyOK(); break; @@ -143,6 +157,7 @@ namespace Ryujinx.HLE.Debugger { goto unknownCommand; } + CommandQuery(); break; case 'c': @@ -153,6 +168,7 @@ namespace Ryujinx.HLE.Debugger { goto unknownCommand; } + CommandDetach(); break; case 'g': @@ -160,6 +176,7 @@ namespace Ryujinx.HLE.Debugger { goto unknownCommand; } + CommandReadGeneralRegisters(); break; case 'G': @@ -168,7 +185,7 @@ namespace Ryujinx.HLE.Debugger case 'H': { char op = ss.ReadChar(); - ulong threadId = ss.ReadRemainingAsHex(); + ulong? threadId = ss.ReadRemainingAsThreadUid(); CommandSetThread(op, threadId); break; } @@ -209,31 +226,39 @@ namespace Ryujinx.HLE.Debugger Reply($"name:Ryujinx;version:{ReleaseInformation.GetVersion()};"); break; } + if (ss.ConsumeRemaining("HostInfo")) { - Reply($"triple:{ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};"); + Reply( + $"triple:{ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};"); break; } + if (ss.ConsumeRemaining("ProcessInfo")) { - Reply($"pid:1;cputype:100000c;cpusubtype:0;triple:{ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;"); + Reply( + $"pid:1;cputype:100000c;cpusubtype:0;triple:{ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;"); break; } + if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported")) { - Reply("PacketSize=10000,qXfer:features:read+"); + Reply("PacketSize=10000;qXfer:features:read+"); break; } + if (ss.ConsumeRemaining("fThreadInfo")) { Reply($"m{string.Join(",", GetThreadIds().Select(x => $"{x:x}"))}"); break; } + if (ss.ConsumeRemaining("sThreadInfo")) { Reply("l"); break; } + if (ss.ConsumePrefix("Xfer:features:read:")) { string feature = ss.ReadUntil(':'); @@ -266,11 +291,21 @@ namespace Ryujinx.HLE.Debugger break; } } + goto unknownCommand; case 'Q': goto unknownCommand; + case 's': + CommandStep(ss.IsEmpty() ? null : ss.ReadRemainingAsHex()); + break; + case 'T': + { + ulong? threadId = ss.ReadRemainingAsThreadUid(); + CommandIsAlive(threadId); + break; + } default: - unknownCommand: + unknownCommand: // Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); Reply(""); break; @@ -282,14 +317,21 @@ namespace Ryujinx.HLE.Debugger // GDB is performing initial contact. Stop everything. HaltApplication(); gThread = cThread = GetThreadIds().First(); - Reply($"T05thread:{cThread:x}"); + Reply($"T05thread:{cThread:x};"); + //Reply("S05"); } void CommandContinue(ulong? newPc) { if (newPc.HasValue) { - GetThread(cThread).DebugPc = newPc.Value; + if (cThread == null) + { + ReplyError(); + return; + } + + GetThread(cThread.Value).DebugPc = newPc.Value; } foreach (var thread in GetThreads()) @@ -306,22 +348,36 @@ namespace Ryujinx.HLE.Debugger void CommandReadGeneralRegisters() { - var ctx = GetThread(gThread); + if (gThread == null) + { + ReplyError(); + return; + } + + var ctx = GetThread(gThread.Value); string registers = ""; for (int i = 0; i < GdbRegisterCount; i++) { registers += GdbReadRegister(ctx, i); } + Reply(registers); } void CommandWriteGeneralRegisters(StringStream ss) { - var ctx = GetThread(gThread); + if (gThread == null) + { + ReplyError(); + return; + } + + var ctx = GetThread(gThread.Value); for (int i = 0; i < GdbRegisterCount; i++) { GdbWriteRegister(ctx, i, ss.ReadLengthAsLEHex(GdbRegisterHexSize(i))); } + if (ss.IsEmpty()) { ReplyOK(); @@ -332,8 +388,13 @@ namespace Ryujinx.HLE.Debugger } } - void CommandSetThread(char op, ulong threadId) + void CommandSetThread(char op, ulong? threadId) { + if (threadId == 0) + { + threadId = GetThreadUid(GetThreads().First()); + } + switch (op) { case 'c': @@ -373,6 +434,7 @@ namespace Ryujinx.HLE.Debugger { data[i] = (byte)ss.ReadLengthAsHex(2); } + GetMemory().Write(addr, data); ReplyOK(); } @@ -384,7 +446,13 @@ namespace Ryujinx.HLE.Debugger void CommandReadGeneralRegister(int gdbRegId) { - var ctx = GetThread(gThread); + if (gThread == null) + { + ReplyError(); + return; + } + + var ctx = GetThread(gThread.Value); string result = GdbReadRegister(ctx, gdbRegId); if (result != null) { @@ -398,7 +466,13 @@ namespace Ryujinx.HLE.Debugger void CommandWriteGeneralRegister(int gdbRegId, ulong value) { - var ctx = GetThread(gThread); + if (gThread == null) + { + ReplyError(); + return; + } + + var ctx = GetThread(gThread.Value); if (GdbWriteRegister(ctx, gdbRegId, value)) { ReplyOK(); @@ -409,6 +483,37 @@ namespace Ryujinx.HLE.Debugger } } + private void CommandStep(ulong? newPc) + { + if (cThread == null) + { + ReplyError(); + return; + } + + var ctx = GetThread(cThread.Value); + + if (newPc.HasValue) + { + ctx.DebugPc = newPc.Value; + } + + ctx.DebugStep(); + Reply($"T00thread:{GetThreadUid(ctx):x};"); + } + + private void CommandIsAlive(ulong? threadId) + { + if (GetThreads().Any(x => GetThreadUid(x) == threadId)) + { + ReplyOK(); + } + else + { + Reply("E00"); + } + } + private void Reply(string cmd) { Logger.Notice.Print(LogClass.GdbStub, $"Reply: {cmd}"); @@ -427,7 +532,7 @@ namespace Ryujinx.HLE.Debugger private void SocketReaderThreadMain() { - restartListen: + restartListen: try { var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort); @@ -475,7 +580,7 @@ namespace Ryujinx.HLE.Debugger } } - eof: + eof: Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); goto restartListen; } @@ -496,6 +601,7 @@ namespace Ryujinx.HLE.Debugger checksum += (byte)x; } } + return checksum; } @@ -522,6 +628,7 @@ namespace Ryujinx.HLE.Debugger { Messages.Add(new AbortMessage()); } + ListenerSocket.Stop(); ClientSocket?.Shutdown(SocketShutdown.Both); ClientSocket?.Close(); @@ -532,4 +639,4 @@ namespace Ryujinx.HLE.Debugger } } } -} +} \ No newline at end of file diff --git a/src/Ryujinx.HLE/Debugger/StringStream.cs b/src/Ryujinx.HLE/Debugger/StringStream.cs index 3197d2e55..5542aff01 100644 --- a/src/Ryujinx.HLE/Debugger/StringStream.cs +++ b/src/Ryujinx.HLE/Debugger/StringStream.cs @@ -74,6 +74,13 @@ namespace Ryujinx.HLE.Debugger } return result; } + + public ulong? ReadRemainingAsThreadUid() + { + string s = ReadRemaining(); + return s == "-1" ? null : ulong.Parse(s, NumberStyles.HexNumber); + } + public bool ConsumePrefix(string prefix) { if (Data.Substring(Position).StartsWith(prefix)) @@ -99,4 +106,4 @@ namespace Ryujinx.HLE.Debugger return Position >= Data.Length; } } -} +} \ No newline at end of file From 54bf7507d78b9c5b0eaa968f80b02811e94cb699 Mon Sep 17 00:00:00 2001 From: merry Date: Sun, 13 Feb 2022 14:21:23 +0000 Subject: [PATCH 10/65] Call InvalidateCacheRegion # Conflicts: # Ryujinx.HLE/HOS/ArmProcessContext.cs # src/Ryujinx.HLE/Debugger/Debugger.cs # src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs # src/Ryujinx.HLE/HOS/Horizon.cs # src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs --- src/Ryujinx.HLE/Debugger/Debugger.cs | 3 ++ .../Debugger/IDebuggableProcess.cs | 9 ++-- src/Ryujinx.HLE/HOS/Horizon.cs | 5 +- .../HOS/Kernel/Process/IProcessContext.cs | 2 +- .../HOS/Kernel/Process/KProcess.cs | 49 +++++++++++++------ .../HOS/Kernel/Process/ProcessContext.cs | 2 +- src/Ryujinx.Tests/Cpu/CpuContext.cs | 2 +- 7 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 95099053f..0a2c43b16 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -47,6 +47,8 @@ namespace Ryujinx.HLE.Debugger private Ryujinx.Cpu.IExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); private ulong? GetThreadUid(Ryujinx.Cpu.IExecutionContext thread) => GetThreadIds().Where(x => GetThread(x) == thread).First(); private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; + private void InvalidateCacheRegion(ulong address, ulong size) => + Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size); const int GdbRegisterCount = 68; @@ -436,6 +438,7 @@ namespace Ryujinx.HLE.Debugger } GetMemory().Write(addr, data); + InvalidateCacheRegion(addr, len); ReplyOK(); } catch (InvalidMemoryRegionException) diff --git a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs index c86d4acd5..e31747146 100644 --- a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs +++ b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs @@ -4,9 +4,10 @@ namespace Ryujinx.HLE.Debugger { public interface IDebuggableProcess { - public void DebugStopAllThreads(); - public ulong[] DebugGetThreadUids(); - public Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid); - public IVirtualMemoryManager CpuMemory { get; } + void DebugStopAllThreads(); + ulong[] DebugGetThreadUids(); + Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid); + IVirtualMemoryManager CpuMemory { get; } + void InvalidateCacheRegion(ulong address, ulong size); } } diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 69086c0bc..a19546811 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -5,6 +5,7 @@ using LibHac.Fs.Shim; using LibHac.FsSystem; using LibHac.Tools.FsSystem; using Ryujinx.Cpu; +using Ryujinx.HLE.Debugger; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Memory; @@ -474,11 +475,11 @@ namespace Ryujinx.HLE.HOS IsPaused = pause; } - public Debugger.IDebuggableProcess DebugGetApplicationProcess() + public IDebuggableProcess DebugGetApplicationProcess() { lock (KernelContext.Processes) { - return KernelContext.Processes.Values.Where(x => x.IsApplication).First(); + return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.GdbStubInterface; } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs index ac36b781b..ff66b0815 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs @@ -14,4 +14,4 @@ namespace Ryujinx.HLE.HOS.Kernel.Process void Execute(IExecutionContext context, ulong codeAddress); void InvalidateCacheRegion(ulong address, ulong size); } -} +} \ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 0c0004be5..c18fadf8d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1,6 +1,7 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Cpu; +using Ryujinx.HLE.Debugger; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory; @@ -14,7 +15,7 @@ using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Process { - class KProcess : KSynchronizationObject, Debugger.IDebuggableProcess + class KProcess : KSynchronizationObject { public const uint KernelVersionMajor = 10; public const uint KernelVersionMinor = 4; @@ -89,6 +90,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public IVirtualMemoryManager CpuMemory => Context.AddressSpace; public HleProcessDebugger Debugger { get; private set; } + public IDebuggableProcess GdbStubInterface { get { return new DebuggerInterface(this); } } public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context) { @@ -1176,30 +1178,47 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return Capabilities.IsSvcPermitted(svcId); } - public void DebugStopAllThreads() + private class DebuggerInterface : IDebuggableProcess { - lock (_threadingLock) + private readonly KProcess _parent; + + public DebuggerInterface(KProcess p) { - foreach (KThread thread in _threads) + _parent = p; + } + + public void DebugStopAllThreads() + { + lock (_parent._threadingLock) { - thread.Context.DebugStop(); + foreach (KThread thread in _parent._threads) + { + thread.Context.DebugStop(); + } } } - } - public ulong[] DebugGetThreadUids() - { - lock (_threadingLock) + public ulong[] DebugGetThreadUids() { - return _threads.Select(x => x.ThreadUid).ToArray(); + lock (_parent._threadingLock) + { + return _parent._threads.Select(x => x.ThreadUid).ToArray(); + } } - } - public Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid) - { - lock (_threadingLock) + public Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid) { - return _threads.Where(x => x.ThreadUid == threadUid).FirstOrDefault()?.Context; + lock (_parent._threadingLock) + { + return _parent._threads.FirstOrDefault(x => x.ThreadUid == threadUid)?.Context; + } + } + + public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } } + + public void InvalidateCacheRegion(ulong address, ulong size) + { + _parent.Context.InvalidateCacheRegion(address, size); } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs index b4ae6ec4e..037994425 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs @@ -34,4 +34,4 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { } } -} +} \ No newline at end of file diff --git a/src/Ryujinx.Tests/Cpu/CpuContext.cs b/src/Ryujinx.Tests/Cpu/CpuContext.cs index 96b4965a2..1ae5f2d4d 100644 --- a/src/Ryujinx.Tests/Cpu/CpuContext.cs +++ b/src/Ryujinx.Tests/Cpu/CpuContext.cs @@ -36,4 +36,4 @@ namespace Ryujinx.Tests.Cpu _translator.InvalidateJitCacheRegion(address, size); } } -} +} \ No newline at end of file From d0fbcced57ad41e707511e1dd34710a364b31891 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 5 Aug 2023 14:25:41 +0300 Subject: [PATCH 11/65] Put ThreadUid in the execution context classes --- src/ARMeilleure/State/ExecutionContext.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 7 +++++++ src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs | 2 +- src/Ryujinx.Cpu/IExecutionContext.cs | 5 +++++ src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 7 +++++++ src/Ryujinx.HLE/Debugger/Debugger.cs | 7 +++---- .../HOS/Kernel/Process/ProcessExecutionContext.cs | 2 ++ src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 1 + 10 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs index 2d57df725..e4e9cd0e1 100644 --- a/src/ARMeilleure/State/ExecutionContext.cs +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -71,6 +71,8 @@ namespace ARMeilleure.State public bool IsAarch32 { get; set; } + public ulong ThreadUid { get; set; } + internal ExecutionMode ExecutionMode { get diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index 63b4b2edc..33549237f 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -48,6 +48,13 @@ namespace Ryujinx.Cpu.AppleHv set => _impl.Fpsr = value; } + /// + public ulong ThreadUid + { + get => _impl.ThreadUid; + set => _impl.ThreadUid = value; + } + /// public bool IsAarch32 { diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs index b9cc08fb2..034795f08 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs @@ -18,6 +18,8 @@ namespace Ryujinx.Cpu.AppleHv public bool IsAarch32 { get; set; } + public ulong ThreadUid { get; set; } + private readonly ulong[] _x; private readonly V128[] _v; diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index c581698b9..690a5f30d 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -21,6 +21,8 @@ namespace Ryujinx.Cpu.AppleHv // This is only valid while debugging is enabled. public ulong DebugPc { get; set; } + public ulong ThreadUid { get; set; } + static HvExecutionContextVcpu() { // .NET does not support passing vectors by value, so we need to pass a pointer and use a native diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs index f12ba9dfc..17be8cc45 100644 --- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Cpu.AppleHv uint Fpcr { get; set; } uint Fpsr { get; set; } - + ulong ThreadUid { get; set; } ulong GetX(int index); void SetX(int index, ulong value); diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index 927ba120d..b44c4f318 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -46,6 +46,11 @@ namespace Ryujinx.Cpu /// bool IsAarch32 { get; set; } + /// + /// Thread UID. + /// + public ulong ThreadUid { get; set; } + /// /// Indicates whenever the CPU is still running code. /// diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs index fda9e54cb..2f3e0d59b 100644 --- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs @@ -53,6 +53,13 @@ namespace Ryujinx.Cpu.Jit set => _impl.IsAarch32 = value; } + /// + public ulong ThreadUid + { + get => _impl.ThreadUid; + set => _impl.ThreadUid = value; + } + /// public bool Running => _impl.Running; diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 0a2c43b16..63e540b2a 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -45,7 +45,6 @@ namespace Ryujinx.HLE.Debugger private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids(); private Ryujinx.Cpu.IExecutionContext GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThreadContext(threadUid); private Ryujinx.Cpu.IExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); - private ulong? GetThreadUid(Ryujinx.Cpu.IExecutionContext thread) => GetThreadIds().Where(x => GetThread(x) == thread).First(); private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; private void InvalidateCacheRegion(ulong address, ulong size) => Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size); @@ -394,7 +393,7 @@ namespace Ryujinx.HLE.Debugger { if (threadId == 0) { - threadId = GetThreadUid(GetThreads().First()); + threadId = GetThreads().First().ThreadUid; } switch (op) @@ -502,12 +501,12 @@ namespace Ryujinx.HLE.Debugger } ctx.DebugStep(); - Reply($"T00thread:{GetThreadUid(ctx):x};"); + Reply($"T00thread:{ctx.ThreadUid:x};"); } private void CommandIsAlive(ulong? threadId) { - if (GetThreads().Any(x => GetThreadUid(x) == threadId)) + if (GetThreads().Any(x => x.ThreadUid == threadId)) { ReplyOK(); } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs index f9718280e..2cfdbd687 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs @@ -17,6 +17,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public bool IsAarch32 { get => false; set { } } + public ulong ThreadUid { get; set; } + public bool Running { get; private set; } = true; private readonly ulong[] _x = new ulong[32]; diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 835bf5d40..4e71faf01 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -204,6 +204,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading Context.TpidrroEl0 = (long)_tlsAddress; ThreadUid = KernelContext.NewThreadUid(); + Context.ThreadUid = ThreadUid; HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}"; From a6cbb899962cb761123d2a62494fb685e136f69e Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 5 Aug 2023 14:43:31 +0300 Subject: [PATCH 12/65] Don't store ThreadUid in the shadow context and/or vcpu --- src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index 33549237f..77a981b10 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -49,11 +49,7 @@ namespace Ryujinx.Cpu.AppleHv } /// - public ulong ThreadUid - { - get => _impl.ThreadUid; - set => _impl.ThreadUid = value; - } + public ulong ThreadUid { get; set; } /// public bool IsAarch32 From a366890cb5c7788a84e68ca23c1abca964313ed9 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 5 Aug 2023 15:02:36 +0300 Subject: [PATCH 13/65] Fix spacing --- src/ARMeilleure/Optimizations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs index 3cc8acc8d..aaae7a39a 100644 --- a/src/ARMeilleure/Optimizations.cs +++ b/src/ARMeilleure/Optimizations.cs @@ -9,7 +9,7 @@ namespace ARMeilleure public static bool AllowLcqInFunctionTable { get; set; } = true; public static bool UseUnmanagedDispatchLoop { get; set; } = true; - public static bool EnableDebugging { get; set; } = false; + public static bool EnableDebugging { get; set; } = false; public static bool UseAdvSimdIfAvailable { get; set; } = true; public static bool UseArm64AesIfAvailable { get; set; } = true; From 9b9137bf0ae2c3cb8db074e6d881f26bc7b13666 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 5 Aug 2023 21:37:28 +0300 Subject: [PATCH 14/65] Log GDB server exceptions --- src/Ryujinx.HLE/Debugger/Debugger.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 63e540b2a..a5a60cf7c 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -586,8 +586,9 @@ namespace Ryujinx.HLE.Debugger Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); goto restartListen; } - catch (Exception) + catch (Exception ex) { + Logger.Error?.Print(LogClass.GdbStub, ex.ToString()); Logger.Notice.Print(LogClass.GdbStub, "GDB stub socket closed"); return; } From 09a63ba2b50e2d76ef83d7de72575dec2f3e04b0 Mon Sep 17 00:00:00 2001 From: merry Date: Sun, 13 Feb 2022 16:43:55 +0000 Subject: [PATCH 15/65] Notify debugger on BRK instruction # Conflicts: # src/Ryujinx.HLE/Debugger/Debugger.cs # src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs # src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs --- src/Ryujinx.HLE/Debugger/Debugger.cs | 19 +++++++++++++++++-- .../Debugger/Message/ThreadBreakMessage.cs | 18 ++++++++++++++++++ src/Ryujinx.HLE/FileSystem/ContentManager.cs | 2 +- .../HOS/Kernel/Process/KProcess.cs | 9 ++++++++- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index a5a60cf7c..7c9815d60 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -1,4 +1,4 @@ -using Ryujinx.Common; +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; @@ -8,6 +8,7 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; +using IExecutionContext = Ryujinx.Cpu.IExecutionContext; namespace Ryujinx.HLE.Debugger { @@ -134,6 +135,11 @@ namespace Ryujinx.HLE.Debugger WriteStream.WriteByte((byte)'+'); ProcessCommand(cmd); break; + + case ThreadBreakMessage msg: + HaltApplication(); + Reply($"T05thread:{msg.Context.ThreadUid:x};"); + break; } } } @@ -641,5 +647,14 @@ namespace Ryujinx.HLE.Debugger HandlerThread.Join(); } } + + public void ThreadBreak(IExecutionContext ctx, ulong address, int imm) + { + ctx.DebugStop(); + + Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}"); + + Messages.Add(new ThreadBreakMessage(ctx, address, imm)); + } } -} \ No newline at end of file +} diff --git a/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs b/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs new file mode 100644 index 000000000..ac0ecdefb --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs @@ -0,0 +1,18 @@ +using IExecutionContext = Ryujinx.Cpu.IExecutionContext; + +namespace Ryujinx.HLE.Debugger +{ + public class ThreadBreakMessage : IMessage + { + public IExecutionContext Context { get; } + public ulong Address { get; } + public int Opcode { get; } + + public ThreadBreakMessage(IExecutionContext context, ulong address, int opcode) + { + Context = context; + Address = address; + Opcode = opcode; + } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index e6c0fce08..ff9bef55b 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -964,4 +964,4 @@ namespace Ryujinx.HLE.FileSystem return null; } } -} +} \ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index c18fadf8d..31a8ab916 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -730,9 +730,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public IExecutionContext CreateExecutionContext() { + ExceptionCallback breakCallback = null; + + if (KernelContext.Device.Configuration.EnableGdbStub) + { + breakCallback = KernelContext.Device.Debugger.ThreadBreak; + } + return Context?.CreateExecutionContext(new ExceptionCallbacks( InterruptHandler, - null, + breakCallback, KernelContext.SyscallHandler.SvcCall, UndefinedInstructionHandler)); } From 3bdf9d980509bf018a9c14ef2e4a4583a9e944cd Mon Sep 17 00:00:00 2001 From: merry Date: Sat, 19 Feb 2022 15:16:39 +0000 Subject: [PATCH 16/65] Implement binary data escaping --- src/Ryujinx.HLE/Debugger/Debugger.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 7c9815d60..84f04db61 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -283,12 +283,12 @@ namespace Ryujinx.HLE.Debugger if (len >= (ulong)data.Length - addr) { - Reply("l" + data.Substring((int)addr)); + Reply("l" + ToBinaryFormat(data.Substring((int)addr))); break; } else { - Reply("m" + data.Substring((int)addr, (int)len)); + Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len))); break; } } @@ -624,6 +624,25 @@ namespace Ryujinx.HLE.Debugger return ToHex(Encoding.ASCII.GetBytes(str)); } + private string ToBinaryFormat(byte[] bytes) + { + return string.Join("", bytes.Select(x => + x switch + { + (byte)'#' => "}\x03", + (byte)'$' => "}\x04", + (byte)'*' => "}\x0a", + (byte)'}' => "}\x5d", + _ => Convert.ToChar(x).ToString(), + } + )); + } + + private string ToBinaryFormat(string str) + { + return ToBinaryFormat(Encoding.ASCII.GetBytes(str)); + } + public void Dispose() { Dispose(true); From a9538a54ffe0206ff00d87b189b07b54da619263 Mon Sep 17 00:00:00 2001 From: merry Date: Sat, 19 Feb 2022 15:32:43 +0000 Subject: [PATCH 17/65] cleanup --- src/Ryujinx.HLE/Debugger/Debugger.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 84f04db61..67f2bdbd8 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -131,7 +131,7 @@ namespace Ryujinx.HLE.Debugger break; case CommandMessage {Command: var cmd}: - Logger.Notice.Print(LogClass.GdbStub, $"Received Command: {cmd}"); + Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}"); WriteStream.WriteByte((byte)'+'); ProcessCommand(cmd); break; @@ -313,7 +313,7 @@ namespace Ryujinx.HLE.Debugger } default: unknownCommand: - // Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); + Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); Reply(""); break; } @@ -325,7 +325,6 @@ namespace Ryujinx.HLE.Debugger HaltApplication(); gThread = cThread = GetThreadIds().First(); Reply($"T05thread:{cThread:x};"); - //Reply("S05"); } void CommandContinue(ulong? newPc) @@ -524,7 +523,7 @@ namespace Ryujinx.HLE.Debugger private void Reply(string cmd) { - Logger.Notice.Print(LogClass.GdbStub, $"Reply: {cmd}"); + Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}"); WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}")); } @@ -581,9 +580,15 @@ namespace Ryujinx.HLE.Debugger } string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}"; - // Debug.Assert(checksum == $"{CalculateChecksum(cmd):x2}"); + if (checksum == $"{CalculateChecksum(cmd):x2}") + { + Messages.Add(new CommandMessage(cmd)); + } + else + { + Messages.Add(new SendNackMessage()); + } - Messages.Add(new CommandMessage(cmd)); break; } } From 1cef40131a0481325a0647ce0213d9e4c1726735 Mon Sep 17 00:00:00 2001 From: merry Date: Sat, 19 Feb 2022 15:59:07 +0000 Subject: [PATCH 18/65] Support vectors in write registers # Conflicts: # src/Ryujinx.HLE/Debugger/Debugger.cs --- src/Ryujinx.HLE/Debugger/Debugger.cs | 68 ++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 67f2bdbd8..9fbdc2175 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -1,3 +1,4 @@ +using ARMeilleure.State; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Memory; @@ -94,19 +95,47 @@ namespace Ryujinx.HLE.Debugger } } - private bool GdbWriteRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId, ulong value) + private bool GdbWriteRegister(IExecutionContext state, int gdbRegId, StringStream ss) { switch (gdbRegId) { case >= 0 and <= 31: - state.SetX(gdbRegId, value); - return true; + { + ulong value = ss.ReadLengthAsHex(16); + state.SetX(gdbRegId, value); + return true; + } case 32: - state.DebugPc = value; - return true; + { + ulong value = ss.ReadLengthAsHex(8); + state.DebugPc = value; + return true; + } case 33: - state.Pstate = (uint)value; - return true; + { + ulong value = ss.ReadLengthAsHex(8); + state.Pstate = (uint)value; + return true; + } + case >= 34 and <= 65: + { + ulong value0 = ss.ReadLengthAsHex(16); + ulong value1 = ss.ReadLengthAsHex(16); + state.SetV(gdbRegId - 34, new V128(value0, value1)); + return true; + } + case 66: + { + ulong value = ss.ReadLengthAsHex(8); + state.Fpsr = (uint)value; + return true; + } + case 67: + { + ulong value = ss.ReadLengthAsHex(8); + state.Fpcr = (uint)value; + return true; + } default: return false; } @@ -184,10 +213,10 @@ namespace Ryujinx.HLE.Debugger goto unknownCommand; } - CommandReadGeneralRegisters(); + CommandReadRegisters(); break; case 'G': - CommandWriteGeneralRegisters(ss); + CommandWriteRegisters(ss); break; case 'H': { @@ -217,14 +246,13 @@ namespace Ryujinx.HLE.Debugger case 'p': { ulong gdbRegId = ss.ReadRemainingAsHex(); - CommandReadGeneralRegister((int)gdbRegId); + CommandReadRegister((int)gdbRegId); break; } case 'P': { ulong gdbRegId = ss.ReadUntilAsHex('='); - ulong value = ss.ReadRemainingAsHex(); - CommandWriteGeneralRegister((int)gdbRegId, value); + CommandWriteRegister((int)gdbRegId, ss); break; } case 'q': @@ -352,7 +380,7 @@ namespace Ryujinx.HLE.Debugger CommandContinue(null); } - void CommandReadGeneralRegisters() + void CommandReadRegisters() { if (gThread == null) { @@ -370,7 +398,7 @@ namespace Ryujinx.HLE.Debugger Reply(registers); } - void CommandWriteGeneralRegisters(StringStream ss) + void CommandWriteRegisters(StringStream ss) { if (gThread == null) { @@ -381,7 +409,11 @@ namespace Ryujinx.HLE.Debugger var ctx = GetThread(gThread.Value); for (int i = 0; i < GdbRegisterCount; i++) { - GdbWriteRegister(ctx, i, ss.ReadLengthAsLEHex(GdbRegisterHexSize(i))); + if (!GdbWriteRegister(ctx, i, ss)) + { + ReplyError(); + return; + } } if (ss.IsEmpty()) @@ -451,7 +483,7 @@ namespace Ryujinx.HLE.Debugger } } - void CommandReadGeneralRegister(int gdbRegId) + void CommandReadRegister(int gdbRegId) { if (gThread == null) { @@ -471,7 +503,7 @@ namespace Ryujinx.HLE.Debugger } } - void CommandWriteGeneralRegister(int gdbRegId, ulong value) + void CommandWriteRegister(int gdbRegId, StringStream ss) { if (gThread == null) { @@ -480,7 +512,7 @@ namespace Ryujinx.HLE.Debugger } var ctx = GetThread(gThread.Value); - if (GdbWriteRegister(ctx, gdbRegId, value)) + if (GdbWriteRegister(ctx, gdbRegId, ss) && ss.IsEmpty()) { ReplyOK(); } From 5a34d80f98ec3e10ad9897ebcf1de4ac982dd6a2 Mon Sep 17 00:00:00 2001 From: merry Date: Sat, 19 Feb 2022 15:59:24 +0000 Subject: [PATCH 19/65] Restart socket when able --- src/Ryujinx.HLE/Debugger/Debugger.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 9fbdc2175..d065d4aaa 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -26,6 +26,7 @@ namespace Ryujinx.HLE.Debugger private BlockingCollection Messages = new BlockingCollection(1); private Thread SocketThread; private Thread HandlerThread; + private bool _shuttingDown = false; private ulong? cThread; private ulong? gThread; @@ -633,7 +634,10 @@ namespace Ryujinx.HLE.Debugger { Logger.Error?.Print(LogClass.GdbStub, ex.ToString()); Logger.Notice.Print(LogClass.GdbStub, "GDB stub socket closed"); - return; + if (!_shuttingDown) + { + goto restartListen; + } } } @@ -689,6 +693,8 @@ namespace Ryujinx.HLE.Debugger { if (disposing) { + _shuttingDown = true; + if (HandlerThread.IsAlive) { Messages.Add(new AbortMessage()); From 7e4944cc88e7e6ab33480cef0ceab4d0dd97d368 Mon Sep 17 00:00:00 2001 From: svc64 Date: Tue, 8 Aug 2023 21:37:11 +0300 Subject: [PATCH 20/65] Make DebugState public and remove AppleHV's DebugState --- src/ARMeilleure/State/DebugState.cs | 2 +- src/Ryujinx.Cpu/AppleHv/DebugState.cs | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 src/Ryujinx.Cpu/AppleHv/DebugState.cs diff --git a/src/ARMeilleure/State/DebugState.cs b/src/ARMeilleure/State/DebugState.cs index dd05ddb61..0b00781fe 100644 --- a/src/ARMeilleure/State/DebugState.cs +++ b/src/ARMeilleure/State/DebugState.cs @@ -1,6 +1,6 @@ namespace ARMeilleure.State { - enum DebugState + public enum DebugState { Running, Stopping, diff --git a/src/Ryujinx.Cpu/AppleHv/DebugState.cs b/src/Ryujinx.Cpu/AppleHv/DebugState.cs deleted file mode 100644 index 5a5f2ba03..000000000 --- a/src/Ryujinx.Cpu/AppleHv/DebugState.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Cpu.AppleHv -{ - enum DebugState - { - Running, - Stopping, - Stopped, - } -} From 8bd4417b2450d15ab6343d0356761416feaba4f7 Mon Sep 17 00:00:00 2001 From: merry Date: Sat, 19 Feb 2022 16:12:19 +0000 Subject: [PATCH 21/65] Implement qThreadExtraInfo --- src/ARMeilleure/State/ExecutionContext.cs | 5 +++++ src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 3 +++ .../AppleHv/HvExecutionContextShadow.cs | 5 +++++ .../AppleHv/HvExecutionContextVcpu.cs | 5 +++++ .../AppleHv/IHvExecutionContext.cs | 1 + src/Ryujinx.Cpu/IExecutionContext.cs | 3 ++- src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 3 +++ src/Ryujinx.HLE/Debugger/Debugger.cs | 21 +++++++++++++++++++ .../Kernel/Process/ProcessExecutionContext.cs | 5 +++++ 9 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs index e4e9cd0e1..47c864bfe 100644 --- a/src/ARMeilleure/State/ExecutionContext.cs +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -185,6 +185,11 @@ namespace ARMeilleure.State _debugHalt.Set(); } + public DebugState GetDebugState() + { + return (DebugState)_debugState; + } + internal void OnBreak(ulong address, int imm) { _breakCallback?.Invoke(this, address, imm); diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index 77a981b10..e6e33ea6b 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -139,6 +139,9 @@ namespace Ryujinx.Cpu.AppleHv /// public void DebugContinue() => _impl.DebugContinue(); + /// + public DebugState GetDebugState() => _impl.GetDebugState(); + /// public ulong DebugPc { diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs index 034795f08..24eefc42d 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs @@ -68,6 +68,11 @@ namespace Ryujinx.Cpu.AppleHv { } + public DebugState GetDebugState() + { + return DebugState.Stopped; + } + public bool GetAndClearInterruptRequested() { return false; diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index 690a5f30d..03d426331 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -228,6 +228,11 @@ namespace Ryujinx.Cpu.AppleHv HvApi.hv_vcpu_run(_vcpu); } + public DebugState GetDebugState() + { + return (DebugState)_debugState; + } + public bool GetAndClearInterruptRequested() { return Interlocked.Exchange(ref _interruptRequested, 0) != 0; diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs index 17be8cc45..f49509db9 100644 --- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs @@ -47,6 +47,7 @@ namespace Ryujinx.Cpu.AppleHv void DebugStop(); bool DebugStep(); void DebugContinue(); + DebugState GetDebugState(); ulong DebugPc { get; set; } } diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index b44c4f318..6df28292e 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -118,7 +118,8 @@ namespace Ryujinx.Cpu void DebugStop(); bool DebugStep(); void DebugContinue(); - + DebugState GetDebugState(); + ulong DebugPc { get; set; } } } diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs index 2f3e0d59b..1a8d54c96 100644 --- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs @@ -125,6 +125,9 @@ namespace Ryujinx.Cpu.Jit /// public void DebugContinue() => _impl.DebugContinue(); + /// + public DebugState GetDebugState() => _impl.GetDebugState(); + /// public ulong DebugPc { diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index d065d4aaa..00986c3b5 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -295,6 +295,27 @@ namespace Ryujinx.HLE.Debugger break; } + if (ss.ConsumePrefix("ThreadExtraInfo,")) + { + ulong? threadId = ss.ReadRemainingAsThreadUid(); + if (threadId == null) + { + ReplyError(); + break; + } + + IExecutionContext ctx = GetThread(threadId.Value); + if (ctx.GetDebugState() == DebugState.Stopped) + { + Reply(ToHex("Stopped")); + } + else + { + Reply(ToHex("Not stopped")); + } + break; + } + if (ss.ConsumePrefix("Xfer:features:read:")) { string feature = ss.ReadUntil(':'); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs index 2cfdbd687..7dc117881 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs @@ -48,6 +48,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { } + public DebugState GetDebugState() + { + return DebugState.Stopped; + } + public void StopRunning() { Running = false; From 5583a60ace7cb5a6d04b55524b9b24db26258a9e Mon Sep 17 00:00:00 2001 From: merry Date: Tue, 22 Feb 2022 20:52:37 +0000 Subject: [PATCH 22/65] stepping is less screwed up now --- src/Ryujinx.HLE/Debugger/Debugger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 00986c3b5..fbb12f419 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -560,7 +560,7 @@ namespace Ryujinx.HLE.Debugger } ctx.DebugStep(); - Reply($"T00thread:{ctx.ThreadUid:x};"); + Reply($"T05thread:{ctx.ThreadUid:x};"); } private void CommandIsAlive(ulong? threadId) From 917a292256721a10880d51adda13f10697e12e85 Mon Sep 17 00:00:00 2001 From: svc64 Date: Wed, 9 Aug 2023 20:29:44 +0300 Subject: [PATCH 23/65] Initialize the debugger before HOS --- src/Ryujinx.HLE/Switch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 89bf7b975..fc7809ff4 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -50,11 +50,11 @@ namespace Ryujinx.HLE AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver); Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags); Gpu = new GpuContext(Configuration.GpuRenderer); + Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, configuration.GdbStubPort) : null; System = new HOS.Horizon(this); Statistics = new PerformanceStatistics(); Hid = new Hid(this, System.HidStorage); Processes = new ProcessLoader(this); - Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, configuration.GdbStubPort) : null; TamperMachine = new TamperMachine(); System.InitializeServices(); From fc361f82a78e8451c357517f2f8f1bdf087c32b0 Mon Sep 17 00:00:00 2001 From: svc64 Date: Wed, 9 Aug 2023 21:12:37 +0300 Subject: [PATCH 24/65] Don't recreate the GDB server socket --- src/Ryujinx.HLE/Debugger/Debugger.cs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index fbb12f419..2f13e531a 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -593,14 +593,12 @@ namespace Ryujinx.HLE.Debugger private void SocketReaderThreadMain() { - restartListen: - try - { - var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort); - ListenerSocket = new TcpListener(endpoint); - ListenerSocket.Start(); - Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client"); + var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort); + ListenerSocket = new TcpListener(endpoint); + ListenerSocket.Start(); + Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client"); + while (true) { ClientSocket = ListenerSocket.AcceptSocket(); ClientSocket.NoDelay = true; ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read); @@ -649,16 +647,6 @@ namespace Ryujinx.HLE.Debugger eof: Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); - goto restartListen; - } - catch (Exception ex) - { - Logger.Error?.Print(LogClass.GdbStub, ex.ToString()); - Logger.Notice.Print(LogClass.GdbStub, "GDB stub socket closed"); - if (!_shuttingDown) - { - goto restartListen; - } } } From 841aa89581b00168e8d7535f8e2d2dce246e6a06 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 23 Sep 2023 17:17:13 +0300 Subject: [PATCH 25/65] Return the address of the current instruction in EmitSynchronization --- src/ARMeilleure/Translation/Translator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 410444c78..5e25394fa 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -498,7 +498,7 @@ namespace ARMeilleure.Translation context.MarkLabel(lblEnd); } - internal static void EmitSynchronization(EmitterContext context) + internal static void EmitSynchronization(ArmEmitterContext context) { long countOffs = NativeContext.GetCounterOffset(); @@ -512,7 +512,8 @@ namespace ARMeilleure.Translation Operand running = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization))); context.BranchIfTrue(lblExit, running, BasicBlockFrequency.Cold); - context.Return(Const(0L)); + OpCode op = context.CurrOp; + context.Return(op != null ? Const(op.Address) : Const(0L)); context.MarkLabel(lblNonZero); count = context.Subtract(count, Const(1)); From ac438d657298a69d1d4c2e6b0775cf40b68e0d09 Mon Sep 17 00:00:00 2001 From: svc64 Date: Mon, 25 Sep 2023 11:00:07 +0300 Subject: [PATCH 26/65] KThread based debug This commit starts a big refactor to the original debugger PR by merryhime. The debugger now interfaces with KThreads instead of the ExecutionContext. The corresponding KThread debug functions properly suspend/resume the thread and call the underlying debug related function in the ExecutionContext. I also added debugging support to the AppleHV ExecutionContext. --- .../Instructions/NativeInterface.cs | 5 ++ src/ARMeilleure/State/ExecutionContext.cs | 28 +++----- src/ARMeilleure/Translation/Translator.cs | 17 ++--- src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 65 +++++++++++++++---- .../AppleHv/HvExecutionContextShadow.cs | 20 ------ .../AppleHv/HvExecutionContextVcpu.cs | 43 ------------ .../AppleHv/IHvExecutionContext.cs | 8 --- src/Ryujinx.Cpu/IExecutionContext.cs | 1 - src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 3 - .../Debugger}/DebugState.cs | 2 +- src/Ryujinx.HLE/Debugger/Debugger.cs | 33 ++++++---- .../Debugger/IDebuggableProcess.cs | 5 +- src/Ryujinx.HLE/HOS/Horizon.cs | 2 +- .../HOS/Kernel/Process/KProcess.cs | 15 ++++- .../Kernel/Process/ProcessExecutionContext.cs | 5 -- .../HOS/Kernel/Threading/KScheduler.cs | 1 + .../HOS/Kernel/Threading/KThread.cs | 48 ++++++++++++++ 17 files changed, 157 insertions(+), 144 deletions(-) rename src/{ARMeilleure/State => Ryujinx.HLE/Debugger}/DebugState.cs (73%) diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs index 0cd3754f7..dd53b17c9 100644 --- a/src/ARMeilleure/Instructions/NativeInterface.cs +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -175,6 +175,11 @@ namespace ARMeilleure.Instructions ExecutionContext context = GetContext(); + if (context.DebugStopped == 1) + { + return false; + } + context.CheckInterrupt(); Statistics.ResumeTimer(); diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs index 47c864bfe..de4528d11 100644 --- a/src/ARMeilleure/State/ExecutionContext.cs +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -101,10 +101,8 @@ namespace ARMeilleure.State private readonly ExceptionCallback _supervisorCallback; private readonly ExceptionCallback _undefinedCallback; - internal int _debugState = (int)DebugState.Running; - internal int _shouldStep = 0; - internal ManualResetEvent _debugHalt = new ManualResetEvent(true); - internal Barrier _stepBarrier = new Barrier(2); + internal int ShouldStep; + internal int DebugStopped; // This is only valid while debugging is enabled. public ulong DebugPc; @@ -160,34 +158,26 @@ namespace ARMeilleure.State public void DebugStop() { - _debugHalt.Reset(); - Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, (int)DebugState.Running); + if (Interlocked.CompareExchange(ref DebugStopped, 1, 0) == 0) + { + RequestInterrupt(); + } } public bool DebugStep() { - if (_debugState != (int)DebugState.Stopped) + if (DebugStopped != 1) { return false; } - _shouldStep = 1; - _debugHalt.Set(); - _stepBarrier.SignalAndWait(); - _debugHalt.Reset(); - _stepBarrier.SignalAndWait(); - + ShouldStep = 1; return true; } public void DebugContinue() { - _debugHalt.Set(); - } - - public DebugState GetDebugState() - { - return (DebugState)_debugState; + Interlocked.CompareExchange(ref DebugStopped, 0, 1); } internal void OnBreak(ulong address, int imm) diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 5e25394fa..99db92280 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -147,21 +147,14 @@ namespace ARMeilleure.Translation { context.DebugPc = ExecuteSingle(context, context.DebugPc); - while (context._debugState != (int)DebugState.Running) + while (context.DebugStopped == 1) { - Interlocked.CompareExchange(ref context._debugState, (int)DebugState.Stopped, (int)DebugState.Stopping); - context._debugHalt.WaitOne(); - if (Interlocked.CompareExchange(ref context._shouldStep, 0, 1) == 1) + if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1) { context.DebugPc = Step(context, context.DebugPc); - - context._stepBarrier.SignalAndWait(); - context._stepBarrier.SignalAndWait(); - } - else - { - Interlocked.CompareExchange(ref context._debugState, (int)DebugState.Running, (int)DebugState.Stopped); + context.RequestInterrupt(); } + context.CheckInterrupt(); } } while (context.Running && context.DebugPc != 0); @@ -222,7 +215,7 @@ namespace ARMeilleure.Translation return nextAddr; } - public ulong Step(State.ExecutionContext context, ulong address) + private ulong Step(State.ExecutionContext context, ulong address) { TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index e6e33ea6b..4b6f4d2c0 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -70,6 +70,8 @@ namespace Ryujinx.Cpu.AppleHv private readonly ICounter _counter; private readonly IHvExecutionContext _shadowContext; private IHvExecutionContext _impl; + private int _shouldStep; + private int _debugStopped; private readonly ExceptionCallbacks _exceptionCallbacks; @@ -131,22 +133,37 @@ namespace Ryujinx.Cpu.AppleHv } /// - public void DebugStop() => _impl.DebugStop(); + public void DebugStop() + { + if (Interlocked.CompareExchange(ref _debugStopped, 1, 0) == 0) + { + RequestInterrupt(); + } + } /// - public bool DebugStep() => _impl.DebugStep(); + public bool DebugStep() + { + if (_debugStopped != 1) + { + return false; + } + + _shouldStep = 1; + return true; + } /// - public void DebugContinue() => _impl.DebugContinue(); - - /// - public DebugState GetDebugState() => _impl.GetDebugState(); + public void DebugContinue() + { + Interlocked.CompareExchange(ref _debugStopped, 0, 1); + } /// public ulong DebugPc { - get => _impl.DebugPc; - set => _impl.DebugPc = value; + get => Pc; + set => Pc = value; } /// @@ -164,6 +181,18 @@ namespace Ryujinx.Cpu.AppleHv while (Running) { + if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1) + { + uint currentEl = Pstate & ~(0xfffffff0); + if (currentEl == 0b0101) + { + HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError(); + spsr |= (1 << 21); + HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr); + } + HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1); + } + HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError(); HvExitReason reason = vcpu.ExitInfo->Reason; @@ -231,6 +260,21 @@ namespace Ryujinx.Cpu.AppleHv SupervisorCallHandler(elr - 4UL, id); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); break; + case ExceptionClass.SoftwareStepLowerEl: + HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError(); + spsr &= ~((ulong)(1 << 21)); + HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError(); + HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0); + ReturnToPool(vcpu); + InterruptHandler(); + vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); + break; + case ExceptionClass.BrkAarch64: + ReturnToPool(vcpu); + BreakHandler(elr, (ushort)esr); + InterruptHandler(); + vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); + break; default: throw new Exception($"Unhandled guest exception {ec}."); } @@ -241,10 +285,7 @@ namespace Ryujinx.Cpu.AppleHv // TODO: Invalidate only the range that was modified? return HvAddressSpace.KernelRegionTlbiEretAddress; } - else - { - return HvAddressSpace.KernelRegionEretAddress; - } + return HvAddressSpace.KernelRegionEretAddress; } private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr) diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs index 24eefc42d..4ea5f276d 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs @@ -23,8 +23,6 @@ namespace Ryujinx.Cpu.AppleHv private readonly ulong[] _x; private readonly V128[] _v; - public ulong DebugPc { get; set; } - public HvExecutionContextShadow() { _x = new ulong[32]; @@ -55,24 +53,6 @@ namespace Ryujinx.Cpu.AppleHv { } - public void DebugStop() - { - } - - public bool DebugStep() - { - return false; - } - - public void DebugContinue() - { - } - - public DebugState GetDebugState() - { - return DebugState.Stopped; - } - public bool GetAndClearInterruptRequested() { return false; diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index 03d426331..4e4b6ef59 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -13,14 +13,6 @@ namespace Ryujinx.Cpu.AppleHv private static readonly SetSimdFpReg _setSimdFpReg; private static readonly IntPtr _setSimdFpRegNativePtr; - internal int _debugState = (int)DebugState.Running; - internal int _shouldStep = 0; - internal ManualResetEvent _debugHalt = new ManualResetEvent(true); - internal Barrier _stepBarrier = new Barrier(2); - - // This is only valid while debugging is enabled. - public ulong DebugPc { get; set; } - public ulong ThreadUid { get; set; } static HvExecutionContextVcpu() @@ -198,41 +190,6 @@ namespace Ryujinx.Cpu.AppleHv } } - public void DebugStop() - { - _debugHalt.Reset(); - Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, (int)DebugState.Running); - ulong vcpu = _vcpu; - HvApi.hv_vcpus_exit(ref vcpu, 1); - } - - public bool DebugStep() - { - if (_debugState != (int)DebugState.Stopped) - { - return false; - } - - _shouldStep = 1; - _debugHalt.Set(); - _stepBarrier.SignalAndWait(); - _debugHalt.Reset(); - _stepBarrier.SignalAndWait(); - - return true; - } - - public void DebugContinue() - { - _debugHalt.Set(); - HvApi.hv_vcpu_run(_vcpu); - } - - public DebugState GetDebugState() - { - return (DebugState)_debugState; - } - public bool GetAndClearInterruptRequested() { return Interlocked.Exchange(ref _interruptRequested, 0) != 0; diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs index f49509db9..f30030406 100644 --- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs @@ -42,13 +42,5 @@ namespace Ryujinx.Cpu.AppleHv void RequestInterrupt(); bool GetAndClearInterruptRequested(); - - // TODO: comments - void DebugStop(); - bool DebugStep(); - void DebugContinue(); - DebugState GetDebugState(); - - ulong DebugPc { get; set; } } } diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index 6df28292e..2f4abdedc 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -118,7 +118,6 @@ namespace Ryujinx.Cpu void DebugStop(); bool DebugStep(); void DebugContinue(); - DebugState GetDebugState(); ulong DebugPc { get; set; } } diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs index 1a8d54c96..2f3e0d59b 100644 --- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs @@ -125,9 +125,6 @@ namespace Ryujinx.Cpu.Jit /// public void DebugContinue() => _impl.DebugContinue(); - /// - public DebugState GetDebugState() => _impl.GetDebugState(); - /// public ulong DebugPc { diff --git a/src/ARMeilleure/State/DebugState.cs b/src/Ryujinx.HLE/Debugger/DebugState.cs similarity index 73% rename from src/ARMeilleure/State/DebugState.cs rename to src/Ryujinx.HLE/Debugger/DebugState.cs index 0b00781fe..c2c387847 100644 --- a/src/ARMeilleure/State/DebugState.cs +++ b/src/Ryujinx.HLE/Debugger/DebugState.cs @@ -1,4 +1,4 @@ -namespace ARMeilleure.State +namespace Ryujinx.HLE.Debugger { public enum DebugState { diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 2f13e531a..70877daea 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -2,6 +2,8 @@ using ARMeilleure.State; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Memory; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Concurrent; using System.Linq; @@ -46,8 +48,8 @@ namespace Ryujinx.HLE.Debugger private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads(); private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids(); - private Ryujinx.Cpu.IExecutionContext GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThreadContext(threadUid); - private Ryujinx.Cpu.IExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); + private KThread GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThread(threadUid); + private KThread[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; private void InvalidateCacheRegion(ulong address, ulong size) => Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size); @@ -304,8 +306,8 @@ namespace Ryujinx.HLE.Debugger break; } - IExecutionContext ctx = GetThread(threadId.Value); - if (ctx.GetDebugState() == DebugState.Stopped) + KThread thread = GetThread(threadId.Value); + if (thread.GetDebugState() == DebugState.Stopped) { Reply(ToHex("Stopped")); } @@ -387,7 +389,7 @@ namespace Ryujinx.HLE.Debugger return; } - GetThread(cThread.Value).DebugPc = newPc.Value; + GetThread(cThread.Value).Context.DebugPc = newPc.Value; } foreach (var thread in GetThreads()) @@ -410,7 +412,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value); + var ctx = GetThread(gThread.Value).Context; string registers = ""; for (int i = 0; i < GdbRegisterCount; i++) { @@ -428,7 +430,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value); + var ctx = GetThread(gThread.Value).Context; for (int i = 0; i < GdbRegisterCount; i++) { if (!GdbWriteRegister(ctx, i, ss)) @@ -513,7 +515,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value); + var ctx = GetThread(gThread.Value).Context; string result = GdbReadRegister(ctx, gdbRegId); if (result != null) { @@ -533,7 +535,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value); + var ctx = GetThread(gThread.Value).Context; if (GdbWriteRegister(ctx, gdbRegId, ss) && ss.IsEmpty()) { ReplyOK(); @@ -552,15 +554,16 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(cThread.Value); + var thread = GetThread(cThread.Value); if (newPc.HasValue) { - ctx.DebugPc = newPc.Value; + thread.Context.DebugPc = newPc.Value; } - ctx.DebugStep(); - Reply($"T05thread:{ctx.ThreadUid:x};"); + thread.DebugStep(); + + Reply($"T05thread:{thread.ThreadUid:x};"); } private void CommandIsAlive(ulong? threadId) @@ -721,7 +724,9 @@ namespace Ryujinx.HLE.Debugger public void ThreadBreak(IExecutionContext ctx, ulong address, int imm) { - ctx.DebugStop(); + KThread thread = GetThread(ctx.ThreadUid); + + thread.DebugStop(); Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}"); diff --git a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs index e31747146..513892960 100644 --- a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs +++ b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs @@ -1,12 +1,13 @@ using Ryujinx.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; namespace Ryujinx.HLE.Debugger { - public interface IDebuggableProcess + internal interface IDebuggableProcess { void DebugStopAllThreads(); ulong[] DebugGetThreadUids(); - Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid); + public KThread DebugGetThread(ulong threadUid); IVirtualMemoryManager CpuMemory { get; } void InvalidateCacheRegion(ulong address, ulong size); } diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index a19546811..4f9bff96b 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -475,7 +475,7 @@ namespace Ryujinx.HLE.HOS IsPaused = pause; } - public IDebuggableProcess DebugGetApplicationProcess() + internal IDebuggableProcess DebugGetApplicationProcess() { lock (KernelContext.Processes) { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 31a8ab916..261ec649c 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback; namespace Ryujinx.HLE.HOS.Kernel.Process { @@ -748,6 +749,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { KThread currentThread = KernelStatic.GetCurrentThread(); + if (currentThread.GetDebugState() != DebugState.Running) + { + KernelContext.CriticalSection.Enter(); + currentThread.Suspend(ThreadSchedState.ThreadPauseFlag); + currentThread.DebugHalt.Set(); + KernelContext.CriticalSection.Leave(); + } + if (currentThread.Context.Running && currentThread.Owner != null && currentThread.GetUserDisableCount() != 0 && @@ -1200,7 +1209,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { foreach (KThread thread in _parent._threads) { - thread.Context.DebugStop(); + thread.DebugStop(); } } } @@ -1213,11 +1222,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } - public Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid) + public KThread DebugGetThread(ulong threadUid) { lock (_parent._threadingLock) { - return _parent._threads.FirstOrDefault(x => x.ThreadUid == threadUid)?.Context; + return _parent._threads.FirstOrDefault(x => x.ThreadUid == threadUid); } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs index 7dc117881..2cfdbd687 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs @@ -48,11 +48,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { } - public DebugState GetDebugState() - { - return DebugState.Stopped; - } - public void StopRunning() { Running = false; diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs index 8ef77902c..dce99ec88 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -296,6 +296,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.SchedulerWaitEvent.Reset(); currentThread.ThreadContext.Unlock(); + currentThread.DebugHalt.Set(); // Wake all the threads that might be waiting until this thread context is unlocked. for (int core = 0; core < CpuCoresCount; core++) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 4e71faf01..ed4924d3f 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Logging; using Ryujinx.Cpu; +using Ryujinx.HLE.Debugger; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.SupervisorCall; @@ -114,6 +115,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private readonly object _activityOperationLock = new(); + private int _debugState = (int)DebugState.Running; + internal readonly ManualResetEvent DebugHalt = new(false); + public KThread(KernelContext context) : base(context) { WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects]; @@ -1432,5 +1436,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 0); } + + public bool DebugStep() + { + if (_debugState != (int)DebugState.Stopped || !Context.DebugStep()) + { + return false; + } + + DebugHalt.Reset(); + SetActivity(false); + DebugHalt.WaitOne(); + return true; + } + + public void DebugStop() + { + if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, + (int)DebugState.Running) != (int)DebugState.Running) + { + return; + } + + Context.DebugStop(); + DebugHalt.WaitOne(); + DebugHalt.Reset(); + _debugState = (int)DebugState.Stopped; + } + + public void DebugContinue() + { + if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running, + (int)DebugState.Stopped) != (int)DebugState.Stopped) + { + return; + } + + Context.DebugContinue(); + SetActivity(false); + } + + public DebugState GetDebugState() + { + return (DebugState)_debugState; + } } } From 40584e0e4530eeb3f86b4f9231bf9c7b30bf505e Mon Sep 17 00:00:00 2001 From: svc64 Date: Mon, 25 Sep 2023 11:02:31 +0300 Subject: [PATCH 27/65] HvExecutionContext: fix DebugPc --- src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index 4b6f4d2c0..db195e738 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -162,8 +162,8 @@ namespace Ryujinx.Cpu.AppleHv /// public ulong DebugPc { - get => Pc; - set => Pc = value; + get => _impl.ElrEl1; + set => _impl.ElrEl1 = value; } /// From 81c399ec3ebd11f6af6bd62d20d2dce19c28991e Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 30 Sep 2023 11:22:30 +0300 Subject: [PATCH 28/65] Better locking in debug methods --- .../HOS/Kernel/Threading/KThread.cs | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index ed4924d3f..aac0f2f8d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1253,6 +1253,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private void ThreadStart() { _schedulerWaitEvent.WaitOne(); + DebugHalt.Reset(); KernelStatic.SetKernelContext(KernelContext, this); if (_customThreadStart != null) @@ -1439,41 +1440,62 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public bool DebugStep() { - if (_debugState != (int)DebugState.Stopped || !Context.DebugStep()) + lock (_activityOperationLock) { - return false; - } + if (_debugState != (int)DebugState.Stopped + || (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0 + || !Context.DebugStep()) + { + return false; + } - DebugHalt.Reset(); - SetActivity(false); - DebugHalt.WaitOne(); - return true; + DebugHalt.Reset(); + Resume(ThreadSchedState.ThreadPauseFlag); + DebugHalt.WaitOne(); + + return true; + } } public void DebugStop() { - if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, - (int)DebugState.Running) != (int)DebugState.Running) + lock (_activityOperationLock) { - return; - } + if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, + (int)DebugState.Running) != (int)DebugState.Running) + { + return; + } - Context.DebugStop(); - DebugHalt.WaitOne(); - DebugHalt.Reset(); - _debugState = (int)DebugState.Stopped; + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + { + Suspend(ThreadSchedState.ThreadPauseFlag); + } + + Context.DebugStop(); + DebugHalt.WaitOne(); + + _debugState = (int)DebugState.Stopped; + } } public void DebugContinue() { - if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running, - (int)DebugState.Stopped) != (int)DebugState.Stopped) + lock (_activityOperationLock) { - return; - } + if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running, + (int)DebugState.Stopped) != (int)DebugState.Stopped) + { + return; + } - Context.DebugContinue(); - SetActivity(false); + Context.DebugContinue(); + + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) + { + Resume(ThreadSchedState.ThreadPauseFlag); + } + } } public DebugState GetDebugState() From 65d7a16a872d9416efd67e837a7736dc4f5f8e35 Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 6 Oct 2023 15:33:35 +0300 Subject: [PATCH 29/65] Debugger refactor --- .../Instructions/NativeInterface.cs | 4 +- src/ARMeilleure/State/ExecutionContext.cs | 38 +++------- src/ARMeilleure/Translation/Translator.cs | 18 ++--- src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs | 10 +++ src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 69 ++++++++++--------- .../AppleHv/HvExecutionContextVcpu.cs | 1 + src/Ryujinx.Cpu/IExecutionContext.cs | 6 +- src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 13 ++-- src/Ryujinx.HLE/Debugger/Debugger.cs | 30 ++++++-- .../HOS/Kernel/Process/KProcess.cs | 8 --- .../Kernel/Process/ProcessExecutionContext.cs | 13 +--- .../HOS/Kernel/Threading/KThread.cs | 19 ++--- 12 files changed, 115 insertions(+), 114 deletions(-) create mode 100644 src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs index dd53b17c9..f69cc1e0a 100644 --- a/src/ARMeilleure/Instructions/NativeInterface.cs +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -2,6 +2,8 @@ using ARMeilleure.Memory; using ARMeilleure.State; using ARMeilleure.Translation; using System; +using System.Threading; +using ExecutionContext = ARMeilleure.State.ExecutionContext; namespace ARMeilleure.Instructions { @@ -175,7 +177,7 @@ namespace ARMeilleure.Instructions ExecutionContext context = GetContext(); - if (context.DebugStopped == 1) + if (Optimizations.EnableDebugging && context.Interrupted) { return false; } diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs index de4528d11..a414dd3f4 100644 --- a/src/ARMeilleure/State/ExecutionContext.cs +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -14,7 +14,7 @@ namespace ARMeilleure.State internal IntPtr NativeContextPtr => _nativeContext.BasePtr; - private bool _interrupted; + internal bool Interrupted { get; private set; } private readonly ICounter _counter; @@ -103,9 +103,9 @@ namespace ARMeilleure.State internal int ShouldStep; internal int DebugStopped; - - // This is only valid while debugging is enabled. - public ulong DebugPc; + + public ulong DebugPc; // This is only valid while debugging is enabled. + public Barrier StepBarrier = new Barrier(2); public ExecutionContext( IJitMemoryAllocator allocator, @@ -141,9 +141,9 @@ namespace ARMeilleure.State internal void CheckInterrupt() { - if (_interrupted) + if (Interrupted) { - _interrupted = false; + Interrupted = false; _interruptCallback?.Invoke(this); } @@ -153,31 +153,13 @@ namespace ARMeilleure.State public void RequestInterrupt() { - _interrupted = true; + Interrupted = true; } - public void DebugStop() + public void RequestDebugStep() { - if (Interlocked.CompareExchange(ref DebugStopped, 1, 0) == 0) - { - RequestInterrupt(); - } - } - - public bool DebugStep() - { - if (DebugStopped != 1) - { - return false; - } - - ShouldStep = 1; - return true; - } - - public void DebugContinue() - { - Interlocked.CompareExchange(ref DebugStopped, 0, 1); + Interlocked.Exchange(ref ShouldStep, 1); + RequestInterrupt(); } internal void OnBreak(ulong address, int imm) diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 99db92280..035005aa1 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -145,17 +145,17 @@ namespace ARMeilleure.Translation context.DebugPc = address; do { - context.DebugPc = ExecuteSingle(context, context.DebugPc); - - while (context.DebugStopped == 1) + if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1) { - if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1) - { - context.DebugPc = Step(context, context.DebugPc); - context.RequestInterrupt(); - } - context.CheckInterrupt(); + context.DebugPc = Step(context, context.DebugPc); + context.StepBarrier.SignalAndWait(); + context.StepBarrier.SignalAndWait(); } + else + { + context.DebugPc = ExecuteSingle(context, context.DebugPc); + } + context.CheckInterrupt(); } while (context.Running && context.DebugPc != 0); } diff --git a/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs new file mode 100644 index 000000000..96e3ae59a --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Cpu.AppleHv.Arm +{ + enum ExceptionLevel: uint + { + PstateMask = 0xfffffff0, + EL1h = 0b0101, + El1t = 0b0100, + EL0 = 0b0000, + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index db195e738..220b3bad6 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -11,7 +11,18 @@ namespace Ryujinx.Cpu.AppleHv class HvExecutionContext : IExecutionContext { /// - public ulong Pc => _impl.ElrEl1; + public ulong Pc + { + get + { + uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask); + if (currentEl == (uint)ExceptionLevel.EL1h) + { + return _impl.ElrEl1; + } + return _impl.Pc; + } + } /// public long TpidrEl0 @@ -71,7 +82,6 @@ namespace Ryujinx.Cpu.AppleHv private readonly IHvExecutionContext _shadowContext; private IHvExecutionContext _impl; private int _shouldStep; - private int _debugStopped; private readonly ExceptionCallbacks _exceptionCallbacks; @@ -83,6 +93,7 @@ namespace Ryujinx.Cpu.AppleHv _shadowContext = new HvExecutionContextShadow(); _impl = _shadowContext; _exceptionCallbacks = exceptionCallbacks; + StepBarrier = new(2); Running = true; } @@ -133,39 +144,31 @@ namespace Ryujinx.Cpu.AppleHv } /// - public void DebugStop() + public void RequestDebugStep() { - if (Interlocked.CompareExchange(ref _debugStopped, 1, 0) == 0) - { - RequestInterrupt(); - } - } - - /// - public bool DebugStep() - { - if (_debugStopped != 1) - { - return false; - } - - _shouldStep = 1; - return true; - } - - /// - public void DebugContinue() - { - Interlocked.CompareExchange(ref _debugStopped, 0, 1); + Interlocked.Exchange(ref _shouldStep, 1); } /// public ulong DebugPc { - get => _impl.ElrEl1; - set => _impl.ElrEl1 = value; + get => Pc; + set + { + uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask); + if (currentEl == (uint)ExceptionLevel.EL1h) + { + _impl.ElrEl1 = value; + } + else + { + _impl.Pc = value; + } + } } + public Barrier StepBarrier { get; private set; } + /// public void StopRunning() { @@ -183,13 +186,17 @@ namespace Ryujinx.Cpu.AppleHv { if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1) { - uint currentEl = Pstate & ~(0xfffffff0); - if (currentEl == 0b0101) + uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask); + if (currentEl == (uint)ExceptionLevel.EL1h) { HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError(); spsr |= (1 << 21); HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr); } + else + { + Pstate |= (1 << 21); + } HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1); } @@ -206,7 +213,6 @@ namespace Ryujinx.Cpu.AppleHv { throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc})."); } - address = SynchronousException(memoryManager, ref vcpu); HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError(); } @@ -266,13 +272,14 @@ namespace Ryujinx.Cpu.AppleHv HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError(); HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0); ReturnToPool(vcpu); + StepBarrier.SignalAndWait(); + StepBarrier.SignalAndWait(); InterruptHandler(); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); break; case ExceptionClass.BrkAarch64: ReturnToPool(vcpu); BreakHandler(elr, (ushort)esr); - InterruptHandler(); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); break; default: diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index 4e4b6ef59..4299902b2 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -138,6 +138,7 @@ namespace Ryujinx.Cpu.AppleHv private readonly ulong _vcpu; private int _interruptRequested; + private int _breakRequested; public HvExecutionContextVcpu(ulong vcpu) { diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index 2f4abdedc..40438d6fc 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -1,5 +1,6 @@ using ARMeilleure.State; using System; +using System.Threading; namespace Ryujinx.Cpu { @@ -115,10 +116,9 @@ namespace Ryujinx.Cpu void StopRunning(); // TODO: comments - void DebugStop(); - bool DebugStep(); - void DebugContinue(); + void RequestDebugStep(); ulong DebugPc { get; set; } + Barrier StepBarrier { get; } } } diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs index 2f3e0d59b..e12c28d30 100644 --- a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs @@ -1,5 +1,7 @@ using ARMeilleure.Memory; using ARMeilleure.State; +using System.Threading; +using ExecutionContext = ARMeilleure.State.ExecutionContext; namespace Ryujinx.Cpu.Jit { @@ -117,13 +119,7 @@ namespace Ryujinx.Cpu.Jit } /// - public void DebugStop() => _impl.DebugStop(); - - /// - public bool DebugStep() => _impl.DebugStep(); - - /// - public void DebugContinue() => _impl.DebugContinue(); + public void RequestDebugStep() => _impl.RequestDebugStep(); /// public ulong DebugPc @@ -132,6 +128,9 @@ namespace Ryujinx.Cpu.Jit set => _impl.DebugPc = value; } + /// + public Barrier StepBarrier => _impl.StepBarrier; + /// public void StopRunning() { diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 70877daea..ce0102ecc 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -53,6 +53,7 @@ namespace Ryujinx.HLE.Debugger private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; private void InvalidateCacheRegion(ulong address, ulong size) => Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size); + private KernelContext KernelContext => Device.System.KernelContext; const int GdbRegisterCount = 68; @@ -77,7 +78,7 @@ namespace Ryujinx.HLE.Debugger } } - private string GdbReadRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId) + private string GdbReadRegister(IExecutionContext state, int gdbRegId) { switch (gdbRegId) { @@ -724,13 +725,32 @@ namespace Ryujinx.HLE.Debugger public void ThreadBreak(IExecutionContext ctx, ulong address, int imm) { - KThread thread = GetThread(ctx.ThreadUid); - - thread.DebugStop(); - Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}"); Messages.Add(new ThreadBreakMessage(ctx, address, imm)); + + KThread currentThread = GetThread(ctx.ThreadUid); + + if (currentThread.Context.Running && + currentThread.Owner != null && + currentThread.GetUserDisableCount() != 0 && + currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null) + { + KernelContext.CriticalSection.Enter(); + + currentThread.Owner.PinThread(currentThread); + + currentThread.SetUserInterruptFlag(); + + KernelContext.CriticalSection.Leave(); + } + + if (currentThread.IsSchedulable) + { + KernelContext.Schedulers[currentThread.CurrentCore].Schedule(); + } + + currentThread.HandlePostSyscall(); } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 261ec649c..9cdd90de5 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -749,14 +749,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { KThread currentThread = KernelStatic.GetCurrentThread(); - if (currentThread.GetDebugState() != DebugState.Running) - { - KernelContext.CriticalSection.Enter(); - currentThread.Suspend(ThreadSchedState.ThreadPauseFlag); - currentThread.DebugHalt.Set(); - KernelContext.CriticalSection.Leave(); - } - if (currentThread.Context.Running && currentThread.Owner != null && currentThread.GetUserDisableCount() != 0 && diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs index 2cfdbd687..717ed9c77 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs @@ -1,5 +1,6 @@ using ARMeilleure.State; using Ryujinx.Cpu; +using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Process { @@ -24,6 +25,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private readonly ulong[] _x = new ulong[32]; public ulong DebugPc { get; set; } + public Barrier StepBarrier { get; } public ulong GetX(int index) => _x[index]; public void SetX(int index, ulong value) => _x[index] = value; @@ -35,16 +37,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { } - public void DebugStop() - { - } - - public bool DebugStep() - { - return false; - } - - public void DebugContinue() + public void RequestDebugStep() { } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index aac0f2f8d..dcc992b71 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1443,15 +1443,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading lock (_activityOperationLock) { if (_debugState != (int)DebugState.Stopped - || (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0 - || !Context.DebugStep()) + || (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) { return false; } - DebugHalt.Reset(); + Context.RequestDebugStep(); Resume(ThreadSchedState.ThreadPauseFlag); - DebugHalt.WaitOne(); + Context.StepBarrier.SignalAndWait(); + Suspend(ThreadSchedState.ThreadPauseFlag); + Context.StepBarrier.SignalAndWait(); return true; } @@ -1467,12 +1468,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return; } - if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) - { - Suspend(ThreadSchedState.ThreadPauseFlag); - } - - Context.DebugStop(); + Suspend(ThreadSchedState.ThreadPauseFlag); + Context.RequestInterrupt(); DebugHalt.WaitOne(); _debugState = (int)DebugState.Stopped; @@ -1489,8 +1486,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading return; } - Context.DebugContinue(); - if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) { Resume(ThreadSchedState.ThreadPauseFlag); From 0c57663ea36c29b356f582888f59f86b7f289b25 Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 6 Oct 2023 15:53:17 +0300 Subject: [PATCH 30/65] Add comments on debug stuff & fix warnings --- .../Instructions/NativeInterface.cs | 3 ++- src/ARMeilleure/State/ExecutionContext.cs | 7 +++--- src/Ryujinx.Cpu/IExecutionContext.cs | 25 +++++++++++++++++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs index f69cc1e0a..982d6915f 100644 --- a/src/ARMeilleure/Instructions/NativeInterface.cs +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -176,7 +176,8 @@ namespace ARMeilleure.Instructions Statistics.PauseTimer(); ExecutionContext context = GetContext(); - + + // If debugging, we'll handle interrupts outside if (Optimizations.EnableDebugging && context.Interrupted) { return false; diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs index a414dd3f4..2df13e286 100644 --- a/src/ARMeilleure/State/ExecutionContext.cs +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -102,10 +102,8 @@ namespace ARMeilleure.State private readonly ExceptionCallback _undefinedCallback; internal int ShouldStep; - internal int DebugStopped; - - public ulong DebugPc; // This is only valid while debugging is enabled. - public Barrier StepBarrier = new Barrier(2); + public ulong DebugPc { get; set; } + public Barrier StepBarrier { get; } public ExecutionContext( IJitMemoryAllocator allocator, @@ -123,6 +121,7 @@ namespace ARMeilleure.State _undefinedCallback = undefinedCallback; Running = true; + StepBarrier = new Barrier(2); _nativeContext.SetCounter(MinCountForCheck); } diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index 40438d6fc..ec9ef6a42 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -115,10 +115,31 @@ namespace Ryujinx.Cpu /// void StopRunning(); - // TODO: comments + /// + /// Requests the thread to stop running temporarily and call . + /// + /// + /// The thread might not pause immediately. + /// One must not assume that guest code is no longer being executed by the thread after calling this function. + /// After single stepping, the thread should signal and wait on twice to allow + /// changing the thread state after stepping. + /// void RequestDebugStep(); - ulong DebugPc { get; set; } + /// + /// Step barrier + /// + /// + /// Should be signaled and waited on twice after single-stepping. + /// Barrier StepBarrier { get; } + + /// + /// Current Program Counter (for debugging). + /// + /// + /// PC register for the debugger. Must not be accessed while the thread isn't stopped for debugging. + /// + ulong DebugPc { get; set; } } } From bc0ba93e92096f67c434fd14bc4e9d8d58886c89 Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 6 Oct 2023 15:56:29 +0300 Subject: [PATCH 31/65] Remove old variable --- src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index 4299902b2..4e4b6ef59 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -138,7 +138,6 @@ namespace Ryujinx.Cpu.AppleHv private readonly ulong _vcpu; private int _interruptRequested; - private int _breakRequested; public HvExecutionContextVcpu(ulong vcpu) { From 6ecc8295163b8bee2a2b056aa112c227fd4ce1d2 Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 6 Oct 2023 16:46:18 +0300 Subject: [PATCH 32/65] Clarify StepBarrier --- src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 2 +- src/Ryujinx.Cpu/IExecutionContext.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index 220b3bad6..1bd6db34d 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -167,7 +167,7 @@ namespace Ryujinx.Cpu.AppleHv } } - public Barrier StepBarrier { get; private set; } + public Barrier StepBarrier { get; } /// public void StopRunning() diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs index ec9ef6a42..eb5f93147 100644 --- a/src/Ryujinx.Cpu/IExecutionContext.cs +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -130,6 +130,7 @@ namespace Ryujinx.Cpu /// Step barrier /// /// + /// The participant count should be 2. /// Should be signaled and waited on twice after single-stepping. /// Barrier StepBarrier { get; } From 5e65fd88084342585e88f6722c5a80908711cded Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 6 Oct 2023 17:24:58 +0300 Subject: [PATCH 33/65] Reply with an error if we fail to step --- src/Ryujinx.HLE/Debugger/Debugger.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index ce0102ecc..87676cb2f 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -562,9 +562,14 @@ namespace Ryujinx.HLE.Debugger thread.Context.DebugPc = newPc.Value; } - thread.DebugStep(); - - Reply($"T05thread:{thread.ThreadUid:x};"); + if (!thread.DebugStep()) + { + ReplyError(); + } + else + { + Reply($"T05thread:{thread.ThreadUid:x};"); + } } private void CommandIsAlive(ulong? threadId) From de4ec65bd7c7cf869ad1c5c7d48179babb995c49 Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 6 Oct 2023 17:35:23 +0300 Subject: [PATCH 34/65] Handle GDB server reconnections --- src/Ryujinx.HLE/Debugger/Debugger.cs | 75 ++++++++++++++++------------ 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 87676cb2f..e11d12c2c 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -6,6 +6,7 @@ using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Concurrent; +using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; @@ -616,46 +617,56 @@ namespace Ryujinx.HLE.Debugger while (true) { - switch (ReadStream.ReadByte()) + try { - case -1: - goto eof; - case '+': - continue; - case '-': - Logger.Notice.Print(LogClass.GdbStub, "NACK received!"); - continue; - case '\x03': - Messages.Add(new BreakInMessage()); - break; - case '$': - string cmd = ""; - while (true) - { - int x = ReadStream.ReadByte(); - if (x == -1) - goto eof; - if (x == '#') - break; - cmd += (char)x; - } + switch (ReadStream.ReadByte()) + { + case -1: + goto eof; + case '+': + continue; + case '-': + Logger.Notice.Print(LogClass.GdbStub, "NACK received!"); + continue; + case '\x03': + Messages.Add(new BreakInMessage()); + break; + case '$': + string cmd = ""; + while (true) + { + int x = ReadStream.ReadByte(); + if (x == -1) + goto eof; + if (x == '#') + break; + cmd += (char)x; + } - string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}"; - if (checksum == $"{CalculateChecksum(cmd):x2}") - { - Messages.Add(new CommandMessage(cmd)); - } - else - { - Messages.Add(new SendNackMessage()); - } + string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}"; + if (checksum == $"{CalculateChecksum(cmd):x2}") + { + Messages.Add(new CommandMessage(cmd)); + } + else + { + Messages.Add(new SendNackMessage()); + } - break; + break; + } + } + catch (IOException) + { + goto eof; } } eof: Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); + ReadStream.Close(); + WriteStream.Close(); + ClientSocket.Close(); } } From 5d42332d75fb0427c04ce7fbcb6e8fd29450530b Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 7 Oct 2023 10:33:06 +0300 Subject: [PATCH 35/65] Don't step when the thread is blocked --- src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index dcc992b71..1500a9545 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1443,7 +1443,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading lock (_activityOperationLock) { if (_debugState != (int)DebugState.Stopped - || (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + || (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0 + || MutexOwner != null) { return false; } From edbd4bfc298b715b0e25680824136e3d528580a2 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 7 Oct 2023 13:34:54 +0300 Subject: [PATCH 36/65] Revert "Return the address of the current instruction in EmitSynchronization" This reverts commit 1331589a22ccf21c02951db96d0335a10738ae4c. --- src/ARMeilleure/Translation/Translator.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 035005aa1..e68fd34d9 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -491,7 +491,7 @@ namespace ARMeilleure.Translation context.MarkLabel(lblEnd); } - internal static void EmitSynchronization(ArmEmitterContext context) + internal static void EmitSynchronization(EmitterContext context) { long countOffs = NativeContext.GetCounterOffset(); @@ -505,8 +505,7 @@ namespace ARMeilleure.Translation Operand running = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization))); context.BranchIfTrue(lblExit, running, BasicBlockFrequency.Cold); - OpCode op = context.CurrOp; - context.Return(op != null ? Const(op.Address) : Const(0L)); + context.Return(Const(0L)); context.MarkLabel(lblNonZero); count = context.Subtract(count, Const(1)); From 144aa2f5b17420823efee0eeebe0876ace13557c Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 7 Oct 2023 15:11:17 +0300 Subject: [PATCH 37/65] Fix stepping with JIT --- src/ARMeilleure/Instructions/NativeInterface.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs index 982d6915f..d8b8a02bf 100644 --- a/src/ARMeilleure/Instructions/NativeInterface.cs +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -176,14 +176,12 @@ namespace ARMeilleure.Instructions Statistics.PauseTimer(); ExecutionContext context = GetContext(); - - // If debugging, we'll handle interrupts outside - if (Optimizations.EnableDebugging && context.Interrupted) - { - return false; - } - context.CheckInterrupt(); + // If debugging, we'll handle interrupts outside + if (!Optimizations.EnableDebugging) + { + context.CheckInterrupt(); + } Statistics.ResumeTimer(); From f934d23de450508fe2bc8d6244fea9e48d25b7ba Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 7 Oct 2023 15:11:51 +0300 Subject: [PATCH 38/65] (Hopefully) Properly check if the thread is blocked when stepping --- src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 1500a9545..501344c79 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1442,12 +1442,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { lock (_activityOperationLock) { + KernelContext.CriticalSection.Enter(); + bool blocked = MutexOwner != null || WaitingInArbitration || WaitingSync; if (_debugState != (int)DebugState.Stopped || (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0 - || MutexOwner != null) + || blocked) { + KernelContext.CriticalSection.Leave(); return false; } + KernelContext.CriticalSection.Leave(); Context.RequestDebugStep(); Resume(ThreadSchedState.ThreadPauseFlag); From 0f50273d4fa2244b2525af8feac0d909e801ee04 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 21 Oct 2023 11:06:41 +0300 Subject: [PATCH 39/65] Fix GDB stub settings --- src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs index a71d1428e..8aef6796e 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs @@ -117,8 +117,8 @@ namespace Ryujinx.UI.Windows [GUI] ToggleButton _configureController7; [GUI] ToggleButton _configureController8; [GUI] ToggleButton _configureControllerH; - [GUI] ToggleButton _gdbStubToggle; - [GUI] Adjustment _gdbStubPortSpinAdjustment; + [GUI] ToggleButton _gdbStubToggle; + [GUI] Adjustment _gdbStubPortSpinAdjustment; #pragma warning restore CS0649, IDE0044 public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } @@ -667,6 +667,7 @@ namespace Ryujinx.UI.Windows ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse(_scalingFilter.ActiveId); ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; + ConfigurationState.Instance.Debug.EnableGdbStub.Value = _gdbStubToggle.Active; _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; From e3b806041731c3256e99e89009981bea423bf04e Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 11 Nov 2023 16:21:36 +0200 Subject: [PATCH 40/65] Refactor the debugger interface Also support stepping through blocking syscalls --- src/Ryujinx.HLE/Debugger/Debugger.cs | 40 ++++----- .../Debugger/IDebuggableProcess.cs | 9 +- src/Ryujinx.HLE/HOS/Horizon.cs | 2 +- .../HOS/Kernel/Process/KProcess.cs | 90 +++++++++++++++++-- .../HOS/Kernel/Threading/KThread.cs | 66 -------------- 5 files changed, 109 insertions(+), 98 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index e11d12c2c..10022e1d0 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -47,11 +47,9 @@ namespace Ryujinx.HLE.Debugger HandlerThread.Start(); } - private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads(); - private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids(); - private KThread GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThread(threadUid); - private KThread[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); - private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; + private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess(); + private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray(); + private IVirtualMemoryManager GetMemory() => DebugProcess.CpuMemory; private void InvalidateCacheRegion(ulong address, ulong size) => Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size); private KernelContext KernelContext => Device.System.KernelContext; @@ -171,7 +169,7 @@ namespace Ryujinx.HLE.Debugger break; case ThreadBreakMessage msg: - HaltApplication(); + DebugProcess.DebugStop(); Reply($"T05thread:{msg.Context.ThreadUid:x};"); break; } @@ -289,7 +287,7 @@ namespace Ryujinx.HLE.Debugger if (ss.ConsumeRemaining("fThreadInfo")) { - Reply($"m{string.Join(",", GetThreadIds().Select(x => $"{x:x}"))}"); + Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}"); break; } @@ -308,8 +306,7 @@ namespace Ryujinx.HLE.Debugger break; } - KThread thread = GetThread(threadId.Value); - if (thread.GetDebugState() == DebugState.Stopped) + if (DebugProcess.GetDebugState() == DebugState.Stopped) { Reply(ToHex("Stopped")); } @@ -376,8 +373,8 @@ namespace Ryujinx.HLE.Debugger void CommandQuery() { // GDB is performing initial contact. Stop everything. - HaltApplication(); - gThread = cThread = GetThreadIds().First(); + DebugProcess.DebugStop(); + gThread = cThread = DebugProcess.GetThreadUids().First(); Reply($"T05thread:{cThread:x};"); } @@ -391,13 +388,10 @@ namespace Ryujinx.HLE.Debugger return; } - GetThread(cThread.Value).Context.DebugPc = newPc.Value; + DebugProcess.GetThread(cThread.Value).Context.DebugPc = newPc.Value; } - foreach (var thread in GetThreads()) - { - thread.DebugContinue(); - } + DebugProcess.DebugContinue(); } void CommandDetach() @@ -414,7 +408,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value).Context; + var ctx = DebugProcess.GetThread(gThread.Value).Context; string registers = ""; for (int i = 0; i < GdbRegisterCount; i++) { @@ -432,7 +426,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value).Context; + var ctx = DebugProcess.GetThread(gThread.Value).Context; for (int i = 0; i < GdbRegisterCount; i++) { if (!GdbWriteRegister(ctx, i, ss)) @@ -517,7 +511,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value).Context; + var ctx = DebugProcess.GetThread(gThread.Value).Context; string result = GdbReadRegister(ctx, gdbRegId); if (result != null) { @@ -537,7 +531,7 @@ namespace Ryujinx.HLE.Debugger return; } - var ctx = GetThread(gThread.Value).Context; + var ctx = DebugProcess.GetThread(gThread.Value).Context; if (GdbWriteRegister(ctx, gdbRegId, ss) && ss.IsEmpty()) { ReplyOK(); @@ -556,14 +550,14 @@ namespace Ryujinx.HLE.Debugger return; } - var thread = GetThread(cThread.Value); + var thread = DebugProcess.GetThread(cThread.Value); if (newPc.HasValue) { thread.Context.DebugPc = newPc.Value; } - if (!thread.DebugStep()) + if (!DebugProcess.DebugStep(thread)) { ReplyError(); } @@ -745,7 +739,7 @@ namespace Ryujinx.HLE.Debugger Messages.Add(new ThreadBreakMessage(ctx, address, imm)); - KThread currentThread = GetThread(ctx.ThreadUid); + KThread currentThread = DebugProcess.GetThread(ctx.ThreadUid); if (currentThread.Context.Running && currentThread.Owner != null && diff --git a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs index 513892960..98d2223a6 100644 --- a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs +++ b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs @@ -5,9 +5,12 @@ namespace Ryujinx.HLE.Debugger { internal interface IDebuggableProcess { - void DebugStopAllThreads(); - ulong[] DebugGetThreadUids(); - public KThread DebugGetThread(ulong threadUid); + void DebugStop(); + void DebugContinue(); + bool DebugStep(KThread thread); + KThread GetThread(ulong threadUid); + DebugState GetDebugState(); + ulong[] GetThreadUids(); IVirtualMemoryManager CpuMemory { get; } void InvalidateCacheRegion(ulong address, ulong size); } diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 4f9bff96b..4da62d0f0 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -479,7 +479,7 @@ namespace Ryujinx.HLE.HOS { lock (KernelContext.Processes) { - return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.GdbStubInterface; + return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface; } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 9cdd90de5..80671ccc1 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -91,7 +91,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public IVirtualMemoryManager CpuMemory => Context.AddressSpace; public HleProcessDebugger Debugger { get; private set; } - public IDebuggableProcess GdbStubInterface { get { return new DebuggerInterface(this); } } + public IDebuggableProcess DebugInterface { get; private set; } public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context) { @@ -113,6 +113,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process _threads = new LinkedList(); Debugger = new HleProcessDebugger(this); + DebugInterface = new DebuggerInterface(this); } public Result InitializeKip( @@ -1189,24 +1190,103 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private class DebuggerInterface : IDebuggableProcess { private readonly KProcess _parent; + private readonly KernelContext KernelContext; + private int _debugState = (int)DebugState.Running; public DebuggerInterface(KProcess p) { _parent = p; } - public void DebugStopAllThreads() + public void DebugStop() { + if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, + (int)DebugState.Running) != (int)DebugState.Running) + { + return; + } + + _parent.KernelContext.CriticalSection.Enter(); lock (_parent._threadingLock) { foreach (KThread thread in _parent._threads) { - thread.DebugStop(); + thread.Suspend(ThreadSchedState.ThreadPauseFlag); + thread.Context.RequestInterrupt(); + thread.DebugHalt.WaitOne(); } } + + _debugState = (int)DebugState.Stopped; + _parent.KernelContext.CriticalSection.Leave(); } - public ulong[] DebugGetThreadUids() + public void DebugContinue() + { + if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running, + (int)DebugState.Stopped) != (int)DebugState.Stopped) + { + return; + } + + _parent.KernelContext.CriticalSection.Enter(); + lock (_parent._threadingLock) + { + foreach (KThread thread in _parent._threads) + { + thread.Resume(ThreadSchedState.ThreadPauseFlag); + } + } + _parent.KernelContext.CriticalSection.Leave(); + } + + public bool DebugStep(KThread target) + { + if (_debugState != (int)DebugState.Stopped) + { + return false; + } + _parent.KernelContext.CriticalSection.Enter(); + bool wasPaused = (target.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused; + target.Context.RequestDebugStep(); + target.Resume(ThreadSchedState.ThreadPauseFlag); + if (wasPaused) + { + lock (_parent._threadingLock) + { + foreach (KThread thread in _parent._threads) + { + thread.Resume(ThreadSchedState.ThreadPauseFlag); + } + } + } + _parent.KernelContext.CriticalSection.Leave(); + + target.Context.StepBarrier.SignalAndWait(); + target.Context.StepBarrier.SignalAndWait(); + + _parent.KernelContext.CriticalSection.Enter(); + target.Suspend(ThreadSchedState.ThreadPauseFlag); + if (wasPaused) + { + lock (_parent._threadingLock) + { + foreach (KThread thread in _parent._threads) + { + thread.Suspend(ThreadSchedState.ThreadPauseFlag); + } + } + } + _parent.KernelContext.CriticalSection.Leave(); + return true; + } + + public DebugState GetDebugState() + { + return (DebugState)_debugState; + } + + public ulong[] GetThreadUids() { lock (_parent._threadingLock) { @@ -1214,7 +1294,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } - public KThread DebugGetThread(ulong threadUid) + public KThread GetThread(ulong threadUid) { lock (_parent._threadingLock) { diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 501344c79..adef6ed78 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -115,7 +115,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private readonly object _activityOperationLock = new(); - private int _debugState = (int)DebugState.Running; internal readonly ManualResetEvent DebugHalt = new(false); public KThread(KernelContext context) : base(context) @@ -1437,70 +1436,5 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 0); } - - public bool DebugStep() - { - lock (_activityOperationLock) - { - KernelContext.CriticalSection.Enter(); - bool blocked = MutexOwner != null || WaitingInArbitration || WaitingSync; - if (_debugState != (int)DebugState.Stopped - || (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0 - || blocked) - { - KernelContext.CriticalSection.Leave(); - return false; - } - KernelContext.CriticalSection.Leave(); - - Context.RequestDebugStep(); - Resume(ThreadSchedState.ThreadPauseFlag); - Context.StepBarrier.SignalAndWait(); - Suspend(ThreadSchedState.ThreadPauseFlag); - Context.StepBarrier.SignalAndWait(); - - return true; - } - } - - public void DebugStop() - { - lock (_activityOperationLock) - { - if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, - (int)DebugState.Running) != (int)DebugState.Running) - { - return; - } - - Suspend(ThreadSchedState.ThreadPauseFlag); - Context.RequestInterrupt(); - DebugHalt.WaitOne(); - - _debugState = (int)DebugState.Stopped; - } - } - - public void DebugContinue() - { - lock (_activityOperationLock) - { - if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running, - (int)DebugState.Stopped) != (int)DebugState.Stopped) - { - return; - } - - if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) - { - Resume(ThreadSchedState.ThreadPauseFlag); - } - } - } - - public DebugState GetDebugState() - { - return (DebugState)_debugState; - } } } From 1684a9d7a2e077c4d3659c14a01a142fd63d8cd8 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 11 Nov 2023 17:08:25 +0200 Subject: [PATCH 41/65] Fix GDB server crashes --- src/Ryujinx.HLE/Debugger/Debugger.cs | 82 +++++++++---------- .../Debugger/Message/AbortMessage.cs | 6 -- 2 files changed, 39 insertions(+), 49 deletions(-) delete mode 100644 src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 10022e1d0..b163c05b5 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -27,8 +27,7 @@ namespace Ryujinx.HLE.Debugger private NetworkStream ReadStream = null; private NetworkStream WriteStream = null; private BlockingCollection Messages = new BlockingCollection(1); - private Thread SocketThread; - private Thread HandlerThread; + private Thread DebuggerThread; private bool _shuttingDown = false; private ulong? cThread; @@ -41,10 +40,8 @@ namespace Ryujinx.HLE.Debugger ARMeilleure.Optimizations.EnableDebugging = true; - SocketThread = new Thread(SocketReaderThreadMain); - HandlerThread = new Thread(HandlerThreadMain); - SocketThread.Start(); - HandlerThread.Start(); + DebuggerThread = new Thread(DebuggerThreadMain); + DebuggerThread.Start(); } private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess(); @@ -144,35 +141,29 @@ namespace Ryujinx.HLE.Debugger } } - private void HandlerThreadMain() + private void HandleMessage(IMessage msg) { - while (true) + switch (msg) { - switch (Messages.Take()) - { - case AbortMessage _: - return; + case BreakInMessage: + Logger.Notice.Print(LogClass.GdbStub, "Break-in requested"); + CommandQuery(); + break; - case BreakInMessage _: - Logger.Notice.Print(LogClass.GdbStub, "Break-in requested"); - CommandQuery(); - break; + case SendNackMessage: + WriteStream.WriteByte((byte)'-'); + break; - case SendNackMessage _: - WriteStream.WriteByte((byte)'-'); - break; + case CommandMessage {Command: var cmd}: + Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}"); + WriteStream.WriteByte((byte)'+'); + ProcessCommand(cmd); + break; - case CommandMessage {Command: var cmd}: - Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}"); - WriteStream.WriteByte((byte)'+'); - ProcessCommand(cmd); - break; - - case ThreadBreakMessage msg: - DebugProcess.DebugStop(); - Reply($"T05thread:{msg.Context.ThreadUid:x};"); - break; - } + case ThreadBreakMessage {Context: var ctx}: + DebugProcess.DebugStop(); + Reply($"T05thread:{ctx.ThreadUid:x};"); + break; } } @@ -595,15 +586,23 @@ namespace Ryujinx.HLE.Debugger Reply("E01"); } - private void SocketReaderThreadMain() + private void DebuggerThreadMain() { var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort); ListenerSocket = new TcpListener(endpoint); ListenerSocket.Start(); Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client"); - while (true) { - ClientSocket = ListenerSocket.AcceptSocket(); + while (!_shuttingDown) + { + try + { + ClientSocket = ListenerSocket.AcceptSocket(); + } + catch (SocketException) + { + return; + } ClientSocket.NoDelay = true; ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read); WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write); @@ -623,7 +622,7 @@ namespace Ryujinx.HLE.Debugger Logger.Notice.Print(LogClass.GdbStub, "NACK received!"); continue; case '\x03': - Messages.Add(new BreakInMessage()); + HandleMessage(new BreakInMessage()); break; case '$': string cmd = ""; @@ -640,11 +639,11 @@ namespace Ryujinx.HLE.Debugger string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}"; if (checksum == $"{CalculateChecksum(cmd):x2}") { - Messages.Add(new CommandMessage(cmd)); + HandleMessage(new CommandMessage(cmd)); } else { - Messages.Add(new SendNackMessage()); + HandleMessage(new SendNackMessage()); } break; @@ -659,8 +658,11 @@ namespace Ryujinx.HLE.Debugger eof: Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); ReadStream.Close(); + ReadStream = null; WriteStream.Close(); + WriteStream = null; ClientSocket.Close(); + ClientSocket = null; } } @@ -718,18 +720,12 @@ namespace Ryujinx.HLE.Debugger { _shuttingDown = true; - if (HandlerThread.IsAlive) - { - Messages.Add(new AbortMessage()); - } - ListenerSocket.Stop(); ClientSocket?.Shutdown(SocketShutdown.Both); ClientSocket?.Close(); ReadStream?.Close(); WriteStream?.Close(); - SocketThread.Join(); - HandlerThread.Join(); + DebuggerThread.Join(); } } diff --git a/src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs b/src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs deleted file mode 100644 index 2c0d6bb79..000000000 --- a/src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ryujinx.HLE.Debugger -{ - struct AbortMessage : IMessage - { - } -} From 38c15faacf371303883b80c8f2ca9f554e1e5214 Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 17 Nov 2023 10:59:50 +0200 Subject: [PATCH 42/65] Remove useless definitions --- src/Ryujinx.HLE/Debugger/Debugger.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index b163c05b5..abf6a19be 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -46,9 +46,6 @@ namespace Ryujinx.HLE.Debugger private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess(); private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray(); - private IVirtualMemoryManager GetMemory() => DebugProcess.CpuMemory; - private void InvalidateCacheRegion(ulong address, ulong size) => - Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size); private KernelContext KernelContext => Device.System.KernelContext; const int GdbRegisterCount = 68; @@ -465,7 +462,7 @@ namespace Ryujinx.HLE.Debugger try { var data = new byte[len]; - GetMemory().Read(addr, data); + DebugProcess.CpuMemory.Read(addr, data); Reply(ToHex(data)); } catch (InvalidMemoryRegionException) @@ -484,8 +481,8 @@ namespace Ryujinx.HLE.Debugger data[i] = (byte)ss.ReadLengthAsHex(2); } - GetMemory().Write(addr, data); - InvalidateCacheRegion(addr, len); + DebugProcess.CpuMemory.Write(addr, data); + DebugProcess.InvalidateCacheRegion(addr, len); ReplyOK(); } catch (InvalidMemoryRegionException) From 055ac70eaa46670cb7c424e97eda691bc7cc34db Mon Sep 17 00:00:00 2001 From: svc64 Date: Fri, 17 Nov 2023 19:15:37 +0200 Subject: [PATCH 43/65] Kill command - stop emulation --- src/Ryujinx.Gtk3/UI/MainWindow.cs | 9 ++++----- src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs | 15 +++++++-------- src/Ryujinx.HLE/Debugger/Debugger.cs | 2 ++ src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 6 ++++-- src/Ryujinx.HLE/Switch.cs | 6 ++++++ src/Ryujinx/AppHost.cs | 15 ++++++--------- 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs index fe3414fa1..12c42f255 100644 --- a/src/Ryujinx.Gtk3/UI/MainWindow.cs +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -1076,9 +1076,11 @@ namespace Ryujinx.UI RendererWidget.WaitEvent.WaitOne(); RendererWidget.Start(); + _pauseEmulation.Sensitive = false; + _resumeEmulation.Sensitive = false; + UpdateMenuItem.Sensitive = true; _emulationContext.Dispose(); - _deviceExitStatus.Set(); // NOTE: Everything that is here will not be executed when you close the UI. Application.Invoke(delegate @@ -1173,7 +1175,7 @@ namespace Ryujinx.UI RendererWidget.Exit(); // Wait for the other thread to dispose the HLE context before exiting. - _deviceExitStatus.WaitOne(); + _emulationContext.ExitStatus.WaitOne(); RendererWidget.Dispose(); } } @@ -1511,9 +1513,6 @@ namespace Ryujinx.UI UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); } - _pauseEmulation.Sensitive = false; - _resumeEmulation.Sensitive = false; - UpdateMenuItem.Sensitive = true; RendererWidget?.Exit(); } diff --git a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs index 0e636792d..62dacc5ec 100644 --- a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs +++ b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs @@ -49,7 +49,6 @@ namespace Ryujinx.UI public static event EventHandler StatusUpdatedEvent; - private bool _isActive; private bool _isStopped; private bool _toggleFullscreen; @@ -459,7 +458,7 @@ namespace Ryujinx.UI (Toplevel as MainWindow)?.ActivatePauseMenu(); - while (_isActive) + while (Device.IsActive) { if (_isStopped) { @@ -519,7 +518,7 @@ namespace Ryujinx.UI { _chrono.Restart(); - _isActive = true; + Device.IsActive = true; Gtk.Window parent = Toplevel as Gtk.Window; @@ -573,9 +572,9 @@ namespace Ryujinx.UI _isStopped = true; - if (_isActive) + if (Device.IsActive) { - _isActive = false; + Device.IsActive = false; _exitEvent.WaitOne(); _exitEvent.Dispose(); @@ -584,7 +583,7 @@ namespace Ryujinx.UI private void NvidiaStutterWorkaround() { - while (_isActive) + while (Device.IsActive) { // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones. // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity. @@ -603,7 +602,7 @@ namespace Ryujinx.UI public void MainLoop() { - while (_isActive) + while (Device.IsActive) { UpdateFrame(); @@ -616,7 +615,7 @@ namespace Ryujinx.UI private bool UpdateFrame() { - if (!_isActive) + if (!Device.IsActive) { return true; } diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index abf6a19be..4870b5661 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -218,6 +218,8 @@ namespace Ryujinx.HLE.Debugger } case 'k': Logger.Notice.Print(LogClass.GdbStub, "Kill request received"); + Device.IsActive = false; + Device.ExitStatus.WaitOne(); Reply(""); break; case 'm': diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index adef6ed78..2c6d77c15 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -311,7 +311,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { KernelContext.CriticalSection.Enter(); - if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this) + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this) { Owner.UnpinThread(this); } @@ -366,7 +368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { ThreadSchedState state = PrepareForTermination(); - if (state != ThreadSchedState.TerminationPending) + if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending) { KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _); } diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index fc7809ff4..b2ec6f964 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -10,6 +10,7 @@ using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.UI; using Ryujinx.Memory; using System; +using System.Threading; namespace Ryujinx.HLE { @@ -27,11 +28,14 @@ namespace Ryujinx.HLE public TamperMachine TamperMachine { get; } public IHostUIHandler UIHandler { get; } public Debugger.Debugger Debugger { get; } + public ManualResetEvent ExitStatus { get; } public bool EnableDeviceVsync { get; set; } = true; public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable; + public bool IsActive { get; set; } = true; + public Switch(HLEConfiguration configuration) { ArgumentNullException.ThrowIfNull(configuration.GpuRenderer); @@ -56,6 +60,7 @@ namespace Ryujinx.HLE Hid = new Hid(this, System.HidStorage); Processes = new ProcessLoader(this); TamperMachine = new TamperMachine(); + ExitStatus = new ManualResetEvent(false); System.InitializeServices(); System.State.SetLanguage(Configuration.SystemLanguage); @@ -157,6 +162,7 @@ namespace Ryujinx.HLE FileSystem.Dispose(); Memory.Dispose(); Debugger.Dispose(); + ExitStatus.Set(); } } } diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 65ef63926..2e847c620 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -104,7 +104,6 @@ namespace Ryujinx.Ava CursorStates.CursorIsVisible : CursorStates.CursorIsHidden; private bool _isStopped; - private bool _isActive; private bool _renderingStarted; private readonly ManualResetEvent _gpuDoneEvent; @@ -427,8 +426,6 @@ namespace Ryujinx.Ava RendererHost.BoundsChanged += Window_BoundsChanged; - _isActive = true; - _renderingThread.Start(); _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; @@ -497,7 +494,7 @@ namespace Ryujinx.Ava public void Stop() { - _isActive = false; + Device.IsActive = false; } private void Exit() @@ -510,14 +507,14 @@ namespace Ryujinx.Ava } _isStopped = true; - _isActive = false; + Device.IsActive = false; } public void DisposeContext() { Dispose(); - _isActive = false; + Device.IsActive = false; // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. // We only need to wait for all commands submitted during the main gpu loop to be processed. @@ -950,7 +947,7 @@ namespace Ryujinx.Ava private void MainLoop() { - while (_isActive) + while (Device.IsActive) { UpdateFrame(); @@ -1001,7 +998,7 @@ namespace Ryujinx.Ava _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); - while (_isActive) + while (Device.IsActive) { _ticks += _chrono.ElapsedTicks; @@ -1100,7 +1097,7 @@ namespace Ryujinx.Ava private bool UpdateFrame() { - if (!_isActive) + if (!Device.IsActive) { return false; } From 13c53657cc1d7b868ba89744bd4438fa34ec8b93 Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 9 Dec 2023 15:09:56 +0200 Subject: [PATCH 44/65] Fix the kill command, improve stepping --- src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 6 +++--- src/Ryujinx.HLE/Switch.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 80671ccc1..7c73cd09d 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1247,10 +1247,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return false; } _parent.KernelContext.CriticalSection.Enter(); - bool wasPaused = (target.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused; + bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration; target.Context.RequestDebugStep(); target.Resume(ThreadSchedState.ThreadPauseFlag); - if (wasPaused) + if (waiting) { lock (_parent._threadingLock) { @@ -1267,7 +1267,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process _parent.KernelContext.CriticalSection.Enter(); target.Suspend(ThreadSchedState.ThreadPauseFlag); - if (wasPaused) + if (waiting) { lock (_parent._threadingLock) { diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index b2ec6f964..2527d96e5 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -161,8 +161,8 @@ namespace Ryujinx.HLE AudioDeviceDriver.Dispose(); FileSystem.Dispose(); Memory.Dispose(); - Debugger.Dispose(); ExitStatus.Set(); + Debugger.Dispose(); } } } From 64206c7c5ed517d519264d5e9f76fd6546395e0c Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 9 Dec 2023 15:28:33 +0200 Subject: [PATCH 45/65] Fix GDB stub port in the GTK UI --- src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs | 1 + src/Ryujinx.HLE/Switch.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs index 8aef6796e..2ed6eb796 100644 --- a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs @@ -668,6 +668,7 @@ namespace Ryujinx.UI.Windows ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; ConfigurationState.Instance.Debug.EnableGdbStub.Value = _gdbStubToggle.Active; + ConfigurationState.Instance.Debug.GdbStubPort.Value = (ushort)_gdbStubPortSpinAdjustment.Value; _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 2527d96e5..e1e386181 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -54,7 +54,7 @@ namespace Ryujinx.HLE AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver); Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags); Gpu = new GpuContext(Configuration.GpuRenderer); - Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, configuration.GdbStubPort) : null; + Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, Configuration.GdbStubPort) : null; System = new HOS.Horizon(this); Statistics = new PerformanceStatistics(); Hid = new Hid(this, System.HidStorage); From 652423cfebb3e75c7c12063c70465cd00d7b0b1e Mon Sep 17 00:00:00 2001 From: svc64 Date: Sat, 9 Dec 2023 20:08:25 +0200 Subject: [PATCH 46/65] Debug settings in the Avalonia UI --- src/Ryujinx/Assets/Locales/en_US.json | 7 ++- .../UI/ViewModels/SettingsViewModel.cs | 32 +++++++++++ .../UI/Views/Settings/SettingsDebugView.axaml | 54 +++++++++++++++++++ .../Views/Settings/SettingsDebugView.axaml.cs | 13 +++++ src/Ryujinx/UI/Windows/SettingsWindow.axaml | 5 ++ .../UI/Windows/SettingsWindow.axaml.cs | 3 ++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index 74e18056b..00d0aa40d 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -780,5 +780,10 @@ "MultiplayerMode": "Mode:", "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", "MultiplayerModeDisabled": "Disabled", - "MultiplayerModeLdnMitm": "ldn_mitm" + "MultiplayerModeLdnMitm": "ldn_mitm", + "SettingsTabDebug": "Debug", + "SettingsTabDebugTitle": "Debug (WARNING: For developer use only)", + "SettingsTabDebugEnableGDBStub": "Enable GDB Stub", + "GDBStubToggleTooltip": "Enables the GDB stub which makes it possible to debug the running application. For development use only!", + "GDBStubPort": "GDB stub port:" } diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 70e5fa5c7..b85ca8df5 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -54,6 +54,8 @@ namespace Ryujinx.Ava.UI.ViewModels public event Action SaveSettingsEvent; private int _networkInterfaceIndex; private int _multiplayerModeIndex; + private bool _enableGDBStub; + private ushort _gdbStubPort; public int ResolutionScale { @@ -259,6 +261,26 @@ namespace Ryujinx.Ava.UI.ViewModels } } + public bool EnableGdbStub + { + get => _enableGDBStub; + set + { + _enableGDBStub = value; + ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub; + } + } + + public ushort GDBStubPort + { + get => _gdbStubPort; + set + { + _gdbStubPort = value; + ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort; + } + } + public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() { _virtualFileSystem = virtualFileSystem; @@ -472,7 +494,12 @@ namespace Ryujinx.Ava.UI.ViewModels FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; + // Multiplayer MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; + + // Debug + EnableGdbStub = config.Debug.EnableGdbStub.Value; + GDBStubPort = config.Debug.GdbStubPort.Value; } public void SaveSettings() @@ -578,9 +605,14 @@ namespace Ryujinx.Ava.UI.ViewModels config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; + // Multiplayer config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]]; config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; + // Debug + config.Debug.EnableGdbStub.Value = EnableGdbStub; + config.Debug.GdbStubPort.Value = GDBStubPort; + config.ToFileFormat().SaveConfig(Program.ConfigurationPath); MainWindow.UpdateGraphicsConfig(); diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml new file mode 100644 index 000000000..d47d8d8e6 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs new file mode 100644 index 000000000..14a65b8b2 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsDebugView : UserControl + { + public SettingsDebugView() + { + InitializeComponent(); + } + } +} + diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml index de3c2291a..c6f5d3950 100644 --- a/src/Ryujinx/UI/Windows/SettingsWindow.axaml +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml @@ -42,6 +42,7 @@ + +