Refactor SVC handler (#540)

* Refactor SVC handler

* Get rid of KernelErr

* Split kernel code files into multiple folders
This commit is contained in:
gdkchan 2018-12-18 03:33:36 -02:00 committed by GitHub
parent 2534a7f10c
commit 0039bb6394
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 1894 additions and 1982 deletions

View file

@ -0,0 +1,311 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Diagnostics.Demangler;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.Loaders.Elf;
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 long BaseAddress { get; private set; }
public ElfSymbol[] Symbols { get; private set; }
public Image(long baseAddress, ElfSymbol[] symbols)
{
BaseAddress = baseAddress;
Symbols = symbols;
}
}
private List<Image> _images;
private int _loaded;
public HleProcessDebugger(KProcess owner)
{
_owner = owner;
_images = new List<Image>();
}
public void PrintGuestStackTrace(CpuThreadState threadState)
{
EnsureLoaded();
StringBuilder trace = new StringBuilder();
trace.AppendLine("Guest stack trace:");
void AppendTrace(long address)
{
Image image = GetImage(address, out int imageIndex);
if (image == null || !TryGetSubName(image, address, out string subName))
{
subName = $"Sub{address:x16}";
}
else if (subName.StartsWith("_Z"))
{
subName = Demangler.Parse(subName);
}
if (image != null)
{
long offset = address - image.BaseAddress;
string imageName = GetGuessedNsoNameFromIndex(imageIndex);
string imageNameAndOffset = $"[{_owner.Name}] {imageName}:0x{offset:x8}";
trace.AppendLine($" {imageNameAndOffset} {subName}");
}
else
{
trace.AppendLine($" [{_owner.Name}] ??? {subName}");
}
}
long framePointer = (long)threadState.X29;
while (framePointer != 0)
{
if ((framePointer & 7) != 0 ||
!_owner.CpuMemory.IsMapped(framePointer) ||
!_owner.CpuMemory.IsMapped(framePointer + 8))
{
break;
}
//Note: This is the return address, we need to subtract one instruction
//worth of bytes to get the branch instruction address.
AppendTrace(_owner.CpuMemory.ReadInt64(framePointer + 8) - 4);
framePointer = _owner.CpuMemory.ReadInt64(framePointer);
}
Logger.PrintInfo(LogClass.Cpu, trace.ToString());
}
private bool TryGetSubName(Image image, long address, out string name)
{
address -= image.BaseAddress;
int left = 0;
int right = image.Symbols.Length - 1;
while (left <= right)
{
int size = right - left;
int middle = left + (size >> 1);
ElfSymbol symbol = image.Symbols[middle];
long endAddr = symbol.Value + symbol.Size;
if ((ulong)address >= (ulong)symbol.Value && (ulong)address < (ulong)endAddr)
{
name = symbol.Name;
return true;
}
if ((ulong)address < (ulong)symbol.Value)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
name = null;
return false;
}
private Image GetImage(long address, out int index)
{
lock (_images)
{
for (index = _images.Count - 1; index >= 0; index--)
{
if ((ulong)address >= (ulong)_images[index].BaseAddress)
{
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 == MemoryPermission.ReadAndExecute)
{
LoadMod0Symbols(_owner.CpuMemory, (long)info.Address);
}
oldAddress = address;
address = info.Address + info.Size;
}
}
private void LoadMod0Symbols(MemoryManager memory, long textOffset)
{
long mod0Offset = textOffset + memory.ReadUInt32(textOffset + 4);
if (mod0Offset < textOffset || !memory.IsMapped(mod0Offset) || (mod0Offset & 3) != 0)
{
return;
}
Dictionary<ElfDynamicTag, long> dynamic = new Dictionary<ElfDynamicTag, long>();
int mod0Magic = memory.ReadInt32(mod0Offset + 0x0);
if (mod0Magic != Mod0)
{
return;
}
long dynamicOffset = memory.ReadInt32(mod0Offset + 0x4) + mod0Offset;
long bssStartOffset = memory.ReadInt32(mod0Offset + 0x8) + mod0Offset;
long bssEndOffset = memory.ReadInt32(mod0Offset + 0xc) + mod0Offset;
long ehHdrStartOffset = memory.ReadInt32(mod0Offset + 0x10) + mod0Offset;
long ehHdrEndOffset = memory.ReadInt32(mod0Offset + 0x14) + mod0Offset;
long modObjOffset = memory.ReadInt32(mod0Offset + 0x18) + mod0Offset;
while (true)
{
long tagVal = memory.ReadInt64(dynamicOffset + 0);
long value = memory.ReadInt64(dynamicOffset + 8);
dynamicOffset += 0x10;
ElfDynamicTag tag = (ElfDynamicTag)tagVal;
if (tag == ElfDynamicTag.DT_NULL)
{
break;
}
dynamic[tag] = value;
}
if (!dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out long strTab) ||
!dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out long symTab) ||
!dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out long symEntSize))
{
return;
}
long strTblAddr = textOffset + strTab;
long symTblAddr = textOffset + symTab;
List<ElfSymbol> symbols = new List<ElfSymbol>();
while ((ulong)symTblAddr < (ulong)strTblAddr)
{
ElfSymbol sym = GetSymbol(memory, symTblAddr, strTblAddr);
symbols.Add(sym);
symTblAddr += symEntSize;
}
lock (_images)
{
_images.Add(new Image(textOffset, symbols.OrderBy(x => x.Value).ToArray()));
}
}
private ElfSymbol GetSymbol(MemoryManager memory, long address, long strTblAddr)
{
int nameIndex = memory.ReadInt32(address + 0);
int info = memory.ReadByte (address + 4);
int other = memory.ReadByte (address + 5);
int shIdx = memory.ReadInt16(address + 6);
long value = memory.ReadInt64(address + 8);
long size = memory.ReadInt64(address + 16);
string name = string.Empty;
for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;)
{
name += (char)chr;
}
return new ElfSymbol(name, info, other, shIdx, value, size);
}
}
}

View file

@ -0,0 +1,83 @@
using Ryujinx.Common;
using System;
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 = BitUtils.CountLeadingZeros32((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));
}
}
}

View file

@ -0,0 +1,17 @@
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 object Obj { get; set; }
public KHandleEntry(int index)
{
Index = index;
}
}
}

View file

@ -0,0 +1,209 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
class KHandleTable
{
private const int SelfThreadHandle = (0x1ffff << 15) | 0;
private const int SelfProcessHandle = (0x1ffff << 15) | 1;
private Horizon _system;
private KHandleEntry[] _table;
private KHandleEntry _tableHead;
private KHandleEntry _nextFreeEntry;
private int _activeSlotsCount;
private int _size;
private ushort _idCounter;
public KHandleTable(Horizon system)
{
_system = system;
}
public KernelResult Initialize(int size)
{
if ((uint)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 KernelResult.Success;
}
public KernelResult GenerateHandle(object 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 = (int)((_idCounter << 15) & 0xffff8000) | entry.Index;
if ((short)(_idCounter + 1) >= 0)
{
_idCounter++;
}
else
{
_idCounter = 1;
}
}
return KernelResult.Success;
}
public bool CloseHandle(int handle)
{
if ((handle >> 30) != 0 ||
handle == SelfThreadHandle ||
handle == SelfProcessHandle)
{
return false;
}
int index = (handle >> 0) & 0x7fff;
int handleId = (handle >> 15);
bool result = false;
lock (_table)
{
if (handleId != 0 && index < _size)
{
KHandleEntry entry = _table[index];
if (entry.Obj != null && entry.HandleId == handleId)
{
entry.Obj = null;
entry.Next = _nextFreeEntry;
_nextFreeEntry = entry;
_activeSlotsCount--;
result = true;
}
}
}
return result;
}
public T GetObject<T>(int handle)
{
int index = (handle >> 0) & 0x7fff;
int handleId = (handle >> 15);
lock (_table)
{
if ((handle >> 30) == 0 && handleId != 0)
{
KHandleEntry entry = _table[index];
if (entry.HandleId == handleId && entry.Obj is T obj)
{
return obj;
}
}
}
return default(T);
}
public KThread GetKThread(int handle)
{
if (handle == SelfThreadHandle)
{
return _system.Scheduler.GetCurrentThread();
}
else
{
return GetObject<KThread>(handle);
}
}
public KProcess GetKProcess(int handle)
{
if (handle == SelfProcessHandle)
{
return _system.Scheduler.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 = null;
entry.Next = _nextFreeEntry;
_nextFreeEntry = entry;
}
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,314 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
class KProcessCapabilities
{
public byte[] SvcAccessMask { get; private set; }
public byte[] IrqAccessMask { get; private set; }
public long AllowedCpuCoresMask { get; private set; }
public long AllowedThreadPriosMask { get; private set; }
public int DebuggingFlags { get; private set; }
public int HandleTableSize { get; private set; }
public int KernelReleaseVersion { get; private set; }
public int ApplicationType { get; private set; }
public KProcessCapabilities()
{
SvcAccessMask = new byte[0x10];
IrqAccessMask = new byte[0x80];
}
public KernelResult InitializeForKernel(int[] caps, KMemoryManager memoryManager)
{
AllowedCpuCoresMask = 0xf;
AllowedThreadPriosMask = -1;
DebuggingFlags &= ~3;
KernelReleaseVersion = KProcess.KernelVersionPacked;
return Parse(caps, memoryManager);
}
public KernelResult InitializeForUser(int[] caps, KMemoryManager memoryManager)
{
return Parse(caps, memoryManager);
}
private KernelResult Parse(int[] caps, KMemoryManager memoryManager)
{
int mask0 = 0;
int mask1 = 0;
for (int index = 0; index < caps.Length; index++)
{
int cap = caps[index];
if (((cap + 1) & ~cap) != 0x40)
{
KernelResult result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
if (result != KernelResult.Success)
{
return result;
}
}
else
{
if ((uint)index + 1 >= caps.Length)
{
return KernelResult.InvalidCombination;
}
int prevCap = cap;
cap = caps[++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)(uint)prevCap << 5) & 0xffffff000;
long size = ((long)(uint)cap << 5) & 0xfffff000;
if (((ulong)(address + size - 1) >> 36) != 0)
{
return KernelResult.InvalidAddress;
}
MemoryPermission perm = (prevCap >> 31) != 0
? MemoryPermission.Read
: MemoryPermission.ReadAndWrite;
KernelResult result;
if ((cap >> 31) != 0)
{
result = memoryManager.MapNormalMemory(address, size, perm);
}
else
{
result = memoryManager.MapIoMemory(address, size, perm);
}
if (result != KernelResult.Success)
{
return result;
}
}
}
return KernelResult.Success;
}
private KernelResult ParseCapability(int cap, ref int mask0, ref int mask1, KMemoryManager memoryManager)
{
int code = (cap + 1) & ~cap;
if (code == 1)
{
return KernelResult.InvalidCapability;
}
else if (code == 0)
{
return KernelResult.Success;
}
int codeMask = 1 << (32 - BitUtils.CountLeadingZeros32(code + 1));
//Check if the property was already set.
if (((mask0 & codeMask) & 0x1e008) != 0)
{
return KernelResult.InvalidCombination;
}
mask0 |= codeMask;
switch (code)
{
case 8:
{
if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0)
{
return KernelResult.InvalidCapability;
}
int lowestCpuCore = (cap >> 16) & 0xff;
int highestCpuCore = (cap >> 24) & 0xff;
if (lowestCpuCore > highestCpuCore)
{
return KernelResult.InvalidCombination;
}
int highestThreadPrio = (cap >> 4) & 0x3f;
int 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 0x10:
{
int slot = (cap >> 29) & 7;
int svcSlotMask = 1 << slot;
if ((mask1 & svcSlotMask) != 0)
{
return KernelResult.InvalidCombination;
}
mask1 |= svcSlotMask;
int 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 > 0x7f)
{
return KernelResult.MaximumExceeded;
}
SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7));
}
break;
}
case 0x80:
{
long address = ((long)(uint)cap << 4) & 0xffffff000;
memoryManager.MapIoMemory(address, KMemoryManager.PageSize, MemoryPermission.ReadAndWrite);
break;
}
case 0x800:
{
//TODO: GIC distributor check.
int irq0 = (cap >> 12) & 0x3ff;
int irq1 = (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 0x2000:
{
int applicationType = cap >> 14;
if ((uint)applicationType > 7)
{
return KernelResult.ReservedValue;
}
ApplicationType = applicationType;
break;
}
case 0x4000:
{
//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 0x8000:
{
int handleTableSize = cap >> 26;
if ((uint)handleTableSize > 0x3ff)
{
return KernelResult.ReservedValue;
}
HandleTableSize = handleTableSize;
break;
}
case 0x10000:
{
int debuggingFlags = cap >> 19;
if ((uint)debuggingFlags > 3)
{
return KernelResult.ReservedValue;
}
DebuggingFlags &= ~3;
DebuggingFlags |= debuggingFlags;
break;
}
default: return KernelResult.InvalidCapability;
}
return KernelResult.Success;
}
private static long GetMaskFromMinMax(int min, int max)
{
int range = max - min + 1;
long mask = (1L << range) - 1;
return mask << min;
}
}
}

View file

@ -0,0 +1,75 @@
using Ryujinx.HLE.HOS.Kernel.Memory;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
class KTlsPageInfo
{
public const int TlsEntrySize = 0x200;
public ulong PageAddr { get; private set; }
private bool[] _isSlotFree;
public KTlsPageInfo(ulong pageAddress)
{
PageAddr = pageAddress;
_isSlotFree = new bool[KMemoryManager.PageSize / TlsEntrySize];
for (int index = 0; index < _isSlotFree.Length; index++)
{
_isSlotFree[index] = true;
}
}
public bool TryGetFreePage(out ulong address)
{
address = PageAddr;
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 - PageAddr) / TlsEntrySize] = true;
}
}
}

View file

@ -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[KMemoryManager.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--;
}
}
}

View file

@ -0,0 +1,37 @@
namespace Ryujinx.HLE.HOS.Kernel.Process
{
struct ProcessCreationInfo
{
public string Name { get; private set; }
public int Category { get; private set; }
public long TitleId { get; private set; }
public ulong CodeAddress { get; private set; }
public int CodePagesCount { get; private set; }
public int MmuFlags { get; private set; }
public int ResourceLimitHandle { get; private set; }
public int PersonalMmHeapPagesCount { get; private set; }
public ProcessCreationInfo(
string name,
int category,
long titleId,
ulong codeAddress,
int codePagesCount,
int mmuFlags,
int resourceLimitHandle,
int personalMmHeapPagesCount)
{
Name = name;
Category = category;
TitleId = titleId;
CodeAddress = codeAddress;
CodePagesCount = codePagesCount;
MmuFlags = mmuFlags;
ResourceLimitHandle = resourceLimitHandle;
PersonalMmHeapPagesCount = personalMmHeapPagesCount;
}
}
}

View file

@ -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
}
}