mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-27 22:06:24 +02:00
gdb: Implement z0/Z0 software breakpoints
This commit is contained in:
parent
7d5f7bc479
commit
bad1dd8899
2 changed files with 280 additions and 3 deletions
203
src/Ryujinx.HLE/Debugger/BreakpointManager.cs
Normal file
203
src/Ryujinx.HLE/Debugger/BreakpointManager.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,8 @@ namespace Ryujinx.HLE.Debugger
|
||||||
private ulong? cThread;
|
private ulong? cThread;
|
||||||
private ulong? gThread;
|
private ulong? gThread;
|
||||||
|
|
||||||
|
private BreakpointManager BreakpointManager;
|
||||||
|
|
||||||
private string previousThreadListXml = "";
|
private string previousThreadListXml = "";
|
||||||
|
|
||||||
public Debugger(Switch device, ushort port)
|
public Debugger(Switch device, ushort port)
|
||||||
|
@ -48,11 +50,12 @@ namespace Ryujinx.HLE.Debugger
|
||||||
DebuggerThread.Start();
|
DebuggerThread.Start();
|
||||||
MessageHandlerThread = new Thread(MessageHandlerMain);
|
MessageHandlerThread = new Thread(MessageHandlerMain);
|
||||||
MessageHandlerThread.Start();
|
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 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;
|
private KernelContext KernelContext => Device.System.KernelContext;
|
||||||
|
|
||||||
const int GdbRegisterCount64 = 68;
|
const int GdbRegisterCount64 = 68;
|
||||||
|
@ -514,6 +517,75 @@ namespace Ryujinx.HLE.Debugger
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
goto unknownCommand;
|
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:
|
default:
|
||||||
unknownCommand:
|
unknownCommand:
|
||||||
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
|
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
|
||||||
|
@ -666,7 +738,7 @@ namespace Ryujinx.HLE.Debugger
|
||||||
|
|
||||||
void CommandDetach()
|
void CommandDetach()
|
||||||
{
|
{
|
||||||
// TODO: Remove all breakpoints
|
BreakpointManager.ClearAll();
|
||||||
CommandContinue(null);
|
CommandContinue(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1017,6 +1089,8 @@ namespace Ryujinx.HLE.Debugger
|
||||||
WriteStream = null;
|
WriteStream = null;
|
||||||
ClientSocket.Close();
|
ClientSocket.Close();
|
||||||
ClientSocket = null;
|
ClientSocket = null;
|
||||||
|
|
||||||
|
BreakpointManager.ClearAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue