using System; using System.IO; using static BizHawk.BizInvoke.POSIXLibC; namespace BizHawk.BizInvoke { public sealed class MemoryBlockUnix : MemoryBlockBase { /// handle returned by private int _fd; /// /// failed to get file descriptor (never thrown as is thrown first) /// always 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"); } /// is or failed to map memory 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; } /// is or failed to unmap memory 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; } /// is or failed to make memory read-only 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; } /// failed to protect memory 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; } } } /// snapshot already taken, is , or failed to make memory read-only 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); } } }