mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-19 11:45:47 +00:00
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user