Start working on a primitive mmap implementation. It's good enough for VirtualBoyee to boot, but undoubtedly has many bugs.

This commit is contained in:
nattthebear 2017-05-28 22:17:48 -04:00
parent 7d84946daa
commit 855ff7deca
12 changed files with 531 additions and 122 deletions

View File

@ -96,6 +96,9 @@ namespace BizHawk.Client.ApiHawk
case "WSWAN":
return CoreSystem.WonderSwan;
case "VB":
return 0; // like I give a shit
default:
throw new IndexOutOfRangeException(string.Format("{0} is missing in convert list", value));
}

View File

@ -331,6 +331,10 @@ namespace BizHawk.Emulation.Common
case ".DO":
game.System = "AppleII";
break;
case ".VB":
game.System = "VB";
break;
}
game.Name = Path.GetFileNameWithoutExtension(fileName)?.Replace('_', ' ');

View File

@ -1279,6 +1279,7 @@
<Compile Include="Libretro\LibretroCore_Description.cs" />
<Compile Include="Libretro\LibretroCore_InputCallbacks.cs" />
<Compile Include="Waterbox\Heap.cs" />
<Compile Include="Waterbox\MapHeap.cs" />
<Compile Include="Waterbox\MemoryBlock.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Sound\CDAudio.cs" />

View File

@ -25,10 +25,11 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB
{
Path = comm.CoreFileProvider.DllPath(),
Filename = "vb.wbx",
NormalHeapSizeKB = 1024,
NormalHeapSizeKB = 4 * 1024,
SealedHeapSizeKB = 12 * 1024,
InvisibleHeapSizeKB = 6 * 1024,
SpecialHeapSizeKB = 64
SpecialHeapSizeKB = 64,
MmapHeapSizeKB = 16 * 1024
});
_boyee = BizInvoker.GetInvoker<LibVirtualBoyee>(_exe, _exe);
@ -112,22 +113,22 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.VB
#region IVideoProvider
private int[] _videoBuffer = new int[0];
private int[] _videoBuffer = new int[256 * 192];
public int[] GetVideoBuffer()
{
throw new NotImplementedException();
return _videoBuffer;
}
public int VirtualWidth { get; private set; }
public int VirtualHeight { get; private set; }
public int VirtualWidth { get; private set; } = 256;
public int VirtualHeight { get; private set; } = 192;
public int BufferWidth { get; private set; }
public int BufferHeight { get; private set; }
public int BufferWidth { get; private set; } = 256;
public int BufferHeight { get; private set; } = 192;
public int VsyncNumerator { get; private set; }
public int VsyncNumerator { get; private set; } = 60;
public int VsyncDenominator { get; private set; }
public int VsyncDenominator { get; private set; } = 1;
public int BackgroundColor => unchecked((int)0xff000000);

View File

@ -0,0 +1,303 @@
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
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; private set; }
public byte[] XorHash { 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;
}
private class Bin
{
public int StartPage;
public int PageCount;
public MemoryBlock.Protection Protection;
public bool Free
{
get
{
return (byte)Protection == 255;
}
set
{
Protection = value ? (MemoryBlock.Protection)255 : MemoryBlock.Protection.None;
}
}
public Bin Next;
/// <summary>
/// split this bin, keeping only numPages pages
/// </summary>
public bool Cleave(int numPages)
{
int nextPages = PageCount - numPages;
if (nextPages > 0)
{
Next = new Bin
{
StartPage = StartPage + numPages,
PageCount = nextPages,
Next = Next
};
PageCount = numPages;
return true;
}
else
{
return false;
}
}
}
private Bin _root;
public MapHeap(ulong start, ulong size, string name)
{
size = WaterboxUtils.AlignUp(size);
Memory = new MemoryBlock(start, size);
Name = name;
Console.WriteLine("Created mapheap `{1}` at {0:x16}:{2:x16}", start, name, start + size);
_root = new Bin
{
StartPage = 0,
PageCount = (int)(size >> WaterboxUtils.PageShift),
Free = true
};
}
/// <summary>
/// gets the bin that contains a page
/// </summary>
private Bin GetBinForStartPage(int page)
{
Bin curr = _root;
while (curr.StartPage + curr.PageCount <= page)
curr = curr.Next;
return curr;
}
/// <summary>
/// gets the bin that contains the page before the passed page, returning null if
/// any bin along the way is Free
/// </summary>
private Bin GetBinForEndPageEnsureAllocated(int page, Bin start)
{
Bin curr = start;
while (curr != null && curr.StartPage + curr.PageCount < page)
{
if (curr.Free)
return null;
curr = curr.Next;
}
return curr;
}
public ulong Map(ulong size, MemoryBlock.Protection prot)
{
int numPages = WaterboxUtils.PagesNeeded(size);
Bin best = null;
Bin curr = _root;
// find smallest potential bin
do
{
if (curr.Free && curr.PageCount >= numPages)
{
if (best == null || curr.PageCount < best.PageCount)
{
best = curr;
if (curr.PageCount == numPages)
break;
}
}
curr = curr.Next;
} while (curr != null);
if (best == null)
return 0;
if (best.Cleave(numPages))
best.Next.Free = true;
best.Protection = prot;
var ret = GetStartAddr(best.StartPage);
Memory.Protect(ret, ((ulong)numPages) << WaterboxUtils.PageShift, prot);
return ret;
}
public ulong Remap(ulong start, ulong oldSize, ulong newSize, bool canMove)
{
if (start < Memory.Start || start + oldSize > Memory.End)
return 0;
var oldStartPage = GetPage(start);
var oldStartBin = GetBinForStartPage(oldStartPage);
if (oldSize == 0 && canMove)
return Map(newSize, oldStartBin.Protection);
var oldNumPages = WaterboxUtils.PagesNeeded(oldSize);
var oldEndPage = oldStartPage + oldNumPages;
// first, check if the requested area is actually mapped
var oldEndBin = GetBinForEndPageEnsureAllocated(oldEndPage, oldStartBin);
if (oldEndBin == null)
return 0;
var newNumPages = WaterboxUtils.PagesNeeded(newSize);
var newEndPage = oldStartPage + newNumPages;
if (newEndPage > oldEndPage)
{
// increase size
// the only way this will work in place is if all of the remaining space is free
Bin nextBin;
if (oldEndBin.StartPage + oldEndBin.PageCount == oldEndPage // if end bin is too bag, space after that is used by something else
&& (nextBin = oldEndBin.Next) != null // can't go off the edge
&& nextBin.Free
&& nextBin.StartPage + nextBin.PageCount >= newEndPage)
{
nextBin.Protection = oldStartBin.Protection;
if (nextBin.Cleave(newEndPage - nextBin.StartPage))
nextBin.Next.Free = true;
return start;
}
// could not increase in place, so move
if (!canMove)
return 0;
// if there's some free space right before `start`, and some right after, but not enough
// to extend in place, it's possible that a realloc would succeed reusing the same space,
// but would fail anywhere else due to heavy memory pressure.
// that would be a much more complicated algorithm; we'd need to compute a new allocation
// as if this one had been freed, but still be able to preserve this if that allocation
// still failed. instead, we ignore this case.
var ret = Map(newSize, oldStartBin.Protection);
if (ret != 0)
{
// move data
// NB: oldSize > 0
Memory.Protect(start, oldSize, MemoryBlock.Protection.R);
var ss = Memory.GetStream(start, oldSize, false);
Memory.Protect(ret, oldSize, MemoryBlock.Protection.RW);
var ds = Memory.GetStream(ret, oldSize, true);
ss.CopyTo(ds);
Memory.Protect(ret, oldSize, oldStartBin.Protection);
UnmapPagesInternal(oldStartPage, oldNumPages, oldStartBin);
return ret;
}
else
{
return 0;
}
}
else if (newEndPage < oldEndPage)
{
// shrink in place
var s = GetBinForStartPage(newEndPage);
UnmapPagesInternal(newEndPage, oldEndPage - newEndPage, s);
return start;
}
else
{
// no change
return start;
}
}
public bool Unmap(ulong start, ulong size)
{
if (start < Memory.Start || start + size > Memory.End)
return false;
if (size == 0)
return true;
var startPage = GetPage(start);
var numPages = WaterboxUtils.PagesNeeded(size);
var endPage = startPage + numPages;
// check to see if the requested area is actually mapped
var startBin = GetBinForStartPage(startPage);
if (GetBinForEndPageEnsureAllocated(endPage, startBin) == null)
return false;
UnmapPagesInternal(startPage, numPages, startBin);
return true;
}
/// <summary>
/// frees some pages. assumes they are all allocated
/// </summary>
private void UnmapPagesInternal(int startPage, int numPages, Bin startBin)
{
// from the various paths we took to get here, we must be unmapping at least one page
var endPage = startPage + numPages;
Bin freeBin = startBin;
if (!freeBin.Free && freeBin.StartPage != startPage)
{
freeBin.Cleave(startPage - freeBin.StartPage);
freeBin = freeBin.Next;
freeBin.Free = true;
}
MemoryBlock.Protection lastEaten = MemoryBlock.Protection.None;
while (freeBin.StartPage + freeBin.PageCount < endPage)
{
freeBin.PageCount += freeBin.Next.PageCount;
lastEaten = freeBin.Next.Protection;
freeBin.Next = freeBin.Next.Next;
}
if (freeBin.Cleave(freeBin.StartPage + freeBin.PageCount - endPage))
{
freeBin.Next.Protection = lastEaten;
}
Memory.Protect(GetStartAddr(startPage), ((ulong)numPages) << WaterboxUtils.PageShift, MemoryBlock.Protection.None);
}
public void Dispose()
{
if (Memory != null)
{
Memory.Dispose();
Memory = null;
}
}
public void SaveStateBinary(BinaryWriter writer)
{
}
public void LoadStateBinary(BinaryReader reader)
{
}
}
}

View File

@ -7,96 +7,8 @@ using System.IO;
namespace BizHawk.Emulation.Cores.Waterbox
{
// C# is annoying: arithmetic operators for native ints are not exposed.
// So we store them as long/ulong instead in many places, and use these helpers
// to convert to IntPtr when needed
public static class Z
{
public static IntPtr US(ulong l)
{
if (IntPtr.Size == 8)
return (IntPtr)(long)l;
else
return (IntPtr)(int)l;
}
public static UIntPtr UU(ulong l)
{
if (UIntPtr.Size == 8)
return (UIntPtr)l;
else
return (UIntPtr)(uint)l;
}
public static IntPtr SS(long l)
{
if (IntPtr.Size == 8)
return (IntPtr)l;
else
return (IntPtr)(int)l;
}
public static UIntPtr SU(long l)
{
if (UIntPtr.Size == 8)
return (UIntPtr)(ulong)l;
else
return (UIntPtr)(uint)l;
}
}
public sealed class MemoryBlock : IDisposable
{
/// <summary>
/// system page size
/// </summary>
public static int PageSize { get; private set; }
/// <summary>
/// bitshift corresponding to PageSize
/// </summary>
private static readonly int PageShift;
/// <summary>
/// bitmask corresponding to PageSize
/// </summary>
private static readonly ulong PageMask;
static MemoryBlock()
{
int p = PageSize = Environment.SystemPageSize;
while (p != 1)
{
p >>= 1;
PageShift++;
}
PageMask = (ulong)(PageSize - 1);
}
/// <summary>
/// true if addr is aligned
/// </summary>
private static bool Aligned(ulong addr)
{
return (addr & PageMask) == 0;
}
/// <summary>
/// align address down to previous page boundary
/// </summary>
private static ulong AlignDown(ulong addr)
{
return addr & ~PageMask;
}
/// <summary>
/// align address up to next page boundary
/// </summary>
private static ulong AlignUp(ulong addr)
{
return ((addr - 1) | PageMask) + 1;
}
/// <summary>
/// starting address of the memory block
/// </summary>
@ -140,7 +52,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
if (addr < Start || addr >= End)
throw new ArgumentOutOfRangeException();
return (int)((addr - Start) >> PageShift);
return (int)((addr - Start) >> WaterboxUtils.PageShift);
}
/// <summary>
@ -148,7 +60,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// </summary>
private ulong GetStartAddr(int page)
{
return ((ulong)page << PageShift) + Start;
return ((ulong)page << WaterboxUtils.PageShift) + Start;
}
/// <summary>
@ -167,11 +79,11 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// <param name="size"></param>
public MemoryBlock(ulong start, ulong size)
{
if (!Aligned(start))
if (!WaterboxUtils.Aligned(start))
throw new ArgumentOutOfRangeException();
if (size == 0)
throw new ArgumentOutOfRangeException();
size = AlignUp(size);
size = WaterboxUtils.AlignUp(size);
_handle = Kernel32.CreateFileMapping(Kernel32.INVALID_HANDLE_VALUE, IntPtr.Zero,
Kernel32.FileMapProtection.PageExecuteReadWrite | Kernel32.FileMapProtection.SectionCommit, (uint)(size >> 32), (uint)size, null);
@ -325,10 +237,13 @@ namespace BizHawk.Emulation.Cores.Waterbox
if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation
{
// TODO: if using another OS's memory protection calls, they must give the same non-aligned behavior
// as VirtualProtect, or this must be changed
var computedStart = WaterboxUtils.AlignDown(start);
var computedEnd = WaterboxUtils.AlignUp(start + length);
var computedLength = computedEnd - computedStart;
Kernel32.MemoryProtection old;
if (!Kernel32.VirtualProtect(Z.UU(start), Z.UU(length), p, out old))
if (!Kernel32.VirtualProtect(Z.UU(computedStart),
Z.UU(computedLength), p, out old))
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
}
}

View File

@ -26,23 +26,33 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// <summary>
/// how large the normal heap should be. it services sbrk calls
/// can be 0, but sbrk calls will crash.
/// </summary>
public uint NormalHeapSizeKB { get; set; }
/// <summary>
/// how large the sealed heap should be. it services special allocations that become readonly after init
/// Must be > 0 and at least large enough to store argv and envp
/// </summary>
public uint SealedHeapSizeKB { get; set; }
/// <summary>
/// how large the invisible heap should be. it services special allocations which are not savestated
/// Must be > 0 and at least large enough for the internal vtables
/// </summary>
public uint InvisibleHeapSizeKB { get; set; }
/// <summary>
/// how large the special heap should be. it is savestated, and contains ??
/// Must be > 0 and at least large enough for the internal pthread structure
/// </summary>
public uint SpecialHeapSizeKB { get; set; }
/// <summary>
/// how large the mmap heap should be. it is savestated.
/// can be 0, but mmap calls will crash.
/// </summary>
public uint MmapHeapSizeKB { get; set; }
}
@ -385,6 +395,57 @@ namespace BizHawk.Emulation.Cores.Waterbox
time.NanoSeconds = 0;
return 0;
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n9")]
public IntPtr MMap(IntPtr address, UIntPtr size, int prot, int flags, int fd, IntPtr offs)
{
if (address != IntPtr.Zero)
return Z.SS(-1);
MemoryBlock.Protection mprot;
switch (prot)
{
case 0: mprot = MemoryBlock.Protection.None; break;
default:
case 6: // W^X
case 7: // W^X
case 4: // exec only????
case 2: return Z.SS(-1); // write only????
case 3: mprot = MemoryBlock.Protection.RW; break;
case 1: mprot = MemoryBlock.Protection.R; break;
case 5: mprot = MemoryBlock.Protection.RX; break;
}
if ((flags & 0x20) == 0)
{
// MAP_ANONYMOUS is required
return Z.SS(-1);
}
if ((flags & 0xf00) != 0)
{
// various unsupported flags
return Z.SS(-1);
}
var ret = _parent._mmapheap.Map((ulong)size, mprot);
return ret == 0 ? Z.SS(-1) : Z.US(ret);
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n25")]
public IntPtr MRemap(UIntPtr oldAddress, UIntPtr oldSize,
UIntPtr newSize, int flags)
{
if ((flags & 2) != 0)
{
// don't support MREMAP_FIXED
return Z.SS(-1);
}
var ret = _parent._mmapheap.Remap((ulong)oldAddress, (ulong)oldSize, (ulong)newSize,
(flags & 1) != 0);
return ret == 0 ? Z.SS(-1) : Z.US(ret);
}
[BizExport(CallingConvention.Cdecl, EntryPoint = "n11")]
public int MUnmap(UIntPtr address, UIntPtr size)
{
return _parent._mmapheap.Unmap((ulong)address, (ulong)size) ? 0 : -1;
}
}
/// <summary>
@ -511,6 +572,11 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// </summary>
private Heap _specheap;
/// <summary>
/// memory map emulation
/// </summary>
private MapHeap _mmapheap;
/// <summary>
/// all loaded PE files
/// </summary>
@ -548,15 +614,22 @@ namespace BizHawk.Emulation.Cores.Waterbox
private Heap CreateHeapHelper(uint sizeKB, string name, bool saveStated)
{
var heap = new Heap(_nextStart, sizeKB * 1024, name);
heap.Memory.Activate();
ComputeNextStart(sizeKB * 1024);
AddMemoryBlock(heap.Memory);
if (saveStated)
_savestateComponents.Add(heap);
_disposeList.Add(heap);
_heaps.Add(heap);
return heap;
if (sizeKB != 0)
{
var heap = new Heap(_nextStart, sizeKB * 1024, name);
heap.Memory.Activate();
ComputeNextStart(sizeKB * 1024);
AddMemoryBlock(heap.Memory);
if (saveStated)
_savestateComponents.Add(heap);
_disposeList.Add(heap);
_heaps.Add(heap);
return heap;
}
else
{
return null;
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -566,7 +639,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
Initialize(_nextStart);
using (this.EnterExit())
{
{
// load any predefined exports
_psx = new Psx(this);
_exports.Add("libpsxscl.so", BizExvoker.GetExvoker(_psx));
@ -610,6 +683,16 @@ namespace BizHawk.Emulation.Cores.Waterbox
_invisibleheap = CreateHeapHelper(opt.InvisibleHeapSizeKB, "invisible-heap", false);
_specheap = CreateHeapHelper(opt.SpecialHeapSizeKB, "special-heap", true);
if (opt.MmapHeapSizeKB != 0)
{
_mmapheap = new MapHeap(_nextStart, opt.MmapHeapSizeKB * 1024, "mmap-heap");
_mmapheap.Memory.Activate();
ComputeNextStart(opt.MmapHeapSizeKB * 1024);
AddMemoryBlock(_mmapheap.Memory);
_savestateComponents.Add(_mmapheap);
_disposeList.Add(_mmapheap);
}
_syscalls.Init();
//System.Diagnostics.Debugger.Break();
@ -729,6 +812,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_sealedheap = null;
_invisibleheap = null;
_specheap = null;
_mmapheap = null;
}
}
}

View File

@ -294,11 +294,12 @@ namespace BizHawk.Emulation.Cores.Waterbox
s.Size);
}
Console.WriteLine("GDB Symbol Load:");
Console.WriteLine("add-sym {0} {1} -s .data {2} -s .bss {3}",
ModuleName,
_sectionsByName[".text"].Start,
_sectionsByName[".data"].Start,
_sectionsByName[".bss"].Start);
var symload = $"add-sym {ModuleName} {_sectionsByName[".text"].Start}";
if (_sectionsByName.ContainsKey(".data"))
symload += $" -s .data {_sectionsByName[".data"].Start}";
if (_sectionsByName.ContainsKey(".bss"))
symload += $" -s .bss {_sectionsByName[".bss"].Start}";
Console.WriteLine(symload);
}
public IntPtr Resolve(string entryPoint)

View File

@ -56,5 +56,101 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
return DateTime.UtcNow.Ticks;
}
/// <summary>
/// system page size
/// </summary>
public static int PageSize { get; private set; }
/// <summary>
/// bitshift corresponding to PageSize
/// </summary>
public static int PageShift { get; private set; }
/// <summary>
/// bitmask corresponding to PageSize
/// </summary>
public static ulong PageMask { get; private set; }
static WaterboxUtils()
{
int p = PageSize = Environment.SystemPageSize;
while (p != 1)
{
p >>= 1;
PageShift++;
}
PageMask = (ulong)(PageSize - 1);
}
/// <summary>
/// true if addr is aligned
/// </summary>
public static bool Aligned(ulong addr)
{
return (addr & PageMask) == 0;
}
/// <summary>
/// align address down to previous page boundary
/// </summary>
public static ulong AlignDown(ulong addr)
{
return addr & ~PageMask;
}
/// <summary>
/// align address up to next page boundary
/// </summary>
public static ulong AlignUp(ulong addr)
{
return ((addr - 1) | PageMask) + 1;
}
/// <summary>
/// return the minimum number of pages needed to hold size
/// </summary>
public static int PagesNeeded(ulong size)
{
return (int)((size + PageMask) >> PageShift);
}
}
// C# is annoying: arithmetic operators for native ints are not exposed.
// So we store them as long/ulong instead in many places, and use these helpers
// to convert to IntPtr when needed
public static class Z
{
public static IntPtr US(ulong l)
{
if (IntPtr.Size == 8)
return (IntPtr)(long)l;
else
return (IntPtr)(int)l;
}
public static UIntPtr UU(ulong l)
{
if (UIntPtr.Size == 8)
return (UIntPtr)l;
else
return (UIntPtr)(uint)l;
}
public static IntPtr SS(long l)
{
if (IntPtr.Size == 8)
return (IntPtr)l;
else
return (IntPtr)(int)l;
}
public static UIntPtr SU(long l)
{
if (UIntPtr.Size == 8)
return (UIntPtr)(ulong)l;
else
return (UIntPtr)(uint)l;
}
}
}

View File

@ -4,7 +4,7 @@ CCFLAGS:= -I. -I../emulibc \
-Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=implicit-function-declaration \
-std=c++0x -fomit-frame-pointer -fvisibility=hidden -fno-exceptions -fno-rtti \
-DLSB_FIRST \
-O0
-O0 -g
TARGET = vb.wbx
@ -29,7 +29,8 @@ $(TARGET).in: $(OBJS)
@$(CC) -o $@ $(LDFLAGS) $(CCFLAGS) $(OBJS) ../emulibc/libemuhost.so
$(TARGET): $(TARGET).in
strip $< -o $@ -R /4 -R /14 -R /29 -R /41 -R /55 -R /67 -R /78 -R /89 -R /104
# strip $< -o $@ -R /4 -R /14 -R /29 -R /41 -R /55 -R /67 -R /78 -R /89 -R /104
cp $< $@
clean:
rm -rf $(OBJ_DIR)

Binary file not shown.

Binary file not shown.