mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-09-13 05:25:06 +00:00
204 lines
6.7 KiB
C#
204 lines
6.7 KiB
C#
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Manages software breakpoints for the debugger.
|
|
/// </summary>
|
|
public class BreakpointManager
|
|
{
|
|
private readonly Debugger _debugger;
|
|
private readonly ConcurrentDictionary<ulong, Breakpoint> _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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a software breakpoint at a specified address.
|
|
/// </summary>
|
|
/// <param name="address">The memory address to set the breakpoint at.</param>
|
|
/// <param name="length">The length of the instruction to replace.</param>
|
|
/// <param name="isStep">Indicates if this is a single-step breakpoint.</param>
|
|
/// <returns>True if the breakpoint was set successfully; otherwise, false.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears a software breakpoint at a specified address.
|
|
/// </summary>
|
|
/// <param name="address">The memory address of the breakpoint to clear.</param>
|
|
/// <param name="length">The length of the instruction (unused).</param>
|
|
/// <returns>True if the breakpoint was cleared successfully; otherwise, false.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all currently set software breakpoints.
|
|
/// </summary>
|
|
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.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all currently set single-step software breakpoints.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|