diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Kernel/Process')
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 |
