aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Kernel/Process
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Kernel/Process')
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs465
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs83
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs285
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs1196
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs328
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs77
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs61
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs37
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs46
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs24
19 files changed, 2787 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
new file mode 100644
index 00000000..66d56fe3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
@@ -0,0 +1,22 @@
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ static class CapabilityExtensions
+ {
+ public static CapabilityType GetCapabilityType(this uint cap)
+ {
+ return (CapabilityType)(((cap + 1) & ~cap) - 1);
+ }
+
+ public static uint GetFlag(this CapabilityType type)
+ {
+ return (uint)type + 1;
+ }
+
+ public static uint GetId(this CapabilityType type)
+ {
+ return (uint)BitOperations.TrailingZeroCount(type.GetFlag());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs
new file mode 100644
index 00000000..51d92316
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ enum CapabilityType : uint
+ {
+ CorePriority = (1u << 3) - 1,
+ SyscallMask = (1u << 4) - 1,
+ MapRange = (1u << 6) - 1,
+ MapIoPage = (1u << 7) - 1,
+ MapRegion = (1u << 10) - 1,
+ InterruptPair = (1u << 11) - 1,
+ ProgramType = (1u << 13) - 1,
+ KernelVersion = (1u << 14) - 1,
+ HandleTable = (1u << 15) - 1,
+ DebugFlags = (1u << 16) - 1,
+
+ Invalid = 0u,
+ Padding = ~0u
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
new file mode 100644
index 00000000..8fee5c0d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
@@ -0,0 +1,465 @@
+using Ryujinx.HLE.HOS.Diagnostics.Demangler;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.Loaders.Elf;
+using Ryujinx.Memory;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class HleProcessDebugger
+ {
+ private const int Mod0 = 'M' << 0 | 'O' << 8 | 'D' << 16 | '0' << 24;
+
+ private KProcess _owner;
+
+ private class Image
+ {
+ public ulong BaseAddress { get; }
+ public ulong Size { get; }
+ public ulong EndAddress => BaseAddress + Size;
+
+ public ElfSymbol[] Symbols { get; }
+
+ public Image(ulong baseAddress, ulong size, ElfSymbol[] symbols)
+ {
+ BaseAddress = baseAddress;
+ Size = size;
+ Symbols = symbols;
+ }
+ }
+
+ private List<Image> _images;
+
+ private int _loaded;
+
+ public HleProcessDebugger(KProcess owner)
+ {
+ _owner = owner;
+
+ _images = new List<Image>();
+ }
+
+ public string GetGuestStackTrace(KThread thread)
+ {
+ EnsureLoaded();
+
+ var context = thread.Context;
+
+ StringBuilder trace = new StringBuilder();
+
+ trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
+
+ void AppendTrace(ulong address)
+ {
+ if (AnalyzePointer(out PointerInfo info, address, thread))
+ {
+ trace.AppendLine($" 0x{address:x16}\t{info.ImageDisplay}\t{info.SubDisplay}");
+ }
+ else
+ {
+ trace.AppendLine($" 0x{address:x16}");
+ }
+ }
+
+ if (context.IsAarch32)
+ {
+ ulong framePointer = context.GetX(11);
+
+ while (framePointer != 0)
+ {
+ if ((framePointer & 3) != 0 ||
+ !_owner.CpuMemory.IsMapped(framePointer) ||
+ !_owner.CpuMemory.IsMapped(framePointer + 4))
+ {
+ break;
+ }
+
+ AppendTrace(_owner.CpuMemory.Read<uint>(framePointer + 4));
+
+ framePointer = _owner.CpuMemory.Read<uint>(framePointer);
+ }
+ }
+ else
+ {
+ ulong framePointer = context.GetX(29);
+
+ while (framePointer != 0)
+ {
+ if ((framePointer & 7) != 0 ||
+ !_owner.CpuMemory.IsMapped(framePointer) ||
+ !_owner.CpuMemory.IsMapped(framePointer + 8))
+ {
+ break;
+ }
+
+ AppendTrace(_owner.CpuMemory.Read<ulong>(framePointer + 8));
+
+ framePointer = _owner.CpuMemory.Read<ulong>(framePointer);
+ }
+ }
+
+ return trace.ToString();
+ }
+
+ public string GetCpuRegisterPrintout(KThread thread)
+ {
+ EnsureLoaded();
+
+ var context = thread.Context;
+
+ StringBuilder sb = new StringBuilder();
+
+ string GetReg(int x)
+ {
+ var v = x == 32 ? context.Pc : context.GetX(x);
+ if (!AnalyzePointer(out PointerInfo info, v, thread))
+ {
+ return $"0x{v:x16}";
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(info.ImageName))
+ {
+ return $"0x{v:x16} ({info.ImageDisplay})\t=> {info.SubDisplay}";
+ }
+ else
+ {
+ return $"0x{v:x16} ({info.SpDisplay})";
+ }
+ }
+ }
+
+ for (int i = 0; i <= 28; i++)
+ {
+ sb.AppendLine($"\tX[{i:d2}]:\t{GetReg(i)}");
+ }
+ sb.AppendLine($"\tFP:\t{GetReg(29)}");
+ sb.AppendLine($"\tLR:\t{GetReg(30)}");
+ sb.AppendLine($"\tSP:\t{GetReg(31)}");
+ sb.AppendLine($"\tPC:\t{GetReg(32)}");
+
+ return sb.ToString();
+ }
+
+ private bool TryGetSubName(Image image, ulong address, out ElfSymbol symbol)
+ {
+ address -= image.BaseAddress;
+
+ int left = 0;
+ int right = image.Symbols.Length - 1;
+
+ while (left <= right)
+ {
+ int size = right - left;
+
+ int middle = left + (size >> 1);
+
+ symbol = image.Symbols[middle];
+
+ ulong endAddr = symbol.Value + symbol.Size;
+
+ if (address >= symbol.Value && address < endAddr)
+ {
+ return true;
+ }
+
+ if (address < symbol.Value)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ symbol = default;
+
+ return false;
+ }
+
+ struct PointerInfo
+ {
+ public string ImageName;
+ public string SubName;
+
+ public ulong Offset;
+ public ulong SubOffset;
+
+ public string ImageDisplay => $"{ImageName}:0x{Offset:x4}";
+ public string SubDisplay => SubOffset == 0 ? SubName : $"{SubName}:0x{SubOffset:x4}";
+ public string SpDisplay => SubOffset == 0 ? "SP" : $"SP:-0x{SubOffset:x4}";
+ }
+
+ private bool AnalyzePointer(out PointerInfo info, ulong address, KThread thread)
+ {
+ if (AnalyzePointerFromImages(out info, address))
+ {
+ return true;
+ }
+
+ if (AnalyzePointerFromStack(out info, address, thread))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool AnalyzePointerFromImages(out PointerInfo info, ulong address)
+ {
+ info = default;
+
+ Image image = GetImage(address, out int imageIndex);
+
+ if (image == null)
+ {
+ // Value isn't a pointer to a known image...
+ return false;
+ }
+
+ info.Offset = address - image.BaseAddress;
+
+ // Try to find what this pointer is referring to
+ if (TryGetSubName(image, address, out ElfSymbol symbol))
+ {
+ info.SubName = symbol.Name;
+
+ // Demangle string if possible
+ if (info.SubName.StartsWith("_Z"))
+ {
+ info.SubName = Demangler.Parse(info.SubName);
+ }
+ info.SubOffset = info.Offset - symbol.Value;
+ }
+ else
+ {
+ info.SubName = "";
+ }
+
+ info.ImageName = GetGuessedNsoNameFromIndex(imageIndex);
+
+ return true;
+ }
+
+ private bool AnalyzePointerFromStack(out PointerInfo info, ulong address, KThread thread)
+ {
+ info = default;
+
+ ulong sp = thread.Context.GetX(31);
+ var memoryInfo = _owner.MemoryManager.QueryMemory(address);
+ MemoryState memoryState = memoryInfo.State;
+
+ if (!memoryState.HasFlag(MemoryState.Stack)) // Is this pointer within the stack?
+ {
+ return false;
+ }
+
+ info.SubOffset = address - sp;
+
+ return true;
+ }
+
+ private Image GetImage(ulong address, out int index)
+ {
+ lock (_images)
+ {
+ for (index = _images.Count - 1; index >= 0; index--)
+ {
+ if (address >= _images[index].BaseAddress && address < _images[index].EndAddress)
+ {
+ return _images[index];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private string GetGuessedNsoNameFromIndex(int index)
+ {
+ if ((uint)index > 11)
+ {
+ return "???";
+ }
+
+ if (index == 0)
+ {
+ return "rtld";
+ }
+ else if (index == 1)
+ {
+ return "main";
+ }
+ else if (index == GetImagesCount() - 1)
+ {
+ return "sdk";
+ }
+ else
+ {
+ return "subsdk" + (index - 2);
+ }
+ }
+
+ private int GetImagesCount()
+ {
+ lock (_images)
+ {
+ return _images.Count;
+ }
+ }
+
+ private void EnsureLoaded()
+ {
+ if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)
+ {
+ ScanMemoryForTextSegments();
+ }
+ }
+
+ private void ScanMemoryForTextSegments()
+ {
+ ulong oldAddress = 0;
+ ulong address = 0;
+
+ while (address >= oldAddress)
+ {
+ KMemoryInfo info = _owner.MemoryManager.QueryMemory(address);
+
+ if (info.State == MemoryState.Reserved)
+ {
+ break;
+ }
+
+ if (info.State == MemoryState.CodeStatic && info.Permission == KMemoryPermission.ReadAndExecute)
+ {
+ LoadMod0Symbols(_owner.CpuMemory, info.Address, info.Size);
+ }
+
+ oldAddress = address;
+
+ address = info.Address + info.Size;
+ }
+ }
+
+ private void LoadMod0Symbols(IVirtualMemoryManager memory, ulong textOffset, ulong textSize)
+ {
+ ulong mod0Offset = textOffset + memory.Read<uint>(textOffset + 4);
+
+ if (mod0Offset < textOffset || !memory.IsMapped(mod0Offset) || (mod0Offset & 3) != 0)
+ {
+ return;
+ }
+
+ Dictionary<ElfDynamicTag, ulong> dynamic = new Dictionary<ElfDynamicTag, ulong>();
+
+ int mod0Magic = memory.Read<int>(mod0Offset + 0x0);
+
+ if (mod0Magic != Mod0)
+ {
+ return;
+ }
+
+ ulong dynamicOffset = memory.Read<uint>(mod0Offset + 0x4) + mod0Offset;
+ ulong bssStartOffset = memory.Read<uint>(mod0Offset + 0x8) + mod0Offset;
+ ulong bssEndOffset = memory.Read<uint>(mod0Offset + 0xc) + mod0Offset;
+ ulong ehHdrStartOffset = memory.Read<uint>(mod0Offset + 0x10) + mod0Offset;
+ ulong ehHdrEndOffset = memory.Read<uint>(mod0Offset + 0x14) + mod0Offset;
+ ulong modObjOffset = memory.Read<uint>(mod0Offset + 0x18) + mod0Offset;
+
+ bool isAArch32 = memory.Read<ulong>(dynamicOffset) > 0xFFFFFFFF || memory.Read<ulong>(dynamicOffset + 0x10) > 0xFFFFFFFF;
+
+ while (true)
+ {
+ ulong tagVal;
+ ulong value;
+
+ if (isAArch32)
+ {
+ tagVal = memory.Read<uint>(dynamicOffset + 0);
+ value = memory.Read<uint>(dynamicOffset + 4);
+
+ dynamicOffset += 0x8;
+ }
+ else
+ {
+ tagVal = memory.Read<ulong>(dynamicOffset + 0);
+ value = memory.Read<ulong>(dynamicOffset + 8);
+
+ dynamicOffset += 0x10;
+ }
+
+ ElfDynamicTag tag = (ElfDynamicTag)tagVal;
+
+ if (tag == ElfDynamicTag.DT_NULL)
+ {
+ break;
+ }
+
+ dynamic[tag] = value;
+ }
+
+ if (!dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out ulong strTab) ||
+ !dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out ulong symTab) ||
+ !dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out ulong symEntSize))
+ {
+ return;
+ }
+
+ ulong strTblAddr = textOffset + strTab;
+ ulong symTblAddr = textOffset + symTab;
+
+ List<ElfSymbol> symbols = new List<ElfSymbol>();
+
+ while (symTblAddr < strTblAddr)
+ {
+ ElfSymbol sym = isAArch32 ? GetSymbol32(memory, symTblAddr, strTblAddr) : GetSymbol64(memory, symTblAddr, strTblAddr);
+
+ symbols.Add(sym);
+
+ symTblAddr += symEntSize;
+ }
+
+ lock (_images)
+ {
+ _images.Add(new Image(textOffset, textSize, symbols.OrderBy(x => x.Value).ToArray()));
+ }
+ }
+
+ private ElfSymbol GetSymbol64(IVirtualMemoryManager memory, ulong address, ulong strTblAddr)
+ {
+ ElfSymbol64 sym = memory.Read<ElfSymbol64>(address);
+
+ uint nameIndex = sym.NameOffset;
+
+ string name = string.Empty;
+
+ for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
+ {
+ name += (char)chr;
+ }
+
+ return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
+ }
+
+ private ElfSymbol GetSymbol32(IVirtualMemoryManager memory, ulong address, ulong strTblAddr)
+ {
+ ElfSymbol32 sym = memory.Read<ElfSymbol32>(address);
+
+ uint nameIndex = sym.NameOffset;
+
+ string name = string.Empty;
+
+ for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
+ {
+ name += (char)chr;
+ }
+
+ return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs
new file mode 100644
index 00000000..c8063a62
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Cpu;
+using Ryujinx.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ interface IProcessContext : IDisposable
+ {
+ IVirtualMemoryManager AddressSpace { get; }
+
+ IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks);
+ void Execute(IExecutionContext context, ulong codeAddress);
+ void InvalidateCacheRegion(ulong address, ulong size);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs
new file mode 100644
index 00000000..0a24a524
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs
@@ -0,0 +1,9 @@
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ interface IProcessContextFactory
+ {
+ IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs
new file mode 100644
index 00000000..104fe578
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KContextIdManager
+ {
+ private const int IdMasksCount = 8;
+
+ private int[] _idMasks;
+
+ private int _nextFreeBitHint;
+
+ public KContextIdManager()
+ {
+ _idMasks = new int[IdMasksCount];
+ }
+
+ public int GetId()
+ {
+ lock (_idMasks)
+ {
+ int id = 0;
+
+ if (!TestBit(_nextFreeBitHint))
+ {
+ id = _nextFreeBitHint;
+ }
+ else
+ {
+ for (int index = 0; index < IdMasksCount; index++)
+ {
+ int mask = _idMasks[index];
+
+ int firstFreeBit = BitOperations.LeadingZeroCount((uint)((mask + 1) & ~mask));
+
+ if (firstFreeBit < 32)
+ {
+ int baseBit = index * 32 + 31;
+
+ id = baseBit - firstFreeBit;
+
+ break;
+ }
+ else if (index == IdMasksCount - 1)
+ {
+ throw new InvalidOperationException("Maximum number of Ids reached!");
+ }
+ }
+ }
+
+ _nextFreeBitHint = id + 1;
+
+ SetBit(id);
+
+ return id;
+ }
+ }
+
+ public void PutId(int id)
+ {
+ lock (_idMasks)
+ {
+ ClearBit(id);
+ }
+ }
+
+ private bool TestBit(int bit)
+ {
+ return (_idMasks[_nextFreeBitHint / 32] & (1 << (_nextFreeBitHint & 31))) != 0;
+ }
+
+ private void SetBit(int bit)
+ {
+ _idMasks[_nextFreeBitHint / 32] |= (1 << (_nextFreeBitHint & 31));
+ }
+
+ private void ClearBit(int bit)
+ {
+ _idMasks[_nextFreeBitHint / 32] &= ~(1 << (_nextFreeBitHint & 31));
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs
new file mode 100644
index 00000000..b5ca9b5e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KHandleEntry
+ {
+ public KHandleEntry Next { get; set; }
+
+ public int Index { get; private set; }
+
+ public ushort HandleId { get; set; }
+ public KAutoObject Obj { get; set; }
+
+ public KHandleEntry(int index)
+ {
+ Index = index;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs
new file mode 100644
index 00000000..50f04e90
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs
@@ -0,0 +1,285 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KHandleTable
+ {
+ public const int SelfThreadHandle = (0x1ffff << 15) | 0;
+ public const int SelfProcessHandle = (0x1ffff << 15) | 1;
+
+ private readonly KernelContext _context;
+
+ private KHandleEntry[] _table;
+
+ private KHandleEntry _tableHead;
+ private KHandleEntry _nextFreeEntry;
+
+ private int _activeSlotsCount;
+
+ private uint _size;
+
+ private ushort _idCounter;
+
+ public KHandleTable(KernelContext context)
+ {
+ _context = context;
+ }
+
+ public Result Initialize(uint size)
+ {
+ if (size > 1024)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ if (size < 1)
+ {
+ size = 1024;
+ }
+
+ _size = size;
+
+ _idCounter = 1;
+
+ _table = new KHandleEntry[size];
+
+ _tableHead = new KHandleEntry(0);
+
+ KHandleEntry entry = _tableHead;
+
+ for (int index = 0; index < size; index++)
+ {
+ _table[index] = entry;
+
+ entry.Next = new KHandleEntry(index + 1);
+
+ entry = entry.Next;
+ }
+
+ _table[size - 1].Next = null;
+
+ _nextFreeEntry = _tableHead;
+
+ return Result.Success;
+ }
+
+ public Result GenerateHandle(KAutoObject obj, out int handle)
+ {
+ handle = 0;
+
+ lock (_table)
+ {
+ if (_activeSlotsCount >= _size)
+ {
+ return KernelResult.HandleTableFull;
+ }
+
+ KHandleEntry entry = _nextFreeEntry;
+
+ _nextFreeEntry = entry.Next;
+
+ entry.Obj = obj;
+ entry.HandleId = _idCounter;
+
+ _activeSlotsCount++;
+
+ handle = (_idCounter << 15) | entry.Index;
+
+ obj.IncrementReferenceCount();
+
+ if ((short)(_idCounter + 1) >= 0)
+ {
+ _idCounter++;
+ }
+ else
+ {
+ _idCounter = 1;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public Result ReserveHandle(out int handle)
+ {
+ handle = 0;
+
+ lock (_table)
+ {
+ if (_activeSlotsCount >= _size)
+ {
+ return KernelResult.HandleTableFull;
+ }
+
+ KHandleEntry entry = _nextFreeEntry;
+
+ _nextFreeEntry = entry.Next;
+
+ _activeSlotsCount++;
+
+ handle = (_idCounter << 15) | entry.Index;
+
+ if ((short)(_idCounter + 1) >= 0)
+ {
+ _idCounter++;
+ }
+ else
+ {
+ _idCounter = 1;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public void CancelHandleReservation(int handle)
+ {
+ int index = (handle >> 0) & 0x7fff;
+
+ lock (_table)
+ {
+ KHandleEntry entry = _table[index];
+
+ entry.Obj = null;
+ entry.Next = _nextFreeEntry;
+
+ _nextFreeEntry = entry;
+
+ _activeSlotsCount--;
+ }
+ }
+
+ public void SetReservedHandleObj(int handle, KAutoObject obj)
+ {
+ int index = (handle >> 0) & 0x7fff;
+ int handleId = (handle >> 15);
+
+ lock (_table)
+ {
+ KHandleEntry entry = _table[index];
+
+ entry.Obj = obj;
+ entry.HandleId = (ushort)handleId;
+
+ obj.IncrementReferenceCount();
+ }
+ }
+
+ public bool CloseHandle(int handle)
+ {
+ if ((handle >> 30) != 0 ||
+ handle == SelfThreadHandle ||
+ handle == SelfProcessHandle)
+ {
+ return false;
+ }
+
+ int index = (handle >> 0) & 0x7fff;
+ int handleId = (handle >> 15);
+
+ KAutoObject obj = null;
+
+ bool result = false;
+
+ lock (_table)
+ {
+ if (handleId != 0 && index < _size)
+ {
+ KHandleEntry entry = _table[index];
+
+ if ((obj = entry.Obj) != null && entry.HandleId == handleId)
+ {
+ entry.Obj = null;
+ entry.Next = _nextFreeEntry;
+
+ _nextFreeEntry = entry;
+
+ _activeSlotsCount--;
+
+ result = true;
+ }
+ }
+ }
+
+ if (result)
+ {
+ obj.DecrementReferenceCount();
+ }
+
+ return result;
+ }
+
+ public T GetObject<T>(int handle) where T : KAutoObject
+ {
+ int index = (handle >> 0) & 0x7fff;
+ int handleId = (handle >> 15);
+
+ lock (_table)
+ {
+ if ((handle >> 30) == 0 && handleId != 0 && index < _size)
+ {
+ KHandleEntry entry = _table[index];
+
+ if (entry.HandleId == handleId && entry.Obj is T obj)
+ {
+ return obj;
+ }
+ }
+ }
+
+ return default;
+ }
+
+ public KThread GetKThread(int handle)
+ {
+ if (handle == SelfThreadHandle)
+ {
+ return KernelStatic.GetCurrentThread();
+ }
+ else
+ {
+ return GetObject<KThread>(handle);
+ }
+ }
+
+ public KProcess GetKProcess(int handle)
+ {
+ if (handle == SelfProcessHandle)
+ {
+ return KernelStatic.GetCurrentProcess();
+ }
+ else
+ {
+ return GetObject<KProcess>(handle);
+ }
+ }
+
+ public void Destroy()
+ {
+ lock (_table)
+ {
+ for (int index = 0; index < _size; index++)
+ {
+ KHandleEntry entry = _table[index];
+
+ if (entry.Obj != null)
+ {
+ if (entry.Obj is IDisposable disposableObj)
+ {
+ disposableObj.Dispose();
+ }
+
+ entry.Obj.DecrementReferenceCount();
+ entry.Obj = null;
+ entry.Next = _nextFreeEntry;
+
+ _nextFreeEntry = entry;
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
new file mode 100644
index 00000000..21e89944
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -0,0 +1,1196 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KProcess : KSynchronizationObject
+ {
+ public const uint KernelVersionMajor = 10;
+ public const uint KernelVersionMinor = 4;
+ public const uint KernelVersionRevision = 0;
+
+ public const uint KernelVersionPacked =
+ (KernelVersionMajor << 19) |
+ (KernelVersionMinor << 15) |
+ (KernelVersionRevision << 0);
+
+ public KPageTableBase MemoryManager { get; private set; }
+
+ private SortedDictionary<ulong, KTlsPageInfo> _fullTlsPages;
+ private SortedDictionary<ulong, KTlsPageInfo> _freeTlsPages;
+
+ public int DefaultCpuCore { get; set; }
+
+ public bool Debug { get; private set; }
+
+ public KResourceLimit ResourceLimit { get; private set; }
+
+ public ulong PersonalMmHeapPagesCount { get; private set; }
+
+ public ProcessState State { get; private set; }
+
+ private object _processLock;
+ private object _threadingLock;
+
+ public KAddressArbiter AddressArbiter { get; private set; }
+
+ public ulong[] RandomEntropy { get; private set; }
+ public KThread[] PinnedThreads { get; private set; }
+
+ private bool _signaled;
+
+ public string Name { get; private set; }
+
+ private int _threadCount;
+
+ public ProcessCreationFlags Flags { get; private set; }
+
+ private MemoryRegion _memRegion;
+
+ public KProcessCapabilities Capabilities { get; private set; }
+
+ public bool AllowCodeMemoryForJit { get; private set; }
+
+ public ulong TitleId { get; private set; }
+ public bool IsApplication { get; private set; }
+ public ulong Pid { get; private set; }
+
+ private long _creationTimestamp;
+ private ulong _entrypoint;
+ private ThreadStart _customThreadStart;
+ private ulong _imageSize;
+ private ulong _mainThreadStackSize;
+ private ulong _memoryUsageCapacity;
+ private int _version;
+
+ public KHandleTable HandleTable { get; private set; }
+
+ public ulong UserExceptionContextAddress { get; private set; }
+
+ private LinkedList<KThread> _threads;
+
+ public bool IsPaused { get; private set; }
+
+ private long _totalTimeRunning;
+
+ public long TotalTimeRunning => _totalTimeRunning;
+
+ private IProcessContextFactory _contextFactory;
+ public IProcessContext Context { get; private set; }
+ public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
+
+ public HleProcessDebugger Debugger { get; private set; }
+
+ public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
+ {
+ _processLock = new object();
+ _threadingLock = new object();
+
+ AddressArbiter = new KAddressArbiter(context);
+
+ _fullTlsPages = new SortedDictionary<ulong, KTlsPageInfo>();
+ _freeTlsPages = new SortedDictionary<ulong, KTlsPageInfo>();
+
+ Capabilities = new KProcessCapabilities();
+
+ AllowCodeMemoryForJit = allowCodeMemoryForJit;
+
+ RandomEntropy = new ulong[KScheduler.CpuCoresCount];
+ PinnedThreads = new KThread[KScheduler.CpuCoresCount];
+
+ // TODO: Remove once we no longer need to initialize it externally.
+ HandleTable = new KHandleTable(context);
+
+ _threads = new LinkedList<KThread>();
+
+ Debugger = new HleProcessDebugger(this);
+ }
+
+ public Result InitializeKip(
+ ProcessCreationInfo creationInfo,
+ ReadOnlySpan<uint> capabilities,
+ KPageList pageList,
+ KResourceLimit resourceLimit,
+ MemoryRegion memRegion,
+ IProcessContextFactory contextFactory,
+ ThreadStart customThreadStart = null)
+ {
+ ResourceLimit = resourceLimit;
+ _memRegion = memRegion;
+ _contextFactory = contextFactory ?? new ProcessContextFactory();
+ _customThreadStart = customThreadStart;
+
+ AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift);
+
+ Pid = KernelContext.NewKipId();
+
+ if (Pid == 0 || Pid >= KernelConstants.InitialProcessId)
+ {
+ throw new InvalidOperationException($"Invalid KIP Id {Pid}.");
+ }
+
+ InitializeMemoryManager(creationInfo.Flags);
+
+ bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
+
+ ulong codeAddress = creationInfo.CodeAddress;
+
+ ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
+
+ KMemoryBlockSlabManager slabManager = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication)
+ ? KernelContext.LargeMemoryBlockSlabManager
+ : KernelContext.SmallMemoryBlockSlabManager;
+
+ Result result = MemoryManager.InitializeForProcess(
+ addrSpaceType,
+ aslrEnabled,
+ !aslrEnabled,
+ memRegion,
+ codeAddress,
+ codeSize,
+ slabManager);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ if (!MemoryManager.CanContain(codeAddress, codeSize, MemoryState.CodeStatic))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ result = MemoryManager.MapPages(codeAddress, pageList, MemoryState.CodeStatic, KMemoryPermission.None);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = Capabilities.InitializeForKernel(capabilities, MemoryManager);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return ParseProcessInfo(creationInfo);
+ }
+
+ public Result Initialize(
+ ProcessCreationInfo creationInfo,
+ ReadOnlySpan<uint> capabilities,
+ KResourceLimit resourceLimit,
+ MemoryRegion memRegion,
+ IProcessContextFactory contextFactory,
+ ThreadStart customThreadStart = null)
+ {
+ ResourceLimit = resourceLimit;
+ _memRegion = memRegion;
+ _contextFactory = contextFactory ?? new ProcessContextFactory();
+ _customThreadStart = customThreadStart;
+ IsApplication = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication);
+
+ ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion);
+
+ ulong codePagesCount = (ulong)creationInfo.CodePagesCount;
+
+ ulong neededSizeForProcess = personalMmHeapSize + codePagesCount * KPageTableBase.PageSize;
+
+ if (neededSizeForProcess != 0 && resourceLimit != null)
+ {
+ if (!resourceLimit.Reserve(LimitableResource.Memory, neededSizeForProcess))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+ }
+
+ void CleanUpForError()
+ {
+ if (neededSizeForProcess != 0 && resourceLimit != null)
+ {
+ resourceLimit.Release(LimitableResource.Memory, neededSizeForProcess);
+ }
+ }
+
+ PersonalMmHeapPagesCount = (ulong)creationInfo.SystemResourcePagesCount;
+
+ KMemoryBlockSlabManager slabManager;
+
+ if (PersonalMmHeapPagesCount != 0)
+ {
+ slabManager = new KMemoryBlockSlabManager(PersonalMmHeapPagesCount * KPageTableBase.PageSize);
+ }
+ else
+ {
+ slabManager = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication)
+ ? KernelContext.LargeMemoryBlockSlabManager
+ : KernelContext.SmallMemoryBlockSlabManager;
+ }
+
+ AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift);
+
+ Pid = KernelContext.NewProcessId();
+
+ if (Pid == ulong.MaxValue || Pid < KernelConstants.InitialProcessId)
+ {
+ throw new InvalidOperationException($"Invalid Process Id {Pid}.");
+ }
+
+ InitializeMemoryManager(creationInfo.Flags);
+
+ bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
+
+ ulong codeAddress = creationInfo.CodeAddress;
+
+ ulong codeSize = codePagesCount * KPageTableBase.PageSize;
+
+ Result result = MemoryManager.InitializeForProcess(
+ addrSpaceType,
+ aslrEnabled,
+ !aslrEnabled,
+ memRegion,
+ codeAddress,
+ codeSize,
+ slabManager);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ if (!MemoryManager.CanContain(codeAddress, codeSize, MemoryState.CodeStatic))
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidMemRange;
+ }
+
+ result = MemoryManager.MapPages(
+ codeAddress,
+ codePagesCount,
+ MemoryState.CodeStatic,
+ KMemoryPermission.None);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ result = Capabilities.InitializeForUser(capabilities, MemoryManager);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ result = ParseProcessInfo(creationInfo);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+ }
+
+ return result;
+ }
+
+ private Result ParseProcessInfo(ProcessCreationInfo creationInfo)
+ {
+ // Ensure that the current kernel version is equal or above to the minimum required.
+ uint requiredKernelVersionMajor = (uint)Capabilities.KernelReleaseVersion >> 19;
+ uint requiredKernelVersionMinor = ((uint)Capabilities.KernelReleaseVersion >> 15) & 0xf;
+
+ if (KernelContext.EnableVersionChecks)
+ {
+ if (requiredKernelVersionMajor > KernelVersionMajor)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if (requiredKernelVersionMajor != KernelVersionMajor && requiredKernelVersionMajor < 3)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if (requiredKernelVersionMinor > KernelVersionMinor)
+ {
+ return KernelResult.InvalidCombination;
+ }
+ }
+
+ Result result = AllocateThreadLocalStorage(out ulong userExceptionContextAddress);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ UserExceptionContextAddress = userExceptionContextAddress;
+
+ MemoryHelper.FillWithZeros(CpuMemory, userExceptionContextAddress, KTlsPageInfo.TlsEntrySize);
+
+ Name = creationInfo.Name;
+
+ State = ProcessState.Created;
+
+ _creationTimestamp = PerformanceCounter.ElapsedMilliseconds;
+
+ Flags = creationInfo.Flags;
+ _version = creationInfo.Version;
+ TitleId = creationInfo.TitleId;
+ _entrypoint = creationInfo.CodeAddress;
+ _imageSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
+
+ switch (Flags & ProcessCreationFlags.AddressSpaceMask)
+ {
+ case ProcessCreationFlags.AddressSpace32Bit:
+ case ProcessCreationFlags.AddressSpace64BitDeprecated:
+ case ProcessCreationFlags.AddressSpace64Bit:
+ _memoryUsageCapacity = MemoryManager.HeapRegionEnd -
+ MemoryManager.HeapRegionStart;
+ break;
+
+ case ProcessCreationFlags.AddressSpace32BitWithoutAlias:
+ _memoryUsageCapacity = MemoryManager.HeapRegionEnd -
+ MemoryManager.HeapRegionStart +
+ MemoryManager.AliasRegionEnd -
+ MemoryManager.AliasRegionStart;
+ break;
+
+ default: throw new InvalidOperationException($"Invalid MMU flags value 0x{Flags:x2}.");
+ }
+
+ GenerateRandomEntropy();
+
+ return Result.Success;
+ }
+
+ public Result AllocateThreadLocalStorage(out ulong address)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ Result result;
+
+ if (_freeTlsPages.Count > 0)
+ {
+ // If we have free TLS pages available, just use the first one.
+ KTlsPageInfo pageInfo = _freeTlsPages.Values.First();
+
+ if (!pageInfo.TryGetFreePage(out address))
+ {
+ throw new InvalidOperationException("Unexpected failure getting free TLS page!");
+ }
+
+ if (pageInfo.IsFull())
+ {
+ _freeTlsPages.Remove(pageInfo.PageVirtualAddress);
+
+ _fullTlsPages.Add(pageInfo.PageVirtualAddress, pageInfo);
+ }
+
+ result = Result.Success;
+ }
+ else
+ {
+ // Otherwise, we need to create a new one.
+ result = AllocateTlsPage(out KTlsPageInfo pageInfo);
+
+ if (result == Result.Success)
+ {
+ if (!pageInfo.TryGetFreePage(out address))
+ {
+ throw new InvalidOperationException("Unexpected failure getting free TLS page!");
+ }
+
+ _freeTlsPages.Add(pageInfo.PageVirtualAddress, pageInfo);
+ }
+ else
+ {
+ address = 0;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ private Result AllocateTlsPage(out KTlsPageInfo pageInfo)
+ {
+ pageInfo = default;
+
+ if (!KernelContext.UserSlabHeapPages.TryGetItem(out ulong tlsPagePa))
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ ulong regionStart = MemoryManager.TlsIoRegionStart;
+ ulong regionSize = MemoryManager.TlsIoRegionEnd - regionStart;
+
+ ulong regionPagesCount = regionSize / KPageTableBase.PageSize;
+
+ Result result = MemoryManager.MapPages(
+ 1,
+ KPageTableBase.PageSize,
+ tlsPagePa,
+ true,
+ regionStart,
+ regionPagesCount,
+ MemoryState.ThreadLocal,
+ KMemoryPermission.ReadAndWrite,
+ out ulong tlsPageVa);
+
+ if (result != Result.Success)
+ {
+ KernelContext.UserSlabHeapPages.Free(tlsPagePa);
+ }
+ else
+ {
+ pageInfo = new KTlsPageInfo(tlsPageVa, tlsPagePa);
+
+ MemoryHelper.FillWithZeros(CpuMemory, tlsPageVa, KPageTableBase.PageSize);
+ }
+
+ return result;
+ }
+
+ public Result FreeThreadLocalStorage(ulong tlsSlotAddr)
+ {
+ ulong tlsPageAddr = BitUtils.AlignDown<ulong>(tlsSlotAddr, KPageTableBase.PageSize);
+
+ KernelContext.CriticalSection.Enter();
+
+ Result result = Result.Success;
+
+ KTlsPageInfo pageInfo;
+
+ if (_fullTlsPages.TryGetValue(tlsPageAddr, out pageInfo))
+ {
+ // TLS page was full, free slot and move to free pages tree.
+ _fullTlsPages.Remove(tlsPageAddr);
+
+ _freeTlsPages.Add(tlsPageAddr, pageInfo);
+ }
+ else if (!_freeTlsPages.TryGetValue(tlsPageAddr, out pageInfo))
+ {
+ result = KernelResult.InvalidAddress;
+ }
+
+ if (pageInfo != null)
+ {
+ pageInfo.FreeTlsSlot(tlsSlotAddr);
+
+ if (pageInfo.IsEmpty())
+ {
+ // TLS page is now empty, we should ensure it is removed
+ // from all trees, and free the memory it was using.
+ _freeTlsPages.Remove(tlsPageAddr);
+
+ KernelContext.CriticalSection.Leave();
+
+ FreeTlsPage(pageInfo);
+
+ return Result.Success;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ private Result FreeTlsPage(KTlsPageInfo pageInfo)
+ {
+ Result result = MemoryManager.UnmapForKernel(pageInfo.PageVirtualAddress, 1, MemoryState.ThreadLocal);
+
+ if (result == Result.Success)
+ {
+ KernelContext.UserSlabHeapPages.Free(pageInfo.PagePhysicalAddress);
+ }
+
+ return result;
+ }
+
+ private void GenerateRandomEntropy()
+ {
+ // TODO.
+ }
+
+ public Result Start(int mainThreadPriority, ulong stackSize)
+ {
+ lock (_processLock)
+ {
+ if (State > ProcessState.CreatedAttached)
+ {
+ return KernelResult.InvalidState;
+ }
+
+ if (ResourceLimit != null && !ResourceLimit.Reserve(LimitableResource.Thread, 1))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ KResourceLimit threadResourceLimit = ResourceLimit;
+ KResourceLimit memoryResourceLimit = null;
+
+ if (_mainThreadStackSize != 0)
+ {
+ throw new InvalidOperationException("Trying to start a process with a invalid state!");
+ }
+
+ ulong stackSizeRounded = BitUtils.AlignUp<ulong>(stackSize, KPageTableBase.PageSize);
+
+ ulong neededSize = stackSizeRounded + _imageSize;
+
+ // Check if the needed size for the code and the stack will fit on the
+ // memory usage capacity of this Process. Also check for possible overflow
+ // on the above addition.
+ if (neededSize > _memoryUsageCapacity || neededSize < stackSizeRounded)
+ {
+ threadResourceLimit?.Release(LimitableResource.Thread, 1);
+
+ return KernelResult.OutOfMemory;
+ }
+
+ if (stackSizeRounded != 0 && ResourceLimit != null)
+ {
+ memoryResourceLimit = ResourceLimit;
+
+ if (!memoryResourceLimit.Reserve(LimitableResource.Memory, stackSizeRounded))
+ {
+ threadResourceLimit?.Release(LimitableResource.Thread, 1);
+
+ return KernelResult.ResLimitExceeded;
+ }
+ }
+
+ Result result;
+
+ KThread mainThread = null;
+
+ ulong stackTop = 0;
+
+ void CleanUpForError()
+ {
+ HandleTable.Destroy();
+
+ mainThread?.DecrementReferenceCount();
+
+ if (_mainThreadStackSize != 0)
+ {
+ ulong stackBottom = stackTop - _mainThreadStackSize;
+
+ ulong stackPagesCount = _mainThreadStackSize / KPageTableBase.PageSize;
+
+ MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack);
+
+ _mainThreadStackSize = 0;
+ }
+
+ memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded);
+ threadResourceLimit?.Release(LimitableResource.Thread, 1);
+ }
+
+ if (stackSizeRounded != 0)
+ {
+ ulong stackPagesCount = stackSizeRounded / KPageTableBase.PageSize;
+
+ ulong regionStart = MemoryManager.StackRegionStart;
+ ulong regionSize = MemoryManager.StackRegionEnd - regionStart;
+
+ ulong regionPagesCount = regionSize / KPageTableBase.PageSize;
+
+ result = MemoryManager.MapPages(
+ stackPagesCount,
+ KPageTableBase.PageSize,
+ 0,
+ false,
+ regionStart,
+ regionPagesCount,
+ MemoryState.Stack,
+ KMemoryPermission.ReadAndWrite,
+ out ulong stackBottom);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ _mainThreadStackSize += stackSizeRounded;
+
+ stackTop = stackBottom + stackSizeRounded;
+ }
+
+ ulong heapCapacity = _memoryUsageCapacity - _mainThreadStackSize - _imageSize;
+
+ result = MemoryManager.SetHeapCapacity(heapCapacity);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ HandleTable = new KHandleTable(KernelContext);
+
+ result = HandleTable.Initialize(Capabilities.HandleTableSize);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ mainThread = new KThread(KernelContext);
+
+ result = mainThread.Initialize(
+ _entrypoint,
+ 0,
+ stackTop,
+ mainThreadPriority,
+ DefaultCpuCore,
+ this,
+ ThreadType.User,
+ _customThreadStart);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ result = HandleTable.GenerateHandle(mainThread, out int mainThreadHandle);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ mainThread.SetEntryArguments(0, mainThreadHandle);
+
+ ProcessState oldState = State;
+ ProcessState newState = State != ProcessState.Created
+ ? ProcessState.Attached
+ : ProcessState.Started;
+
+ SetState(newState);
+
+ result = mainThread.Start();
+
+ if (result != Result.Success)
+ {
+ SetState(oldState);
+
+ CleanUpForError();
+ }
+
+ if (result == Result.Success)
+ {
+ mainThread.IncrementReferenceCount();
+ }
+
+ mainThread.DecrementReferenceCount();
+
+ return result;
+ }
+ }
+
+ private void SetState(ProcessState newState)
+ {
+ if (State != newState)
+ {
+ State = newState;
+ _signaled = true;
+
+ Signal();
+ }
+ }
+
+ public Result InitializeThread(
+ KThread thread,
+ ulong entrypoint,
+ ulong argsPtr,
+ ulong stackTop,
+ int priority,
+ int cpuCore,
+ ThreadStart customThreadStart = null)
+ {
+ lock (_processLock)
+ {
+ return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this, ThreadType.User, customThreadStart);
+ }
+ }
+
+ public IExecutionContext CreateExecutionContext()
+ {
+ return Context?.CreateExecutionContext(new ExceptionCallbacks(
+ InterruptHandler,
+ null,
+ KernelContext.SyscallHandler.SvcCall,
+ UndefinedInstructionHandler));
+ }
+
+ private void InterruptHandler(IExecutionContext context)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (currentThread.Context.Running &&
+ currentThread.Owner != null &&
+ currentThread.GetUserDisableCount() != 0 &&
+ currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ currentThread.Owner.PinThread(currentThread);
+
+ currentThread.SetUserInterruptFlag();
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ if (currentThread.IsSchedulable)
+ {
+ KernelContext.Schedulers[currentThread.CurrentCore].Schedule();
+ }
+
+ currentThread.HandlePostSyscall();
+ }
+
+ public void IncrementThreadCount()
+ {
+ Interlocked.Increment(ref _threadCount);
+ }
+
+ public void DecrementThreadCountAndTerminateIfZero()
+ {
+ if (Interlocked.Decrement(ref _threadCount) == 0)
+ {
+ Terminate();
+ }
+ }
+
+ public void DecrementToZeroWhileTerminatingCurrent()
+ {
+ while (Interlocked.Decrement(ref _threadCount) != 0)
+ {
+ Destroy();
+ TerminateCurrentProcess();
+ }
+
+ // Nintendo panic here because if it reaches this point, the current thread should be already dead.
+ // As we handle the death of the thread in the post SVC handler and inside the CPU emulator, we don't panic here.
+ }
+
+ public ulong GetMemoryCapacity()
+ {
+ ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory);
+
+ totalCapacity += MemoryManager.GetTotalHeapSize();
+
+ totalCapacity += GetPersonalMmHeapSize();
+
+ totalCapacity += _imageSize + _mainThreadStackSize;
+
+ if (totalCapacity <= _memoryUsageCapacity)
+ {
+ return totalCapacity;
+ }
+
+ return _memoryUsageCapacity;
+ }
+
+ public ulong GetMemoryUsage()
+ {
+ return _imageSize + _mainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize();
+ }
+
+ public ulong GetMemoryCapacityWithoutPersonalMmHeap()
+ {
+ return GetMemoryCapacity() - GetPersonalMmHeapSize();
+ }
+
+ public ulong GetMemoryUsageWithoutPersonalMmHeap()
+ {
+ return GetMemoryUsage() - GetPersonalMmHeapSize();
+ }
+
+ private ulong GetPersonalMmHeapSize()
+ {
+ return GetPersonalMmHeapSize(PersonalMmHeapPagesCount, _memRegion);
+ }
+
+ private static ulong GetPersonalMmHeapSize(ulong personalMmHeapPagesCount, MemoryRegion memRegion)
+ {
+ if (memRegion == MemoryRegion.Applet)
+ {
+ return 0;
+ }
+
+ return personalMmHeapPagesCount * KPageTableBase.PageSize;
+ }
+
+ public void AddCpuTime(long ticks)
+ {
+ Interlocked.Add(ref _totalTimeRunning, ticks);
+ }
+
+ public void AddThread(KThread thread)
+ {
+ lock (_threadingLock)
+ {
+ thread.ProcessListNode = _threads.AddLast(thread);
+ }
+ }
+
+ public void RemoveThread(KThread thread)
+ {
+ lock (_threadingLock)
+ {
+ _threads.Remove(thread.ProcessListNode);
+ }
+ }
+
+ public bool IsCpuCoreAllowed(int core)
+ {
+ return (Capabilities.AllowedCpuCoresMask & (1UL << core)) != 0;
+ }
+
+ public bool IsPriorityAllowed(int priority)
+ {
+ return (Capabilities.AllowedThreadPriosMask & (1UL << priority)) != 0;
+ }
+
+ public override bool IsSignaled()
+ {
+ return _signaled;
+ }
+
+ public Result Terminate()
+ {
+ Result result;
+
+ bool shallTerminate = false;
+
+ KernelContext.CriticalSection.Enter();
+
+ lock (_processLock)
+ {
+ if (State >= ProcessState.Started)
+ {
+ if (State == ProcessState.Started ||
+ State == ProcessState.Crashed ||
+ State == ProcessState.Attached ||
+ State == ProcessState.DebugSuspended)
+ {
+ SetState(ProcessState.Exiting);
+
+ shallTerminate = true;
+ }
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ if (shallTerminate)
+ {
+ UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread());
+
+ HandleTable.Destroy();
+
+ SignalExitToDebugTerminated();
+ SignalExit();
+ }
+
+ return result;
+ }
+
+ public void TerminateCurrentProcess()
+ {
+ bool shallTerminate = false;
+
+ KernelContext.CriticalSection.Enter();
+
+ lock (_processLock)
+ {
+ if (State >= ProcessState.Started)
+ {
+ if (State == ProcessState.Started ||
+ State == ProcessState.Attached ||
+ State == ProcessState.DebugSuspended)
+ {
+ SetState(ProcessState.Exiting);
+
+ shallTerminate = true;
+ }
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ if (shallTerminate)
+ {
+ UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread());
+
+ HandleTable.Destroy();
+
+ // NOTE: this is supposed to be called in receiving of the mailbox.
+ SignalExitToDebugExited();
+ SignalExit();
+ }
+
+ KernelStatic.GetCurrentThread().Exit();
+ }
+
+ private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
+ {
+ lock (_threadingLock)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (currentThread != null && PinnedThreads[currentThread.CurrentCore] == currentThread)
+ {
+ UnpinThread(currentThread);
+ }
+
+ foreach (KThread thread in _threads)
+ {
+ if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
+ {
+ thread.PrepareForTermination();
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ while (true)
+ {
+ KThread blockedThread = null;
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
+ {
+ thread.IncrementReferenceCount();
+
+ blockedThread = thread;
+ break;
+ }
+ }
+ }
+
+ if (blockedThread == null)
+ {
+ break;
+ }
+
+ blockedThread.Terminate();
+ blockedThread.DecrementReferenceCount();
+ }
+ }
+
+ private void SignalExitToDebugTerminated()
+ {
+ // TODO: Debug events.
+ }
+
+ private void SignalExitToDebugExited()
+ {
+ // TODO: Debug events.
+ }
+
+ private void SignalExit()
+ {
+ if (ResourceLimit != null)
+ {
+ ResourceLimit.Release(LimitableResource.Memory, GetMemoryUsage());
+ }
+
+ KernelContext.CriticalSection.Enter();
+
+ SetState(ProcessState.Exited);
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public Result ClearIfNotExited()
+ {
+ Result result;
+
+ KernelContext.CriticalSection.Enter();
+
+ lock (_processLock)
+ {
+ if (State != ProcessState.Exited && _signaled)
+ {
+ _signaled = false;
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ private void InitializeMemoryManager(ProcessCreationFlags flags)
+ {
+ int addrSpaceBits = (flags & ProcessCreationFlags.AddressSpaceMask) switch
+ {
+ ProcessCreationFlags.AddressSpace32Bit => 32,
+ ProcessCreationFlags.AddressSpace64BitDeprecated => 36,
+ ProcessCreationFlags.AddressSpace32BitWithoutAlias => 32,
+ ProcessCreationFlags.AddressSpace64Bit => 39,
+ _ => 39
+ };
+
+ bool for64Bit = flags.HasFlag(ProcessCreationFlags.Is64Bit);
+
+ Context = _contextFactory.Create(KernelContext, Pid, 1UL << addrSpaceBits, InvalidAccessHandler, for64Bit);
+
+ MemoryManager = new KPageTable(KernelContext, CpuMemory);
+ }
+
+ private bool InvalidAccessHandler(ulong va)
+ {
+ KernelStatic.GetCurrentThread()?.PrintGuestStackTrace();
+ KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
+
+ Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}.");
+
+ return false;
+ }
+
+ private void UndefinedInstructionHandler(IExecutionContext context, ulong address, int opCode)
+ {
+ KernelStatic.GetCurrentThread().PrintGuestStackTrace();
+ KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
+
+ throw new UndefinedInstructionException(address, opCode);
+ }
+
+ protected override void Destroy() => Context.Dispose();
+
+ public Result SetActivity(bool pause)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (State != ProcessState.Exiting && State != ProcessState.Exited)
+ {
+ if (pause)
+ {
+ if (IsPaused)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ thread.Suspend(ThreadSchedState.ProcessPauseFlag);
+ }
+ }
+
+ IsPaused = true;
+ }
+ else
+ {
+ if (!IsPaused)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ thread.Resume(ThreadSchedState.ProcessPauseFlag);
+ }
+ }
+
+ IsPaused = false;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ public void PinThread(KThread thread)
+ {
+ if (!thread.TerminationRequested)
+ {
+ PinnedThreads[thread.CurrentCore] = thread;
+
+ thread.Pin();
+
+ KernelContext.ThreadReselectionRequested = true;
+ }
+ }
+
+ public void UnpinThread(KThread thread)
+ {
+ if (!thread.TerminationRequested)
+ {
+ thread.Unpin();
+
+ PinnedThreads[thread.CurrentCore] = null;
+
+ KernelContext.ThreadReselectionRequested = true;
+ }
+ }
+
+ public bool IsExceptionUserThread(KThread thread)
+ {
+ // TODO
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
new file mode 100644
index 00000000..c99e3112
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
@@ -0,0 +1,328 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KProcessCapabilities
+ {
+ public byte[] SvcAccessMask { get; }
+ public byte[] IrqAccessMask { get; }
+
+ public ulong AllowedCpuCoresMask { get; private set; }
+ public ulong AllowedThreadPriosMask { get; private set; }
+
+ public uint DebuggingFlags { get; private set; }
+ public uint HandleTableSize { get; private set; }
+ public uint KernelReleaseVersion { get; private set; }
+ public uint ApplicationType { get; private set; }
+
+ public KProcessCapabilities()
+ {
+ // length / number of bits of the underlying type
+ SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8];
+ IrqAccessMask = new byte[0x80];
+ }
+
+ public Result InitializeForKernel(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
+ {
+ AllowedCpuCoresMask = 0xf;
+ AllowedThreadPriosMask = ulong.MaxValue;
+ DebuggingFlags &= ~3u;
+ KernelReleaseVersion = KProcess.KernelVersionPacked;
+
+ return Parse(capabilities, memoryManager);
+ }
+
+ public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
+ {
+ return Parse(capabilities, memoryManager);
+ }
+
+ private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
+ {
+ int mask0 = 0;
+ int mask1 = 0;
+
+ for (int index = 0; index < capabilities.Length; index++)
+ {
+ uint cap = capabilities[index];
+
+ if (cap.GetCapabilityType() != CapabilityType.MapRange)
+ {
+ Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+ else
+ {
+ if ((uint)index + 1 >= capabilities.Length)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ uint prevCap = cap;
+
+ cap = capabilities[++index];
+
+ if (((cap + 1) & ~cap) != 0x40)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if ((cap & 0x78000000) != 0)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ if ((cap & 0x7ffff80) == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ long address = ((long)prevCap << 5) & 0xffffff000;
+ long size = ((long)cap << 5) & 0xfffff000;
+
+ if (((ulong)(address + size - 1) >> 36) != 0)
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ KMemoryPermission perm = (prevCap >> 31) != 0
+ ? KMemoryPermission.Read
+ : KMemoryPermission.ReadAndWrite;
+
+ Result result;
+
+ if ((cap >> 31) != 0)
+ {
+ result = memoryManager.MapNormalMemory(address, size, perm);
+ }
+ else
+ {
+ result = memoryManager.MapIoMemory(address, size, perm);
+ }
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+ }
+
+ return Result.Success;
+ }
+
+ private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
+ {
+ CapabilityType code = cap.GetCapabilityType();
+
+ if (code == CapabilityType.Invalid)
+ {
+ return KernelResult.InvalidCapability;
+ }
+ else if (code == CapabilityType.Padding)
+ {
+ return Result.Success;
+ }
+
+ int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1));
+
+ // Check if the property was already set.
+ if (((mask0 & codeMask) & 0x1e008) != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ mask0 |= codeMask;
+
+ switch (code)
+ {
+ case CapabilityType.CorePriority:
+ {
+ if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0)
+ {
+ return KernelResult.InvalidCapability;
+ }
+
+ uint lowestCpuCore = (cap >> 16) & 0xff;
+ uint highestCpuCore = (cap >> 24) & 0xff;
+
+ if (lowestCpuCore > highestCpuCore)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ uint highestThreadPrio = (cap >> 4) & 0x3f;
+ uint lowestThreadPrio = (cap >> 10) & 0x3f;
+
+ if (lowestThreadPrio > highestThreadPrio)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if (highestCpuCore >= KScheduler.CpuCoresCount)
+ {
+ return KernelResult.InvalidCpuCore;
+ }
+
+ AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore);
+ AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio);
+
+ break;
+ }
+
+ case CapabilityType.SyscallMask:
+ {
+ int slot = ((int)cap >> 29) & 7;
+
+ int svcSlotMask = 1 << slot;
+
+ if ((mask1 & svcSlotMask) != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ mask1 |= svcSlotMask;
+
+ uint svcMask = (cap >> 5) & 0xffffff;
+
+ int baseSvc = slot * 24;
+
+ for (int index = 0; index < 24; index++)
+ {
+ if (((svcMask >> index) & 1) == 0)
+ {
+ continue;
+ }
+
+ int svcId = baseSvc + index;
+
+ if (svcId >= KernelConstants.SupervisorCallCount)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7));
+ }
+
+ break;
+ }
+
+ case CapabilityType.MapIoPage:
+ {
+ long address = ((long)cap << 4) & 0xffffff000;
+
+ memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite);
+
+ break;
+ }
+
+ case CapabilityType.MapRegion:
+ {
+ // TODO: Implement capabilities for MapRegion
+
+ break;
+ }
+
+ case CapabilityType.InterruptPair:
+ {
+ // TODO: GIC distributor check.
+ int irq0 = ((int)cap >> 12) & 0x3ff;
+ int irq1 = ((int)cap >> 22) & 0x3ff;
+
+ if (irq0 != 0x3ff)
+ {
+ IrqAccessMask[irq0 / 8] |= (byte)(1 << (irq0 & 7));
+ }
+
+ if (irq1 != 0x3ff)
+ {
+ IrqAccessMask[irq1 / 8] |= (byte)(1 << (irq1 & 7));
+ }
+
+ break;
+ }
+
+ case CapabilityType.ProgramType:
+ {
+ uint applicationType = (cap >> 14);
+
+ if (applicationType > 7)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ ApplicationType = applicationType;
+
+ break;
+ }
+
+ case CapabilityType.KernelVersion:
+ {
+ // Note: This check is bugged on kernel too, we are just replicating the bug here.
+ if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ KernelReleaseVersion = cap;
+
+ break;
+ }
+
+ case CapabilityType.HandleTable:
+ {
+ uint handleTableSize = cap >> 26;
+
+ if (handleTableSize > 0x3ff)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ HandleTableSize = handleTableSize;
+
+ break;
+ }
+
+ case CapabilityType.DebugFlags:
+ {
+ uint debuggingFlags = cap >> 19;
+
+ if (debuggingFlags > 3)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ DebuggingFlags &= ~3u;
+ DebuggingFlags |= debuggingFlags;
+
+ break;
+ }
+
+ default: return KernelResult.InvalidCapability;
+ }
+
+ return Result.Success;
+ }
+
+ private static ulong GetMaskFromMinMax(uint min, uint max)
+ {
+ uint range = max - min + 1;
+
+ if (range == 64)
+ {
+ return ulong.MaxValue;
+ }
+
+ ulong mask = (1UL << (int)range) - 1;
+
+ return mask << (int)min;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs
new file mode 100644
index 00000000..f55e3c10
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs
@@ -0,0 +1,77 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KTlsPageInfo
+ {
+ public const int TlsEntrySize = 0x200;
+
+ public ulong PageVirtualAddress { get; }
+ public ulong PagePhysicalAddress { get; }
+
+ private readonly bool[] _isSlotFree;
+
+ public KTlsPageInfo(ulong pageVirtualAddress, ulong pagePhysicalAddress)
+ {
+ PageVirtualAddress = pageVirtualAddress;
+ PagePhysicalAddress = pagePhysicalAddress;
+
+ _isSlotFree = new bool[KPageTableBase.PageSize / TlsEntrySize];
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ _isSlotFree[index] = true;
+ }
+ }
+
+ public bool TryGetFreePage(out ulong address)
+ {
+ address = PageVirtualAddress;
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ if (_isSlotFree[index])
+ {
+ _isSlotFree[index] = false;
+
+ return true;
+ }
+
+ address += TlsEntrySize;
+ }
+
+ address = 0;
+
+ return false;
+ }
+
+ public bool IsFull()
+ {
+ bool hasFree = false;
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ hasFree |= _isSlotFree[index];
+ }
+
+ return !hasFree;
+ }
+
+ public bool IsEmpty()
+ {
+ bool allFree = true;
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ allFree &= _isSlotFree[index];
+ }
+
+ return allFree;
+ }
+
+ public void FreeTlsSlot(ulong address)
+ {
+ _isSlotFree[(address - PageVirtualAddress) / TlsEntrySize] = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs
new file mode 100644
index 00000000..0fde495c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs
@@ -0,0 +1,61 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KTlsPageManager
+ {
+ private const int TlsEntrySize = 0x200;
+
+ private long _pagePosition;
+
+ private int _usedSlots;
+
+ private bool[] _slots;
+
+ public bool IsEmpty => _usedSlots == 0;
+ public bool IsFull => _usedSlots == _slots.Length;
+
+ public KTlsPageManager(long pagePosition)
+ {
+ _pagePosition = pagePosition;
+
+ _slots = new bool[KPageTableBase.PageSize / TlsEntrySize];
+ }
+
+ public bool TryGetFreeTlsAddr(out long position)
+ {
+ position = _pagePosition;
+
+ for (int index = 0; index < _slots.Length; index++)
+ {
+ if (!_slots[index])
+ {
+ _slots[index] = true;
+
+ _usedSlots++;
+
+ return true;
+ }
+
+ position += TlsEntrySize;
+ }
+
+ position = 0;
+
+ return false;
+ }
+
+ public void FreeTlsSlot(int slot)
+ {
+ if ((uint)slot > _slots.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(slot));
+ }
+
+ _slots[slot] = false;
+
+ _usedSlots--;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs
new file mode 100644
index 00000000..87296830
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs
@@ -0,0 +1,34 @@
+using Ryujinx.Cpu;
+using Ryujinx.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessContext : IProcessContext
+ {
+ public IVirtualMemoryManager AddressSpace { get; }
+
+ public ProcessContext(IVirtualMemoryManager asManager)
+ {
+ AddressSpace = asManager;
+ }
+
+ public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
+ {
+ return new ProcessExecutionContext();
+ }
+
+ public void Execute(IExecutionContext context, ulong codeAddress)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void InvalidateCacheRegion(ulong address, ulong size)
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs
new file mode 100644
index 00000000..1c5798b4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessContextFactory : IProcessContextFactory
+ {
+ public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
+ {
+ return new ProcessContext(new AddressSpaceManager(context.Memory, addressSpaceSize));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs
new file mode 100644
index 00000000..a79978ac
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ [Flags]
+ enum ProcessCreationFlags
+ {
+ Is64Bit = 1 << 0,
+
+ AddressSpaceShift = 1,
+ AddressSpace32Bit = 0 << AddressSpaceShift,
+ AddressSpace64BitDeprecated = 1 << AddressSpaceShift,
+ AddressSpace32BitWithoutAlias = 2 << AddressSpaceShift,
+ AddressSpace64Bit = 3 << AddressSpaceShift,
+ AddressSpaceMask = 7 << AddressSpaceShift,
+
+ EnableDebug = 1 << 4,
+ EnableAslr = 1 << 5,
+ IsApplication = 1 << 6,
+ DeprecatedUseSecureMemory = 1 << 7,
+
+ PoolPartitionShift = 7,
+ PoolPartitionApplication = 0 << PoolPartitionShift,
+ PoolPartitionApplet = 1 << PoolPartitionShift,
+ PoolPartitionSystem = 2 << PoolPartitionShift,
+ PoolPartitionSystemNonSecure = 3 << PoolPartitionShift,
+ PoolPartitionMask = 0xf << PoolPartitionShift,
+
+ OptimizeMemoryAllocation = 1 << 11,
+
+ All =
+ Is64Bit |
+ AddressSpaceMask |
+ EnableDebug |
+ EnableAslr |
+ IsApplication |
+ DeprecatedUseSecureMemory |
+ PoolPartitionMask |
+ OptimizeMemoryAllocation
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs
new file mode 100644
index 00000000..c05bb574
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ readonly struct ProcessCreationInfo
+ {
+ public string Name { get; }
+
+ public int Version { get; }
+ public ulong TitleId { get; }
+
+ public ulong CodeAddress { get; }
+ public int CodePagesCount { get; }
+
+ public ProcessCreationFlags Flags { get; }
+ public int ResourceLimitHandle { get; }
+ public int SystemResourcePagesCount { get; }
+
+ public ProcessCreationInfo(
+ string name,
+ int version,
+ ulong titleId,
+ ulong codeAddress,
+ int codePagesCount,
+ ProcessCreationFlags flags,
+ int resourceLimitHandle,
+ int systemResourcePagesCount)
+ {
+ Name = name;
+ Version = version;
+ TitleId = titleId;
+ CodeAddress = codeAddress;
+ CodePagesCount = codePagesCount;
+ Flags = flags;
+ ResourceLimitHandle = resourceLimitHandle;
+ SystemResourcePagesCount = systemResourcePagesCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
new file mode 100644
index 00000000..77fcdf33
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
@@ -0,0 +1,46 @@
+using ARMeilleure.State;
+using Ryujinx.Cpu;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessExecutionContext : IExecutionContext
+ {
+ public ulong Pc => 0UL;
+
+ public ulong CntfrqEl0 { get; set; }
+ public ulong CntpctEl0 => 0UL;
+
+ public long TpidrEl0 { get; set; }
+ public long TpidrroEl0 { get; set; }
+
+ public uint Pstate { get; set; }
+
+ public uint Fpcr { get; set; }
+ public uint Fpsr { get; set; }
+
+ public bool IsAarch32 { get => false; set { } }
+
+ public bool Running { get; private set; } = true;
+
+ private readonly ulong[] _x = new ulong[32];
+
+ public ulong GetX(int index) => _x[index];
+ public void SetX(int index, ulong value) => _x[index] = value;
+
+ public V128 GetV(int index) => default;
+ public void SetV(int index, V128 value) { }
+
+ public void RequestInterrupt()
+ {
+ }
+
+ public void StopRunning()
+ {
+ Running = false;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs
new file mode 100644
index 00000000..5ef3077e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ enum ProcessState : byte
+ {
+ Created = 0,
+ CreatedAttached = 1,
+ Started = 2,
+ Crashed = 3,
+ Attached = 4,
+ Exiting = 5,
+ Exited = 6,
+ DebugSuspended = 7
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
new file mode 100644
index 00000000..4cf67172
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessTamperInfo
+ {
+ public KProcess Process { get; }
+ public IEnumerable<string> BuildIds { get; }
+ public IEnumerable<ulong> CodeAddresses { get; }
+ public ulong HeapAddress { get; }
+ public ulong AliasAddress { get; }
+ public ulong AslrAddress { get; }
+
+ public ProcessTamperInfo(KProcess process, IEnumerable<string> buildIds, IEnumerable<ulong> codeAddresses, ulong heapAddress, ulong aliasAddress, ulong aslrAddress)
+ {
+ Process = process;
+ BuildIds = buildIds;
+ CodeAddresses = codeAddresses;
+ HeapAddress = heapAddress;
+ AliasAddress = aliasAddress;
+ AslrAddress = aslrAddress;
+ }
+ }
+} \ No newline at end of file