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; } } } }