diff --git a/src/Ryujinx.HLE/Debugger/BreakpointManager.cs b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs
new file mode 100644
index 000000000..bf462a781
--- /dev/null
+++ b/src/Ryujinx.HLE/Debugger/BreakpointManager.cs
@@ -0,0 +1,203 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+using System.Collections.Concurrent;
+using System.Linq;
+
+namespace Ryujinx.HLE.Debugger
+{
+ internal class Breakpoint
+ {
+ public byte[] OriginalData { get; }
+
+ public bool IsStep { get; }
+
+ public Breakpoint(byte[] originalData, bool isStep)
+ {
+ OriginalData = originalData;
+ IsStep = isStep;
+ }
+ }
+
+ ///
+ /// Manages software breakpoints for the debugger.
+ ///
+ public class BreakpointManager
+ {
+ 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 };
+
+ public BreakpointManager(Debugger debugger)
+ {
+ _debugger = debugger;
+ }
+
+ ///
+ /// Sets a software breakpoint at a specified address.
+ ///
+ /// The memory address to set the breakpoint at.
+ /// The length of the instruction to replace.
+ /// Indicates if this is a single-step breakpoint.
+ /// True if the breakpoint was set successfully; otherwise, false.
+ public bool SetBreakPoint(ulong address, ulong length, bool isStep = false)
+ {
+ if (_breakpoints.ContainsKey(address))
+ {
+ return false;
+ }
+
+ byte[] breakInstruction = GetBreakInstruction(length);
+ if (breakInstruction == null)
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Unsupported instruction length for breakpoint: {length}");
+ return false;
+ }
+
+ var originalInstruction = new byte[length];
+ if (!ReadMemory(address, originalInstruction))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to read memory at 0x{address:X16} to set breakpoint.");
+ return false;
+ }
+
+ if (!WriteMemory(address, breakInstruction))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to write breakpoint at 0x{address:X16}.");
+ return false;
+ }
+
+ var breakpoint = new Breakpoint(originalInstruction, isStep);
+ if (_breakpoints.TryAdd(address, breakpoint))
+ {
+ Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint set at 0x{address:X16}");
+ return true;
+ }
+
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to add breakpoint at 0x{address:X16}.");
+ return false;
+ }
+
+ ///
+ /// Clears a software breakpoint at a specified address.
+ ///
+ /// The memory address of the breakpoint to clear.
+ /// The length of the instruction (unused).
+ /// True if the breakpoint was cleared successfully; otherwise, false.
+ public bool ClearBreakPoint(ulong address, ulong length)
+ {
+ if (_breakpoints.TryGetValue(address, out Breakpoint breakpoint))
+ {
+ if (!WriteMemory(address, breakpoint.OriginalData))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{address:X16} to clear breakpoint.");
+ return false;
+ }
+
+ _breakpoints.TryRemove(address, out _);
+ Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint cleared at 0x{address:X16}");
+ return true;
+ }
+
+ Logger.Warning?.Print(LogClass.GdbStub, $"No breakpoint found at address 0x{address:X16}");
+ return false;
+ }
+
+ ///
+ /// Clears all currently set software breakpoints.
+ ///
+ public void ClearAll()
+ {
+ foreach (var bp in _breakpoints)
+ {
+ if (!WriteMemory(bp.Key, bp.Value.OriginalData))
+ {
+ Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{bp.Key:X16} while clearing all breakpoints.");
+ }
+
+ }
+ _breakpoints.Clear();
+ Logger.Debug?.Print(LogClass.GdbStub, "All breakpoints cleared.");
+ }
+
+ ///
+ /// Clears all currently set single-step software breakpoints.
+ ///
+ public void ClearAllStepBreakpoints()
+ {
+ var stepBreakpoints = _breakpoints.Where(p => p.Value.IsStep).ToList();
+
+ if (stepBreakpoints.Count == 0)
+ {
+ return;
+ }
+
+ foreach (var bp in stepBreakpoints)
+ {
+ if (_breakpoints.TryRemove(bp.Key, out Breakpoint removedBreakpoint))
+ {
+ WriteMemory(bp.Key, removedBreakpoint.OriginalData);
+ }
+ }
+
+ Logger.Debug?.Print(LogClass.GdbStub, "All step breakpoints cleared.");
+ }
+
+
+ private byte[] GetBreakInstruction(ulong length)
+ {
+ if (_debugger.IsProcessAarch32)
+ {
+ if (length == 2)
+ {
+ return _aarch32ThumbBreakInstruction;
+ }
+
+ if (length == 4)
+ {
+ return _aarch32BreakInstruction;
+ }
+ }
+ else
+ {
+ if (length == 4)
+ {
+ return _aarch64BreakInstruction;
+ }
+ }
+
+ return null;
+ }
+
+ private bool ReadMemory(ulong address, byte[] data)
+ {
+ try
+ {
+ _debugger.DebugProcess.CpuMemory.Read(address, data);
+ return true;
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ return false;
+ }
+ }
+
+ private bool WriteMemory(ulong address, byte[] data)
+ {
+ try
+ {
+ _debugger.DebugProcess.CpuMemory.Write(address, data);
+ _debugger.DebugProcess.InvalidateCacheRegion(address, (ulong)data.Length);
+ return true;
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index d1b41c7f7..3c051639d 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -35,6 +35,8 @@ namespace Ryujinx.HLE.Debugger
private ulong? cThread;
private ulong? gThread;
+ private BreakpointManager BreakpointManager;
+
private string previousThreadListXml = "";
public Debugger(Switch device, ushort port)
@@ -48,11 +50,12 @@ namespace Ryujinx.HLE.Debugger
DebuggerThread.Start();
MessageHandlerThread = new Thread(MessageHandlerMain);
MessageHandlerThread.Start();
+ BreakpointManager = new BreakpointManager(this);
}
- private IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
+ internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
- private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
+ internal bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
const int GdbRegisterCount64 = 68;
@@ -514,6 +517,75 @@ namespace Ryujinx.HLE.Debugger
break;
}
goto unknownCommand;
+ case 'Z':
+ {
+ string type = ss.ReadUntil(',');
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadLengthAsHex(1);
+ string extra = ss.ReadRemaining();
+
+ if (extra.Length > 0)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Unsupported Z command extra data: {extra}");
+ ReplyError();
+ return;
+ }
+
+ switch (type)
+ {
+ case "0": // Software breakpoint
+ if (!BreakpointManager.SetBreakPoint(addr, len, false))
+ {
+ ReplyError();
+ }
+ ReplyOK();
+ return;
+ case "1": // Hardware breakpoint
+ case "2": // Write watchpoint
+ case "3": // Read watchpoint
+ case "4": // Access watchpoint
+ ReplyError();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
+ case 'z':
+ {
+ string type = ss.ReadUntil(',');
+ ss.ConsumePrefix(",");
+ ulong addr = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadLengthAsHex(1);
+ string extra = ss.ReadRemaining();
+
+ if (extra.Length > 0)
+ {
+ Logger.Notice.Print(LogClass.GdbStub, $"Unsupported z command extra data: {extra}");
+ ReplyError();
+ return;
+ }
+
+ switch (type)
+ {
+ case "0": // Software breakpoint
+ if (!BreakpointManager.ClearBreakPoint(addr, len))
+ {
+ ReplyError();
+ }
+ ReplyOK();
+ return;
+ case "1": // Hardware breakpoint
+ case "2": // Write watchpoint
+ case "3": // Read watchpoint
+ case "4": // Access watchpoint
+ ReplyError();
+ return;
+ default:
+ ReplyError();
+ return;
+ }
+ }
default:
unknownCommand:
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
@@ -666,7 +738,7 @@ namespace Ryujinx.HLE.Debugger
void CommandDetach()
{
- // TODO: Remove all breakpoints
+ BreakpointManager.ClearAll();
CommandContinue(null);
}
@@ -1017,6 +1089,8 @@ namespace Ryujinx.HLE.Debugger
WriteStream = null;
ClientSocket.Close();
ClientSocket = null;
+
+ BreakpointManager.ClearAll();
}
}