BizHawk/BizHawk.Emulation.Cores/MemoryBlock.cs

498 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
namespace BizHawk.Emulation.Cores
{
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>
public ulong Start { get; private set; }
/// <summary>
/// total size of the memory block
/// </summary>
public ulong Size { get; private set; }
/// <summary>
/// ending address of the memory block; equal to start + size
/// </summary>
public ulong End { get; private set; }
/// <summary>
/// handle returned by CreateFileMapping
/// </summary>
private IntPtr _handle;
/// <summary>
/// true if this is currently swapped in
/// </summary>
public bool Active { get; private set; }
/// <summary>
/// stores last set memory protection value for each page
/// </summary>
private readonly Protection[] _pageData;
/// <summary>
/// get a page index within the block
/// </summary>
private int GetPage(ulong addr)
{
if (addr < Start || addr >= End)
throw new ArgumentOutOfRangeException();
return (int)((addr - Start) >> PageShift);
}
/// <summary>
/// get a start address for a page index within the block
/// </summary>
private ulong GetStartAddr(int page)
{
return ((ulong)page << PageShift) + Start;
}
/// <summary>
/// allocate size bytes at any address
/// </summary>
/// <param name="size"></param>
public MemoryBlock(ulong size)
: this(0, size)
{
}
/// <summary>
/// allocate size bytes starting at a particular address
/// </summary>
/// <param name="start"></param>
/// <param name="size"></param>
public MemoryBlock(ulong start, ulong size)
{
if (!Aligned(start))
throw new ArgumentOutOfRangeException();
if (size == 0)
throw new ArgumentOutOfRangeException();
size = AlignUp(size);
_handle = Kernel32.CreateFileMapping(Kernel32.INVALID_HANDLE_VALUE, IntPtr.Zero,
Kernel32.FileMapProtection.PageExecuteReadWrite | Kernel32.FileMapProtection.SectionCommit, (uint)(size >> 32), (uint)size, null);
if (_handle == IntPtr.Zero)
throw new InvalidOperationException("CreateFileMapping() returned NULL");
Start = start;
End = start + size;
Size = size;
_pageData = new Protection[GetPage(End - 1) + 1];
}
/// <summary>
/// activate the memory block, swapping it in at the specified address
/// </summary>
public void Activate()
{
if (Active)
throw new InvalidOperationException("Already active");
if (Kernel32.MapViewOfFileEx(_handle, Kernel32.FileMapAccessType.Read | Kernel32.FileMapAccessType.Write | Kernel32.FileMapAccessType.Execute,
0, 0, Z.UU(Size), Z.US(Start)) != Z.US(Start))
{
throw new InvalidOperationException("MapViewOfFileEx() returned NULL");
}
ProtectAll();
Active = true;
}
/// <summary>
/// deactivate the memory block, removing it from RAM but leaving it immediately available to swap back in
/// </summary>
public void Deactivate()
{
if (!Active)
throw new InvalidOperationException("Not active");
if (!Kernel32.UnmapViewOfFile(Z.US(Start)))
throw new InvalidOperationException("UnmapViewOfFile() returned NULL");
Active = false;
}
/// <summary>
/// Memory protection constant
/// </summary>
public enum Protection : byte
{
None, R, RW, RX
}
/// <summary>
/// Get a stream that can be used to read or write from part of the block. Does not check for or change Protect()!
/// </summary>
public Stream GetStream(ulong start, ulong length, bool writer)
{
if (start < Start)
throw new ArgumentOutOfRangeException("start");
if (start + length > End)
throw new ArgumentOutOfRangeException("length");
return new MemoryViewStream(!writer, writer, (long)start, (long)length, this);
}
private static Kernel32.MemoryProtection GetKernelMemoryProtectionValue(Protection prot)
{
Kernel32.MemoryProtection p;
switch (prot)
{
case Protection.None: p = Kernel32.MemoryProtection.NOACCESS; break;
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;
default: throw new ArgumentOutOfRangeException("prot");
}
return p;
}
/// <summary>
/// restore all recorded protections
/// </summary>
private void ProtectAll()
{
int ps = 0;
for (int i = 0; i < _pageData.Length; i++)
{
if (i == _pageData.Length - 1 || _pageData[i] != _pageData[i + 1])
{
var p = GetKernelMemoryProtectionValue(_pageData[i]);
ulong zstart = GetStartAddr(ps);
ulong zend = GetStartAddr(i + 1);
Kernel32.MemoryProtection old;
if (!Kernel32.VirtualProtect(Z.UU(zstart), Z.UU(zend - zstart), p, out old))
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
ps = i + 1;
}
}
}
/// <summary>
/// set r/w/x protection on a portion of memory. rounded to encompassing pages
/// </summary>
public void Protect(ulong start, ulong length, Protection prot)
{
if (length == 0)
return;
int pstart = GetPage(start);
int pend = GetPage(start + length - 1);
var p = GetKernelMemoryProtectionValue(prot);
for (int i = pstart; i <= pend; i++)
_pageData[i] = prot; // also store the value for later use
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
Kernel32.MemoryProtection old;
if (!Kernel32.VirtualProtect(Z.UU(start), Z.UU(length), p, out old))
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_handle != IntPtr.Zero)
{
if (Active)
Deactivate();
Kernel32.CloseHandle(_handle);
_handle = IntPtr.Zero;
}
}
~MemoryBlock()
{
Dispose(false);
}
private class MemoryViewStream : Stream
{
public MemoryViewStream(bool readable, bool writable, long ptr, long length, MemoryBlock owner)
{
_readable = readable;
_writable = writable;
_ptr = ptr;
_length = length;
_owner = owner;
_pos = 0;
}
private void EnsureNotDisposed()
{
if (_owner.Start == 0)
throw new ObjectDisposedException("MemoryBlock");
}
private MemoryBlock _owner;
private bool _readable;
private bool _writable;
private long _length;
private long _pos;
private long _ptr;
public override bool CanRead { get { return _readable; } }
public override bool CanSeek { get { return true; } }
public override bool CanWrite { get { return _writable; } }
public override void Flush() { }
public override long Length { get { return _length; } }
public override long Position
{
get { return _pos; } set
{
if (value < 0 || value > _length)
throw new ArgumentOutOfRangeException();
_pos = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
if (!_readable)
throw new InvalidOperationException();
if (count < 0 || count > buffer.Length)
throw new ArgumentOutOfRangeException();
EnsureNotDisposed();
count = (int)Math.Min(count, _length - _pos);
Marshal.Copy(Z.SS(_ptr + _pos), buffer, 0, count);
_pos += count;
return count;
}
public override long Seek(long offset, SeekOrigin origin)
{
long newpos;
switch (origin)
{
default:
case SeekOrigin.Begin:
newpos = 0;
break;
case SeekOrigin.Current:
newpos = _pos + offset;
break;
case SeekOrigin.End:
newpos = _length + offset;
break;
}
Position = newpos;
return newpos;
}
public override void SetLength(long value)
{
throw new InvalidOperationException();
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!_writable)
throw new InvalidOperationException();
if (count < 0 || count > buffer.Length)
throw new ArgumentOutOfRangeException();
EnsureNotDisposed();
count = (int)Math.Min(count, _length - _pos);
Marshal.Copy(buffer, 0, Z.SS(_ptr + _pos), count);
_pos += count;
}
}
private static class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern UIntPtr VirtualAlloc(UIntPtr lpAddress, UIntPtr dwSize,
AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualFree(UIntPtr lpAddress, UIntPtr dwSize,
FreeType dwFreeType);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualProtect(UIntPtr lpAddress, UIntPtr dwSize,
MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
public enum FreeType : uint
{
DECOMMIT = 0x4000,
RELEASE = 0x8000
}
[Flags]
public enum AllocationType : uint
{
COMMIT = 0x1000,
RESERVE = 0x2000,
RESET = 0x80000,
RESET_UNDO = 0x1000000,
LARGE_PAGES = 0x20000000,
PHYSICAL = 0x400000,
TOP_DOWN = 0x100000,
WRITE_WATCH = 0x200000
}
[Flags]
public enum MemoryProtection : uint
{
EXECUTE = 0x10,
EXECUTE_READ = 0x20,
EXECUTE_READWRITE = 0x40,
EXECUTE_WRITECOPY = 0x80,
NOACCESS = 0x01,
READONLY = 0x02,
READWRITE = 0x04,
WRITECOPY = 0x08,
GUARD_Modifierflag = 0x100,
NOCACHE_Modifierflag = 0x200,
WRITECOMBINE_Modifierflag = 0x400
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFileMapping(
IntPtr hFile,
IntPtr lpFileMappingAttributes,
FileMapProtection flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);
[Flags]
public enum FileMapProtection : uint
{
PageReadonly = 0x02,
PageReadWrite = 0x04,
PageWriteCopy = 0x08,
PageExecuteRead = 0x20,
PageExecuteReadWrite = 0x40,
SectionCommit = 0x8000000,
SectionImage = 0x1000000,
SectionNoCache = 0x10000000,
SectionReserve = 0x4000000,
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32.dll")]
public static extern IntPtr MapViewOfFileEx(IntPtr hFileMappingObject,
FileMapAccessType dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,
UIntPtr dwNumberOfBytesToMap, IntPtr lpBaseAddress);
[Flags]
public enum FileMapAccessType : uint
{
Copy = 0x01,
Write = 0x02,
Read = 0x04,
AllAccess = 0x08,
Execute = 0x20,
}
public static readonly IntPtr INVALID_HANDLE_VALUE = Z.US(0xffffffffffffffff);
}
}
}