From 92d8f0eb1c6435cd4f205c8c8acc9c1d72b4a98b Mon Sep 17 00:00:00 2001
From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
Date: Sat, 21 Jun 2025 10:22:08 +0800
Subject: [PATCH] gdb: Show thread names
Reference: https://github.com/Atmosphere-NX/Atmosphere/blob/d8a37b4b7184b80ba979bcceb98365b8365a1c3a/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_type.os.horizon.hpp
---
src/Ryujinx.HLE/Debugger/Debugger.cs | 62 ++++++++++--
.../HOS/Kernel/Threading/KThread.cs | 95 +++++++++++++++++++
2 files changed, 151 insertions(+), 6 deletions(-)
diff --git a/src/Ryujinx.HLE/Debugger/Debugger.cs b/src/Ryujinx.HLE/Debugger/Debugger.cs
index dd6394e60..9b9850e58 100644
--- a/src/Ryujinx.HLE/Debugger/Debugger.cs
+++ b/src/Ryujinx.HLE/Debugger/Debugger.cs
@@ -34,6 +34,8 @@ namespace Ryujinx.HLE.Debugger
private ulong? cThread;
private ulong? gThread;
+ private string previousThreadListXml = "";
+
public Debugger(Switch device, ushort port)
{
Device = device;
@@ -368,7 +370,7 @@ namespace Ryujinx.HLE.Debugger
if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
{
- Reply("PacketSize=10000;qXfer:features:read+");
+ Reply("PacketSize=10000;qXfer:features:read+;qXfer:threads:read+");
break;
}
@@ -404,10 +406,43 @@ namespace Ryujinx.HLE.Debugger
break;
}
+ if (ss.ConsumePrefix("Xfer:threads:read:"))
+ {
+ ss.ReadUntil(':');
+ ulong offset = ss.ReadUntilAsHex(',');
+ ulong len = ss.ReadRemainingAsHex();
+
+ var data = "";
+ if (offset > 0)
+ {
+ data = previousThreadListXml;
+ } else
+ {
+ previousThreadListXml = data = GetThreadListXml();
+ }
+
+ if (offset >= (ulong)data.Length)
+ {
+ Reply("l");
+ break;
+ }
+
+ if (len >= (ulong)data.Length - offset)
+ {
+ Reply("l" + ToBinaryFormat(data.Substring((int)offset)));
+ break;
+ }
+ else
+ {
+ Reply("m" + ToBinaryFormat(data.Substring((int)offset, (int)len)));
+ break;
+ }
+ }
+
if (ss.ConsumePrefix("Xfer:features:read:"))
{
string feature = ss.ReadUntil(':');
- ulong addr = ss.ReadUntilAsHex(',');
+ ulong offset = ss.ReadUntilAsHex(',');
ulong len = ss.ReadRemainingAsHex();
if (feature == "target.xml")
@@ -418,20 +453,20 @@ namespace Ryujinx.HLE.Debugger
string data;
if (RegisterInformation.Features.TryGetValue(feature, out data))
{
- if (addr >= (ulong)data.Length)
+ if (offset >= (ulong)data.Length)
{
Reply("l");
break;
}
- if (len >= (ulong)data.Length - addr)
+ if (len >= (ulong)data.Length - offset)
{
- Reply("l" + ToBinaryFormat(data.Substring((int)addr)));
+ Reply("l" + ToBinaryFormat(data.Substring((int)offset)));
break;
}
else
{
- Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len)));
+ Reply("m" + ToBinaryFormat(data.Substring((int)offset, (int)len)));
break;
}
}
@@ -469,6 +504,21 @@ namespace Ryujinx.HLE.Debugger
}
}
+ private string GetThreadListXml()
+ {
+ var sb = new StringBuilder();
+ sb.Append("\n");
+
+ foreach (var thread in GetThreads())
+ {
+ string threadName = System.Security.SecurityElement.Escape(thread.GetThreadName());
+ sb.Append($"\n");
+ }
+
+ sb.Append("");
+ return sb.ToString();
+ }
+
void CommandQuery()
{
// GDB is performing initial contact. Stop everything.
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 2b5d11244..bb0548d19 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -5,9 +5,11 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Numerics;
+using System.Text;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Threading
@@ -17,6 +19,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private const int TlsUserDisableCountOffset = 0x100;
private const int TlsUserInterruptFlagOffset = 0x102;
+ // Tls -> ThreadType
+ private const int TlsThreadTypeOffsetAArch64 = 0x1F8;
+ private const int TlsThreadTypeOffsetAArch32 = 0x1FC;
+
+ // Tls -> ThreadType -> Version
+ private const int TlsThreadTypeVersionOffsetAArch64 = 0x46;
+ private const int TlsThreadTypeVersionOffsetAArch32 = 0x26;
+
+ // Tls -> ThreadType (Version 0) -> ThreadNamePointer
+ private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64 = 0x1A8;
+ private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32 = 0xE8;
+
+ // Tls -> ThreadType (Version 1) -> ThreadNamePointer
+ private const int TlsThreadTypeThreadNamePointerOffsetAArch64 = 0x1A0;
+ private const int TlsThreadTypeThreadNamePointerOffsetAArch32 = 0xE4;
+
+
public const int MaxWaitSyncObjects = 64;
private ManualResetEvent _schedulerWaitEvent;
@@ -1439,5 +1458,81 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 0);
}
+
+ public string GetThreadName()
+ {
+ try
+ {
+ ulong threadNamePtr = 0;
+ if (Context.IsAarch32)
+ {
+ uint threadTypePtr32 = Owner.CpuMemory.Read(_tlsAddress + TlsThreadTypeOffsetAArch32);
+ if (threadTypePtr32 == 0)
+ {
+ return "";
+ }
+
+ ushort version = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeVersionOffsetAArch32);
+ switch (version)
+ {
+ case 0x0000:
+ case 0xFFFF:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32);
+ break;
+ case 0x0001:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr32 + TlsThreadTypeThreadNamePointerOffsetAArch32);
+ break;
+ default:
+ Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
+ break;
+ }
+ }
+ else
+ {
+ ulong threadTypePtr64 = Owner.CpuMemory.Read(_tlsAddress + TlsThreadTypeOffsetAArch64);
+ if (threadTypePtr64 == 0)
+ {
+ return "";
+ }
+
+ ushort version = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeVersionOffsetAArch64);
+ switch (version)
+ {
+ case 0x0000:
+ case 0xFFFF:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64);
+ break;
+ case 0x0001:
+ threadNamePtr = Owner.CpuMemory.Read(threadTypePtr64 + TlsThreadTypeThreadNamePointerOffsetAArch64);
+ break;
+ default:
+ Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
+ break;
+ }
+ }
+
+ if (threadNamePtr == 0)
+ {
+ return "";
+ }
+
+ List nameBytes = new();
+ for (int i = 0; i < 0x20; i++)
+ {
+ byte b = Owner.CpuMemory.Read(threadNamePtr + (ulong)i);
+ if (b == 0)
+ {
+ break;
+ }
+ nameBytes.Add(b);
+ }
+ return Encoding.UTF8.GetString(nameBytes.ToArray());
+
+ } catch (InvalidMemoryRegionException)
+ {
+ Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
+ return "";
+ }
+ }
}
}