BizHawk/BizHawk.BizInvoke/MemoryBlockUnix.cs

132 lines
4.9 KiB
C#

using System;
using System.IO;
using static BizHawk.BizInvoke.POSIXLibC;
namespace BizHawk.BizInvoke
{
public sealed class MemoryBlockUnix : MemoryBlockBase
{
/// <summary>handle returned by <see cref="memfd_create"/></summary>
private int _fd;
/// <inheritdoc cref="MemoryBlockBase(ulong,ulong)"/>
/// <exception cref="InvalidOperationException">failed to get file descriptor (never thrown as <see cref="NotImplementedException"/> is thrown first)</exception>
/// <exception cref="NotImplementedException">always</exception>
public MemoryBlockUnix(ulong start, ulong size) : base(start, size)
{
throw new NotImplementedException($"{nameof(MemoryBlockUnix)} ctor");
_fd = memfd_create("MemoryBlockUnix", 0);
if (_fd == -1) throw new InvalidOperationException($"{nameof(memfd_create)}() returned -1");
}
/// <exception cref="InvalidOperationException"><see cref="MemoryBlockBase.Active"/> is <see langword="true"/> or failed to map memory</exception>
public override void Activate()
{
if (Active) throw new InvalidOperationException("Already active");
var ptr = mmap(Z.US(AddressRange.Start), Z.UU(Size), MemoryProtection.Read | MemoryProtection.Write | MemoryProtection.Execute, 16, _fd, IntPtr.Zero);
if (ptr != Z.US(AddressRange.Start)) throw new InvalidOperationException($"{nameof(mmap)}() returned NULL or the wrong pointer");
ProtectAll();
Active = true;
}
/// <exception cref="InvalidOperationException"><see cref="MemoryBlockBase.Active"/> is <see langword="false"/> or failed to unmap memory</exception>
public override void Deactivate()
{
if (!Active) throw new InvalidOperationException("Not active");
var exitCode = munmap(Z.US(AddressRange.Start), Z.UU(Size));
if (exitCode != 0) throw new InvalidOperationException($"{nameof(munmap)}() returned {exitCode}");
Active = false;
}
/// <exception cref="InvalidOperationException"><see cref="MemoryBlockBase.Active"/> is <see langword="false"/> or failed to make memory read-only</exception>
public override byte[] FullHash()
{
if (!Active) throw new InvalidOperationException("Not active");
// temporarily switch the entire block to `R`
var exitCode = mprotect(Z.US(AddressRange.Start), Z.UU(Size), MemoryProtection.Read);
if (exitCode != 0) throw new InvalidOperationException($"{nameof(mprotect)}() returned {exitCode}!");
var ret = WaterboxUtils.Hash(GetStream(AddressRange.Start, Size, false));
ProtectAll();
return ret;
}
/// <exception cref="InvalidOperationException">failed to protect memory</exception>
public override void Protect(ulong start, ulong length, Protection prot)
{
if (length == 0) return;
var pstart = GetPage(start);
var pend = GetPage(start + length - 1);
for (var i = pstart; i <= pend; i++) _pageData[i] = prot; // also store the value for later use
if (!Active) return; // it's legal to call this method if we're not active; the information is just saved for the next activation
var computedStart = WaterboxUtils.AlignDown(start);
var protEnum = prot.ToMemoryProtection();
var exitCode = mprotect(
Z.US(computedStart),
Z.UU(WaterboxUtils.AlignUp(start + length) - computedStart),
protEnum
);
if (exitCode != 0) throw new InvalidOperationException($"{nameof(mprotect)}() returned {exitCode}!");
}
protected override void ProtectAll()
{
var ps = 0;
for (var i = 0; i < _pageData.Length; i++)
{
if (i == _pageData.Length - 1 || _pageData[i] != _pageData[i + 1])
{
var protEnum = _pageData[i].ToMemoryProtection();
var zstart = GetStartAddr(ps);
var exitCode = mprotect(
Z.US(zstart),
Z.UU(GetStartAddr(i + 1) - zstart),
protEnum
);
if (exitCode != 0) throw new InvalidOperationException($"{nameof(mprotect)}() returned {exitCode}!");
ps = i + 1;
}
}
}
/// <exception cref="InvalidOperationException">snapshot already taken, <see cref="MemoryBlockBase.Active"/> is <see langword="false"/>, or failed to make memory read-only</exception>
public override void SaveXorSnapshot()
{
if (_snapshot != null) throw new InvalidOperationException("Snapshot already taken");
if (!Active) throw new InvalidOperationException("Not active");
// temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want that to complicate things
var exitCode = mprotect(Z.US(AddressRange.Start), Z.UU(Size), MemoryProtection.Read);
if (exitCode != 0) throw new InvalidOperationException($"{nameof(mprotect)}() returned {exitCode}!");
_snapshot = new byte[Size];
GetStream(AddressRange.Start, Size, false).CopyTo(new MemoryStream(_snapshot, true));
XorHash = WaterboxUtils.Hash(_snapshot);
ProtectAll();
}
public override void Dispose(bool disposing)
{
if (_fd == 0) return;
if (Active) Deactivate();
close(_fd);
_fd = -1;
}
~MemoryBlockUnix()
{
Dispose(false);
}
}
}