diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs')
-rw-r--r-- | src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 1196 |
1 files changed, 1196 insertions, 0 deletions
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 |