Compare commits

...

4 commits

Author SHA1 Message Date
Coxxs
50fb918326 Merge branch 'gdb-memory-mapping' into 'master'
gdb: add `monitor get mapping`

See merge request [ryubing/ryujinx!215](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/215)
2025-12-07 09:48:40 -06:00
Coxxs
37ddd478d1 gdb: Fix GetAttributeFlags 2025-12-07 23:43:33 +08:00
Coxxs
17a575a505 gdb: refactor rcmd address parsing 2025-12-07 23:43:33 +08:00
Coxxs
ccca2e8282 gdb: add monitor get mapping
monitor get mappings
monitor get mappings <address>
monitor get mapping <address>
2025-12-07 23:43:33 +08:00
2 changed files with 244 additions and 21 deletions

View file

@ -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<Debugger, string, string> 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<string>()));
_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<string[], Func<Debugger, string>> _rcmdDelegates = new();
private static readonly List<RcmdEntry> _rcmdDelegates = [];
public static Func<Debugger, string> FindRcmdDelegate(string command)
public static string CallRcmdDelegate(Debugger debugger, string command)
{
Func<Debugger, string> searchResult = _ => $"Unknown command: {command}\n";
string originalCommand = command ?? string.Empty;
string trimmedCommand = originalCommand.Trim();
foreach ((string[] names, Func<Debugger, string> 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,181 @@ 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";
}
string trimmedArgs = arguments?.Trim() ?? string.Empty;
ulong startAddress = 0;
if (!string.IsNullOrEmpty(trimmedArgs))
{
if (!TryParseAddressArgument(trimmedArgs, 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";
}
if (!TryParseAddressArgument(trimmedArgs, out ulong 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 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.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' : '-';
return $"{locked}{ipc}{device}{uncached}";
}
}
}

View file

@ -404,9 +404,8 @@ namespace Ryujinx.HLE.Debugger.Gdb
string command = Helpers.FromHex(hexCommand);
Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}");
Func<Debugger, string> rcmd = Debugger.FindRcmdDelegate(command);
Processor.ReplyHex(rcmd(Debugger));
string response = Debugger.CallRcmdDelegate(Debugger, command);
Processor.ReplyHex(response);
}
catch (Exception e)
{