Debugger: Initial implementation
# Conflicts: # src/Ryujinx.HLE/Debugger/Debugger.cs # src/Ryujinx.HLE/Debugger/GdbSignal.cs # src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs # src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs # src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs # src/Ryujinx.HLE/Debugger/Message/IMessage.cs # src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs # src/Ryujinx.HLE/Debugger/StringStream.cs # src/Ryujinx.HLE/Switch.cs
This commit is contained in:
parent
2a17f1314d
commit
6edc00ec9c
19 changed files with 569 additions and 3 deletions
|
@ -136,6 +136,13 @@ namespace Ryujinx.Cpu.AppleHv
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void DebugContinue() => _impl.DebugContinue();
|
public void DebugContinue() => _impl.DebugContinue();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong DebugPc
|
||||||
|
{
|
||||||
|
get => _impl.DebugPc;
|
||||||
|
set => _impl.DebugPc = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void StopRunning()
|
public void StopRunning()
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,6 +21,8 @@ namespace Ryujinx.Cpu.AppleHv
|
||||||
private readonly ulong[] _x;
|
private readonly ulong[] _x;
|
||||||
private readonly V128[] _v;
|
private readonly V128[] _v;
|
||||||
|
|
||||||
|
public ulong DebugPc { get; set; }
|
||||||
|
|
||||||
public HvExecutionContextShadow()
|
public HvExecutionContextShadow()
|
||||||
{
|
{
|
||||||
_x = new ulong[32];
|
_x = new ulong[32];
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||||
internal Barrier _stepBarrier = new Barrier(2);
|
internal Barrier _stepBarrier = new Barrier(2);
|
||||||
|
|
||||||
// This is only valid while debugging is enabled.
|
// This is only valid while debugging is enabled.
|
||||||
public ulong DebugPc;
|
public ulong DebugPc { get; set; }
|
||||||
|
|
||||||
static HvExecutionContextVcpu()
|
static HvExecutionContextVcpu()
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,5 +47,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||||
void DebugStop();
|
void DebugStop();
|
||||||
bool DebugStep();
|
bool DebugStep();
|
||||||
void DebugContinue();
|
void DebugContinue();
|
||||||
|
|
||||||
|
ulong DebugPc { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,5 +113,7 @@ namespace Ryujinx.Cpu
|
||||||
void DebugStop();
|
void DebugStop();
|
||||||
bool DebugStep();
|
bool DebugStep();
|
||||||
void DebugContinue();
|
void DebugContinue();
|
||||||
|
|
||||||
|
ulong DebugPc { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,13 @@ namespace Ryujinx.Cpu.Jit
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void DebugContinue() => _impl.DebugContinue();
|
public void DebugContinue() => _impl.DebugContinue();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong DebugPc
|
||||||
|
{
|
||||||
|
get => _impl.DebugPc;
|
||||||
|
set => _impl.DebugPc = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void StopRunning()
|
public void StopRunning()
|
||||||
{
|
{
|
||||||
|
|
410
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
410
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
public class Debugger : IDisposable
|
||||||
|
{
|
||||||
|
internal Switch Device { get; private set; }
|
||||||
|
|
||||||
|
public ushort GdbStubPort { get; private set; }
|
||||||
|
|
||||||
|
private TcpListener ListenerSocket;
|
||||||
|
private Socket ClientSocket = null;
|
||||||
|
private NetworkStream ReadStream = null;
|
||||||
|
private NetworkStream WriteStream = null;
|
||||||
|
private BlockingCollection<IMessage> Messages = new BlockingCollection<IMessage>(1);
|
||||||
|
private Thread SocketThread;
|
||||||
|
private Thread HandlerThread;
|
||||||
|
|
||||||
|
private ulong cThread;
|
||||||
|
private ulong gThread;
|
||||||
|
|
||||||
|
public Debugger(Switch device, ushort port)
|
||||||
|
{
|
||||||
|
Device = device;
|
||||||
|
GdbStubPort = port;
|
||||||
|
|
||||||
|
ARMeilleure.Optimizations.EnableDebugging = true;
|
||||||
|
|
||||||
|
SocketThread = new Thread(SocketReaderThreadMain);
|
||||||
|
HandlerThread = new Thread(HandlerThreadMain);
|
||||||
|
SocketThread.Start();
|
||||||
|
HandlerThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads();
|
||||||
|
private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids();
|
||||||
|
private Ryujinx.Cpu.IExecutionContext GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThreadContext(threadUid);
|
||||||
|
private Ryujinx.Cpu.IExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray();
|
||||||
|
private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory;
|
||||||
|
|
||||||
|
const int GdbRegisterCount = 34;
|
||||||
|
|
||||||
|
private int GdbRegisterHexSize(int gdbRegId)
|
||||||
|
{
|
||||||
|
switch (gdbRegId)
|
||||||
|
{
|
||||||
|
case >= 0 and <= 31:
|
||||||
|
return 16;
|
||||||
|
case 32:
|
||||||
|
return 16;
|
||||||
|
case 33:
|
||||||
|
return 8;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GdbReadRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId)
|
||||||
|
{
|
||||||
|
switch (gdbRegId)
|
||||||
|
{
|
||||||
|
case >= 0 and <= 31:
|
||||||
|
return $"{state.GetX(gdbRegId):x16}";
|
||||||
|
case 32:
|
||||||
|
return $"{state.DebugPc:x16}";
|
||||||
|
case 33:
|
||||||
|
return $"{state.Pstate:x8}";
|
||||||
|
default:
|
||||||
|
throw new ArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GdbWriteRegister(Ryujinx.Cpu.IExecutionContext state, int gdbRegId, ulong value)
|
||||||
|
{
|
||||||
|
switch (gdbRegId)
|
||||||
|
{
|
||||||
|
case >= 0 and <= 31:
|
||||||
|
state.SetX(gdbRegId, value);
|
||||||
|
return;
|
||||||
|
case 32:
|
||||||
|
state.DebugPc = value;
|
||||||
|
return;
|
||||||
|
case 33:
|
||||||
|
state.Pstate = (uint)value;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlerThreadMain()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (Messages.Take())
|
||||||
|
{
|
||||||
|
case AbortMessage _:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case BreakInMessage _:
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SendNackMessage _:
|
||||||
|
WriteStream.WriteByte((byte)'-');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CommandMessage { Command: var cmd }:
|
||||||
|
Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
|
||||||
|
WriteStream.WriteByte((byte)'+');
|
||||||
|
ProcessCommand(cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessCommand(string cmd)
|
||||||
|
{
|
||||||
|
StringStream ss = new StringStream(cmd);
|
||||||
|
|
||||||
|
switch (ss.ReadChar())
|
||||||
|
{
|
||||||
|
case '!':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
// Enable extended mode
|
||||||
|
Reply("OK");
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
CommandQuery();
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
CommandDetach();
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
if (!ss.IsEmpty())
|
||||||
|
{
|
||||||
|
goto default;
|
||||||
|
}
|
||||||
|
CommandReadGeneralRegisters();
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
CommandWriteGeneralRegisters(ss);
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
{
|
||||||
|
char op = ss.ReadChar();
|
||||||
|
ulong threadId = ss.ReadRemainingAsHex();
|
||||||
|
CommandSetThread(op, threadId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'k':
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "Kill request received");
|
||||||
|
Reply("");
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
{
|
||||||
|
ulong addr = ss.ReadUntilAsHex(',');
|
||||||
|
ulong len = ss.ReadRemainingAsHex();
|
||||||
|
CommandReadMemory(addr, len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'M':
|
||||||
|
{
|
||||||
|
ulong addr = ss.ReadUntilAsHex(',');
|
||||||
|
ulong len = ss.ReadUntilAsHex(':');
|
||||||
|
CommandWriteMemory(addr, len, ss);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'p':
|
||||||
|
{
|
||||||
|
ulong gdbRegId = ss.ReadRemainingAsHex();
|
||||||
|
CommandReadGeneralRegister((int)gdbRegId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'P':
|
||||||
|
{
|
||||||
|
ulong gdbRegId = ss.ReadUntilAsHex('=');
|
||||||
|
ulong value = ss.ReadRemainingAsHex();
|
||||||
|
CommandWriteGeneralRegister((int)gdbRegId, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
|
||||||
|
Reply("");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQuery()
|
||||||
|
{
|
||||||
|
// GDB is performing initial contact. Stop everything.
|
||||||
|
HaltApplication();
|
||||||
|
gThread = cThread = GetThreadIds().First();
|
||||||
|
Reply($"T05thread:{cThread:x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandContinue(ulong? newPc)
|
||||||
|
{
|
||||||
|
if (newPc.HasValue)
|
||||||
|
{
|
||||||
|
GetThread(cThread).DebugPc = newPc.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var thread in GetThreads())
|
||||||
|
{
|
||||||
|
thread.DebugContinue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDetach()
|
||||||
|
{
|
||||||
|
// TODO: Remove all breakpoints
|
||||||
|
CommandContinue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandReadGeneralRegisters()
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
string registers = "";
|
||||||
|
for (int i = 0; i < GdbRegisterCount; i++)
|
||||||
|
{
|
||||||
|
registers += GdbReadRegister(ctx, i);
|
||||||
|
}
|
||||||
|
Reply(registers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandWriteGeneralRegisters(StringStream ss)
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
for (int i = 0; i < GdbRegisterCount; i++)
|
||||||
|
{
|
||||||
|
GdbWriteRegister(ctx, i, ss.ReadLengthAsHex(GdbRegisterHexSize(i)));
|
||||||
|
}
|
||||||
|
Reply(ss.IsEmpty() ? "OK" : "E99");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandSetThread(char op, ulong threadId)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case 'c':
|
||||||
|
cThread = threadId;
|
||||||
|
Reply("OK");
|
||||||
|
return;
|
||||||
|
case 'g':
|
||||||
|
gThread = threadId;
|
||||||
|
Reply("OK");
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
Reply("E99");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandReadMemory(ulong addr, ulong len)
|
||||||
|
{
|
||||||
|
var data = new byte[len];
|
||||||
|
GetMemory().Read(addr, data);
|
||||||
|
Reply(string.Join("", data.Select(x => $"{x:x2}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandWriteMemory(ulong addr, ulong len, StringStream ss)
|
||||||
|
{
|
||||||
|
var data = new byte[len];
|
||||||
|
for (ulong i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
data[i] = (byte)ss.ReadLengthAsHex(2);
|
||||||
|
}
|
||||||
|
GetMemory().Write(addr, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandReadGeneralRegister(int gdbRegId)
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
Reply(GdbReadRegister(ctx, gdbRegId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandWriteGeneralRegister(int gdbRegId, ulong value)
|
||||||
|
{
|
||||||
|
var ctx = GetThread(gThread);
|
||||||
|
GdbWriteRegister(ctx, gdbRegId, value);
|
||||||
|
Reply("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reply(string cmd)
|
||||||
|
{
|
||||||
|
WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SocketReaderThreadMain()
|
||||||
|
{
|
||||||
|
restartListen:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort);
|
||||||
|
ListenerSocket = new TcpListener(endpoint);
|
||||||
|
ListenerSocket.Start();
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client");
|
||||||
|
|
||||||
|
ClientSocket = ListenerSocket.AcceptSocket();
|
||||||
|
ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read);
|
||||||
|
WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write);
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "GDB client connected");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (ReadStream.ReadByte())
|
||||||
|
{
|
||||||
|
case -1:
|
||||||
|
goto eof;
|
||||||
|
case '+':
|
||||||
|
continue;
|
||||||
|
case '-':
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "NACK received!");
|
||||||
|
continue;
|
||||||
|
case '\x03':
|
||||||
|
Messages.Add(new BreakInMessage());
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
string cmd = "";
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int x = ReadStream.ReadByte();
|
||||||
|
if (x == -1)
|
||||||
|
goto eof;
|
||||||
|
if (x == '#')
|
||||||
|
break;
|
||||||
|
cmd += (char)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}";
|
||||||
|
// Debug.Assert(checksum == $"{CalculateChecksum(cmd):x2}");
|
||||||
|
|
||||||
|
Messages.Add(new CommandMessage(cmd));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eof:
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection");
|
||||||
|
goto restartListen;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Logger.Notice.Print(LogClass.GdbStub, "GDB stub socket closed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte CalculateChecksum(string cmd)
|
||||||
|
{
|
||||||
|
byte checksum = 0;
|
||||||
|
foreach (char x in cmd)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
checksum += (byte)x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (HandlerThread.IsAlive)
|
||||||
|
{
|
||||||
|
Messages.Add(new AbortMessage());
|
||||||
|
}
|
||||||
|
ListenerSocket.Stop();
|
||||||
|
ClientSocket?.Shutdown(SocketShutdown.Both);
|
||||||
|
ClientSocket?.Close();
|
||||||
|
ReadStream?.Close();
|
||||||
|
WriteStream?.Close();
|
||||||
|
SocketThread.Join();
|
||||||
|
HandlerThread.Join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
enum GdbSignal
|
||||||
|
{
|
||||||
|
Zero = 0,
|
||||||
|
Int = 2,
|
||||||
|
Quit = 3,
|
||||||
|
Trap = 5,
|
||||||
|
Abort = 6,
|
||||||
|
Alarm = 14,
|
||||||
|
IO = 23,
|
||||||
|
XCPU = 24,
|
||||||
|
Unknown = 143
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct AbortMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct BreakInMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct CommandMessage : IMessage
|
||||||
|
{
|
||||||
|
public string Command;
|
||||||
|
|
||||||
|
public CommandMessage(string cmd)
|
||||||
|
{
|
||||||
|
Command = cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
interface IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct SendNackMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
68
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
68
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
class StringStream
|
||||||
|
{
|
||||||
|
private readonly string Data;
|
||||||
|
private int Position;
|
||||||
|
|
||||||
|
public StringStream(string s)
|
||||||
|
{
|
||||||
|
Data = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char ReadChar()
|
||||||
|
{
|
||||||
|
return Data[Position++];
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadUntil(char needle)
|
||||||
|
{
|
||||||
|
int needlePos = Data.IndexOf(needle, Position);
|
||||||
|
|
||||||
|
if (needlePos == -1)
|
||||||
|
{
|
||||||
|
needlePos = Data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
string result = Data.Substring(Position, needlePos - Position);
|
||||||
|
Position = needlePos + 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadLength(int len)
|
||||||
|
{
|
||||||
|
string result = Data.Substring(Position, len);
|
||||||
|
Position += len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadRemaining()
|
||||||
|
{
|
||||||
|
string result = Data.Substring(Position);
|
||||||
|
Position = Data.Length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadRemainingAsHex()
|
||||||
|
{
|
||||||
|
return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadUntilAsHex(char needle)
|
||||||
|
{
|
||||||
|
return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadLengthAsHex(int len)
|
||||||
|
{
|
||||||
|
return ulong.Parse(ReadLength(len), NumberStyles.HexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty()
|
||||||
|
{
|
||||||
|
return Position >= Data.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
|
||||||
private readonly ulong[] _x = new ulong[32];
|
private readonly ulong[] _x = new ulong[32];
|
||||||
|
|
||||||
|
public ulong DebugPc { get; set; }
|
||||||
|
|
||||||
public ulong GetX(int index) => _x[index];
|
public ulong GetX(int index) => _x[index];
|
||||||
public void SetX(int index, ulong value) => _x[index] = value;
|
public void SetX(int index, ulong value) => _x[index] = value;
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace Ryujinx.HLE
|
||||||
public Hid Hid { get; }
|
public Hid Hid { get; }
|
||||||
public TamperMachine TamperMachine { get; }
|
public TamperMachine TamperMachine { get; }
|
||||||
public IHostUIHandler UIHandler { get; }
|
public IHostUIHandler UIHandler { get; }
|
||||||
|
public Debugger.Debugger Debugger { get; }
|
||||||
|
|
||||||
public bool EnableDeviceVsync { get; set; } = true;
|
public bool EnableDeviceVsync { get; set; } = true;
|
||||||
|
|
||||||
|
@ -53,6 +54,7 @@ namespace Ryujinx.HLE
|
||||||
Statistics = new PerformanceStatistics();
|
Statistics = new PerformanceStatistics();
|
||||||
Hid = new Hid(this, System.HidStorage);
|
Hid = new Hid(this, System.HidStorage);
|
||||||
Processes = new ProcessLoader(this);
|
Processes = new ProcessLoader(this);
|
||||||
|
Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, configuration.GdbStubPort) : null;
|
||||||
TamperMachine = new TamperMachine();
|
TamperMachine = new TamperMachine();
|
||||||
|
|
||||||
System.InitializeServices();
|
System.InitializeServices();
|
||||||
|
@ -154,6 +156,7 @@ namespace Ryujinx.HLE
|
||||||
AudioDeviceDriver.Dispose();
|
AudioDeviceDriver.Dispose();
|
||||||
FileSystem.Dispose();
|
FileSystem.Dispose();
|
||||||
Memory.Dispose();
|
Memory.Dispose();
|
||||||
|
Debugger.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,5 +229,13 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
||||||
public string InputPath { get; set; }
|
public string InputPath { get; set; }
|
||||||
|
|
||||||
|
// Debugging
|
||||||
|
|
||||||
|
[Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enable the GDB stub.")]
|
||||||
|
public bool EnableGdbStub { get; set; }
|
||||||
|
|
||||||
|
[Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "GDB stub port.")]
|
||||||
|
public ushort GdbStubPort { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -571,7 +571,9 @@ namespace Ryujinx.Headless.SDL2
|
||||||
options.AudioVolume,
|
options.AudioVolume,
|
||||||
options.UseHypervisor ?? true,
|
options.UseHypervisor ?? true,
|
||||||
options.MultiplayerLanInterfaceId,
|
options.MultiplayerLanInterfaceId,
|
||||||
Common.Configuration.Multiplayer.MultiplayerMode.Disabled);
|
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
|
||||||
|
options.EnableGdbStub,
|
||||||
|
options.GdbStubPort);
|
||||||
|
|
||||||
return new Switch(configuration);
|
return new Switch(configuration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -872,7 +872,9 @@ namespace Ryujinx.Ava
|
||||||
ConfigurationState.Instance.System.AudioVolume,
|
ConfigurationState.Instance.System.AudioVolume,
|
||||||
ConfigurationState.Instance.System.UseHypervisor,
|
ConfigurationState.Instance.System.UseHypervisor,
|
||||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
|
||||||
ConfigurationState.Instance.Multiplayer.Mode);
|
ConfigurationState.Instance.Multiplayer.Mode,
|
||||||
|
ConfigurationState.Instance.Debug.EnableGdbStub.Value,
|
||||||
|
ConfigurationState.Instance.Debug.GdbStubPort.Value);
|
||||||
|
|
||||||
Device = new Switch(configuration);
|
Device = new Switch(configuration);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue