gdb: Implement QRcmd (monitor) commands

monitor backtrace (mo bt)
monitor registers (mo reg)
monitor get info
This commit is contained in:
Coxxs 2025-06-23 06:18:34 +08:00
parent 7d189ab2c0
commit 009d319bc2
4 changed files with 144 additions and 4 deletions

View file

@ -2,6 +2,7 @@ using ARMeilleure.State;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
@ -53,7 +54,8 @@ namespace Ryujinx.HLE.Debugger
BreakpointManager = new BreakpointManager(this); BreakpointManager = new BreakpointManager(this);
} }
internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcess(); internal KProcess Process => Device.System?.DebugGetApplicationProcess();
internal IDebuggableProcess DebugProcess => Device.System?.DebugGetApplicationProcessDebugInterface();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray(); private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
internal 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;
@ -379,6 +381,13 @@ namespace Ryujinx.HLE.Debugger
break; break;
} }
if (ss.ConsumePrefix("Rcmd,"))
{
string hexCommand = ss.ReadRemaining();
HandleQRcmdCommand(hexCommand);
break;
}
if (ss.ConsumeRemaining("fThreadInfo")) if (ss.ConsumeRemaining("fThreadInfo"))
{ {
Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}"); Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
@ -982,6 +991,97 @@ namespace Ryujinx.HLE.Debugger
} }
} }
private void HandleQRcmdCommand(string hexCommand)
{
try
{
string command = FromHex(hexCommand);
Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
string response = command.Trim().ToLowerInvariant() switch
{
"help" => "backtrace\nbt\nregisters\nreg\nget info\n",
"get info" => GetProcessInfo(),
"backtrace" => GetStackTrace(),
"bt" => GetStackTrace(),
"registers" => GetRegisters(),
"reg" => GetRegisters(),
_ => $"Unknown command: {command}\n"
};
Reply(ToHex(response));
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.GdbStub, $"Error processing Rcmd: {e.Message}");
ReplyError();
}
}
private string GetStackTrace()
{
if (gThread == null)
return "No thread selected\n";
if (Process == null)
return "No application process found\n";
return Process.Debugger.GetGuestStackTrace(DebugProcess.GetThread(gThread.Value));
}
private string GetRegisters()
{
if (gThread == null)
return "No thread selected\n";
if (Process == null)
return "No application process found\n";
return Process.Debugger.GetCpuRegisterPrintout(DebugProcess.GetThread(gThread.Value));
}
private string GetProcessInfo()
{
try
{
if (Process == null)
return "No application process found\n";
KProcess kProcess = Process;
var sb = new StringBuilder();
sb.AppendLine($"Program Id: 0x{kProcess.TitleId:x16}");
sb.AppendLine($"Application: {(kProcess.IsApplication ? 1 : 0)}");
sb.AppendLine("Layout:");
sb.AppendLine($" Alias: 0x{kProcess.MemoryManager.AliasRegionStart:x10} - 0x{kProcess.MemoryManager.AliasRegionEnd - 1:x10}");
sb.AppendLine($" Heap: 0x{kProcess.MemoryManager.HeapRegionStart:x10} - 0x{kProcess.MemoryManager.HeapRegionEnd - 1:x10}");
sb.AppendLine($" Aslr: 0x{kProcess.MemoryManager.AslrRegionStart:x10} - 0x{kProcess.MemoryManager.AslrRegionEnd - 1:x10}");
sb.AppendLine($" Stack: 0x{kProcess.MemoryManager.StackRegionStart:x10} - 0x{kProcess.MemoryManager.StackRegionEnd - 1:x10}");
sb.AppendLine("Modules:");
var debugger = kProcess.Debugger;
if (debugger != null)
{
var images = debugger.GetLoadedImages();
for (int i = 0; i < images.Count; i++)
{
var image = images[i];
ulong endAddress = image.BaseAddress + image.Size - 1;
string name = debugger.GetGuessedNsoNameFromIndex(i);
sb.AppendLine($" 0x{image.BaseAddress:x10} - 0x{endAddress:x10} {name}");
}
}
return sb.ToString();
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.GdbStub, $"Error getting process info: {e.Message}");
return $"Error getting process info: {e.Message}\n";
}
}
private void Reply(string cmd) private void Reply(string cmd)
{ {
Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}"); Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}");
@ -1108,6 +1208,15 @@ namespace Ryujinx.HLE.Debugger
return checksum; return checksum;
} }
private string FromHex(string hexString)
{
if (string.IsNullOrEmpty(hexString))
return string.Empty;
byte[] bytes = Convert.FromHexString(hexString);
return Encoding.ASCII.GetString(bytes);
}
private string ToHex(byte[] bytes) private string ToHex(byte[] bytes)
{ {
return string.Join("", bytes.Select(x => $"{x:x2}")); return string.Join("", bytes.Select(x => $"{x:x2}"));

View file

@ -502,12 +502,20 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause; IsPaused = pause;
} }
internal IDebuggableProcess DebugGetApplicationProcess() internal IDebuggableProcess DebugGetApplicationProcessDebugInterface()
{ {
lock (KernelContext.Processes) lock (KernelContext.Processes)
{ {
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface; return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
} }
} }
internal KProcess DebugGetApplicationProcess()
{
lock (KernelContext.Processes)
{
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication);
}
}
} }
} }

View file

@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.Loaders.Elf; using Ryujinx.HLE.Loaders.Elf;
using Ryujinx.Memory; using Ryujinx.Memory;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -17,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private readonly KProcess _owner; private readonly KProcess _owner;
private class Image public class Image
{ {
public ulong BaseAddress { get; } public ulong BaseAddress { get; }
public ulong Size { get; } public ulong Size { get; }
@ -54,6 +55,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}"); trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
string ThreadName = thread.GetThreadName();
if (!String.IsNullOrEmpty(ThreadName))
{
trace.AppendLine($"Thread ID: {thread.ThreadUid} ({ThreadName})");
} else {
trace.AppendLine($"Thread ID: {thread.ThreadUid}");
}
void AppendTrace(ulong address) void AppendTrace(ulong address)
{ {
if (AnalyzePointer(out PointerInfo info, address, thread)) if (AnalyzePointer(out PointerInfo info, address, thread))
@ -283,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return null; return null;
} }
private string GetGuessedNsoNameFromIndex(int index) public string GetGuessedNsoNameFromIndex(int index)
{ {
if ((uint)index > 11) if ((uint)index > 11)
{ {
@ -316,6 +326,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
} }
} }
public List<Image> GetLoadedImages()
{
EnsureLoaded();
lock (_images)
{
return [.. _images];
}
}
private void EnsureLoaded() private void EnsureLoaded()
{ {
if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0) if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)

View file

@ -1532,6 +1532,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name."); Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
return ""; return "";
} catch (Exception e) {
Logger.Error?.Print(LogClass.Kernel, $"Error getting thread name: {e.Message}");
return "";
} }
} }
} }