diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs index 3c051639d..5bde92aed 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.cs @@ -2,6 +2,7 @@ using ARMeilleure.State; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Memory; using System; @@ -53,7 +54,8 @@ namespace Ryujinx.HLE.Debugger 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(); internal bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32; private KernelContext KernelContext => Device.System.KernelContext; @@ -379,6 +381,13 @@ namespace Ryujinx.HLE.Debugger break; } + if (ss.ConsumePrefix("Rcmd,")) + { + string hexCommand = ss.ReadRemaining(); + HandleQRcmdCommand(hexCommand); + break; + } + if (ss.ConsumeRemaining("fThreadInfo")) { 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) { Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}"); @@ -1108,6 +1208,15 @@ namespace Ryujinx.HLE.Debugger 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) { return string.Join("", bytes.Select(x => $"{x:x2}")); diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 44adb9674..517f8ef16 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -502,12 +502,20 @@ namespace Ryujinx.HLE.HOS IsPaused = pause; } - internal IDebuggableProcess DebugGetApplicationProcess() + internal IDebuggableProcess DebugGetApplicationProcessDebugInterface() { lock (KernelContext.Processes) { return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface; } } + + internal KProcess DebugGetApplicationProcess() + { + lock (KernelContext.Processes) + { + return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication); + } + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs index c4a9835cc..87da9f7a6 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs @@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.Loaders.Elf; using Ryujinx.Memory; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -17,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private readonly KProcess _owner; - private class Image + public class Image { public ulong BaseAddress { get; } public ulong Size { get; } @@ -54,6 +55,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process 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) { if (AnalyzePointer(out PointerInfo info, address, thread)) @@ -283,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process return null; } - private string GetGuessedNsoNameFromIndex(int index) + public string GetGuessedNsoNameFromIndex(int index) { if ((uint)index > 11) { @@ -316,6 +326,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } + public List GetLoadedImages() + { + EnsureLoaded(); + + lock (_images) + { + return [.. _images]; + } + } + private void EnsureLoaded() { if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index bb0548d19..20fb426ba 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1532,6 +1532,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name."); return ""; + } catch (Exception e) { + Logger.Error?.Print(LogClass.Kernel, $"Error getting thread name: {e.Message}"); + return ""; } } }