Better process implementation (#491)

* Initial implementation of KProcess

* Some improvements to the memory manager, implement back guest stack trace printing

* Better GetInfo implementation, improve checking in some places with information from process capabilities

* Allow the cpu to read/write from the correct memory locations for accesses crossing a page boundary

* Change long -> ulong for address/size on memory related methods to avoid unnecessary casts

* Attempt at implementing ldr:ro with new KProcess

* Allow BSS with size 0 on ldr:ro

* Add checking for memory block slab heap usage, return errors if full, exit gracefully

* Use KMemoryBlockSize const from KMemoryManager

* Allow all methods to read from non-contiguous locations

* Fix for TransactParcelAuto

* Address PR feedback, additionally fix some small issues related to the KIP loader and implement SVCs GetProcessId, GetProcessList, GetSystemInfo, CreatePort and ManageNamedPort

* Fix wrong check for source pages count from page list on MapPhysicalMemory

* Fix some issues with UnloadNro on ldr:ro
This commit is contained in:
gdkchan 2018-11-28 20:18:09 -02:00 committed by GitHub
parent e7fe7d7247
commit 00579927e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
119 changed files with 7998 additions and 3232 deletions

View file

@ -11,32 +11,68 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Nso = Ryujinx.HLE.Loaders.Executables.Nso;
using System.Reflection;
using System.Threading;
using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject;
namespace Ryujinx.HLE.HOS
{
public class Horizon : IDisposable
{
internal const int InitialKipId = 1;
internal const int InitialProcessId = 0x51;
internal const int HidSize = 0x40000;
internal const int FontSize = 0x1100000;
private Switch Device;
private const int MemoryBlockAllocatorSize = 0x2710;
private ConcurrentDictionary<int, Process> Processes;
private const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
private const ulong UserSlabHeapItemSize = KMemoryManager.PageSize;
private const ulong UserSlabHeapSize = 0x3de000;
internal long PrivilegedProcessLowestId { get; set; } = 1;
internal long PrivilegedProcessHighestId { get; set; } = 8;
internal Switch Device { get; private set; }
public SystemStateMgr State { get; private set; }
internal KRecursiveLock CriticalSectionLock { get; private set; }
internal bool KernelInitialized { get; private set; }
internal KResourceLimit ResourceLimit { get; private set; }
internal KMemoryRegionManager[] MemoryRegions { get; private set; }
internal KMemoryBlockAllocator LargeMemoryBlockAllocator { get; private set; }
internal KMemoryBlockAllocator SmallMemoryBlockAllocator { get; private set; }
internal KSlabHeap UserSlabHeapPages { get; private set; }
internal KCriticalSection CriticalSection { get; private set; }
internal KScheduler Scheduler { get; private set; }
internal KTimeManager TimeManager { get; private set; }
internal KAddressArbiter AddressArbiter { get; private set; }
internal KSynchronization Synchronization { get; private set; }
internal LinkedList<KThread> Withholders { get; private set; }
internal KContextIdManager ContextIdManager { get; private set; }
private long KipId;
private long ProcessId;
private long ThreadUid;
internal CountdownEvent ThreadCounter;
internal SortedDictionary<long, KProcess> Processes;
internal ConcurrentDictionary<string, KAutoObject> AutoObjectNames;
internal bool EnableVersionChecks { get; private set; }
internal AppletStateMgr AppletState { get; private set; }
internal KSharedMemory HidSharedMem { get; private set; }
internal KSharedMemory FontSharedMem { get; private set; }
@ -57,38 +93,74 @@ namespace Ryujinx.HLE.HOS
public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
internal long HidBaseAddress { get; private set; }
public Horizon(Switch Device)
{
this.Device = Device;
Processes = new ConcurrentDictionary<int, Process>();
State = new SystemStateMgr();
CriticalSectionLock = new KRecursiveLock(this);
ResourceLimit = new KResourceLimit(this);
KernelInit.InitializeResourceLimit(ResourceLimit);
MemoryRegions = KernelInit.GetMemoryRegions();
LargeMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize * 2);
SmallMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize);
UserSlabHeapPages = new KSlabHeap(
UserSlabHeapBase,
UserSlabHeapItemSize,
UserSlabHeapSize);
CriticalSection = new KCriticalSection(this);
Scheduler = new KScheduler(this);
TimeManager = new KTimeManager();
AddressArbiter = new KAddressArbiter(this);
Synchronization = new KSynchronization(this);
Withholders = new LinkedList<KThread>();
ContextIdManager = new KContextIdManager();
KipId = InitialKipId;
ProcessId = InitialProcessId;
Scheduler.StartAutoPreemptionThread();
if (!Device.Memory.Allocator.TryAllocate(HidSize, out long HidPA) ||
!Device.Memory.Allocator.TryAllocate(FontSize, out long FontPA))
{
throw new InvalidOperationException();
}
KernelInitialized = true;
HidSharedMem = new KSharedMemory(HidPA, HidSize);
FontSharedMem = new KSharedMemory(FontPA, FontSize);
ThreadCounter = new CountdownEvent(1);
Font = new SharedFontManager(Device, FontSharedMem.PA);
Processes = new SortedDictionary<long, KProcess>();
AutoObjectNames = new ConcurrentDictionary<string, KAutoObject>();
//Note: This is not really correct, but with HLE of services, the only memory
//region used that is used is Application, so we can use the other ones for anything.
KMemoryRegionManager Region = MemoryRegions[(int)MemoryRegion.NvServices];
ulong HidPa = Region.Address;
ulong FontPa = Region.Address + HidSize;
HidBaseAddress = (long)(HidPa - DramMemoryMap.DramBase);
KPageList HidPageList = new KPageList();
KPageList FontPageList = new KPageList();
HidPageList .AddRange(HidPa, HidSize / KMemoryManager.PageSize);
FontPageList.AddRange(FontPa, FontSize / KMemoryManager.PageSize);
HidSharedMem = new KSharedMemory(HidPageList, 0, 0, MemoryPermission.Read);
FontSharedMem = new KSharedMemory(FontPageList, 0, 0, MemoryPermission.Read);
AppletState = new AppletStateMgr(this);
AppletState.SetFocus(true);
Font = new SharedFontManager(Device, (long)(FontPa - DramMemoryMap.DramBase));
VsyncEvent = new KEvent(this);
@ -120,13 +192,15 @@ namespace Ryujinx.HLE.HOS
else
{
Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!");
MetaData = GetDefaultNpdm();
}
Process MainProcess = MakeProcess(MetaData);
List<IExecutable> StaticObjects = new List<IExecutable>();
void LoadNso(string FileName)
void LoadNso(string SearchPattern)
{
foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
foreach (string File in Directory.GetFiles(ExeFsDir, SearchPattern))
{
if (Path.GetExtension(File) != string.Empty)
{
@ -137,33 +211,28 @@ namespace Ryujinx.HLE.HOS
using (FileStream Input = new FileStream(File, FileMode.Open))
{
string Name = Path.GetFileNameWithoutExtension(File);
NxStaticObject StaticObject = new NxStaticObject(Input);
Nso Program = new Nso(Input, Name);
MainProcess.LoadProgram(Program);
StaticObjects.Add(StaticObject);
}
}
}
if (!(MainProcess.MetaData?.Is64Bits ?? true))
if (!MetaData.Is64Bits)
{
throw new NotImplementedException("32-bit titles are unsupported!");
}
CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16");
CurrentTitle = MetaData.ACI0.TitleId.ToString("x16");
LoadNso("rtld");
MainProcess.SetEmptyArgs();
LoadNso("main");
LoadNso("subsdk*");
LoadNso("sdk");
ContentManager.LoadEntries();
MainProcess.Run();
ProgramLoader.LoadStaticObjects(this, MetaData, StaticObjects.ToArray());
}
public void LoadXci(string XciFile)
@ -356,9 +425,11 @@ namespace Ryujinx.HLE.HOS
else
{
Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!");
MetaData = GetDefaultNpdm();
}
Process MainProcess = MakeProcess(MetaData);
List<IExecutable> StaticObjects = new List<IExecutable>();
void LoadNso(string Filename)
{
@ -371,11 +442,9 @@ namespace Ryujinx.HLE.HOS
Logger.PrintInfo(LogClass.Loader, $"Loading {Filename}...");
string Name = Path.GetFileNameWithoutExtension(File.Name);
NxStaticObject StaticObject = new NxStaticObject(Exefs.OpenFile(File));
Nso Program = new Nso(Exefs.OpenFile(File), Name);
MainProcess.LoadProgram(Program);
StaticObjects.Add(StaticObject);
}
}
@ -401,69 +470,52 @@ namespace Ryujinx.HLE.HOS
if (ControlNca != null)
{
MainProcess.ControlData = ReadControlData();
ReadControlData();
}
else
{
CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16");
CurrentTitle = MetaData.ACI0.TitleId.ToString("x16");
}
if (!MainProcess.MetaData.Is64Bits)
if (!MetaData.Is64Bits)
{
throw new NotImplementedException("32-bit titles are unsupported!");
throw new NotImplementedException("32-bit titles are not supported!");
}
LoadNso("rtld");
MainProcess.SetEmptyArgs();
LoadNso("main");
LoadNso("subsdk");
LoadNso("sdk");
ContentManager.LoadEntries();
MainProcess.Run();
ProgramLoader.LoadStaticObjects(this, MetaData, StaticObjects.ToArray());
}
public void LoadProgram(string FilePath)
{
Npdm MetaData = GetDefaultNpdm();
bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro";
string Name = Path.GetFileNameWithoutExtension(FilePath);
string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath);
if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/")))
{
string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}";
string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
string SwitchDir = Path.GetDirectoryName(TempPath);
if (!Directory.Exists(SwitchDir))
{
Directory.CreateDirectory(SwitchDir);
}
File.Copy(FilePath, TempPath, true);
FilePath = TempPath;
}
Process MainProcess = MakeProcess();
using (FileStream Input = new FileStream(FilePath, FileMode.Open))
{
MainProcess.LoadProgram(IsNro
? (IExecutable)new Nro(Input, FilePath)
: (IExecutable)new Nso(Input, FilePath));
IExecutable StaticObject = IsNro
? (IExecutable)new NxRelocatableObject(Input)
: (IExecutable)new NxStaticObject(Input);
ProgramLoader.LoadStaticObjects(this, MetaData, new IExecutable[] { StaticObject });
}
}
MainProcess.SetEmptyArgs();
private Npdm GetDefaultNpdm()
{
Assembly Asm = Assembly.GetCallingAssembly();
ContentManager.LoadEntries();
MainProcess.Run(IsNro);
using (Stream NpdmStream = Asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
{
return new Npdm(NpdmStream);
}
}
public void LoadKeySet()
@ -507,51 +559,19 @@ namespace Ryujinx.HLE.HOS
VsyncEvent.ReadableEvent.Signal();
}
private Process MakeProcess(Npdm MetaData = null)
internal long GetThreadUid()
{
HasStarted = true;
Process Process;
lock (Processes)
{
int ProcessId = 0;
while (Processes.ContainsKey(ProcessId))
{
ProcessId++;
}
Process = new Process(Device, ProcessId, MetaData);
Processes.TryAdd(ProcessId, Process);
}
InitializeProcess(Process);
return Process;
return Interlocked.Increment(ref ThreadUid) - 1;
}
private void InitializeProcess(Process Process)
internal long GetKipId()
{
Process.AppletState.SetFocus(true);
return Interlocked.Increment(ref KipId) - 1;
}
internal void ExitProcess(int ProcessId)
internal long GetProcessId()
{
if (Processes.TryRemove(ProcessId, out Process Process))
{
Process.Dispose();
if (Processes.Count == 0)
{
Scheduler.Dispose();
TimeManager.Dispose();
Device.Unload();
}
}
return Interlocked.Increment(ref ProcessId) - 1;
}
public void EnableMultiCoreScheduling()
@ -579,10 +599,25 @@ namespace Ryujinx.HLE.HOS
{
if (Disposing)
{
foreach (Process Process in Processes.Values)
//Force all threads to exit.
lock (Processes)
{
Process.Dispose();
foreach (KProcess Process in Processes.Values)
{
Process.StopAllThreads();
}
}
//It's only safe to release resources once all threads
//have exited.
ThreadCounter.Signal();
ThreadCounter.Wait();
Scheduler.Dispose();
TimeManager.Dispose();
Device.Unload();
}
}
}