ryujinx/src/Ryujinx.HLE/Debugger/Debugger.cs

114 lines
4 KiB
C#

using Ryujinx.Common.Logging;
using Ryujinx.HLE.Debugger.Gdb;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
namespace Ryujinx.HLE.Debugger
{
public partial class Debugger : IDisposable
{
internal Switch Device { get; private set; }
public ushort GdbStubPort { get; private set; }
private readonly BlockingCollection<Message.IMarker> _messages = new(1);
private readonly Thread _debuggerThread;
private readonly Thread _messageHandlerThread;
private TcpListener _listenerSocket;
private Socket _clientSocket;
private NetworkStream _readStream;
private NetworkStream _writeStream;
private GdbCommandProcessor _commandProcessor;
private GdbCommands _commands;
private bool _shuttingDown;
private readonly ManualResetEventSlim _breakHandlerEvent = new(false);
internal ulong? CThread;
internal ulong? GThread;
public readonly BreakpointManager BreakpointManager;
public Debugger(Switch device, ushort port)
{
Device = device;
GdbStubPort = port;
ARMeilleure.Optimizations.EnableDebugging = true;
_debuggerThread = new Thread(DebuggerThreadMain);
_debuggerThread.Start();
_messageHandlerThread = new Thread(MessageHandlerMain);
_messageHandlerThread.Start();
BreakpointManager = new BreakpointManager(this);
}
internal KProcess Process => Device.System?.DebugGetApplicationProcess();
internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcessDebugInterface();
internal KThread[] GetThreads() => DebugProcess.ThreadUids.Select(DebugProcess.GetThread).ToArray();
internal bool IsProcess32Bit => DebugProcess.GetThread(GThread ?? DebugProcess.ThreadUids.First()).Context.IsAarch32;
internal bool WriteRegister(IExecutionContext ctx, int registerId, StringStream ss) =>
IsProcess32Bit
? ctx.WriteRegister32(registerId, ss)
: ctx.WriteRegister64(registerId, ss);
internal string ReadRegister(IExecutionContext ctx, int registerId) =>
IsProcess32Bit
? ctx.ReadRegister32(registerId)
: ctx.ReadRegister64(registerId);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_shuttingDown = true;
_listenerSocket.Stop();
_clientSocket?.Shutdown(SocketShutdown.Both);
_clientSocket?.Close();
_readStream?.Close();
_writeStream?.Close();
_debuggerThread.Join();
_messages.Add(Message.Kill);
_messageHandlerThread.Join();
_messages.Dispose();
_breakHandlerEvent.Dispose();
}
}
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
{
DebugProcess.DebugInterruptHandler(ctx);
_breakHandlerEvent.Reset();
_messages.Add(new ThreadBreakMessage(ctx, address, imm));
// Messages.Add can block, so we log it after adding the message to make sure user can see the log at the same time GDB receives the break message
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
// Wait for the process to stop before returning to avoid BreakHandler being called multiple times from the same breakpoint
_breakHandlerEvent.Wait(5000);
}
public void StepHandler(IExecutionContext ctx)
{
DebugProcess.DebugInterruptHandler(ctx);
}
}
}