Fix linux waterbox

Sorry about that -- some things were removed but not others to go with them.  It had been working before.
This commit is contained in:
nattthebear 2020-07-03 12:02:11 -04:00
parent 2c2ed72dc6
commit 42ceba7d21
8 changed files with 2 additions and 1295 deletions

View File

@ -32,31 +32,5 @@ namespace BizHawk.BizInvoke
/// after this call; protections will be applied via Protect() immediately after this call.
/// </summary>
void Commit(ulong length);
/// <summary>
/// Gets the current write detection status on each page in the block. Pages marked with CanChange
/// that are also committed and set to R, will not trigger a segmentation violation on write; instead
/// automatically changing to RW and setting DidChange
/// </summary>
/// <param name="dest">Caller-owned array that the PAL will overwrite with page data</param>
/// <param name="pagedata">
/// Caller-owned array that should indicate which areas were set to RW_Stack.
/// Will not be modified by callee. Some implementations need this to get all of the correct information in dest.
/// </param>
void GetWriteStatus(WriteDetectionStatus[] dest, MemoryBlock.Protection[] pagedata);
/// <summary>
/// Sets the current write detection status on each page in the block. Pages marked with CanChange
/// that are also committed and set to R, will not trigger a segmentation violation on write; instead
/// automatically changing to RW and setting DidChange
/// </summary>
/// <param name="src">Caller-owned array that the PAL will read data from into its internal buffers</param>
void SetWriteStatus(WriteDetectionStatus[] src);
}
[Flags]
public enum WriteDetectionStatus : byte
{
/// <summary>If set, the page will be allowed to transition from R to RW</summary>
CanChange = 1,
/// <summary>If set, the page transitioned from R to RW</summary>
DidChange = 2
}
}

View File

@ -22,7 +22,6 @@ namespace BizHawk.BizInvoke
Size = WaterboxUtils.AlignUp(size);
EndExclusive = Start + Size;
_pageData = (Protection[])(object)new byte[GetPage(EndExclusive - 1) + 1];
_dirtydata = (WriteDetectionStatus[])(object)new byte[GetPage(EndExclusive - 1) + 1];
_pal = OSTailoredCode.IsUnixHost
? (IMemoryBlockPal)new MemoryBlockLinuxPal(Start, Size)
@ -39,7 +38,6 @@ namespace BizHawk.BizInvoke
/// <summary>stores last set memory protection value for each page</summary>
private Protection[] _pageData;
private WriteDetectionStatus[] _dirtydata;
/// <summary>
/// end address of the memory block (not part of the block; class invariant: equal to <see cref="Start"/> + <see cref="Size"/>)
@ -52,11 +50,6 @@ namespace BizHawk.BizInvoke
/// <summary>starting address of the memory block</summary>
public readonly ulong Start;
/// <summary>snapshot containing a clean state for all committed pages</summary>
private byte[] _snapshot;
private byte[] _hash;
/// <summary>true if this is currently swapped in</summary>
public bool Active { get; private set; }
@ -76,13 +69,6 @@ namespace BizHawk.BizInvoke
if (!Active)
throw new InvalidOperationException("MemoryBlock is not currently active");
}
private void EnsureSealed()
{
if (!_sealed)
throw new InvalidOperationException("MemoryBlock is not currently sealed");
}
private bool _sealed;
/// <summary>
/// Get a stream that can be used to read or write from part of the block. Does not check for or change <see cref="Protect"/>!
@ -108,8 +94,6 @@ namespace BizHawk.BizInvoke
throw new InvalidOperationException("Already active");
_pal.Activate();
ProtectAll();
if (_sealed)
_pal.SetWriteStatus(_dirtydata);
Active = true;
}
@ -120,22 +104,10 @@ namespace BizHawk.BizInvoke
public void Deactivate()
{
EnsureActive();
if (_sealed)
_pal.GetWriteStatus(_dirtydata, _pageData);
_pal.Deactivate();
Active = false;
}
/// <summary>
/// read the of the current full contents of the block, including unreadable areas
/// but not uncommitted areas
/// </summary>
public byte[] FullHash()
{
EnsureSealed();
return _hash;
}
/// <summary>set r/w/x protection on a portion of memory. rounded to encompassing pages</summary>
/// <exception cref="InvalidOperationException">failed to protect memory</exception>
public void Protect(ulong start, ulong length, Protection prot)
@ -143,9 +115,7 @@ namespace BizHawk.BizInvoke
EnsureActive();
if (length == 0)
return;
if (_sealed)
_pal.GetWriteStatus(_dirtydata, _pageData);
// Note: asking for prot.none on memory that was not previously committed, commits it
var computedStart = WaterboxUtils.AlignDown(start);
@ -166,17 +136,10 @@ namespace BizHawk.BizInvoke
for (int i = pstart; i <= pend; i++)
{
_pageData[i] = prot;
// inform the low level code what addresses might fault on it
if (prot == Protection.RW || prot == Protection.RW_Stack)
_dirtydata[i] |= WriteDetectionStatus.CanChange;
else
_dirtydata[i] &= ~WriteDetectionStatus.CanChange;
}
// TODO: restore the previous behavior where we would only reprotect a partial range
ProtectAll();
if (_sealed)
_pal.SetWriteStatus(_dirtydata);
}
/// <summary>restore all recorded protections</summary>
@ -188,211 +151,17 @@ namespace BizHawk.BizInvoke
int pageLimit = (int)(CommittedSize >> WaterboxUtils.PageShift);
for (int i = 0; i < pageLimit; i++)
{
if (i == pageLimit - 1 || _pageData[i] != _pageData[i + 1] || _dirtydata[i] != _dirtydata[i + 1])
if (i == pageLimit - 1 || _pageData[i] != _pageData[i + 1])
{
ulong zstart = GetStartAddr(ps);
ulong zend = GetStartAddr(i + 1);
var prot = _pageData[i];
// adjust frontend notion of prot to the PAL layer's expectation
if (prot == Protection.RW_Stack)
{
if (!_sealed)
{
// don't activate this protection yet
prot = Protection.RW;
}
else
{
var didChange = (_dirtydata[i] & WriteDetectionStatus.DidChange) != 0;
if (didChange)
// don't needlessly retrigger
prot = Protection.RW;
}
}
else if (prot == Protection.RW_Invisible)
{
// this never matters to the backend
prot = Protection.RW;
}
else if (_sealed && prot == Protection.RW)
{
var didChange = (_dirtydata[i] & WriteDetectionStatus.DidChange) != 0;
if (!didChange)
// set to trigger if we have not before
prot = Protection.R;
}
_pal.Protect(zstart, zend - zstart, prot);
ps = i + 1;
}
}
}
public void Seal()
{
EnsureActive();
if (_sealed)
throw new InvalidOperationException("Already sealed");
_snapshot = new byte[CommittedSize];
if (CommittedSize > 0)
{
// temporarily switch the committed parts to `R` so we can read them
_pal.Protect(Start, CommittedSize, Protection.R);
Marshal.Copy(Z.US(Start), _snapshot, 0, (int)CommittedSize);
}
_hash = WaterboxUtils.Hash(_snapshot);
_sealed = true;
ProtectAll();
_pal.SetWriteStatus(_dirtydata);
}
const ulong MAGIC = 18123868458638683;
public void SaveState(BinaryWriter w)
{
EnsureActive();
EnsureSealed();
_pal.GetWriteStatus(_dirtydata, _pageData);
w.Write(MAGIC);
w.Write(Start);
w.Write(Size);
w.Write(_hash);
w.Write(CommittedSize);
w.Write((byte[])(object)_pageData);
w.Write((byte[])(object)_dirtydata);
var buff = new byte[4096];
int p;
ulong addr;
ulong endAddr = Start + CommittedSize;
for (p = 0, addr = Start; addr < endAddr; p++, addr += 4096)
{
if (_pageData[p] != Protection.RW_Invisible && (_dirtydata[p] & WriteDetectionStatus.DidChange) != 0)
{
// TODO: It's slow to toggle individual pages like this.
// Maybe that's OK because None is not used much?
if (_pageData[p] == Protection.None)
_pal.Protect(addr, 4096, Protection.R);
Marshal.Copy(Z.US(addr), buff, 0, 4096);
w.Write(buff);
if (_pageData[p] == Protection.None)
_pal.Protect(addr, 4096, Protection.None);
}
}
// Console.WriteLine($"{Start:x16}");
// var voom = _pageData.Take((int)(CommittedSize >> 12)).Select(p =>
// {
// switch (p)
// {
// case Protection.None: return ' ';
// case Protection.R: return 'R';
// case Protection.RW: return 'W';
// case Protection.RX: return 'X';
// case Protection.RW_Invisible: return '!';
// case Protection.RW_Stack: return '+';
// default: return '?';
// }
// }).Select((c, i) => new { c, i }).GroupBy(a => a.i / 60);
// var zoom = _dirtydata.Take((int)(CommittedSize >> 12)).Select(p =>
// {
// switch (p)
// {
// case WriteDetectionStatus.CanChange: return '.';
// case WriteDetectionStatus.CanChange | WriteDetectionStatus.DidChange: return '*';
// case 0: return ' ';
// case WriteDetectionStatus.DidChange: return '!';
// default: return '?';
// }
// }).Select((c, i) => new { c, i }).GroupBy(a => a.i / 60);
// foreach (var l in voom.Zip(zoom, (a, b) => new { a, b }))
// {
// Console.WriteLine("____" + new string(l.a.Select(a => a.c).ToArray()));
// Console.WriteLine("____" + new string(l.b.Select(a => a.c).ToArray()));
// }
}
public void LoadState(BinaryReader r)
{
EnsureActive();
EnsureSealed();
_pal.GetWriteStatus(_dirtydata, _pageData);
if (r.ReadUInt64() != MAGIC || r.ReadUInt64() != Start || r.ReadUInt64() != Size)
throw new InvalidOperationException("Savestate internal mismatch");
if (!r.ReadBytes(_hash.Length).SequenceEqual(_hash))
{
// romhackurz need this not to throw on them.
// anywhere where non-sync settings enter non-invisible ram, we need this not to throw
Console.Error.WriteLine("WARNING: MEMORY BLOCK CONSISTENCY CHECK FAILED");
}
var newCommittedSize = r.ReadUInt64();
if (newCommittedSize > CommittedSize)
{
_pal.Commit(newCommittedSize);
}
else if (newCommittedSize < CommittedSize)
{
// PAL layer won't let us shrink commits, but that's kind of OK
var start = Start + newCommittedSize;
var size = CommittedSize - newCommittedSize;
_pal.Protect(start, size, Protection.RW);
WaterboxUtils.ZeroMemory(Z.US(start), (long)size);
_pal.Protect(start, size, Protection.None);
}
CommittedSize = newCommittedSize;
var newPageData = (Protection[])(object)r.ReadBytes(_pageData.Length);
var newDirtyData = (WriteDetectionStatus[])(object)r.ReadBytes(_dirtydata.Length);
var buff = new byte[4096];
int p;
ulong addr;
ulong endAddr = Start + CommittedSize;
for (p = 0, addr = Start; addr < endAddr; p++, addr += 4096)
{
var dirty = (_dirtydata[p] & WriteDetectionStatus.DidChange) != 0;
var newDirty = (newDirtyData[p] & WriteDetectionStatus.DidChange) != 0;
var inState = newPageData[p] != Protection.RW_Invisible && newDirty;
if (dirty || inState)
{
// must write out changed data
// TODO: It's slow to toggle individual pages like this
if (_pageData[p] != Protection.RW)
_pal.Protect(addr, 4096, Protection.RW);
// NB: There are some weird behaviors possible if a block transitions to or from RW_Invisible,
// but nothing really "broken" (as far as I know); just reflecting the fact that you cannot track it.
if (inState)
{
// changed data comes from the savestate
r.Read(buff, 0, 4096);
Marshal.Copy(buff, 0, Z.US(addr), 4096);
}
else
{
// data comes from the snapshot
var offs = (int)(addr - Start);
if (offs < _snapshot.Length)
{
Marshal.Copy(_snapshot, offs, Z.US(addr), 4096);
}
else
{
// or was not in the snapshot at all, so had never been changed by seal
WaterboxUtils.ZeroMemory(Z.US(addr), 4096);
}
}
}
}
_pageData = newPageData;
_dirtydata = newDirtyData;
ProtectAll();
_pal.SetWriteStatus(_dirtydata);
}
public void Dispose()
{
if (_pal != null)
@ -415,15 +184,6 @@ namespace BizHawk.BizInvoke
R,
RW,
RX,
/// <summary>
/// This area should not be tracked for changes, and should not be saved.
/// </summary>
RW_Invisible,
/// <summary>
/// This area may be used as a stack and should use a change detection that will work on stacks.
/// (In windows, this is an inferior detection that triggers on reads. In Linux, this flag has no effect.)
/// </summary>
RW_Stack,
}
}
}

View File

@ -74,10 +74,6 @@ namespace BizHawk.BizInvoke
throw new InvalidOperationException($"{nameof(mmap)}() failed with error {Marshal.GetLastWin32Error()}");
}
}
if ((IntPtr)LinGuard.AddTripGuard(Z.UU(_start), Z.UU(_size)) == IntPtr.Zero)
{
throw new InvalidOperationException($"{nameof(LinGuard.AddTripGuard)}() returned NULL");
}
_active = true;
}
@ -89,8 +85,6 @@ namespace BizHawk.BizInvoke
if (errorCode != 0)
throw new InvalidOperationException($"{nameof(munmap)}() failed with error {Marshal.GetLastWin32Error()}");
}
if (!LinGuard.RemoveTripGuard(Z.UU(_start), Z.UU(_size)))
throw new InvalidOperationException($"{nameof(LinGuard.RemoveTripGuard)}() returned FALSE");
_active = false;
}
@ -123,12 +117,6 @@ namespace BizHawk.BizInvoke
return MemoryProtection.Read | MemoryProtection.Write;
case Protection.RX:
return MemoryProtection.Read | MemoryProtection.Execute;
case Protection.RW_Invisible:
return MemoryProtection.Read | MemoryProtection.Write;
case Protection.RW_Stack:
// because of sigaltstack, LinGuard has no issues with readonly stacks and the special distinction that
// the windows port draws between stack vs non stack memory is ignored here
return MemoryProtection.Read;
default:
throw new ArgumentOutOfRangeException(nameof(prot));
}
@ -144,56 +132,5 @@ namespace BizHawk.BizInvoke
if (errorCode != 0)
throw new InvalidOperationException($"{nameof(mprotect)}() failed with error {Marshal.GetLastWin32Error()}!");
}
public void GetWriteStatus(WriteDetectionStatus[] dest, Protection[] pagedata)
{
var p = (IntPtr)LinGuard.ExamineTripGuard(Z.UU(_start), Z.UU(_size));
if (p == IntPtr.Zero)
throw new InvalidOperationException($"{nameof(LinGuard.ExamineTripGuard)}() returned NULL!");
Marshal.Copy(p, (byte[])(object)dest, 0, dest.Length);
}
public void SetWriteStatus(WriteDetectionStatus[] src)
{
var p = (IntPtr)LinGuard.ExamineTripGuard(Z.UU(_start), Z.UU(_size));
if (p == IntPtr.Zero)
throw new InvalidOperationException($"{nameof(LinGuard.ExamineTripGuard)}() returned NULL!");
Marshal.Copy((byte[])(object)src, 0, p, src.Length);
}
private static unsafe class LinGuard
{
/// <summary>
/// Add write detection to an area of memory. Any page in the specified range that has CanChange
/// set and triggers an access violation on write
/// will be noted, set to read+write permissions, and execution will be continued.
/// CALLER'S RESPONSIBILITY: All addresses are page aligned.
/// CALLER'S RESPONSIBILITY: No other thread enters any LinGuard function, or trips any tracked page during this call.
/// CALLER'S RESPONSIBILITY: Pages to be tracked are mprotected to R. Pages with write permission
/// cause no issues, but they will not trip.
/// </summary>
/// <returns>The same information as ExamineTripGuard, or null on failure</returns>
[DllImport("linguard.so")]
public static extern WriteDetectionStatus* AddTripGuard(UIntPtr start, UIntPtr length);
/// <summary>
/// Remove write detection from the specified addresses.
/// CALLER'S RESPONSIBILITY: All addresses are page aligned.
/// CALLER'S RESPONSIBILITY: No other thread enters any LinGuard function, or trips any tracked guard page during this call.
/// </summary>
/// <returns>false on failure (usually, the address range did not match a known one)</returns>
[DllImport("linguard.so")]
public static extern bool RemoveTripGuard(UIntPtr start, UIntPtr length);
/// <summary>
/// Examines a previously installed guard page detection.
/// CALLER'S RESPONSIBILITY: All addresses are page aligned.
/// CALLER'S RESPONSIBILITY: No other thread enters any LinGuard function, or trips any tracked guard page during this call.
/// </summary>
/// <returns>
/// A pointer to an array of bytes, one byte for each memory page in the range. Caller should set CanChange on pages to
/// observe, and read back DidChange to see if things changed.
/// </returns>
[DllImport("linguard.so")]
public static extern WriteDetectionStatus* ExamineTripGuard(UIntPtr start, UIntPtr length);
}
}
}

View File

@ -50,10 +50,6 @@ namespace BizHawk.BizInvoke
{
throw new InvalidOperationException($"{nameof(Kernel32.MapViewOfFileEx)}() returned NULL");
}
if ((IntPtr)WinGuard.AddTripGuard(Z.UU(_start), Z.UU(_size)) == IntPtr.Zero)
{
throw new InvalidOperationException($"{nameof(WinGuard.AddTripGuard)}() returned NULL");
}
_active = true;
}
@ -61,8 +57,6 @@ namespace BizHawk.BizInvoke
{
if (!Kernel32.UnmapViewOfFile(Z.US(_start)))
throw new InvalidOperationException($"{nameof(Kernel32.UnmapViewOfFile)}() returned NULL");
if (!WinGuard.RemoveTripGuard(Z.UU(_start), Z.UU(_size)))
throw new InvalidOperationException($"{nameof(WinGuard.RemoveTripGuard)}() returned FALSE");
_active = false;
}
@ -87,11 +81,6 @@ namespace BizHawk.BizInvoke
case Protection.R: p = Kernel32.MemoryProtection.READONLY; break;
case Protection.RW: p = Kernel32.MemoryProtection.READWRITE; break;
case Protection.RX: p = Kernel32.MemoryProtection.EXECUTE_READ; break;
// VEH can't work when the stack is not writable, because VEH is delivered on that selfsame stack. The kernel
// simply declines to return to user mode on a first chance exception if the stack is not writable.
// So we use guard pages instead, which are much worse because reads set them off as well, but it doesn't matter
// in this case.
case Protection.RW_Stack: p = Kernel32.MemoryProtection.READWRITE | Kernel32.MemoryProtection.GUARD_Modifierflag; break;
default: throw new ArgumentOutOfRangeException(nameof(prot));
}
return p;
@ -116,51 +105,6 @@ namespace BizHawk.BizInvoke
}
}
public void GetWriteStatus(WriteDetectionStatus[] dest, Protection[] pagedata)
{
var p = (IntPtr)WinGuard.ExamineTripGuard(Z.UU(_start), Z.UU(_size));
if (p == IntPtr.Zero)
throw new InvalidOperationException($"{nameof(WinGuard.ExamineTripGuard)}() returned NULL!");
Marshal.Copy(p, (byte[])(object)dest, 0, dest.Length);
// guard pages do not trigger VEH when they are actually being used as a stack and there are other
// free pages below them, so virtualquery to get that information out
Kernel32.MEMORY_BASIC_INFORMATION mbi;
for (int i = 0; i < pagedata.Length;)
{
if (pagedata[i] == Protection.RW_Stack)
{
var q = ((ulong)i << WaterboxUtils.PageShift) + _start;
Kernel32.VirtualQuery(Z.UU(q), &mbi, Z.SU(sizeof(Kernel32.MEMORY_BASIC_INFORMATION)));
var pstart = (int)(((ulong)mbi.BaseAddress - _start) >> WaterboxUtils.PageShift);
var pend = pstart + (int)((ulong)mbi.RegionSize >> WaterboxUtils.PageShift);
if (pstart != i)
throw new Exception("Huh?");
if ((mbi.Protect & Kernel32.MemoryProtection.GUARD_Modifierflag) == 0)
{
// tripped!
for (int j = pstart; j < pend; j++)
dest[j] |= WriteDetectionStatus.DidChange;
}
i = pend;
}
else
{
i++;
}
}
}
public void SetWriteStatus(WriteDetectionStatus[] src)
{
var p = (IntPtr)WinGuard.ExamineTripGuard(Z.UU(_start), Z.UU(_size));
if (p == IntPtr.Zero)
throw new InvalidOperationException($"{nameof(WinGuard.ExamineTripGuard)}() returned NULL!");
Marshal.Copy((byte[])(object)src, 0, p, src.Length);
}
~MemoryBlockWindowsPal()
{
Dispose();
@ -279,40 +223,5 @@ namespace BizHawk.BizInvoke
[DllImport("kernel32.dll")]
public static extern UIntPtr VirtualQuery(UIntPtr lpAddress, MEMORY_BASIC_INFORMATION* lpBuffer, UIntPtr dwLength);
}
private static unsafe class WinGuard
{
/// <summary>
/// Add write detection to an area of memory. Any page in the specified range that has CanChange
/// set and triggers an access violation on write
/// will be noted, set to read+write permissions, and execution will be continued.
/// CALLER'S RESPONSIBILITY: All addresses are page aligned.
/// CALLER'S RESPONSIBILITY: No other thread enters any WinGuard function, or trips any tracked page during this call.
/// CALLER'S RESPONSIBILITY: Pages to be tracked are VirtualProtected to R (no G) beforehand. Pages with write permission
/// cause no issues, but they will not trip. WinGuard will not intercept Guard flag exceptions in any way.
/// </summary>
/// <returns>The same information as ExamineTripGuard, or null on failure</returns>
[DllImport("winguard.dll")]
public static extern WriteDetectionStatus* AddTripGuard(UIntPtr start, UIntPtr length);
/// <summary>
/// Remove write detection from the specified addresses.
/// CALLER'S RESPONSIBILITY: All addresses are page aligned.
/// CALLER'S RESPONSIBILITY: No other thread enters any WinGuard function, or trips any tracked guard page during this call.
/// </summary>
/// <returns>false on failure (usually, the address range did not match a known one)</returns>
[DllImport("winguard.dll")]
public static extern bool RemoveTripGuard(UIntPtr start, UIntPtr length);
/// <summary>
/// Examines a previously installed guard page detection.
/// CALLER'S RESPONSIBILITY: All addresses are page aligned.
/// CALLER'S RESPONSIBILITY: No other thread enters any WinGuard function, or trips any tracked guard page during this call.
/// </summary>
/// <returns>
/// A pointer to an array of bytes, one byte for each memory page in the range. Caller should set CanChange on pages to
/// observe, and read back DidChange to see if things changed.
/// </returns>
[DllImport("winguard.dll")]
public static extern WriteDetectionStatus* ExamineTripGuard(UIntPtr start, UIntPtr length);
}
}
}

View File

@ -5,7 +5,6 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ELFSharp" Version="2.10.0" />
<PackageReference Include="System.Memory" Version="4.5.4" />
<Reference Include="FlatBuffers.Core" HintPath="$(ProjectDir)../../References/FlatBuffers.Core.dll" Private="true" />
<Reference Include="Virtu" HintPath="$(ProjectDir)../../References/Virtu.dll" Private="true" />

View File

@ -1,372 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using BizHawk.BizInvoke;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using ELFSharp.ELF;
using ELFSharp.ELF.Sections;
using ELFSharp.ELF.Segments;
namespace BizHawk.Emulation.Cores.Waterbox
{
public class ElfLoader : IImportResolver, IDisposable, IBinaryStateable
{
private readonly ELF<ulong> _elf;
private readonly byte[] _elfHash;
private readonly List<SymbolEntry<ulong>> _allSymbols;
private readonly Dictionary<string, SymbolEntry<ulong>> _visibleSymbols;
private readonly Dictionary<string, Section<ulong>> _sectionsByName;
private readonly List<SymbolEntry<ulong>> _importSymbols;
private readonly Section<ulong> _imports;
private readonly Section<ulong> _sealed;
private readonly Section<ulong> _invisible;
private readonly bool _skipCoreConsistencyCheck;
private readonly bool _skipMemoryConsistencyCheck;
private bool _everythingSealed;
public MemoryBlock Memory { get; private set; }
public string ModuleName { get; }
/// <summary>
/// Where writable data begins
/// </summary>
private ulong _writeStart;
/// <summary>
/// Where writiable data begins after seal
/// </summary>
private ulong _postSealWriteStart;
/// <summary>
/// Where executable data ends
/// </summary>
private ulong _execEnd;
public ElfLoader(string moduleName, byte[] fileData, ulong assumedStart, bool skipCoreConsistencyCheck, bool skipMemoryConsistencyCheck)
{
ModuleName = moduleName;
_skipCoreConsistencyCheck = skipCoreConsistencyCheck;
_skipMemoryConsistencyCheck = skipMemoryConsistencyCheck;
_elfHash = WaterboxUtils.Hash(fileData);
_elf = ELFReader.Load<ulong>(new MemoryStream(fileData, false), true);
var loadsegs = _elf.Segments.Where(s => s.Type == SegmentType.Load);
var start = loadsegs.Min(s => s.Address);
start = WaterboxUtils.AlignDown(start);
var end = loadsegs.Max(s => s.Address + s.Size);
end = WaterboxUtils.AlignUp(end);
var size = end - start;
if (start != assumedStart)
throw new InvalidOperationException($"{nameof(assumedStart)} did not match actual origin in elf file");
if (_elf.Sections.Any(s => s.Name.StartsWith(".rel")))
throw new InvalidOperationException("Elf has relocations!");
_allSymbols = ((ISymbolTable)_elf.GetSection(".symtab"))
.Entries
.Cast<SymbolEntry<ulong>>()
.ToList();
_sectionsByName = _elf.Sections
.ToDictionary(s => s.Name);
_sectionsByName.TryGetValue(".wbxsyscall", out _imports);
if (_imports == null)
{
// Likely cause: This is a valid elf file, but it was not compiled by our toolchain at all
throw new InvalidOperationException("Missing .wbxsyscall section!");
}
_sectionsByName.TryGetValue(".sealed", out _sealed);
_sectionsByName.TryGetValue(".invis", out _invisible);
_visibleSymbols = _allSymbols
.Where(s => s.Binding == SymbolBinding.Global && s.Visibility == SymbolVisibility.Default)
.ToDictionary(s => s.Name);
_importSymbols = _allSymbols
// TODO: No matter what attributes I provide, I seem to end up with Local and/or Hidden symbols in
// .wbxsyscall a lot of the time on heavily optimized release builds.
// Fortunately, there's nothing else in .wbxsyscall so we can just not filter at all.
.Where(s => s.PointedSection == _imports)
.ToList();
Memory = MemoryBlock.Create(start, size);
Memory.Activate();
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
foreach (var seg in loadsegs)
{
var data = seg.GetFileContents();
Marshal.Copy(data, 0, Z.US(seg.Address), Math.Min((int)seg.Size, (int)seg.FileSize));
}
{
// Compute RW boundaries
var allocated = _elf.Sections
.Where(s => (s.Flags & SectionFlags.Allocatable) != 0);
var writable = allocated
.Where(s => (s.Flags & SectionFlags.Writable) != 0);
var postSealWritable = writable
.Where(s => !IsSpecialReadonlySection(s));
var saveable = postSealWritable
.Where(s => s != _invisible);
var executable = allocated
.Where(s => (s.Flags & SectionFlags.Executable) != 0);
_writeStart = WaterboxUtils.AlignDown(writable.Min(s => s.LoadAddress));
_postSealWriteStart = WaterboxUtils.AlignDown(postSealWritable.Min(s => s.LoadAddress));
_execEnd = WaterboxUtils.AlignUp(executable.Max(s => s.LoadAddress + s.Size));
// validate; this may require linkscript cooperation
// due to the segment limitations, the only thing we'd expect to catch is a custom eventually readonly section
// in the wrong place (because the linkscript doesn't know "eventually readonly")
if (_execEnd > _writeStart)
throw new InvalidOperationException($"ElfLoader: Executable data to {_execEnd:X16} overlaps writable data from {_writeStart}");
}
PrintSections();
PrintGdbData();
PrintTopSavableSymbols();
Protect();
}
private void PrintGdbData()
{
Console.WriteLine("GDB Symbol Load:");
Console.WriteLine($" add-sym {ModuleName} -s .text {_elf.Sections.Single(s => s.Name == ".text").LoadAddress}");
}
private void PrintSections()
{
Console.WriteLine($"Mounted `{ModuleName}` @{Memory.Start:x16}");
foreach (var s in _elf.Sections.OrderBy(s => s.LoadAddress))
{
Console.WriteLine(" @{0:x16} {1}{2}{3} `{4}` {5} bytes",
s.LoadAddress,
(s.Flags & SectionFlags.Allocatable) != 0 ? "R" : " ",
(s.Flags & SectionFlags.Writable) != 0 ? "W" : " ",
(s.Flags & SectionFlags.Executable) != 0 ? "X" : " ",
s.Name,
s.Size);
}
}
private void PrintTopSavableSymbols()
{
// TODO: With change detection, this isn't so great anymore.
// It could look at actual intersections with what we saved, but that would mean going into MemoryBlock
var tops = _allSymbols
.OrderByDescending(s => s.Size)
.Where(s => s.Size >= 20 * 1024)
.Take(30)
.Select(s => $" {s.Name} {s.Size / 1024}kiB")
.ToList();
if (tops.Count > 0)
{
Console.WriteLine("Top potential savestate symbols:");
foreach (var text in tops)
{
Console.WriteLine(text);
}
}
}
/// <summary>
/// Returns true if section is readonly after init
/// </summary>
private bool IsSpecialReadonlySection(Section<ulong> sec)
{
// TODO: I don't think there are any more relro sections, right?
return sec.Name.Contains(".rel.ro")
|| sec.Name.StartsWith(".got")
|| sec.Name == ".init_array"
|| sec.Name == ".fini_array"
|| sec.Name == ".tbss"
|| sec == _imports
|| sec == _sealed;
}
/// <summary>
/// Set memory protections, pre or post seal
/// </summary>
private void Protect()
{
var writeStart = _everythingSealed ? _postSealWriteStart : _writeStart;
Memory.Protect(Memory.Start, _execEnd - Memory.Start, MemoryBlock.Protection.RX);
Memory.Protect(_execEnd, writeStart - _execEnd, MemoryBlock.Protection.R);
Memory.Protect(writeStart, Memory.EndExclusive - writeStart, MemoryBlock.Protection.RW);
if (_invisible != null)
{
if (!WaterboxUtils.Aligned(_invisible.LoadAddress))
throw new InvalidOperationException("Invisible section start not aligned! Check linker script");
var end = WaterboxUtils.AlignUp(_invisible.LoadAddress + _invisible.Size);
if (_sectionsByName.Values.Any(s => s != _invisible && s.LoadAddress < end && s.LoadAddress >= _invisible.LoadAddress))
{
throw new InvalidOperationException("Invisible section end not aligned, or not padded! Check linker script");
}
Memory.Protect(
_invisible.LoadAddress,
_invisible.Size,
MemoryBlock.Protection.RW_Invisible);
}
}
// connect all of the .wbxsyscall stuff
public void ConnectSyscalls(IImportResolver syscalls)
{
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
var tmp = new IntPtr[1];
var ptrSize = (ulong)IntPtr.Size;
foreach (var s in _importSymbols)
{
if (s.Size == ptrSize)
{
var p = syscalls.GetProcAddrOrThrow(s.Name);
tmp[0] = p;
Marshal.Copy(tmp, 0, Z.US(s.Value), 1);
}
else
{
if (s.Size % ptrSize != 0)
{
// They're supposed to be arrays of pointers, so uhhh yeah?
throw new InvalidOperationException($"Symbol {s.Name} has unexpected size");
}
var count = (int)(s.Size / ptrSize);
for (var i = 0; i < count; i++)
{
var p = syscalls.GetProcAddrOrThrow($"{s.Name}[{i}]");
tmp[0] = p;
Marshal.Copy(tmp, 0, Z.US(s.Value + ((ulong)i * ptrSize)), 1);
}
}
}
Protect();
}
public void RunNativeInit()
{
CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer<Action>(Z.US(_elf.EntryPoint))();
}
public void SealImportsAndTakeXorSnapshot()
{
if (_everythingSealed)
throw new InvalidOperationException($"{nameof(ElfLoader)} already sealed!");
// save import values, then zero them all (for hash purposes), then take our snapshot, then load them again,
// then set the .wbxsyscall area to read only
byte[] impData = null;
impData = new byte[_imports.Size];
Marshal.Copy(Z.US(_imports.LoadAddress), impData, 0, (int)_imports.Size);
WaterboxUtils.ZeroMemory(Z.US(_imports.LoadAddress), (long)_imports.Size);
byte[] invData = null;
if (_invisible != null)
{
invData = new byte[_invisible.Size];
Marshal.Copy(Z.US(_invisible.LoadAddress), invData, 0, (int)_invisible.Size);
WaterboxUtils.ZeroMemory(Z.US(_invisible.LoadAddress), (long)_invisible.Size);
}
Memory.Seal();
Marshal.Copy(impData, 0, Z.US(_imports.LoadAddress), (int)_imports.Size);
if (_invisible != null)
{
Marshal.Copy(invData, 0, Z.US(_invisible.LoadAddress), (int)_invisible.Size);
}
_everythingSealed = true;
Protect();
}
private bool _disposed = false;
public void Dispose()
{
if (!_disposed)
{
Memory.Dispose();
Memory = null;
_disposed = true;
}
}
public IntPtr GetProcAddrOrZero(string entryPoint)
{
if (_visibleSymbols.TryGetValue(entryPoint, out var sym))
{
return Z.US(sym.Value);
}
else
{
return IntPtr.Zero;
}
}
public IntPtr GetProcAddrOrThrow(string entryPoint)
{
if (_visibleSymbols.TryGetValue(entryPoint, out var sym))
{
return Z.US(sym.Value);
}
else
{
throw new InvalidOperationException($"Couldn't find {nameof(entryPoint)} {entryPoint} in {ModuleName}");
}
}
const ulong MAGIC = 0x6018ab7df99310ca;
public void SaveStateBinary(BinaryWriter bw)
{
if (!_everythingSealed)
throw new InvalidOperationException(".wbxsyscall section must be closed before saving state");
bw.Write(MAGIC);
bw.Write(_elfHash);
Memory.SaveState(bw);
}
public void LoadStateBinary(BinaryReader br)
{
if (!_everythingSealed)
// operations happening in the wrong order. probable cause: internal logic error. make sure frontend calls Seal
throw new InvalidOperationException(".wbxsyscall section must be closed before loading state");
if (br.ReadUInt64() != MAGIC)
// file id is missing. probable cause: garbage savestate
throw new InvalidOperationException("Savestate corrupted!");
var elfHash = br.ReadBytes(_elfHash.Length);
if (_skipCoreConsistencyCheck)
{
throw new InvalidOperationException("We decided that the core consistency check should always run");
}
else
{
if (!elfHash.SequenceEqual(_elfHash))
// the .dll file that is loaded now has a different hash than the .dll that created the savestate
throw new InvalidOperationException("Core consistency check failed. Is this a savestate from a different version?");
}
Memory.LoadState(br);
}
}
}

View File

@ -1,132 +0,0 @@
using BizHawk.BizInvoke;
using BizHawk.Emulation.Common;
using System;
using System.IO;
using System.Linq;
namespace BizHawk.Emulation.Cores.Waterbox
{
/// <summary>
/// a simple grow-only fixed max size heap
/// </summary>
internal sealed class Heap : IBinaryStateable, IDisposable
{
public MemoryBlock Memory { get; private set; }
/// <summary>
/// name, used in identifying errors
/// </summary>
public string Name { get; }
/// <summary>
/// total number of bytes used
/// </summary>
public ulong Used { get; private set; }
/// <summary>
/// true if the heap has been sealed, preventing further changes
/// </summary>
public bool Sealed { get; private set; }
private byte[] _hash;
public Heap(ulong start, ulong size, string name)
{
Memory = MemoryBlock.Create(start, size);
Used = 0;
Name = name;
Console.WriteLine("Created heap `{1}` at {0:x16}:{2:x16}", start, name, start + size);
}
private ulong AlignForAllocation(int align)
{
if (align > 1)
{
ulong newused = ((Used - 1) | (ulong)((uint)align - 1)) + 1;
if (newused > Memory.Size)
{
throw new InvalidOperationException($"Failed to meet alignment {align} on heap {Name}");
}
return newused;
}
return Used;
}
public ulong Allocate(ulong size, int align)
{
if (Sealed)
throw new InvalidOperationException($"Attempt made to allocate from sealed heap {Name}");
ulong allocstart = AlignForAllocation(align);
ulong newused = allocstart + size;
if (newused > Memory.Size)
{
throw new InvalidOperationException($"Failed to allocate {size} bytes from heap {Name}");
}
ulong ret = Memory.Start + allocstart;
Memory.Protect(Memory.Start + Used, newused - Used, MemoryBlock.Protection.RW);
Used = newused;
Console.WriteLine($"Allocated {size} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)");
return ret;
}
public void Seal()
{
if (!Sealed)
{
Memory.Protect(Memory.Start, Used, MemoryBlock.Protection.R);
_hash = WaterboxUtils.Hash(Memory.GetStream(Memory.Start, WaterboxUtils.AlignUp(Used), false));
Sealed = true;
}
else
{
throw new InvalidOperationException($"Attempt to reseal heap {Name}");
}
}
public void SaveStateBinary(BinaryWriter bw)
{
bw.Write(Name);
bw.Write(Used);
if (!Sealed)
{
Memory.SaveState(bw);
}
else
{
bw.Write(_hash);
}
}
public void LoadStateBinary(BinaryReader br)
{
var name = br.ReadString();
if (name != Name)
// probable cause: internal error
throw new InvalidOperationException($"Name did not match for heap {Name}");
var used = br.ReadUInt64();
if (used > Memory.Size)
throw new InvalidOperationException($"Heap {Name} used {used} larger than available {Memory.Size}");
if (!Sealed)
{
Memory.LoadState(br);
Used = used;
}
else
{
var hash = br.ReadBytes(_hash.Length);
if (!hash.SequenceEqual(_hash))
{
throw new InvalidOperationException($"Hash did not match for heap {Name}. Is this the same rom with the same SyncSettings?");
}
}
}
public void Dispose()
{
if (Memory != null)
{
Memory.Dispose();
Memory = null;
}
}
}
}

View File

@ -1,368 +0,0 @@
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
using BizHawk.BizInvoke;
namespace BizHawk.Emulation.Cores.Waterbox
{
/// <summary>
/// a heap that supports basic alloc, free, and realloc calls
/// </summary>
internal sealed class MapHeap : IBinaryStateable, IDisposable
{
public MemoryBlock Memory { get; private set; }
/// <summary>
/// name, used in identifying errors
/// </summary>
public string Name { get; }
/// <summary>
/// total number of bytes allocated
/// </summary>
public ulong Used { get; private set; }
/// <summary>
/// get a page index within the block
/// </summary>
private int GetPage(ulong addr)
{
return (int)((addr - Memory.Start) >> WaterboxUtils.PageShift);
}
/// <summary>
/// get a start address for a page index within the block
/// </summary>
private ulong GetStartAddr(int page)
{
return ((ulong)page << WaterboxUtils.PageShift) + Memory.Start;
}
/// <summary>
/// Sentinel value used to indicate unmapped pages.
/// Needed because mapped pages are allowed to be protected to None
/// </summary>
private const MemoryBlock.Protection FREE = (MemoryBlock.Protection)255;
/// <summary>
/// Bitmap of protections. Similar to MemoryBlock._pageData (and unfortunately mirrors the same information),
/// but also handles FREE
/// </summary>
private readonly MemoryBlock.Protection[] _pages;
/// <summary>
/// alias of _pages used for serialization
/// </summary>
private readonly byte[] _pagesAsBytes;
public MapHeap(ulong start, ulong size, string name)
{
size = WaterboxUtils.AlignUp(size);
Memory = MemoryBlock.Create(start, size);
Name = name;
_pagesAsBytes = new byte[size >> WaterboxUtils.PageShift];
_pages = (MemoryBlock.Protection[])(object)_pagesAsBytes;
for (var i = 0; i < _pages.Length; i++)
_pages[i] = FREE;
Console.WriteLine($"Created {nameof(MapHeap)} `{name}` at {start:x16}:{start + size:x16}");
}
// find consecutive unused pages to map
private int FindConsecutiveFreePages(int count)
{
return FindConsecutiveFreePagesAssumingFreed(count, -1, -1);
}
// find consecutive unused pages to map, pretending that [startPage..startPage + numPages) is free
// used in realloc
private int FindConsecutiveFreePagesAssumingFreed(int count, int startPage, int numPages)
{
// TODO: I'm sure there are sublinear algorithms for this, if the right data is maintained
var starts = new List<int>();
var sizes = new List<int>();
var currStart = 0;
for (var i = 0; i <= _pages.Length; i++)
{
if (i == _pages.Length || _pages[i] != FREE && (i < startPage || i >= startPage + numPages))
{
if (currStart < i)
{
starts.Add(currStart);
var size = i - currStart;
if (size == count)
return currStart;
sizes.Add(i - currStart);
}
currStart = i + 1;
}
}
// find smallest hole to reduce fragmentation
// TODO: Is this needed?
int bestIdx = -1;
int bestSize = int.MaxValue;
for (int i = 0; i < sizes.Count; i++)
{
if (sizes[i] < bestSize && sizes[i] >= count)
{
bestSize = sizes[i];
bestIdx = i;
}
}
if (bestIdx != -1)
return starts[bestIdx];
else
return -1;
}
private void ProtectInternal(int startPage, int numPages, MemoryBlock.Protection prot, bool wasUsed)
{
for (var i = startPage; i < startPage + numPages; i++)
_pages[i] = prot;
ulong start = GetStartAddr(startPage);
ulong length = ((ulong)numPages) << WaterboxUtils.PageShift;
if (prot == FREE)
{
Memory.Protect(start, length, MemoryBlock.Protection.RW);
WaterboxUtils.ZeroMemory(Z.US(start), (long)length);
Memory.Protect(start, length, MemoryBlock.Protection.None);
Used -= length;
Console.WriteLine($"Freed {length} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)");
}
else
{
Memory.Protect(start, length, prot);
if (wasUsed)
{
Console.WriteLine($"Set protection for {length} bytes on {Name} to {prot}");
}
else
{
Used += length;
Console.WriteLine($"Allocated {length} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)");
}
}
}
private void RefreshProtections(int startPage, int pageCount)
{
int ps = startPage;
for (int i = startPage; i < startPage + pageCount; i++)
{
if (i == startPage + pageCount - 1 || _pages[i] != _pages[i + 1])
{
var p = _pages[i];
ulong zstart = GetStartAddr(ps);
ulong zlength = (ulong)(i - ps + 1) << WaterboxUtils.PageShift;
Memory.Protect(zstart, zlength, p == FREE ? MemoryBlock.Protection.None : p);
ps = i + 1;
}
}
}
private void RefreshAllProtections()
{
RefreshProtections(0, _pages.Length);
}
private bool EnsureMapped(int startPage, int pageCount)
{
for (int i = startPage; i < startPage + pageCount; i++)
{
if (_pages[i] == FREE)
return false;
}
return true;
}
private bool EnsureMappedNonStack(int startPage, int pageCount)
{
for (int i = startPage; i < startPage + pageCount; i++)
{
if (_pages[i] == FREE || _pages[i] == MemoryBlock.Protection.RW_Stack)
return false;
}
return true;
}
public ulong Map(ulong size, MemoryBlock.Protection prot)
{
if (size == 0)
return 0;
int numPages = WaterboxUtils.PagesNeeded(size);
int startPage = FindConsecutiveFreePages(numPages);
if (startPage == -1)
return 0;
var ret = GetStartAddr(startPage);
ProtectInternal(startPage, numPages, prot, false);
return ret;
}
public ulong Remap(ulong start, ulong oldSize, ulong newSize, bool canMove)
{
// TODO: what is the expected behavior when everything requested for remap is allocated,
// but with different protections?
if (start < Memory.Start || start + oldSize > Memory.EndExclusive || oldSize == 0 || newSize == 0)
return 0;
var oldStartPage = GetPage(start);
var oldNumPages = WaterboxUtils.PagesNeeded(oldSize);
if (!EnsureMappedNonStack(oldStartPage, oldNumPages))
return 0;
var oldProt = _pages[oldStartPage];
int newNumPages = WaterboxUtils.PagesNeeded(newSize);
if (!canMove)
{
if (newNumPages <= oldNumPages)
{
if (newNumPages < oldNumPages)
ProtectInternal(oldStartPage + newNumPages, oldNumPages - newNumPages, FREE, true);
return start;
}
else if (newNumPages > oldNumPages)
{
for (var i = oldStartPage + oldNumPages; i < oldStartPage + newNumPages; i++)
if (_pages[i] != FREE)
return 0;
ProtectInternal(oldStartPage + oldNumPages, newNumPages - oldNumPages, oldProt, false);
return start;
}
}
// if moving is allowed, we always move to simplify and defragment when possible
int newStartPage = FindConsecutiveFreePagesAssumingFreed(newNumPages, oldStartPage, oldNumPages);
if (newStartPage == -1)
return 0;
var copyDataLen = Math.Min(oldSize, newSize);
var copyPageLen = Math.Min(oldNumPages, newNumPages);
var data = new byte[copyDataLen];
Memory.Protect(start, copyDataLen, MemoryBlock.Protection.RW);
Marshal.Copy(Z.US(start), data, 0, (int)copyDataLen);
var pages = new MemoryBlock.Protection[copyPageLen];
Array.Copy(_pages, oldStartPage, pages, 0, copyPageLen);
ProtectInternal(oldStartPage, oldNumPages, FREE, true);
ProtectInternal(newStartPage, newNumPages, MemoryBlock.Protection.RW, false);
var ret = GetStartAddr(newStartPage);
Marshal.Copy(data, 0, Z.US(ret), (int)copyDataLen);
Array.Copy(pages, 0, _pages, newStartPage, copyPageLen);
RefreshProtections(newStartPage, copyPageLen);
if (newNumPages > oldNumPages)
ProtectInternal(newStartPage + oldNumPages, newNumPages - oldNumPages, oldProt, true);
return ret;
}
public bool Unmap(ulong start, ulong size)
{
// TODO: eliminate copy+pasta between unmap and protect
if (start < Memory.Start || start + size > Memory.EndExclusive || size == 0)
return false;
var startPage = GetPage(start);
var numPages = WaterboxUtils.PagesNeeded(size);
if (!EnsureMapped(startPage, numPages))
return false;
ProtectInternal(startPage, numPages, FREE, true);
return true;
}
public bool Protect(ulong start, ulong size, MemoryBlock.Protection prot)
{
// TODO: eliminate copy+pasta between unmap and protect
if (start < Memory.Start || start + size > Memory.EndExclusive || size == 0)
return false;
var startPage = GetPage(start);
var numPages = WaterboxUtils.PagesNeeded(size);
if (!EnsureMappedNonStack(startPage, numPages))
return false;
ProtectInternal(startPage, numPages, prot, true);
return true;
}
public void Dispose()
{
if (Memory != null)
{
Memory.Dispose();
Memory = null;
}
}
private const ulong MAGIC = 0x1590abbcdeef5910;
public void SaveStateBinary(BinaryWriter bw)
{
bw.Write(Name);
bw.Write(Memory.Size);
bw.Write(Used);
bw.Write(_pagesAsBytes);
Memory.SaveState(bw);
bw.Write(MAGIC);
}
public void LoadStateBinary(BinaryReader br)
{
var name = br.ReadString();
if (name != Name)
throw new InvalidOperationException($"Name did not match for {nameof(MapHeap)} {Name}");
var size = br.ReadUInt64();
if (size != Memory.Size)
throw new InvalidOperationException($"Size did not match for {nameof(MapHeap)} {Name}");
Used = br.ReadUInt64();
br.Read(_pagesAsBytes, 0, _pagesAsBytes.Length);
Memory.LoadState(br);
if (br.ReadUInt64() != MAGIC)
throw new InvalidOperationException("Savestate internal error");
}
public static void StressTest()
{
var allocs = new Dictionary<ulong, ulong>();
var mmo = new MapHeap(0x36a00000000, 256 * 1024 * 1024, "ballsacks");
var rnd = new Random(12512);
for (int i = 0; i < 40; i++)
{
ulong siz = (ulong)(rnd.Next(256 * 1024) + 384 * 1024);
siz = siz / 4096 * 4096;
var ptr = mmo.Map(siz, MemoryBlock.Protection.RW);
allocs.Add(ptr, siz);
}
for (int i = 0; i < 20; i++)
{
int idx = rnd.Next(allocs.Count);
var elt = allocs.ElementAt(idx);
mmo.Unmap(elt.Key, elt.Value);
allocs.Remove(elt.Key);
}
for (int i = 0; i < 40; i++)
{
ulong siz = (ulong)(rnd.Next(256 * 1024) + 384 * 1024);
siz = siz / 4096 * 4096;
var ptr = mmo.Map(siz, MemoryBlock.Protection.RW);
allocs.Add(ptr, siz);
}
for (int i = 0; i < 20; i++)
{
int idx = rnd.Next(allocs.Count);
var elt = allocs.ElementAt(idx);
mmo.Unmap(elt.Key, elt.Value);
}
}
}
}