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

This commit is contained in:
gdkchan 2018-11-21 18:10:49 -03:00
commit dbcba511b2
10 changed files with 301 additions and 52 deletions

View file

@ -39,6 +39,9 @@ namespace Ryujinx.HLE.HOS
internal KMemoryRegionManager[] MemoryRegions { get; private set; }
internal KMemoryBlockAllocator MemoryBlockAllocatorSys { get; private set; }
internal KMemoryBlockAllocator MemoryBlockAllocator2 { get; private set; }
internal KSlabHeap UserSlabHeapPages { get; private set; }
internal KCriticalSection CriticalSection { get; private set; }
@ -55,6 +58,10 @@ namespace Ryujinx.HLE.HOS
private long ProcessId;
private long ThreadUid;
internal CountdownEvent ThreadCounter;
internal LinkedList<KProcess> Processes;
internal AppletStateMgr AppletState { get; private set; }
internal KSharedMemory HidSharedMem { get; private set; }
@ -90,6 +97,9 @@ namespace Ryujinx.HLE.HOS
MemoryRegions = KernelInit.GetMemoryRegions();
MemoryBlockAllocatorSys = new KMemoryBlockAllocator(0x4e20000);
MemoryBlockAllocator2 = new KMemoryBlockAllocator(0x2710000);
UserSlabHeapPages = new KSlabHeap(
UserSlabHeapBase,
UserSlabHeapItemSize,
@ -112,6 +122,10 @@ namespace Ryujinx.HLE.HOS
KernelInitialized = true;
ThreadCounter = new CountdownEvent(1);
Processes = new LinkedList<KProcess>();
KMemoryRegionManager Region = MemoryRegions[(int)MemoryRegion.Service];
ulong HidPa = Region.Address;
@ -571,6 +585,17 @@ namespace Ryujinx.HLE.HOS
{
if (Disposing)
{
//Force all threads to exit.
foreach (KProcess Process in Processes)
{
Process.StopAllThreads();
}
//It's only safe to release resources once all threads
//have exited.
ThreadCounter.Signal();
ThreadCounter.Wait();
Scheduler.Dispose();
TimeManager.Dispose();

View file

@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public bool MultiCoreScheduling { get; set; }
private HleCoreManager CoreManager;
public HleCoreManager CoreManager { get; private set; }
private bool KeepPreempting;

View file

@ -0,0 +1,21 @@
namespace Ryujinx.HLE.HOS.Kernel
{
class KMemoryBlockAllocator
{
private const int KMemoryBlockSize = 0x40;
private ulong Size;
public int Count { get; set; }
public KMemoryBlockAllocator(ulong Size)
{
this.Size = Size;
}
public bool CanAllocate(int Count)
{
return (ulong)(this.Count + Count) * KMemoryBlockSize <= Size;
}
}
}

View file

@ -3,15 +3,17 @@ using Ryujinx.Common;
using System;
using System.Collections.Generic;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Kernel
{
class KMemoryManager
{
public const int PageSize = 0x1000;
private const int KMemoryBlockSize = 0x40;
public const int KMemoryBlockSize = 0x40;
//We need 2 blocks for the case where a big block
//needs to be split in 2, plus one block that will be the new one inserted.
private const int MaxBlocksNeededForInsertion = 2;
private LinkedList<KMemoryBlock> Blocks;
@ -41,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel
private ulong HeapCapacity;
public ulong PersonalMmHeapUsage { get; private set; }
public ulong PhysicalMemoryUsage { get; private set; }
private MemoryRegion MemRegion;
@ -52,6 +54,8 @@ namespace Ryujinx.HLE.HOS.Kernel
private bool IsKernel;
private bool AslrEnabled;
private KMemoryBlockAllocator BlockAllocator;
private int ContextId;
private MersenneTwister RandomNumberGenerator;
@ -72,7 +76,8 @@ namespace Ryujinx.HLE.HOS.Kernel
bool AslrDisabled,
MemoryRegion MemRegion,
ulong Address,
ulong Size)
ulong Size,
KMemoryBlockAllocator BlockAllocator)
{
if ((uint)AddrSpaceType > (uint)AddressSpaceType.Addr39Bits)
{
@ -92,7 +97,8 @@ namespace Ryujinx.HLE.HOS.Kernel
AddrSpaceSize,
MemRegion,
Address,
Size);
Size,
BlockAllocator);
if (Result != KernelResult.Success)
{
@ -118,7 +124,8 @@ namespace Ryujinx.HLE.HOS.Kernel
ulong AddrSpaceEnd,
MemoryRegion MemRegion,
ulong Address,
ulong Size)
ulong Size,
KMemoryBlockAllocator BlockAllocator)
{
ulong EndAddr = Address + Size;
@ -211,9 +218,13 @@ namespace Ryujinx.HLE.HOS.Kernel
ulong AslrMaxOffset = MapAvailableSize - MapTotalSize;
this.AslrEnabled = AslrEnabled;
this.AddrSpaceStart = AddrSpaceStart;
this.AddrSpaceEnd = AddrSpaceEnd;
this.BlockAllocator = BlockAllocator;
if (MapAvailableSize < MapTotalSize)
{
return KernelResult.OutOfMemory;
@ -274,14 +285,12 @@ namespace Ryujinx.HLE.HOS.Kernel
CurrentHeapAddr = HeapRegionStart;
HeapCapacity = 0;
PersonalMmHeapUsage = 0;
PhysicalMemoryUsage = 0;
this.MemRegion = MemRegion;
this.AslrDisabled = AslrDisabled;
InitializeBlocks(AddrSpaceStart, AddrSpaceEnd);
return KernelResult.Success;
return InitializeBlocks(AddrSpaceStart, AddrSpaceEnd);
}
private ulong GetRandomValue(ulong Min, ulong Max)
@ -313,11 +322,20 @@ namespace Ryujinx.HLE.HOS.Kernel
}
}
private void InitializeBlocks(ulong AddrSpaceStart, ulong AddrSpaceEnd)
private KernelResult InitializeBlocks(ulong AddrSpaceStart, ulong AddrSpaceEnd)
{
//First insertion will always need only a single block,
//because there's nothing else to split.
if (!BlockAllocator.CanAllocate(1))
{
return KernelResult.OutOfResource;
}
ulong AddrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize;
InsertBlock(AddrSpaceStart, AddrSpacePagesCount, MemoryState.Unmapped);
return KernelResult.Success;
}
public KernelResult MapPages(
@ -342,6 +360,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.InvalidMemState;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = MapPages(Address, PageList, Permission);
if (Result == KernelResult.Success)
@ -403,6 +426,11 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out _))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = MmuUnmap(Address, PagesCount);
if (Result == KernelResult.Success)
@ -525,6 +553,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.OutOfMemory;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
MemoryOperation Operation = Map
? MemoryOperation.MapPa
: MemoryOperation.Allocate;
@ -568,6 +601,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.InvalidMemState;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = DoMmuOperation(
Address,
PagesCount,
@ -609,6 +647,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Success)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
KPageList PageList = new KPageList();
AddVaRangeToPageList(PageList, Src, PagesCount);
@ -684,6 +727,13 @@ namespace Ryujinx.HLE.HOS.Kernel
return Result;
}
//TODO: Missing some checks here.
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
InsertBlock(Dst, PagesCount, MemoryState.Unmapped);
InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite);
@ -705,6 +755,8 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.OutOfMemory;
}
KProcess CurrentProcess = System.Scheduler.GetCurrentProcess();
ulong CurrentHeapSize = GetHeapSize();
if (CurrentHeapSize <= Size)
@ -714,19 +766,49 @@ namespace Ryujinx.HLE.HOS.Kernel
lock (Blocks)
{
if (CurrentProcess.ResourceLimit != null && DiffSize != 0 &&
!CurrentProcess.ResourceLimit.Reserve(LimitableResource.Memory, DiffSize))
{
return KernelResult.ResLimitExceeded;
}
ulong PagesCount = DiffSize / PageSize;
KMemoryRegionManager Region = GetMemoryRegionManager();
KernelResult Result = Region.AllocatePages(PagesCount, AslrDisabled, out KPageList PageList);
void CleanUpForError()
{
if (PageList != null)
{
Region.FreePages(PageList);
}
if (CurrentProcess.ResourceLimit != null && DiffSize != 0)
{
CurrentProcess.ResourceLimit.Release(LimitableResource.Memory, DiffSize);
}
}
if (Result != KernelResult.Success)
{
CleanUpForError();
return Result;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
CleanUpForError();
return KernelResult.OutOfResource;
}
if (!IsUnmapped(CurrentHeapAddr, DiffSize))
{
CleanUpForError();
return KernelResult.InvalidMemState;
}
@ -739,6 +821,8 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Result != KernelResult.Success)
{
CleanUpForError();
return Result;
}
@ -753,6 +837,11 @@ namespace Ryujinx.HLE.HOS.Kernel
lock (Blocks)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
if (!CheckRange(
FreeAddr,
DiffSize,
@ -779,6 +868,8 @@ namespace Ryujinx.HLE.HOS.Kernel
return Result;
}
CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, BitUtils.AlignDown(DiffSize, PageSize));
InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped);
}
}
@ -794,7 +885,7 @@ namespace Ryujinx.HLE.HOS.Kernel
{
lock (Blocks)
{
return GetHeapSize() + PersonalMmHeapUsage;
return GetHeapSize() + PhysicalMemoryUsage;
}
}
@ -813,7 +904,7 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.Success;
}
public long SetMemoryAttribute(
public KernelResult SetMemoryAttribute(
ulong Address,
ulong Size,
MemoryAttribute AttributeMask,
@ -835,6 +926,11 @@ namespace Ryujinx.HLE.HOS.Kernel
out MemoryPermission Permission,
out MemoryAttribute Attribute))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
Attribute &= ~AttributeMask;
@ -842,11 +938,13 @@ namespace Ryujinx.HLE.HOS.Kernel
InsertBlock(Address, PagesCount, State, Permission, Attribute);
return 0;
return KernelResult.Success;
}
else
{
return KernelResult.InvalidMemState;
}
}
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
}
public KMemoryInfo QueryMemory(ulong Address)
@ -896,6 +994,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Success)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
KPageList PageList = new KPageList();
@ -953,6 +1056,11 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out _))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = MmuUnmap(Address, PagesCount);
if (Result == KernelResult.Success)
@ -1005,6 +1113,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Success)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
KPageList SrcPageList = new KPageList();
@ -1046,7 +1159,7 @@ namespace Ryujinx.HLE.HOS.Kernel
}
}
public long ReserveTransferMemory(ulong Address, ulong Size, MemoryPermission Permission)
public KernelResult ReserveTransferMemory(ulong Address, ulong Size, MemoryPermission Permission)
{
lock (Blocks)
{
@ -1064,20 +1177,29 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out MemoryAttribute Attribute))
{
//TODO: Missing checks.
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
Attribute |= MemoryAttribute.Borrowed;
InsertBlock(Address, PagesCount, State, Permission, Attribute);
return 0;
return KernelResult.Success;
}
else
{
return KernelResult.InvalidMemState;
}
}
}
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
}
public long ResetTransferMemory(ulong Address, ulong Size)
public KernelResult ResetTransferMemory(ulong Address, ulong Size)
{
lock (Blocks)
{
@ -1095,15 +1217,22 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out _))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
InsertBlock(Address, PagesCount, State, MemoryPermission.ReadAndWrite);
return 0;
return KernelResult.Success;
}
else
{
return KernelResult.InvalidMemState;
}
}
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
}
public KernelResult SetProcessMemoryPermission(ulong Address, ulong Size, MemoryPermission Permission)
@ -1146,6 +1275,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (NewState != OldState || Permission != OldPermission)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
MemoryOperation Operation = (Permission & MemoryPermission.Execute) != 0
@ -1217,16 +1351,33 @@ namespace Ryujinx.HLE.HOS.Kernel
KernelResult Result = Region.AllocatePages(RemainingPages, AslrDisabled, out KPageList PageList);
void CleanUpForError()
{
if (PageList != null)
{
Region.FreePages(PageList);
}
CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, RemainingSize);
}
if (Result != KernelResult.Success)
{
CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, RemainingSize);
CleanUpForError();
return Result;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
CleanUpForError();
return KernelResult.OutOfResource;
}
MapPhysicalMemory(PageList, Address, EndAddr);
PersonalMmHeapUsage += RemainingSize;
PhysicalMemoryUsage += RemainingSize;
ulong PagesCount = Size / PageSize;
@ -1294,6 +1445,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.Success;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
//Try to unmap all the heap mapped memory inside range.
KernelResult Result = KernelResult.Success;
@ -1329,7 +1485,7 @@ namespace Ryujinx.HLE.HOS.Kernel
{
GetMemoryRegionManager().FreePages(PageList);
PersonalMmHeapUsage -= HeapMappedSize;
PhysicalMemoryUsage -= HeapMappedSize;
KProcess CurrentProcess = System.Scheduler.GetCurrentProcess();
@ -1545,6 +1701,8 @@ namespace Ryujinx.HLE.HOS.Kernel
//Insert new block on the list only on areas where the state
//of the block matches the state specified on the Old* state
//arguments, otherwise leave it as is.
int OldCount = Blocks.Count;
OldAttribute |= MemoryAttribute.IpcAndDeviceMapped;
ulong EndAddr = PagesCount * PageSize + BaseAddress;
@ -1637,6 +1795,8 @@ namespace Ryujinx.HLE.HOS.Kernel
Node = NextNode;
}
BlockAllocator.Count += Blocks.Count - OldCount;
}
private void InsertBlock(
@ -1650,6 +1810,8 @@ namespace Ryujinx.HLE.HOS.Kernel
//existing blocks as needed.
KMemoryBlock Block = new KMemoryBlock(BaseAddress, PagesCount, State, Permission, Attribute);
int OldCount = Blocks.Count;
ulong EndAddr = PagesCount * PageSize + BaseAddress;
LinkedListNode<KMemoryBlock> NewNode = null;
@ -1729,6 +1891,8 @@ namespace Ryujinx.HLE.HOS.Kernel
}
MergeEqualStateNeighbours(NewNode);
BlockAllocator.Count += Blocks.Count - OldCount;
}
private void MergeEqualStateNeighbours(LinkedListNode<KMemoryBlock> Node)

View file

@ -288,6 +288,8 @@ namespace Ryujinx.HLE.HOS.Kernel
FreePages(PageNode.Address, PageNode.PagesCount);
}
PageList = null;
return KernelResult.OutOfMemory;
}

View file

@ -124,13 +124,18 @@ namespace Ryujinx.HLE.HOS.Kernel
ulong CodeSize = (ulong)CreationInfo.CodePagesCount * KMemoryManager.PageSize;
KMemoryBlockAllocator MemoryBlockAllocator = (MmuFlags & 0x40) != 0
? System.MemoryBlockAllocatorSys
: System.MemoryBlockAllocator2;
KernelResult Result = MemoryManager.InitializeForProcess(
AddrSpaceType,
AslrEnabled,
!AslrEnabled,
MemRegion,
CodeAddress,
CodeSize);
CodeSize,
MemoryBlockAllocator);
if (Result != KernelResult.Success)
{
@ -205,9 +210,17 @@ namespace Ryujinx.HLE.HOS.Kernel
PersonalMmHeapPagesCount = (ulong)CreationInfo.PersonalMmHeapPagesCount;
KMemoryBlockAllocator MemoryBlockAllocator;
if (PersonalMmHeapPagesCount != 0)
{
MemoryBlockAllocator = new KMemoryBlockAllocator(PersonalMmHeapPagesCount * KMemoryManager.PageSize);
}
else
{
MemoryBlockAllocator = (MmuFlags & 0x40) != 0
? System.MemoryBlockAllocatorSys
: System.MemoryBlockAllocator2;
}
AddressSpaceType AddrSpaceType = (AddressSpaceType)((CreationInfo.MmuFlags >> 1) & 7);
@ -224,7 +237,8 @@ namespace Ryujinx.HLE.HOS.Kernel
!AslrEnabled,
MemRegion,
CodeAddress,
CodeSize);
CodeSize,
MemoryBlockAllocator);
if (Result != KernelResult.Success)
{
@ -771,10 +785,14 @@ namespace Ryujinx.HLE.HOS.Kernel
public void IncrementThreadCount()
{
Interlocked.Increment(ref ThreadCount);
System.ThreadCounter.AddCount();
}
public void DecrementThreadCountAndTerminateIfZero()
{
System.ThreadCounter.Signal();
if (Interlocked.Decrement(ref ThreadCount) == 0)
{
Terminate();
@ -801,8 +819,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public ulong GetMemoryUsage()
{
//TODO: Personal Mm Heap.
return ImageSize + MainThreadStackSize + MemoryManager.GetTotalHeapSize();
return ImageSize + MainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize();
}
public ulong GetMemoryCapacityWithoutPersonalMmHeap()
@ -895,7 +912,7 @@ namespace Ryujinx.HLE.HOS.Kernel
if (ShallTerminate)
{
UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
//UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
HandleTable.Destroy();
@ -955,6 +972,19 @@ namespace Ryujinx.HLE.HOS.Kernel
return Result;
}
public void StopAllThreads()
{
lock (ThreadingLock)
{
foreach (KThread Thread in Threads)
{
Thread.Context.StopExecution();
System.Scheduler.CoreManager.Set(Thread.Context.Work);
}
}
}
private void CpuTraceHandler(object sender, EventArgs e)
{
System.Scheduler.GetCurrentThread().PrintGuestStackTrace();

View file

@ -7,6 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel
ThreadTerminating = 0x7601,
InvalidSize = 0xca01,
InvalidAddress = 0xcc01,
OutOfResource = 0xce01,
OutOfMemory = 0xd001,
HandleTableFull = 0xd201,
InvalidMemState = 0xd401,

View file

@ -72,15 +72,15 @@ namespace Ryujinx.HLE.HOS.Kernel
return;
}
long Result = Process.MemoryManager.SetMemoryAttribute(
KernelResult Result = Process.MemoryManager.SetMemoryAttribute(
Position,
Size,
AttributeMask,
AttributeValue);
if (Result != 0)
if (Result != KernelResult.Success)
{
Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\".");
}
else
{

View file

@ -221,6 +221,8 @@ namespace Ryujinx.HLE.HOS.Kernel
Message,
MessagePtr));
System.ThreadCounter.AddCount();
System.CriticalSection.Leave();
ThreadState.X0 = (ulong)CurrentThread.ObjSyncResult;
@ -245,6 +247,8 @@ namespace Ryujinx.HLE.HOS.Kernel
IpcMessage.Message,
IpcMessage.MessagePtr);
System.ThreadCounter.Signal();
IpcMessage.Thread.Reschedule(ThreadSchedState.Running);
}

View file

@ -151,6 +151,8 @@ namespace Ryujinx.HLE.HOS
return false;
}
System.Processes.AddLast(Process);
return true;
}
}