From 2a2ab523cbb8661a3cac81021c4893ead2e45f08 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Fri, 17 Oct 2025 00:09:51 -0500 Subject: [PATCH] gdb: Code cleanup pass #2 Moved the reply functionality into the command processor, move the main debugger thread into a dedicated class part, and more --- src/Ryujinx.HLE/Debugger/BreakpointManager.cs | 8 +- .../Debugger/Debugger.MainThread.cs | 115 ++++++++ src/Ryujinx.HLE/Debugger/Debugger.cs | 172 +++--------- .../Debugger/Gdb/CommandProcessor.cs | 216 ++++++++------- src/Ryujinx.HLE/Debugger/Gdb/Commands.cs | 246 ++++++++---------- src/Ryujinx.HLE/Debugger/Gdb/Registers.cs | 4 +- src/Ryujinx.HLE/Debugger/Helpers.cs | 2 +- .../Debugger/RegisterInformation.cs | 2 +- .../HOS/Kernel/Process/KProcess.cs | 24 +- 9 files changed, 391 insertions(+), 398 deletions(-) create mode 100644 src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs diff --git a/src/Ryujinx.HLE/Debugger/BreakpointManager.cs b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs index c660b298d..7f45fbf8b 100644 --- a/src/Ryujinx.HLE/Debugger/BreakpointManager.cs +++ b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs @@ -25,9 +25,9 @@ namespace Ryujinx.HLE.Debugger private readonly Debugger _debugger; private readonly ConcurrentDictionary _breakpoints = new(); - private static readonly byte[] _aarch64BreakInstruction = { 0x00, 0x00, 0x20, 0xD4 }; // BRK #0 - private static readonly byte[] _aarch32BreakInstruction = { 0xFE, 0xDE, 0xFF, 0xE7 }; // TRAP - private static readonly byte[] _aarch32ThumbBreakInstruction = { 0x80, 0xB6 }; + private static readonly byte[] _aarch64BreakInstruction = [0x00, 0x00, 0x20, 0xD4]; // BRK #0 + private static readonly byte[] _aarch32BreakInstruction = [0xFE, 0xDE, 0xFF, 0xE7]; // TRAP + private static readonly byte[] _aarch32ThumbBreakInstruction = [0x80, 0xB6]; public BreakpointManager(Debugger debugger) { @@ -123,7 +123,7 @@ namespace Ryujinx.HLE.Debugger private byte[] GetBreakInstruction(ulong length) { - if (_debugger.IsProcessAarch32) + if (_debugger.IsProcess32Bit) { if (length == 2) { diff --git a/src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs b/src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs new file mode 100644 index 000000000..2e1f40120 --- /dev/null +++ b/src/Ryujinx.HLE/Debugger/Debugger.MainThread.cs @@ -0,0 +1,115 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Debugger.Gdb; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Ryujinx.HLE.Debugger +{ + public partial class Debugger + { + private void DebuggerThreadMain() + { + IPEndPoint endpoint = new(IPAddress.Any, GdbStubPort); + _listenerSocket = new TcpListener(endpoint); + _listenerSocket.Start(); + Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client"); + + while (!_shuttingDown) + { + try + { + _clientSocket = _listenerSocket.AcceptSocket(); + } + catch (SocketException) + { + return; + } + + // If the user connects before the application is running, wait for the application to start. + int retries = 10; + while ((DebugProcess == null || GetThreads().Length == 0) && retries-- > 0) + { + Thread.Sleep(200); + } + + if (DebugProcess == null || GetThreads().Length == 0) + { + Logger.Warning?.Print(LogClass.GdbStub, + "Application is not running, cannot accept GDB client connection"); + _clientSocket.Close(); + continue; + } + + _clientSocket.NoDelay = true; + _readStream = new NetworkStream(_clientSocket, System.IO.FileAccess.Read); + _writeStream = new NetworkStream(_clientSocket, System.IO.FileAccess.Write); + _commands = new GdbCommands(_listenerSocket, _clientSocket, _readStream, _writeStream, this); + _commandProcessor = _commands.CreateProcessor(); + + Logger.Notice.Print(LogClass.GdbStub, "GDB client connected"); + + while (true) + { + try + { + switch (_readStream.ReadByte()) + { + case -1: + goto EndOfLoop; + 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 EndOfLoop; + if (x == '#') + break; + cmd += (char)x; + } + + string checksum = $"{(char)_readStream.ReadByte()}{(char)_readStream.ReadByte()}"; + if (checksum == $"{Helpers.CalculateChecksum(cmd):x2}") + { + _messages.Add(new CommandMessage(cmd)); + } + else + { + _messages.Add(new SendNackMessage()); + } + + break; + } + } + catch (IOException) + { + goto EndOfLoop; + } + } + + EndOfLoop: + Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); + _readStream.Close(); + _readStream = null; + _writeStream.Close(); + _writeStream = null; + _clientSocket.Close(); + _clientSocket = null; + _commandProcessor = null; + _commands = null; + + BreakpointManager.ClearAll(); + } + } + } +} diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index cc64a38eb..1c28870b7 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -6,37 +6,39 @@ using System; using System.Collections.Concurrent; using System.IO; using System.Linq; -using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using IExecutionContext = Ryujinx.Cpu.IExecutionContext; -using static Ryujinx.HLE.Debugger.Helpers; + namespace Ryujinx.HLE.Debugger { - public class Debugger : IDisposable + public partial 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(1); - private Thread DebuggerThread; - private Thread MessageHandlerThread; - private bool _shuttingDown = false; - private ManualResetEventSlim _breakHandlerEvent = new(false); + private readonly BlockingCollection _messages = new(1); + private readonly Thread _debuggerThread; + private readonly Thread _messageHandlerThread; - private GdbCommandProcessor CommandProcessor = null; + private TcpListener _listenerSocket; + private Socket _clientSocket; + private NetworkStream _readStream; + private NetworkStream _writeStream; + + private GdbCommandProcessor _commandProcessor; + private GdbCommands _commands; + + private bool _shuttingDown; + private readonly ManualResetEventSlim _breakHandlerEvent = new(false); internal ulong? CThread; internal ulong? GThread; - internal BreakpointManager BreakpointManager; + public readonly BreakpointManager BreakpointManager; public Debugger(Switch device, ushort port) { @@ -45,10 +47,10 @@ namespace Ryujinx.HLE.Debugger ARMeilleure.Optimizations.EnableDebugging = true; - DebuggerThread = new Thread(DebuggerThreadMain); - DebuggerThread.Start(); - MessageHandlerThread = new Thread(MessageHandlerMain); - MessageHandlerThread.Start(); + _debuggerThread = new Thread(DebuggerThreadMain); + _debuggerThread.Start(); + _messageHandlerThread = new Thread(MessageHandlerMain); + _messageHandlerThread.Start(); BreakpointManager = new BreakpointManager(this); } @@ -58,37 +60,37 @@ namespace Ryujinx.HLE.Debugger internal KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray(); - internal bool IsProcessAarch32 => DebugProcess.GetThread(GThread.Value).Context.IsAarch32; + internal bool IsProcess32Bit => DebugProcess.GetThread(GThread.Value).Context.IsAarch32; private void MessageHandlerMain() { while (!_shuttingDown) { - IMessage msg = Messages.Take(); + IMessage msg = _messages.Take(); try { switch (msg) { case BreakInMessage: Logger.Notice.Print(LogClass.GdbStub, "Break-in requested"); - CommandProcessor.Commands.CommandInterrupt(); + _commandProcessor.Commands.Interrupt(); break; case SendNackMessage: - WriteStream.WriteByte((byte)'-'); + _writeStream.WriteByte((byte)'-'); break; case CommandMessage { Command: var cmd }: Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}"); - WriteStream.WriteByte((byte)'+'); - CommandProcessor.Process(cmd); + _writeStream.WriteByte((byte)'+'); + _commandProcessor.Process(cmd); break; case ThreadBreakMessage { Context: var ctx }: DebugProcess.DebugStop(); GThread = CThread = ctx.ThreadUid; _breakHandlerEvent.Set(); - CommandProcessor.Commands.Reply($"T05thread:{ctx.ThreadUid:x};"); + _commandProcessor.Reply($"T05thread:{ctx.ThreadUid:x};"); break; case KillMessage: @@ -214,106 +216,6 @@ namespace Ryujinx.HLE.Debugger } } - 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 (!_shuttingDown) - { - try - { - ClientSocket = ListenerSocket.AcceptSocket(); - } - catch (SocketException) - { - return; - } - - // If the user connects before the application is running, wait for the application to start. - int retries = 10; - while ((DebugProcess == null || GetThreads().Length == 0) && retries-- > 0) - { - Thread.Sleep(200); - } - - if (DebugProcess == null || GetThreads().Length == 0) - { - Logger.Warning?.Print(LogClass.GdbStub, - "Application is not running, cannot accept GDB client connection"); - ClientSocket.Close(); - continue; - } - - ClientSocket.NoDelay = true; - ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read); - WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write); - CommandProcessor = new GdbCommandProcessor(ListenerSocket, ClientSocket, ReadStream, WriteStream, this); - Logger.Notice.Print(LogClass.GdbStub, "GDB client connected"); - - while (true) - { - try - { - switch (ReadStream.ReadByte()) - { - case -1: - goto EndOfLoop; - 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 EndOfLoop; - 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()); - } - - break; - } - } - catch (IOException) - { - goto EndOfLoop; - } - } - - EndOfLoop: - Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection"); - ReadStream.Close(); - ReadStream = null; - WriteStream.Close(); - WriteStream = null; - ClientSocket.Close(); - ClientSocket = null; - CommandProcessor = null; - - BreakpointManager.ClearAll(); - } - } - public void Dispose() { Dispose(true); @@ -325,15 +227,15 @@ namespace Ryujinx.HLE.Debugger { _shuttingDown = true; - ListenerSocket.Stop(); - ClientSocket?.Shutdown(SocketShutdown.Both); - ClientSocket?.Close(); - ReadStream?.Close(); - WriteStream?.Close(); - DebuggerThread.Join(); - Messages.Add(new KillMessage()); - MessageHandlerThread.Join(); - Messages.Dispose(); + _listenerSocket.Stop(); + _clientSocket?.Shutdown(SocketShutdown.Both); + _clientSocket?.Close(); + _readStream?.Close(); + _writeStream?.Close(); + _debuggerThread.Join(); + _messages.Add(new KillMessage()); + _messageHandlerThread.Join(); + _messages.Dispose(); _breakHandlerEvent.Dispose(); } } @@ -343,7 +245,7 @@ namespace Ryujinx.HLE.Debugger DebugProcess.DebugInterruptHandler(ctx); _breakHandlerEvent.Reset(); - Messages.Add(new ThreadBreakMessage(ctx, address, imm)); + _messages.Add(new ThreadBreakMessage(ctx, address, imm)); // Messages.Add can block, so we log it after adding the message to make sure user can see the log at the same time GDB receives the break message Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}"); // Wait for the process to stop before returning to avoid BreakHandler being called multiple times from the same breakpoint diff --git a/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs b/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs index e9986647d..997b635e4 100644 --- a/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs +++ b/src/Ryujinx.HLE/Debugger/Gdb/CommandProcessor.cs @@ -1,7 +1,8 @@ +using Gommon; using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; using System.Linq; -using System.Net.Sockets; using System.Text; namespace Ryujinx.HLE.Debugger.Gdb @@ -10,13 +11,41 @@ namespace Ryujinx.HLE.Debugger.Gdb { public readonly GdbCommands Commands; - public GdbCommandProcessor(TcpListener listenerSocket, Socket clientSocket, NetworkStream readStream, NetworkStream writeStream, Debugger debugger) + private Debugger Debugger => Commands.Debugger; + private BreakpointManager BreakpointManager => Commands.Debugger.BreakpointManager; + private IDebuggableProcess DebugProcess => Commands.Debugger.DebugProcess; + + public GdbCommandProcessor(GdbCommands commands) { - Commands = new GdbCommands(listenerSocket, clientSocket, readStream, writeStream, debugger); + Commands = commands; } - - private string previousThreadListXml = ""; - + + public void Reply(string cmd) + { + Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}"); + Commands.WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{Helpers.CalculateChecksum(cmd):x2}")); + } + + public void ReplyOK() => Reply("OK"); + + public void ReplyError() => Reply("E01"); + + public void Reply(bool success) + { + if (success) + ReplyOK(); + else ReplyError(); + } + + public void Reply(bool success, string cmd) + { + if (success) + Reply(cmd); + else ReplyError(); + } + + private string _previousThreadListXml = string.Empty; + public void Process(string cmd) { StringStream ss = new(cmd); @@ -30,7 +59,7 @@ namespace Ryujinx.HLE.Debugger.Gdb } // Enable extended mode - Commands.ReplyOK(); + ReplyOK(); break; case '?': if (!ss.IsEmpty()) @@ -38,10 +67,10 @@ namespace Ryujinx.HLE.Debugger.Gdb goto unknownCommand; } - Commands.CommandQuery(); + Commands.Query(); break; case 'c': - Commands.CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex()); + Commands.Continue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex()); break; case 'D': if (!ss.IsEmpty()) @@ -49,7 +78,7 @@ namespace Ryujinx.HLE.Debugger.Gdb goto unknownCommand; } - Commands.CommandDetach(); + Commands.Detach(); break; case 'g': if (!ss.IsEmpty()) @@ -57,110 +86,99 @@ namespace Ryujinx.HLE.Debugger.Gdb goto unknownCommand; } - Commands.CommandReadRegisters(); + Commands.ReadRegisters(); break; case 'G': - Commands.CommandWriteRegisters(ss); + Commands.WriteRegisters(ss); break; case 'H': { char op = ss.ReadChar(); ulong? threadId = ss.ReadRemainingAsThreadUid(); - Commands.CommandSetThread(op, threadId); + Commands.SetThread(op, threadId); break; } case 'k': Logger.Notice.Print(LogClass.GdbStub, "Kill request received, detach instead"); - Commands.Reply(""); - Commands.CommandDetach(); + Reply(string.Empty); + Commands.Detach(); break; case 'm': { ulong addr = ss.ReadUntilAsHex(','); ulong len = ss.ReadRemainingAsHex(); - Commands.CommandReadMemory(addr, len); + Commands.ReadMemory(addr, len); break; } case 'M': { ulong addr = ss.ReadUntilAsHex(','); ulong len = ss.ReadUntilAsHex(':'); - Commands.CommandWriteMemory(addr, len, ss); + Commands.WriteMemory(addr, len, ss); break; } case 'p': { ulong gdbRegId = ss.ReadRemainingAsHex(); - Commands.CommandReadRegister((int)gdbRegId); + Commands.ReadRegister((int)gdbRegId); break; } case 'P': { ulong gdbRegId = ss.ReadUntilAsHex('='); - Commands.CommandWriteRegister((int)gdbRegId, ss); + Commands.WriteRegister((int)gdbRegId, ss); break; } case 'q': if (ss.ConsumeRemaining("GDBServerVersion")) { - Commands.Reply($"name:Ryujinx;version:{ReleaseInformation.Version};"); + Reply($"name:Ryujinx;version:{ReleaseInformation.Version};"); break; } if (ss.ConsumeRemaining("HostInfo")) { - if (Commands.Debugger.IsProcessAarch32) - { - Commands.Reply( - $"triple:{Helpers.ToHex("arm-unknown-linux-android")};endian:little;ptrsize:4;hostname:{Helpers.ToHex("Ryujinx")};"); - } - else - { - Commands.Reply( - $"triple:{Helpers.ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{Helpers.ToHex("Ryujinx")};"); - } + Reply( + Debugger.IsProcess32Bit + ? $"triple:{Helpers.ToHex("arm-unknown-linux-android")};endian:little;ptrsize:4;hostname:{Helpers.ToHex("Ryujinx")};" + : $"triple:{Helpers.ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{Helpers.ToHex("Ryujinx")};"); break; } if (ss.ConsumeRemaining("ProcessInfo")) { - if (Commands.Debugger.IsProcessAarch32) - { - Commands.Reply( - $"pid:1;cputype:12;cpusubtype:0;triple:{Helpers.ToHex("arm-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:4;"); - } - else - { - Commands.Reply( - $"pid:1;cputype:100000c;cpusubtype:0;triple:{Helpers.ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;"); - } + Reply( + Debugger.IsProcess32Bit + ? $"pid:1;cputype:12;cpusubtype:0;triple:{Helpers.ToHex("arm-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:4;" + : $"pid:1;cputype:100000c;cpusubtype:0;triple:{Helpers.ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;"); break; } if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported")) { - Commands.Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+;vContSupported+"); + Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+;vContSupported+"); break; } if (ss.ConsumePrefix("Rcmd,")) { string hexCommand = ss.ReadRemaining(); - Commands.HandleQRcmdCommand(hexCommand); + Commands.Q_Rcmd(hexCommand); break; } if (ss.ConsumeRemaining("fThreadInfo")) { - Commands. Reply($"m{string.Join(",", Commands.Debugger.DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}"); + Reply( + $"m{Debugger.DebugProcess.GetThreadUids().Select(x => $"{x:x}").JoinToString(",")}"); break; } if (ss.ConsumeRemaining("sThreadInfo")) { - Commands.Reply("l"); + Reply("l"); break; } @@ -169,15 +187,14 @@ namespace Ryujinx.HLE.Debugger.Gdb ulong? threadId = ss.ReadRemainingAsThreadUid(); if (threadId == null) { - Commands.ReplyError(); + ReplyError(); break; } - Commands.Reply(Helpers.ToHex( - Commands.Debugger.DebugProcess.IsThreadPaused( - Commands.Debugger.DebugProcess.GetThread(threadId.Value)) - ? "Paused" - : "Running" + Reply(Helpers.ToHex( + DebugProcess.IsThreadPaused(DebugProcess.GetThread(threadId.Value)) + ? "Paused" + : "Running" ) ); @@ -190,32 +207,32 @@ namespace Ryujinx.HLE.Debugger.Gdb ulong offset = ss.ReadUntilAsHex(','); ulong len = ss.ReadRemainingAsHex(); - var data = ""; + string data; if (offset > 0) { - data = previousThreadListXml; + data = _previousThreadListXml; } else { - previousThreadListXml = data = GetThreadListXml(); + _previousThreadListXml = data = GetThreadListXml(); } if (offset >= (ulong)data.Length) { - Commands.Reply("l"); + Reply("l"); break; } if (len >= (ulong)data.Length - offset) { - Commands.Reply("l" + Helpers.ToBinaryFormat(data.Substring((int)offset))); - break; + Reply("l" + Helpers.ToBinaryFormat(data.Substring((int)offset))); } else { - Commands.Reply("m" + Helpers.ToBinaryFormat(data.Substring((int)offset, (int)len))); - break; + Reply("m" + Helpers.ToBinaryFormat(data.Substring((int)offset, (int)len))); } + + break; } if (ss.ConsumePrefix("Xfer:features:read:")) @@ -226,46 +243,43 @@ namespace Ryujinx.HLE.Debugger.Gdb if (feature == "target.xml") { - feature = Commands.Debugger.IsProcessAarch32 ? "target32.xml" : "target64.xml"; + feature = Debugger.IsProcess32Bit ? "target32.xml" : "target64.xml"; } - string data; - if (RegisterInformation.Features.TryGetValue(feature, out data)) + if (!RegisterInformation.Features.TryGetValue(feature, out string data)) { - if (offset >= (ulong)data.Length) - { - Commands.Reply("l"); - break; - } + Reply("E00"); // Invalid annex + break; + } - if (len >= (ulong)data.Length - offset) - { - Commands.Reply("l" + Helpers.ToBinaryFormat(data.Substring((int)offset))); - break; - } - else - { - Commands.Reply("m" + Helpers.ToBinaryFormat(data.Substring((int)offset, (int)len))); - break; - } + if (offset >= (ulong)data.Length) + { + Reply("l"); + break; + } + + if (len >= (ulong)data.Length - offset) + { + Reply("l" + Helpers.ToBinaryFormat(data[(int)offset..])); } else { - Commands.Reply("E00"); // Invalid annex - break; + Reply("m" + Helpers.ToBinaryFormat(data.Substring((int)offset, (int)len))); } + + break; } goto unknownCommand; case 'Q': goto unknownCommand; case 's': - Commands.CommandStep(ss.IsEmpty() ? null : ss.ReadRemainingAsHex()); + Commands.Step(ss.IsEmpty() ? null : ss.ReadRemainingAsHex()); break; case 'T': { ulong? threadId = ss.ReadRemainingAsThreadUid(); - Commands.CommandIsAlive(threadId); + Commands.IsAlive(threadId); break; } case 'v': @@ -273,13 +287,13 @@ namespace Ryujinx.HLE.Debugger.Gdb { if (ss.ConsumeRemaining("?")) { - Commands.Reply("vCont;c;C;s;S"); + Reply("vCont;c;C;s;S"); break; } if (ss.ConsumePrefix(";")) { - Commands.HandleVContCommand(ss); + Commands.VCont(ss); break; } @@ -288,7 +302,7 @@ namespace Ryujinx.HLE.Debugger.Gdb if (ss.ConsumeRemaining("MustReplyEmpty")) { - Commands.Reply(""); + Reply(string.Empty); break; } @@ -303,29 +317,29 @@ namespace Ryujinx.HLE.Debugger.Gdb if (extra.Length > 0) { Logger.Notice.Print(LogClass.GdbStub, $"Unsupported Z command extra data: {extra}"); - Commands.ReplyError(); + ReplyError(); return; } switch (type) { case "0": // Software breakpoint - if (!Commands.Debugger.BreakpointManager.SetBreakPoint(addr, len)) + if (!BreakpointManager.SetBreakPoint(addr, len)) { - Commands.ReplyError(); + ReplyError(); return; } - Commands.ReplyOK(); + ReplyOK(); return; + // ReSharper disable RedundantCaseLabel case "1": // Hardware breakpoint case "2": // Write watchpoint case "3": // Read watchpoint case "4": // Access watchpoint - Commands.ReplyError(); - return; + // ReSharper restore RedundantCaseLabel default: - Commands.ReplyError(); + ReplyError(); return; } } @@ -340,50 +354,50 @@ namespace Ryujinx.HLE.Debugger.Gdb if (extra.Length > 0) { Logger.Notice.Print(LogClass.GdbStub, $"Unsupported z command extra data: {extra}"); - Commands.ReplyError(); + ReplyError(); return; } switch (type) { case "0": // Software breakpoint - if (!Commands.Debugger.BreakpointManager.ClearBreakPoint(addr, len)) + if (!BreakpointManager.ClearBreakPoint(addr, len)) { - Commands.ReplyError(); + ReplyError(); return; } - Commands.ReplyOK(); + ReplyOK(); return; + // ReSharper disable RedundantCaseLabel case "1": // Hardware breakpoint case "2": // Write watchpoint case "3": // Read watchpoint case "4": // Access watchpoint - Commands.ReplyError(); - return; + // ReSharper restore RedundantCaseLabel default: - Commands.ReplyError(); + ReplyError(); return; } } default: unknownCommand: Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}"); - Commands.Reply(""); + Reply(string.Empty); break; } } private string GetThreadListXml() { - var sb = new StringBuilder(); + StringBuilder sb = new(); sb.Append("\n"); - foreach (var thread in Commands.Debugger.GetThreads()) + foreach (KThread thread in Debugger.GetThreads()) { string threadName = System.Security.SecurityElement.Escape(thread.GetThreadName()); sb.Append( - $"{(Commands.Debugger.DebugProcess.IsThreadPaused(thread) ? "Paused" : "Running")}\n"); + $"{(DebugProcess.IsThreadPaused(thread) ? "Paused" : "Running")}\n"); } sb.Append(""); diff --git a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs index 6c0a258a0..33c7f3675 100644 --- a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs +++ b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs @@ -1,10 +1,11 @@ using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Memory; using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; -using System.Text; namespace Ryujinx.HLE.Debugger.Gdb { @@ -15,41 +16,48 @@ namespace Ryujinx.HLE.Debugger.Gdb public readonly Debugger Debugger; - private readonly TcpListener _listenerSocket; - private readonly Socket _clientSocket; - private readonly NetworkStream _readStream; - private readonly NetworkStream _writeStream; - + public GdbCommandProcessor Processor { get; private set; } + + internal readonly TcpListener ListenerSocket; + internal readonly Socket ClientSocket; + internal readonly NetworkStream ReadStream; + internal readonly NetworkStream WriteStream; + public GdbCommands(TcpListener listenerSocket, Socket clientSocket, NetworkStream readStream, NetworkStream writeStream, Debugger debugger) { - _listenerSocket = listenerSocket; - _clientSocket = clientSocket; - _readStream = readStream; - _writeStream = writeStream; + ListenerSocket = listenerSocket; + ClientSocket = clientSocket; + ReadStream = readStream; + WriteStream = writeStream; Debugger = debugger; } - public void Reply(string cmd) + public void SetProcessor(GdbCommandProcessor commandProcessor) { - Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}"); - _writeStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{Helpers.CalculateChecksum(cmd):x2}")); + if (Processor != null) return; + + Processor = commandProcessor; + } + + public GdbCommandProcessor CreateProcessor() + { + if (Processor != null) + return Processor; + + return Processor = new GdbCommandProcessor(this); } - public void ReplyOK() => Reply("OK"); - - public void ReplyError() => Reply("E01"); - - internal void CommandQuery() + internal void Query() { // GDB is performing initial contact. Stop everything. Debugger.DebugProcess.DebugStop(); Debugger.GThread = Debugger.CThread = Debugger.DebugProcess.GetThreadUids().First(); - Reply($"T05thread:{Debugger.CThread:x};"); + Processor.Reply($"T05thread:{Debugger.CThread:x};"); } - internal void CommandInterrupt() + internal void Interrupt() { // GDB is requesting an interrupt. Stop everything. Debugger.DebugProcess.DebugStop(); @@ -58,16 +66,16 @@ namespace Ryujinx.HLE.Debugger.Gdb Debugger.GThread = Debugger.CThread = Debugger.DebugProcess.GetThreadUids().First(); } - Reply($"T02thread:{Debugger.GThread:x};"); + Processor.Reply($"T02thread:{Debugger.GThread:x};"); } - internal void CommandContinue(ulong? newPc) + internal void Continue(ulong? newPc) { if (newPc.HasValue) { if (Debugger.CThread == null) { - ReplyError(); + Processor.ReplyError(); return; } @@ -77,23 +85,23 @@ namespace Ryujinx.HLE.Debugger.Gdb Debugger.DebugProcess.DebugContinue(); } - internal void CommandDetach() + internal void Detach() { Debugger.BreakpointManager.ClearAll(); - CommandContinue(null); + Continue(null); } - internal void CommandReadRegisters() + internal void ReadRegisters() { if (Debugger.GThread == null) { - ReplyError(); + Processor.ReplyError(); return; } - var ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; - string registers = ""; - if (Debugger.IsProcessAarch32) + IExecutionContext ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; + string registers = string.Empty; + if (Debugger.IsProcess32Bit) { for (int i = 0; i < GdbRegisterCount32; i++) { @@ -108,25 +116,25 @@ namespace Ryujinx.HLE.Debugger.Gdb } } - Reply(registers); + Processor.Reply(registers); } - internal void CommandWriteRegisters(StringStream ss) + internal void WriteRegisters(StringStream ss) { if (Debugger.GThread == null) { - ReplyError(); + Processor.ReplyError(); return; } - var ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; - if (Debugger.IsProcessAarch32) + IExecutionContext ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; + if (Debugger.IsProcess32Bit) { for (int i = 0; i < GdbRegisterCount32; i++) { if (!GdbRegisters.Write32(ctx, i, ss)) { - ReplyError(); + Processor.ReplyError(); return; } } @@ -137,30 +145,23 @@ namespace Ryujinx.HLE.Debugger.Gdb { if (!GdbRegisters.Write64(ctx, i, ss)) { - ReplyError(); + Processor.ReplyError(); return; } } } - if (ss.IsEmpty()) - { - ReplyOK(); - } - else - { - ReplyError(); - } + Processor.Reply(ss.IsEmpty()); } - internal void CommandSetThread(char op, ulong? threadId) + internal void SetThread(char op, ulong? threadId) { if (threadId is 0 or null) { - var threads = Debugger.GetThreads(); + KThread[] threads = Debugger.GetThreads(); if (threads.Length == 0) { - ReplyError(); + Processor.ReplyError(); return; } @@ -169,7 +170,7 @@ namespace Ryujinx.HLE.Debugger.Gdb if (Debugger.DebugProcess.GetThread(threadId.Value) == null) { - ReplyError(); + Processor.ReplyError(); return; } @@ -177,36 +178,36 @@ namespace Ryujinx.HLE.Debugger.Gdb { case 'c': Debugger.CThread = threadId; - ReplyOK(); + Processor.ReplyOK(); return; case 'g': Debugger.GThread = threadId; - ReplyOK(); + Processor.ReplyOK(); return; default: - ReplyError(); + Processor.ReplyError(); return; } } - internal void CommandReadMemory(ulong addr, ulong len) + internal void ReadMemory(ulong addr, ulong len) { try { var data = new byte[len]; Debugger.DebugProcess.CpuMemory.Read(addr, data); - Reply(Helpers.ToHex(data)); + Processor.Reply(Helpers.ToHex(data)); } catch (InvalidMemoryRegionException) { // InvalidAccessHandler will show an error message, we log it again to tell user the error is from GDB (which can be ignored) // TODO: Do not let InvalidAccessHandler show the error message Logger.Notice.Print(LogClass.GdbStub, $"GDB failed to read memory at 0x{addr:X16}"); - ReplyError(); + Processor.ReplyError(); } } - internal void CommandWriteMemory(ulong addr, ulong len, StringStream ss) + internal void WriteMemory(ulong addr, ulong len, StringStream ss) { try { @@ -218,92 +219,58 @@ namespace Ryujinx.HLE.Debugger.Gdb Debugger.DebugProcess.CpuMemory.Write(addr, data); Debugger.DebugProcess.InvalidateCacheRegion(addr, len); - ReplyOK(); + Processor.ReplyOK(); } catch (InvalidMemoryRegionException) { - ReplyError(); + Processor.ReplyError(); } } - internal void CommandReadRegister(int gdbRegId) + internal void ReadRegister(int gdbRegId) { if (Debugger.GThread == null) { - ReplyError(); + Processor.ReplyError(); return; } - var ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; - string result; - if (Debugger.IsProcessAarch32) - { - result = GdbRegisters.Read32(ctx, gdbRegId); - if (result != null) - { - Reply(result); - } - else - { - ReplyError(); - } - } - else - { - result = GdbRegisters.Read64(ctx, gdbRegId); - if (result != null) - { - Reply(result); - } - else - { - ReplyError(); - } - } + IExecutionContext ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; + string result = Debugger.IsProcess32Bit + ? GdbRegisters.Read32(ctx, gdbRegId) + : GdbRegisters.Read64(ctx, gdbRegId); + + Processor.Reply(result != null, result); } - internal void CommandWriteRegister(int gdbRegId, StringStream ss) + internal void WriteRegister(int gdbRegId, StringStream ss) { if (Debugger.GThread == null) { - ReplyError(); + Processor.ReplyError(); return; } - var ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; - if (Debugger.IsProcessAarch32) + IExecutionContext ctx = Debugger.DebugProcess.GetThread(Debugger.GThread.Value).Context; + if (Debugger.IsProcess32Bit) { - if (GdbRegisters.Write32(ctx, gdbRegId, ss) && ss.IsEmpty()) - { - ReplyOK(); - } - else - { - ReplyError(); - } + Processor.Reply(GdbRegisters.Write32(ctx, gdbRegId, ss) && ss.IsEmpty()); } else { - if (GdbRegisters.Write64(ctx, gdbRegId, ss) && ss.IsEmpty()) - { - ReplyOK(); - } - else - { - ReplyError(); - } + Processor.Reply(GdbRegisters.Write64(ctx, gdbRegId, ss) && ss.IsEmpty()); } } - internal void CommandStep(ulong? newPc) + internal void Step(ulong? newPc) { if (Debugger.CThread == null) { - ReplyError(); + Processor.ReplyError(); return; } - var thread = Debugger.DebugProcess.GetThread(Debugger.CThread.Value); + KThread thread = Debugger.DebugProcess.GetThread(Debugger.CThread.Value); if (newPc.HasValue) { @@ -312,24 +279,24 @@ namespace Ryujinx.HLE.Debugger.Gdb if (!Debugger.DebugProcess.DebugStep(thread)) { - ReplyError(); + Processor.ReplyError(); } else { Debugger.GThread = Debugger.CThread = thread.ThreadUid; - Reply($"T05thread:{thread.ThreadUid:x};"); + Processor.Reply($"T05thread:{thread.ThreadUid:x};"); } } - internal void CommandIsAlive(ulong? threadId) + internal void IsAlive(ulong? threadId) { if (Debugger.GetThreads().Any(x => x.ThreadUid == threadId)) { - ReplyOK(); + Processor.ReplyOK(); } else { - Reply("E00"); + Processor.Reply("E00"); } } @@ -341,14 +308,14 @@ namespace Ryujinx.HLE.Debugger.Gdb Step } - record VContPendingAction(VContAction Action, ushort? Signal = null); + record VContPendingAction(VContAction Action/*, ushort? Signal = null*/); - internal void HandleVContCommand(StringStream ss) + internal void VCont(StringStream ss) { string[] rawActions = ss.ReadRemaining().Split(';', StringSplitOptions.RemoveEmptyEntries); - var threadActionMap = new Dictionary(); - foreach (var thread in Debugger.GetThreads()) + Dictionary threadActionMap = new(); + foreach (KThread thread in Debugger.GetThreads()) { threadActionMap[thread.ThreadUid] = new VContPendingAction(VContAction.None); } @@ -358,8 +325,8 @@ namespace Ryujinx.HLE.Debugger.Gdb // For each inferior thread, the *leftmost* action with a matching thread-id is applied. for (int i = rawActions.Length - 1; i >= 0; i--) { - var rawAction = rawActions[i]; - var stream = new StringStream(rawAction); + string rawAction = rawActions[i]; + StringStream stream = new(rawAction); char cmd = stream.ReadChar(); VContAction action = cmd switch @@ -371,10 +338,12 @@ namespace Ryujinx.HLE.Debugger.Gdb }; // Note: We don't support signals yet. - ushort? signal = null; + //ushort? signal = null; if (cmd is 'C' or 'S') { - signal = (ushort)stream.ReadLengthAsHex(2); + /*signal = (ushort)*/stream.ReadLengthAsHex(2); + // we still call the read length method even if we have signals commented + // since that method advances the underlying string position } ulong? threadId = null; @@ -387,14 +356,14 @@ namespace Ryujinx.HLE.Debugger.Gdb { if (threadActionMap.ContainsKey(threadId.Value)) { - threadActionMap[threadId.Value] = new VContPendingAction(action, signal); + threadActionMap[threadId.Value] = new VContPendingAction(action/*, signal*/); } } else { - foreach (var row in threadActionMap.ToList()) + foreach (ulong thread in threadActionMap.Keys) { - threadActionMap[row.Key] = new VContPendingAction(action, signal); + threadActionMap[thread] = new VContPendingAction(action/*, signal*/); } if (action == VContAction.Continue) @@ -411,11 +380,11 @@ namespace Ryujinx.HLE.Debugger.Gdb bool hasError = false; - foreach (var (threadUid, action) in threadActionMap) + foreach ((ulong threadUid, VContPendingAction action) in threadActionMap) { if (action.Action == VContAction.Step) { - var thread = Debugger.DebugProcess.GetThread(threadUid); + KThread thread = Debugger.DebugProcess.GetThread(threadUid); if (!Debugger.DebugProcess.DebugStep(thread)) { hasError = true; @@ -432,7 +401,7 @@ namespace Ryujinx.HLE.Debugger.Gdb } else if (defaultAction == VContAction.None) { - foreach (var (threadUid, action) in threadActionMap) + foreach ((ulong threadUid, VContPendingAction action) in threadActionMap) { if (action.Action == VContAction.Continue) { @@ -441,26 +410,19 @@ namespace Ryujinx.HLE.Debugger.Gdb } } - if (hasError) - { - ReplyError(); - } - else - { - ReplyOK(); - } + Processor.Reply(!hasError); - foreach (var (threadUid, action) in threadActionMap) + foreach ((ulong threadUid, VContPendingAction action) in threadActionMap) { if (action.Action == VContAction.Step) { Debugger.GThread = Debugger.CThread = threadUid; - Reply($"T05thread:{threadUid:x};"); + Processor.Reply($"T05thread:{threadUid:x};"); } } } - - internal void HandleQRcmdCommand(string hexCommand) + + internal void Q_Rcmd(string hexCommand) { try { @@ -477,12 +439,12 @@ namespace Ryujinx.HLE.Debugger.Gdb _ => $"Unknown command: {command}\n" }; - Reply(Helpers.ToHex(response)); + Processor.Reply(Helpers.ToHex(response)); } catch (Exception e) { Logger.Error?.Print(LogClass.GdbStub, $"Error processing Rcmd: {e.Message}"); - ReplyError(); + Processor.ReplyError(); } } } diff --git a/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs b/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs index de2f6c25d..f9ce7b153 100644 --- a/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs +++ b/src/Ryujinx.HLE/Debugger/Gdb/Registers.cs @@ -9,10 +9,10 @@ namespace Ryujinx.HLE.Debugger.Gdb /* FPCR = FPSR & ~FpcrMask All of FPCR's bits are reserved in FPCR and vice versa, - see ARM's documentation. + see ARM's documentation. */ private const uint FpcrMask = 0xfc1fffff; - + public static string Read64(IExecutionContext state, int gdbRegId) { switch (gdbRegId) diff --git a/src/Ryujinx.HLE/Debugger/Helpers.cs b/src/Ryujinx.HLE/Debugger/Helpers.cs index a2b802525..eb243dc5b 100644 --- a/src/Ryujinx.HLE/Debugger/Helpers.cs +++ b/src/Ryujinx.HLE/Debugger/Helpers.cs @@ -43,7 +43,7 @@ namespace Ryujinx.HLE.Debugger (byte)'$' => "}\x04", (byte)'*' => "}\x0a", (byte)'}' => "}\x5d", - _ => Convert.ToChar(x).ToString(), + _ => Convert.ToChar(x).ToString() } ).JoinToString(string.Empty); } diff --git a/src/Ryujinx.HLE/Debugger/RegisterInformation.cs b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs index b43899271..c1c576558 100644 --- a/src/Ryujinx.HLE/Debugger/RegisterInformation.cs +++ b/src/Ryujinx.HLE/Debugger/RegisterInformation.cs @@ -18,7 +18,7 @@ namespace Ryujinx.HLE.Debugger private static string GetEmbeddedResourceContent(string resourceName) { Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.Gdb.Xml." + resourceName); - StreamReader reader = new StreamReader(stream); + StreamReader reader = new(stream); string result = reader.ReadToEnd(); reader.Dispose(); stream.Dispose(); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index b823d14f7..eb75fb9a1 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1092,7 +1092,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process MemoryManager = new KPageTable(KernelContext, CpuMemory, Context.AddressSpaceSize); } - private bool InvalidAccessHandler(ulong va) + private static bool InvalidAccessHandler(ulong va) { KernelStatic.GetCurrentThread()?.PrintGuestStackTrace(); KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout(); @@ -1104,7 +1104,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return false; } - private void UndefinedInstructionHandler(IExecutionContext context, ulong address, int opCode) + private static void UndefinedInstructionHandler(IExecutionContext context, ulong address, int opCode) { KernelStatic.GetCurrentThread().PrintGuestStackTrace(); KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout(); @@ -1208,16 +1208,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private class DebuggerInterface : IDebuggableProcess { - private Barrier StepBarrier; + private readonly Barrier _stepBarrier; private readonly KProcess _parent; private readonly KernelContext _kernelContext; - private KThread steppingThread; + private KThread _steppingThread; public DebuggerInterface(KProcess p) { _parent = p; _kernelContext = p.KernelContext; - StepBarrier = new(2); + _stepBarrier = new(2); } public void DebugStop() @@ -1285,7 +1285,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } _kernelContext.CriticalSection.Enter(); - steppingThread = target; + _steppingThread = target; bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration; target.Context.RequestDebugStep(); if (waiting) @@ -1305,14 +1305,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process _kernelContext.CriticalSection.Leave(); bool stepTimedOut = false; - if (!StepBarrier.SignalAndWait(TimeSpan.FromMilliseconds(2000))) + if (!_stepBarrier.SignalAndWait(TimeSpan.FromMilliseconds(2000))) { Logger.Warning?.Print(LogClass.Kernel, $"Failed to step thread {target.ThreadUid} in time."); stepTimedOut = true; } _kernelContext.CriticalSection.Enter(); - steppingThread = null; + _steppingThread = null; if (waiting) { lock (_parent._threadingLock) @@ -1334,7 +1334,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return false; } - StepBarrier.SignalAndWait(); + _stepBarrier.SignalAndWait(); return true; } @@ -1369,12 +1369,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public void DebugInterruptHandler(IExecutionContext ctx) { _kernelContext.CriticalSection.Enter(); - bool stepping = steppingThread != null; + bool stepping = _steppingThread != null; _kernelContext.CriticalSection.Leave(); if (stepping) { - StepBarrier.SignalAndWait(); - StepBarrier.SignalAndWait(); + _stepBarrier.SignalAndWait(); + _stepBarrier.SignalAndWait(); } _parent.InterruptHandler(ctx); }