From 71eb844dd897c0ce47c17f687ae963d45e71bc45 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Mon, 20 Oct 2025 21:18:06 -0500 Subject: [PATCH] gdb: dynamic rcmd system & more cleanups --- .../Debugger/Debugger.MainThread.cs | 4 +- .../Debugger/Debugger.MessageHandler.cs | 6 +-- src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs | 37 +++++++++++++++++++ src/Ryujinx.HLE/Debugger/Debugger.cs | 6 +-- .../Debugger/Gdb/CommandProcessor.cs | 12 +++--- src/Ryujinx.HLE/Debugger/Gdb/Commands.cs | 14 ++----- src/Ryujinx.HLE/Debugger/Gdb/Registers.cs | 4 +- .../Debugger/IDebuggableProcess.cs | 7 ++-- .../Debugger/{IMessage.cs => Message.cs} | 22 +++++------ 9 files changed, 71 insertions(+), 41 deletions(-) rename src/Ryujinx.HLE/Debugger/{IMessage.cs => Message.cs} (56%) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs b/src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs index c4ec001bf..9ad80a58c 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs @@ -64,7 +64,7 @@ namespace Ryujinx.HLE.Debugger Logger.Notice.Print(LogClass.GdbStub, "NACK received!"); continue; case '\x03': - _messages.Add(StatelessMessage.BreakIn); + _messages.Add(Message.BreakIn); break; case '$': string cmd = string.Empty; @@ -85,7 +85,7 @@ namespace Ryujinx.HLE.Debugger } else { - _messages.Add(StatelessMessage.SendNack); + _messages.Add(Message.SendNack); } break; diff --git a/src/Ryujinx.HLE/Debugger/Debugger.MessageHandler.cs b/src/Ryujinx.HLE/Debugger/Debugger.MessageHandler.cs index 4e2cdc237..69d0b15fe 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.MessageHandler.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.MessageHandler.cs @@ -14,16 +14,16 @@ namespace Ryujinx.HLE.Debugger { switch (_messages.Take()) { - case StatelessMessage { Type: MessageType.BreakIn }: + case Message { Type: MessageType.BreakIn }: Logger.Notice.Print(LogClass.GdbStub, "Break-in requested"); _commands.Interrupt(); break; - case StatelessMessage { Type: MessageType.SendNack }: + case Message { Type: MessageType.SendNack }: _writeStream.WriteByte((byte)'-'); break; - case StatelessMessage { Type: MessageType.Kill }: + case Message { Type: MessageType.Kill }: return; case CommandMessage { Command: { } cmd }: diff --git a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs index ef1d7f394..d2c8cd513 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs @@ -1,13 +1,50 @@ +using Gommon; +using JetBrains.Annotations; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using System; +using System.Collections.Generic; +using System.Linq; using System.Text; namespace Ryujinx.HLE.Debugger { public partial class Debugger { + static Debugger() + { + _rcmdDelegates.Add(["help"], + _ => _rcmdDelegates.Keys + .Where(x => !x[0].Equals("help")) + .Select(x => x.JoinToString('\n')) + .JoinToString('\n') + '\n' + ); + _rcmdDelegates.Add(["get info"], dbgr => dbgr.GetProcessInfo()); + _rcmdDelegates.Add(["backtrace", "bt"], dbgr => dbgr.GetStackTrace()); + _rcmdDelegates.Add(["registers", "reg"], dbgr => dbgr.GetRegisters()); + _rcmdDelegates.Add(["minidump"], dbgr => dbgr.GetMinidump()); + } + + private static readonly Dictionary> _rcmdDelegates = new(); + + [CanBeNull] + public static Func FindRcmdDelegate(string command) + { + Func searchResult = null; + + foreach ((string[] names, Func dlg) in _rcmdDelegates) + { + if (names.ContainsIgnoreCase(command.Trim())) + { + searchResult = dlg; + break; + } + } + + return searchResult; + } + public string GetStackTrace() { if (GThread == null) diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 6a5da60ee..76c9e0f8d 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -4,10 +4,8 @@ using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Concurrent; -using System.IO; using System.Linq; using System.Net.Sockets; -using System.Text; using System.Threading; using IExecutionContext = Ryujinx.Cpu.IExecutionContext; @@ -20,7 +18,7 @@ namespace Ryujinx.HLE.Debugger public ushort GdbStubPort { get; private set; } - private readonly BlockingCollection _messages = new(1); + private readonly BlockingCollection _messages = new(1); private readonly Thread _debuggerThread; private readonly Thread _messageHandlerThread; @@ -89,7 +87,7 @@ namespace Ryujinx.HLE.Debugger _readStream?.Close(); _writeStream?.Close(); _debuggerThread.Join(); - _messages.Add(StatelessMessage.Kill); + _messages.Add(Message.Kill); _messageHandlerThread.Join(); _messages.Dispose(); _breakHandlerEvent.Dispose(); diff --git a/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs b/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs index cef1123f7..84f4dd7e6 100644 --- a/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs +++ b/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs @@ -20,6 +20,9 @@ namespace Ryujinx.HLE.Debugger.Gdb Commands = commands; } + public void ReplyHex(string data) => Reply(Helpers.ToHex(data)); + public void ReplyHex(byte[] data) => Reply(Helpers.ToHex(data)); + public void Reply(string cmd) { Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}"); @@ -197,11 +200,10 @@ namespace Ryujinx.HLE.Debugger.Gdb break; } - Reply(Helpers.ToHex( - DebugProcess.IsThreadPaused(DebugProcess.GetThread(threadId.Value)) - ? "Paused" - : "Running" - ) + ReplyHex( + DebugProcess.IsThreadPaused(DebugProcess.GetThread(threadId.Value)) + ? "Paused" + : "Running" ); break; diff --git a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs index a665e657e..4a1fe3c93 100644 --- a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs +++ b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs @@ -197,7 +197,7 @@ namespace Ryujinx.HLE.Debugger.Gdb { byte[] data = new byte[len]; Debugger.DebugProcess.CpuMemory.Read(addr, data); - Processor.Reply(Helpers.ToHex(data)); + Processor.ReplyHex(data); } catch (InvalidMemoryRegionException) { @@ -422,17 +422,9 @@ namespace Ryujinx.HLE.Debugger.Gdb string command = Helpers.FromHex(hexCommand); Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}"); - string response = command.Trim().ToLowerInvariant() switch - { - "help" => "backtrace\nbt\nregisters\nreg\nget info\nminidump\n", - "get info" => Debugger.GetProcessInfo(), - "backtrace" or "bt" => Debugger.GetStackTrace(), - "registers" or "reg" => Debugger.GetRegisters(), - "minidump" => Debugger.GetMinidump(), - _ => $"Unknown command: {command}\n" - }; + var rcmdDelegate = Debugger.FindRcmdDelegate(command); - Processor.Reply(Helpers.ToHex(response)); + Processor.ReplyHex(rcmdDelegate?.Invoke(Debugger) ?? $"Unknown command: {command}\n"); } catch (Exception e) { diff --git a/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs b/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs index 31203b62b..7d3083b31 100644 --- a/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs +++ b/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs @@ -20,8 +20,8 @@ namespace Ryujinx.HLE.Debugger.Gdb 32 => Helpers.ToHex(BitConverter.GetBytes(state.DebugPc)), 33 => Helpers.ToHex(BitConverter.GetBytes(state.Pstate)), >= 34 and <= 65 => Helpers.ToHex(state.GetV(registerId - 34).ToArray()), - 66 => Helpers.ToHex(BitConverter.GetBytes((uint)state.Fpsr)), - 67 => Helpers.ToHex(BitConverter.GetBytes((uint)state.Fpcr)), + 66 => Helpers.ToHex(BitConverter.GetBytes(state.Fpsr)), + 67 => Helpers.ToHex(BitConverter.GetBytes(state.Fpcr)), _ => null }; diff --git a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs index a632cea33..6d0cf3029 100644 --- a/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs +++ b/src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs @@ -6,6 +6,10 @@ namespace Ryujinx.HLE.Debugger { internal interface IDebuggableProcess { + IVirtualMemoryManager CpuMemory { get; } + ulong[] ThreadUids { get; } + DebugState DebugState { get; } + void DebugStop(); void DebugContinue(); void DebugContinue(KThread thread); @@ -13,9 +17,6 @@ namespace Ryujinx.HLE.Debugger KThread GetThread(ulong threadUid); bool IsThreadPaused(KThread thread); public void DebugInterruptHandler(IExecutionContext ctx); - IVirtualMemoryManager CpuMemory { get; } - ulong[] ThreadUids { get; } - DebugState DebugState { get; } void InvalidateCacheRegion(ulong address, ulong size); } } diff --git a/src/Ryujinx.HLE/Debugger/IMessage.cs b/src/Ryujinx.HLE/Debugger/Message.cs similarity index 56% rename from src/Ryujinx.HLE/Debugger/IMessage.cs rename to src/Ryujinx.HLE/Debugger/Message.cs index 9877bcd5e..c5f9e9554 100644 --- a/src/Ryujinx.HLE/Debugger/IMessage.cs +++ b/src/Ryujinx.HLE/Debugger/Message.cs @@ -2,11 +2,6 @@ using Ryujinx.Cpu; namespace Ryujinx.HLE.Debugger { - /// - /// Marker interface for debugger messages. - /// - interface IMessage; - public enum MessageType { Kill, @@ -14,14 +9,19 @@ namespace Ryujinx.HLE.Debugger SendNack } - record struct StatelessMessage(MessageType Type) : IMessage + record struct Message(MessageType Type) : Message.IMarker { - public static StatelessMessage Kill => new(MessageType.Kill); - public static StatelessMessage BreakIn => new(MessageType.BreakIn); - public static StatelessMessage SendNack => new(MessageType.SendNack); + /// + /// Marker interface for debugger messages. + /// + internal interface IMarker; + + public static Message Kill => new(MessageType.Kill); + public static Message BreakIn => new(MessageType.BreakIn); + public static Message SendNack => new(MessageType.SendNack); } - struct CommandMessage : IMessage + struct CommandMessage : Message.IMarker { public readonly string Command; @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.Debugger } } - public class ThreadBreakMessage : IMessage + public class ThreadBreakMessage : Message.IMarker { public IExecutionContext Context { get; } public ulong Address { get; }