From ccca2e82825c2abc2b3b3491cb66cc3e4f76f59e Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Tue, 11 Nov 2025 10:12:31 +0800
Subject: [PATCH 1/3] gdb: add `monitor get mapping`
monitor get mappings
monitor get mappings
monitor get mapping
---
src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs | 246 ++++++++++++++++++++--
src/Ryujinx.HLE/Debugger/Gdb/Commands.cs | 5 +-
2 files changed, 230 insertions(+), 21 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
index d3094d842..ea4c5aa98 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
@@ -1,43 +1,91 @@
using Gommon;
using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Memory;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
+using System.Text;
namespace Ryujinx.HLE.Debugger
{
public partial class Debugger
{
+ private sealed record RcmdEntry(string[] Names, Func Handler, string[] HelpLines);
+
+ // Atmosphere/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp
+ private static readonly string[] _memoryStateNames =
+ {
+ "----- Free -----",
+ "Io ",
+ "Static ",
+ "Code ",
+ "CodeData ",
+ "Normal ",
+ "Shared ",
+ "Alias ",
+ "AliasCode ",
+ "AliasCodeData ",
+ "Ipc ",
+ "Stack ",
+ "ThreadLocal ",
+ "Transfered ",
+ "SharedTransfered",
+ "SharedCode ",
+ "Inaccessible ",
+ "NonSecureIpc ",
+ "NonDeviceIpc ",
+ "Kernel ",
+ "GeneratedCode ",
+ "CodeOut ",
+ "Coverage ",
+ };
+
static Debugger()
{
- _rcmdDelegates.Add(["help"],
- _ => _rcmdDelegates.Keys
- .Where(x => !x[0].Equals("help"))
- .Select(x => x.JoinToString('\n'))
- .JoinToString('\n') + '\n'
- );
- _rcmdDelegates.Add(["get info"], dbgr => dbgr.GetProcessInfo());
- _rcmdDelegates.Add(["backtrace", "bt"], dbgr => dbgr.GetStackTrace());
- _rcmdDelegates.Add(["registers", "reg"], dbgr => dbgr.GetRegisters());
- _rcmdDelegates.Add(["minidump"], dbgr => dbgr.GetMinidump());
+ _rcmdDelegates.Add(new RcmdEntry(
+ ["help"],
+ (dbgr, _) => _rcmdDelegates
+ .Where(entry => entry.HelpLines.Length > 0)
+ .SelectMany(entry => entry.HelpLines)
+ .JoinToString('\n') + '\n',
+ Array.Empty()));
+
+ _rcmdDelegates.Add(new RcmdEntry(["get info"], (dbgr, _) => dbgr.GetProcessInfo(), ["get info"]));
+ _rcmdDelegates.Add(new RcmdEntry(["backtrace", "bt"], (dbgr, _) => dbgr.GetStackTrace(), ["backtrace", "bt"]));
+ _rcmdDelegates.Add(new RcmdEntry(["registers", "reg"], (dbgr, _) => dbgr.GetRegisters(), ["registers", "reg"]));
+ _rcmdDelegates.Add(new RcmdEntry(["minidump"], (dbgr, _) => dbgr.GetMinidump(), ["minidump"]));
+ _rcmdDelegates.Add(new RcmdEntry(["get mappings"], (dbgr, args) => dbgr.GetMemoryMappings(args), ["get mappings", "get mappings {address}"]));
+ _rcmdDelegates.Add(new RcmdEntry(["get mapping"], (dbgr, args) => dbgr.GetMemoryMapping(args), ["get mapping {address}"]));
}
- private static readonly Dictionary> _rcmdDelegates = new();
+ private static readonly List _rcmdDelegates = [];
- public static Func FindRcmdDelegate(string command)
+ public static string CallRcmdDelegate(Debugger debugger, string command)
{
- Func searchResult = _ => $"Unknown command: {command}\n";
+ string originalCommand = command ?? string.Empty;
+ string trimmedCommand = originalCommand.Trim();
- foreach ((string[] names, Func dlg) in _rcmdDelegates)
+ foreach (RcmdEntry entry in _rcmdDelegates)
{
- if (names.ContainsIgnoreCase(command.Trim()))
+ foreach (string name in entry.Names)
{
- searchResult = dlg;
- break;
+ if (trimmedCommand.Equals(name, StringComparison.OrdinalIgnoreCase))
+ {
+ return entry.Handler(debugger, string.Empty);
+ }
+
+ if (trimmedCommand.Length > name.Length &&
+ trimmedCommand.StartsWith(name, StringComparison.OrdinalIgnoreCase) &&
+ char.IsWhiteSpace(trimmedCommand[name.Length]))
+ {
+ string arguments = trimmedCommand[name.Length..].TrimStart();
+ return entry.Handler(debugger, arguments);
+ }
}
}
- return searchResult;
+ return $"Unknown command: {originalCommand}\n";
}
public string GetStackTrace()
@@ -86,5 +134,167 @@ namespace Ryujinx.HLE.Debugger
return $"Error getting process info: {e.Message}\n";
}
}
+
+ public string GetMemoryMappings(string arguments)
+ {
+ if (Process?.MemoryManager is not { } memoryManager)
+ {
+ return "No application process found\n";
+ }
+
+ ulong startAddress = 0;
+ string trimmedArgs = arguments?.Trim() ?? string.Empty;
+
+ if (!string.IsNullOrEmpty(trimmedArgs))
+ {
+ string value = trimmedArgs.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
+ ? trimmedArgs[2..]
+ : trimmedArgs;
+
+ if (value.Length == 0 || !ulong.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out startAddress))
+ {
+ return $"Invalid address: {trimmedArgs}\n";
+ }
+ }
+
+ ulong requestedAddress = startAddress;
+ ulong currentAddress = Math.Max(requestedAddress, memoryManager.AddrSpaceStart);
+ StringBuilder sb = new();
+ sb.AppendLine($"Mappings (starting from 0x{requestedAddress:x10}):");
+
+ if (currentAddress >= memoryManager.AddrSpaceEnd)
+ {
+ return sb.ToString();
+ }
+
+ while (currentAddress < memoryManager.AddrSpaceEnd)
+ {
+ KMemoryInfo info = memoryManager.QueryMemory(currentAddress);
+
+ try
+ {
+ if (info.Size == 0 || info.Address >= memoryManager.AddrSpaceEnd)
+ {
+ break;
+ }
+
+ sb.AppendLine(FormatMapping(info, indent: true));
+
+ if (info.Address > ulong.MaxValue - info.Size)
+ {
+ break;
+ }
+
+ ulong nextAddress = info.Address + info.Size;
+ if (nextAddress <= currentAddress)
+ {
+ break;
+ }
+
+ currentAddress = nextAddress;
+ }
+ finally
+ {
+ KMemoryInfo.Pool.Release(info);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public string GetMemoryMapping(string arguments)
+ {
+ if (Process?.MemoryManager is not { } memoryManager)
+ {
+ return "No application process found\n";
+ }
+
+ string trimmedArgs = arguments?.Trim() ?? string.Empty;
+
+ if (string.IsNullOrEmpty(trimmedArgs))
+ {
+ return "Missing address argument for `get mapping`\n";
+ }
+
+ ulong address;
+
+ string value = trimmedArgs.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
+ ? trimmedArgs[2..]
+ : trimmedArgs;
+
+ if (value.Length == 0 || !ulong.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out address))
+ {
+ return $"Invalid address: {trimmedArgs}\n";
+ }
+
+ KMemoryInfo info = memoryManager.QueryMemory(address);
+
+ try
+ {
+ return FormatMapping(info, indent: false) + '\n';
+ }
+ finally
+ {
+ KMemoryInfo.Pool.Release(info);
+ }
+ }
+
+ private static string FormatMapping(KMemoryInfo info, bool indent)
+ {
+ ulong endAddress;
+
+ if (info.Size == 0)
+ {
+ endAddress = info.Address;
+ }
+ else if (info.Address > ulong.MaxValue - (info.Size - 1))
+ {
+ endAddress = ulong.MaxValue;
+ }
+ else
+ {
+ endAddress = info.Address + info.Size - 1;
+ }
+
+ string prefix = indent ? " " : string.Empty;
+ return $"{prefix}0x{info.Address:x10} - 0x{endAddress:x10} {GetPermissionString(info)} {GetMemoryStateName(info.State)} {GetAttributeFlags(info)} [{info.IpcRefCount}, {info.DeviceRefCount}]";
+ }
+
+ private static string GetPermissionString(KMemoryInfo info)
+ {
+ if ((info.State & MemoryState.UserMask) == MemoryState.Unmapped)
+ {
+ return " ";
+ }
+
+ return info.Permission switch
+ {
+ KMemoryPermission.ReadAndExecute => "r-x",
+ KMemoryPermission.Read => "r--",
+ KMemoryPermission.ReadAndWrite => "rw-",
+ _ => "---"
+ };
+ }
+
+ private static string GetMemoryStateName(MemoryState state)
+ {
+ int stateIndex = (int)(state & MemoryState.UserMask);
+ if ((uint)stateIndex < _memoryStateNames.Length)
+ {
+ return _memoryStateNames[stateIndex];
+ }
+
+ return "Unknown ";
+ }
+
+ private static string GetAttributeFlags(KMemoryInfo info)
+ {
+ char locked = info.Attribute.HasFlag(MemoryAttribute.PermissionLocked) ? 'L' : '-';
+ char ipc = info.Attribute.HasFlag(MemoryAttribute.IpcMapped) ? 'I' : '-';
+ char device = info.Attribute.HasFlag(MemoryAttribute.DeviceMapped) ? 'D' : '-';
+ char uncached = info.Attribute.HasFlag(MemoryAttribute.Uncached) ? 'U' : '-';
+
+ return $"{locked}{ipc}{device}{uncached}";
+ }
}
}
diff --git a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs
index 66308003f..0b9e12f71 100644
--- a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs
+++ b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs
@@ -404,9 +404,8 @@ namespace Ryujinx.HLE.Debugger.Gdb
string command = Helpers.FromHex(hexCommand);
Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
- Func rcmd = Debugger.FindRcmdDelegate(command);
-
- Processor.ReplyHex(rcmd(Debugger));
+ string response = Debugger.CallRcmdDelegate(Debugger, command);
+ Processor.ReplyHex(response);
}
catch (Exception e)
{
From 17a575a505cd682827fd49e64af3142ef9e5be60 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Tue, 11 Nov 2025 14:15:55 +0800
Subject: [PATCH 2/3] gdb: refactor rcmd address parsing
---
src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs | 40 +++++++++++++++--------
1 file changed, 27 insertions(+), 13 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
index ea4c5aa98..b6e2031d5 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
@@ -142,16 +142,12 @@ namespace Ryujinx.HLE.Debugger
return "No application process found\n";
}
- ulong startAddress = 0;
string trimmedArgs = arguments?.Trim() ?? string.Empty;
+ ulong startAddress = 0;
if (!string.IsNullOrEmpty(trimmedArgs))
{
- string value = trimmedArgs.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
- ? trimmedArgs[2..]
- : trimmedArgs;
-
- if (value.Length == 0 || !ulong.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out startAddress))
+ if (!TryParseAddressArgument(trimmedArgs, out startAddress))
{
return $"Invalid address: {trimmedArgs}\n";
}
@@ -216,13 +212,7 @@ namespace Ryujinx.HLE.Debugger
return "Missing address argument for `get mapping`\n";
}
- ulong address;
-
- string value = trimmedArgs.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
- ? trimmedArgs[2..]
- : trimmedArgs;
-
- if (value.Length == 0 || !ulong.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out address))
+ if (!TryParseAddressArgument(trimmedArgs, out ulong address))
{
return $"Invalid address: {trimmedArgs}\n";
}
@@ -287,6 +277,30 @@ namespace Ryujinx.HLE.Debugger
return "Unknown ";
}
+ private static bool TryParseAddressArgument(string text, out ulong value)
+ {
+ value = 0;
+
+ if (string.IsNullOrWhiteSpace(text))
+ {
+ return false;
+ }
+
+ string trimmed = text.Trim();
+
+ if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
+ {
+ trimmed = trimmed[2..];
+ }
+
+ if (trimmed.Length == 0)
+ {
+ return false;
+ }
+
+ return ulong.TryParse(trimmed, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value);
+ }
+
private static string GetAttributeFlags(KMemoryInfo info)
{
char locked = info.Attribute.HasFlag(MemoryAttribute.PermissionLocked) ? 'L' : '-';
From 37ddd478d15a8a78ebb7352118dbff9e540d8f02 Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Wed, 12 Nov 2025 00:51:47 +0800
Subject: [PATCH 3/3] gdb: Fix GetAttributeFlags
---
src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
index b6e2031d5..0fb70c6b0 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs
@@ -303,7 +303,7 @@ namespace Ryujinx.HLE.Debugger
private static string GetAttributeFlags(KMemoryInfo info)
{
- char locked = info.Attribute.HasFlag(MemoryAttribute.PermissionLocked) ? 'L' : '-';
+ char locked = info.Attribute.HasFlag(MemoryAttribute.Borrowed) ? 'L' : '-';
char ipc = info.Attribute.HasFlag(MemoryAttribute.IpcMapped) ? 'I' : '-';
char device = info.Attribute.HasFlag(MemoryAttribute.DeviceMapped) ? 'D' : '-';
char uncached = info.Attribute.HasFlag(MemoryAttribute.Uncached) ? 'U' : '-';