From 2d420ee561aff235dfc96f184ee4c79830927529 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 | 36 +++++++++++++++++++ src/Ryujinx.HLE/Debugger/Debugger.cs | 4 +-- .../Debugger/Gdb/CommandProcessor.cs | 12 ++++--- src/Ryujinx.HLE/Debugger/Gdb/Commands.cs | 14 ++------ .../Debugger/IDebuggableProcess.cs | 7 ++-- .../Debugger/{IMessage.cs => Message.cs} | 22 ++++++------ 8 files changed, 68 insertions(+), 37 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 16115b971..13be3a0e0 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 302969011..32b81bda0 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs @@ -1,12 +1,48 @@ +using Gommon; +using JetBrains.Annotations; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Process; 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(); + + public static Func FindRcmdDelegate(string command) + { + Func searchResult = _ => $"Unknown command: {command}\n"; + + 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 8f5df20ac..56192806d 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -18,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; @@ -87,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 953515ec9..8220d2aeb 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 { var 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" - }; + Func rcmd = Debugger.FindRcmdDelegate(command); - Processor.Reply(Helpers.ToHex(response)); + Processor.ReplyHex(rcmd(Debugger)); } catch (Exception e) { 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; }