using ChocolArm64; using ChocolArm64.Memory; using ChocolArm64.State; using Ryujinx.Core.Loaders; using Ryujinx.Core.Loaders.Executables; using Ryujinx.Core.OsHle.Exceptions; using Ryujinx.Core.OsHle.Handles; using Ryujinx.Core.OsHle.Svc; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; namespace Ryujinx.Core.OsHle { public class Process : IDisposable { private const int MaxStackSize = 8 * 1024 * 1024; private const int TlsSize = 0x200; private const int TotalTlsSlots = 32; private const int TlsTotalSize = TotalTlsSlots * TlsSize; private const long TlsPageAddr = (AMemoryMgr.AddrSize - TlsTotalSize) & ~AMemoryMgr.PageMask; private Switch Ns; public int ProcessId { get; private set; } public AMemory Memory { get; private set; } public KProcessScheduler Scheduler { get; private set; } private SvcHandler SvcHandler; private ConcurrentDictionary TlsSlots; private ConcurrentDictionary ThreadsByTpidr; private List Executables; private HThread MainThread; private long ImageBase; public Process(Switch Ns, AMemoryAlloc Allocator, int ProcessId) { this.Ns = Ns; this.ProcessId = ProcessId; Memory = new AMemory(Ns.Ram, Allocator); Scheduler = new KProcessScheduler(); SvcHandler = new SvcHandler(Ns, this); TlsSlots = new ConcurrentDictionary(); ThreadsByTpidr = new ConcurrentDictionary(); Executables = new List(); ImageBase = 0x8000000; Memory.Manager.MapPhys( TlsPageAddr, TlsTotalSize, (int)MemoryType.ThreadLocal, AMemoryPerm.RW); } public void LoadProgram(IExecutable Program) { Logging.Info($"Image base at 0x{ImageBase:x16}."); Executable Executable = new Executable(Program, Memory, ImageBase); Executables.Add(Executable); ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd); } public void SetEmptyArgs() { ImageBase += AMemoryMgr.PageSize; } public void InitializeHeap() { Memory.Manager.SetHeapAddr((ImageBase + 0x3fffffff) & ~0x3fffffff); } public bool Run() { if (Executables.Count == 0) { return false; } long StackBot = TlsPageAddr - MaxStackSize; Memory.Manager.MapPhys(StackBot, MaxStackSize, (int)MemoryType.Normal, AMemoryPerm.RW); int Handle = MakeThread(Executables[0].ImageBase, TlsPageAddr, 0, 0, 0); if (Handle == -1) { return false; } MainThread = Ns.Os.Handles.GetData(Handle); Scheduler.StartThread(MainThread); return true; } public void StopAllThreads() { if (MainThread != null) { while (MainThread.Thread.IsAlive) { MainThread.Thread.StopExecution(); } } foreach (AThread Thread in TlsSlots.Values) { while (Thread.IsAlive) { Thread.StopExecution(); } } } public int MakeThread( long EntryPoint, long StackTop, long ArgsPtr, int Priority, int ProcessorId) { ThreadPriority ThreadPrio; if (Priority < 12) { ThreadPrio = ThreadPriority.Highest; } else if (Priority < 24) { ThreadPrio = ThreadPriority.AboveNormal; } else if (Priority < 36) { ThreadPrio = ThreadPriority.Normal; } else if (Priority < 48) { ThreadPrio = ThreadPriority.BelowNormal; } else { ThreadPrio = ThreadPriority.Lowest; } AThread Thread = new AThread(Memory, ThreadPrio, EntryPoint); HThread ThreadHnd = new HThread(Thread, ProcessorId, Priority); int Handle = Ns.Os.Handles.GenerateId(ThreadHnd); int TlsSlot = GetFreeTlsSlot(Thread); if (TlsSlot == -1 || Handle == -1) { return -1; } Thread.ThreadState.Break += BreakHandler; Thread.ThreadState.SvcCall += SvcHandler.SvcCall; Thread.ThreadState.Undefined += UndefinedHandler; Thread.ThreadState.ProcessId = ProcessId; Thread.ThreadState.ThreadId = Ns.Os.IdGen.GenerateId(); Thread.ThreadState.Tpidr = TlsPageAddr + TlsSlot * TlsSize; Thread.ThreadState.X0 = (ulong)ArgsPtr; Thread.ThreadState.X1 = (ulong)Handle; Thread.ThreadState.X31 = (ulong)StackTop; if (Executables[0].Extension == Extensions.NRO) { Homebrew Homebrew_ABI = new Homebrew(Memory, Executables[0].ImageEnd, (long)Handle); Thread.ThreadState.X0 = (ulong)Executables[0].ImageEnd; Thread.ThreadState.X1 = 0xFFFFFFFFFFFFFFFF; } Thread.WorkFinished += ThreadFinished; ThreadsByTpidr.TryAdd(Thread.ThreadState.Tpidr, ThreadHnd); return Handle; } private void BreakHandler(object sender, AInstExceptEventArgs e) { throw new GuestBrokeExecutionException(); } private void UndefinedHandler(object sender, AInstUndEventArgs e) { throw new UndefinedInstructionException(e.Position, e.RawOpCode); } private int GetFreeTlsSlot(AThread Thread) { for (int Index = 1; Index < TotalTlsSlots; Index++) { if (TlsSlots.TryAdd(Index, Thread)) { return Index; } } return -1; } private void ThreadFinished(object sender, EventArgs e) { if (sender is AThread Thread) { TlsSlots.TryRemove(GetTlsSlot(Thread.ThreadState.Tpidr), out _); Ns.Os.IdGen.DeleteId(Thread.ThreadId); } } private int GetTlsSlot(long Position) { return (int)((Position - TlsPageAddr) / TlsSize); } public HThread GetThread(long Tpidr) { if (!ThreadsByTpidr.TryGetValue(Tpidr, out HThread Thread)) { Logging.Error($"Thread with TPIDR 0x{Tpidr:x16} not found!"); } return Thread; } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool Disposing) { if (Disposing) { Scheduler.Dispose(); } } } }