From bad1dd88993edd667cad30881bf3c2b74bbf3ba2 Mon Sep 17 00:00:00 2001 From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app> Date: Sun, 22 Jun 2025 05:28:31 +0800 Subject: [PATCH] gdb: Implement z0/Z0 software breakpoints --- src/Ryujinx.HLE/Debugger/BreakpointManager.cs | 203 ++++++++++++++++++ src/Ryujinx.HLE/Debugger/Debugger.cs | 80 ++++++- 2 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 src/Ryujinx.HLE/Debugger/BreakpointManager.cs 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(); } }