See merge request ryubing/ryujinx!71
This commit is contained in:
Coxxs
2025-08-04 20:45:15 -05:00
committed by LotP
parent 324f18aa5f
commit d22756f1bd
58 changed files with 3394 additions and 25 deletions

View File

@@ -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<Image> GetLoadedImages()
{
EnsureLoaded();
lock (_images)
{
return [.. _images];
}
}
private void EnsureLoaded()
{
if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)

View File

@@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -11,6 +12,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
using ExceptionCallbackNoArgs = Ryujinx.Cpu.ExceptionCallbackNoArgs;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@@ -89,6 +92,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
public HleProcessDebugger Debugger { get; private set; }
public IDebuggableProcess DebugInterface { get; private set; }
protected int debugState = (int)DebugState.Running;
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
{
@@ -110,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_threads = [];
Debugger = new HleProcessDebugger(this);
DebugInterface = new DebuggerInterface(this);
}
public Result InitializeKip(
@@ -679,6 +685,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
SetState(newState);
if (KernelContext.Device.Configuration.DebuggerSuspendOnStart && IsApplication)
{
mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
debugState = (int)DebugState.Stopped;
Logger.Notice.Print(LogClass.Kernel, $"Application is suspended on start for debugging.");
}
result = mainThread.Start();
if (result != Result.Success)
@@ -727,9 +740,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IExecutionContext CreateExecutionContext()
{
ExceptionCallback breakCallback = null;
ExceptionCallbackNoArgs stepCallback = null;
if (KernelContext.Device.Configuration.EnableGdbStub && KernelContext.Device.Debugger != null)
{
breakCallback = KernelContext.Device.Debugger.BreakHandler;
stepCallback = KernelContext.Device.Debugger.StepHandler;
}
return Context?.CreateExecutionContext(new ExceptionCallbacks(
InterruptHandler,
null,
breakCallback,
stepCallback,
KernelContext.SyscallHandler.SvcCall,
UndefinedInstructionHandler));
}
@@ -1174,5 +1197,186 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
return Capabilities.IsSvcPermitted(svcId);
}
private class DebuggerInterface : IDebuggableProcess
{
private Barrier StepBarrier;
private readonly KProcess _parent;
private readonly KernelContext _kernelContext;
private KThread steppingThread;
public DebuggerInterface(KProcess p)
{
_parent = p;
_kernelContext = p.KernelContext;
StepBarrier = new(2);
}
public void DebugStop()
{
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Stopping,
(int)DebugState.Running) != (int)DebugState.Running)
{
return;
}
_kernelContext.CriticalSection.Enter();
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
thread.Context.RequestInterrupt();
if (!thread.DebugHalt.WaitOne(TimeSpan.FromMilliseconds(50)))
{
Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
}
}
}
_parent.debugState = (int)DebugState.Stopped;
_kernelContext.CriticalSection.Leave();
}
public void DebugContinue()
{
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Running,
(int)DebugState.Stopped) != (int)DebugState.Stopped)
{
return;
}
_kernelContext.CriticalSection.Enter();
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Resume(ThreadSchedState.ThreadPauseFlag);
}
}
_kernelContext.CriticalSection.Leave();
}
public void DebugContinue(KThread target)
{
Interlocked.Exchange(ref _parent.debugState, (int)DebugState.Running);
_kernelContext.CriticalSection.Enter();
lock (_parent._threadingLock)
{
target.Resume(ThreadSchedState.ThreadPauseFlag);
}
_kernelContext.CriticalSection.Leave();
}
public bool DebugStep(KThread target)
{
if (!IsThreadPaused(target))
{
return false;
}
_kernelContext.CriticalSection.Enter();
steppingThread = target;
bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration;
target.Context.RequestDebugStep();
if (waiting)
{
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Resume(ThreadSchedState.ThreadPauseFlag);
}
}
}
else
{
target.Resume(ThreadSchedState.ThreadPauseFlag);
}
_kernelContext.CriticalSection.Leave();
bool stepTimedOut = false;
if (!StepBarrier.SignalAndWait(TimeSpan.FromMilliseconds(2000)))
{
Logger.Warning?.Print(LogClass.Kernel, $"Failed to step thread {target.ThreadUid} in time.");
stepTimedOut = true;
}
_kernelContext.CriticalSection.Enter();
steppingThread = null;
if (waiting)
{
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
}
}
}
else
{
target.Suspend(ThreadSchedState.ThreadPauseFlag);
}
_kernelContext.CriticalSection.Leave();
if (stepTimedOut)
{
return false;
}
StepBarrier.SignalAndWait();
return true;
}
public DebugState GetDebugState()
{
return (DebugState)_parent.debugState;
}
public bool IsThreadPaused(KThread target)
{
return (target.SchedFlags & ThreadSchedState.ThreadPauseFlag) != 0;
}
public ulong[] GetThreadUids()
{
lock (_parent._threadingLock)
{
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
return threads.Select(x => x.ThreadUid).ToArray();
}
}
public KThread GetThread(ulong threadUid)
{
lock (_parent._threadingLock)
{
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
return threads.FirstOrDefault(x => x.ThreadUid == threadUid);
}
}
public void DebugInterruptHandler(IExecutionContext ctx)
{
_kernelContext.CriticalSection.Enter();
bool stepping = steppingThread != null;
_kernelContext.CriticalSection.Leave();
if (stepping)
{
StepBarrier.SignalAndWait();
StepBarrier.SignalAndWait();
}
_parent.InterruptHandler(ctx);
}
public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } }
public void InvalidateCacheRegion(ulong address, ulong size)
{
_parent.Context.InvalidateCacheRegion(address, size);
}
}
}
}

View File

@@ -1,5 +1,6 @@
using ARMeilleure.State;
using Ryujinx.Cpu;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
@@ -17,10 +18,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public bool IsAarch32 { get => false; set { } }
public ulong ThreadUid { get; set; }
public bool Running { get; private set; } = true;
private readonly ulong[] _x = new ulong[32];
public ulong DebugPc { get; set; }
public ulong GetX(int index) => _x[index];
public void SetX(int index, ulong value) => _x[index] = value;
@@ -31,6 +36,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
}
public void RequestDebugStep()
{
}
public void StopRunning()
{
Running = false;

View File

@@ -301,6 +301,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.SchedulerWaitEvent.Reset();
currentThread.ThreadContext.Unlock();
currentThread.DebugHalt.Set();
// Wake all the threads that might be waiting until this thread context is unlocked.
for (int core = 0; core < CpuCoresCount; core++)

View File

@@ -1,12 +1,15 @@
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.Debugger;
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
@@ -16,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;
@@ -114,6 +134,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly Lock _activityOperationLock = new();
internal readonly ManualResetEvent DebugHalt = new(false);
public KThread(KernelContext context) : base(context)
{
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
@@ -202,8 +224,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
Context.TpidrroEl0 = (long)_tlsAddress;
Context.DebugPc = _entrypoint;
ThreadUid = KernelContext.NewThreadUid();
Context.ThreadUid = ThreadUid;
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
@@ -307,7 +331,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
KernelContext.CriticalSection.Enter();
if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
KThread currentThread = KernelStatic.GetCurrentThread();
if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this)
{
Owner.UnpinThread(this);
}
@@ -362,7 +388,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
ThreadSchedState state = PrepareForTermination();
if (state != ThreadSchedState.TerminationPending)
if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending)
{
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
}
@@ -1248,6 +1274,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private void ThreadStart()
{
_schedulerWaitEvent.WaitOne();
DebugHalt.Reset();
KernelStatic.SetKernelContext(KernelContext, this);
if (_customThreadStart != null)
@@ -1431,5 +1458,84 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
}
public string GetThreadName()
{
try
{
ulong threadNamePtr = 0;
if (Context.IsAarch32)
{
uint threadTypePtr32 = Owner.CpuMemory.Read<uint>(_tlsAddress + TlsThreadTypeOffsetAArch32);
if (threadTypePtr32 == 0)
{
return "";
}
ushort version = Owner.CpuMemory.Read<ushort>(threadTypePtr32 + TlsThreadTypeVersionOffsetAArch32);
switch (version)
{
case 0x0000:
case 0xFFFF:
threadNamePtr = Owner.CpuMemory.Read<uint>(threadTypePtr32 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32);
break;
case 0x0001:
threadNamePtr = Owner.CpuMemory.Read<uint>(threadTypePtr32 + TlsThreadTypeThreadNamePointerOffsetAArch32);
break;
default:
Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
break;
}
}
else
{
ulong threadTypePtr64 = Owner.CpuMemory.Read<ulong>(_tlsAddress + TlsThreadTypeOffsetAArch64);
if (threadTypePtr64 == 0)
{
return "";
}
ushort version = Owner.CpuMemory.Read<ushort>(threadTypePtr64 + TlsThreadTypeVersionOffsetAArch64);
switch (version)
{
case 0x0000:
case 0xFFFF:
threadNamePtr = Owner.CpuMemory.Read<ulong>(threadTypePtr64 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64);
break;
case 0x0001:
threadNamePtr = Owner.CpuMemory.Read<ulong>(threadTypePtr64 + TlsThreadTypeThreadNamePointerOffsetAArch64);
break;
default:
Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
break;
}
}
if (threadNamePtr == 0)
{
return "";
}
List<byte> nameBytes = new();
for (int i = 0; i < 0x20; i++)
{
byte b = Owner.CpuMemory.Read<byte>(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 "";
} catch (Exception e) {
Logger.Error?.Print(LogClass.Kernel, $"Error getting thread name: {e.Message}");
return "";
}
}
}
}