waterbox: add space at the end of writable data pages, past what was allocated by the core but before the start of cpu enforced write protection, to savestates. theoretically fixes determinism issues from certain wild pointers

This commit is contained in:
nattthebear 2017-06-20 20:51:06 -04:00
parent 06df9634c1
commit a7f7583fcc
6 changed files with 1368 additions and 1330 deletions

View File

@ -186,6 +186,23 @@ namespace BizHawk.Common.BizInvoke
ProtectAll();
}
/// <summary>
/// take a hash of the current full contents of the block, including unreadable areas
/// </summary>
/// <returns></returns>
public byte[] FullHash()
{
if (!Active)
throw new InvalidOperationException("Not active");
// temporarily switch the entire block to `R`
Kernel32.MemoryProtection old;
if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
var ret = WaterboxUtils.Hash(GetStream(Start, Size, false));
ProtectAll();
return ret;
}
private static Kernel32.MemoryProtection GetKernelMemoryProtectionValue(Protection prot)
{
Kernel32.MemoryProtection p;

View File

@ -92,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
try
{
_disposeList.Add(_base);
AddMemoryBlock(_base);
AddMemoryBlock(_base, "elf");
_base.Activate();
_base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.RW);
@ -122,7 +122,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_heap.Memory.Activate();
end = _heap.Memory.End;
_disposeList.Add(_heap);
AddMemoryBlock(_heap.Memory);
AddMemoryBlock(_heap.Memory, "sbrk - heap");
}
if (sealedheapsize > 0)
@ -131,7 +131,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_sealedheap.Memory.Activate();
end = _sealedheap.Memory.End;
_disposeList.Add(_sealedheap);
AddMemoryBlock(_sealedheap.Memory);
AddMemoryBlock(_sealedheap.Memory, "sealed-heap");
}
if (invisibleheapsize > 0)
@ -140,7 +140,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_invisibleheap.Memory.Activate();
end = _invisibleheap.Memory.End;
_disposeList.Add(_invisibleheap);
AddMemoryBlock(_invisibleheap.Memory);
AddMemoryBlock(_invisibleheap.Memory, "invisible-heap");
}
ConnectAllClibPatches();

View File

@ -38,7 +38,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
Console.WriteLine("Created heap `{1}` at {0:x16}:{2:x16}", start, name, start + size);
}
private void EnsureAlignment(int align)
private ulong EnsureAlignment(int align)
{
if (align > 1)
{
@ -47,8 +47,9 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
throw new InvalidOperationException(string.Format("Failed to meet alignment {0} on heap {1}", align, Name));
}
Used = newused;
return newused;
}
return Used;
}
public ulong Allocate(ulong size, int align)
@ -56,15 +57,14 @@ namespace BizHawk.Emulation.Cores.Waterbox
if (Sealed)
throw new InvalidOperationException(string.Format("Attempt made to allocate from sealed heap {0}", Name));
EnsureAlignment(align);
ulong newused = Used + size;
ulong allocstart = EnsureAlignment(align);
ulong newused = allocstart + size;
if (newused > Memory.Size)
{
throw new InvalidOperationException(string.Format("Failed to allocate {0} bytes from heap {1}", size, Name));
}
ulong ret = Memory.Start + Used;
Memory.Protect(ret, newused - Used, MemoryBlock.Protection.RW);
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;
@ -91,7 +91,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
if (!Sealed)
{
bw.Write(Memory.XorHash);
var ms = Memory.GetXorStream(Memory.Start, Used, false);
var ms = Memory.GetXorStream(Memory.Start, WaterboxUtils.AlignUp(Used), false);
ms.CopyTo(bw.BaseStream);
}
else
@ -116,11 +116,12 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
throw new InvalidOperationException(string.Format("Hash did not match for heap {0}. Is this the same rom with the same SyncSettings?", Name));
}
var usedAligned = WaterboxUtils.AlignUp(used);
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.None);
Memory.Protect(Memory.Start, used, MemoryBlock.Protection.RW);
var ms = Memory.GetXorStream(Memory.Start, used, true);
WaterboxUtils.CopySome(br.BaseStream, ms, (long)used);
var ms = Memory.GetXorStream(Memory.Start, usedAligned, true);
WaterboxUtils.CopySome(br.BaseStream, ms, (long)usedAligned);
Used = used;
}
else

File diff suppressed because it is too large Load Diff

View File

@ -1,433 +1,435 @@
using BizHawk.Common;
using BizHawk.Common;
using BizHawk.Common.BizInvoke;
using BizHawk.Emulation.Common;
using PeNet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace BizHawk.Emulation.Cores.Waterbox
{
/// <summary>
/// represents one PE file. used in PeRunner
/// </summary>
internal class PeWrapper : IImportResolver, IBinaryStateable, IDisposable
{
public Dictionary<int, IntPtr> ExportsByOrdinal { get; } = new Dictionary<int, IntPtr>();
/// <summary>
/// ordinal only exports will not show up in this list!
/// </summary>
public Dictionary<string, IntPtr> ExportsByName { get; } = new Dictionary<string, IntPtr>();
public Dictionary<string, Dictionary<string, IntPtr>> ImportsByModule { get; } =
new Dictionary<string, Dictionary<string, IntPtr>>();
private class Section
{
public string Name { get; set; }
public ulong Start { get; set; }
public ulong Size { get; set; }
public bool W { get; set; }
public bool R { get; set; }
public bool X { get; set; }
public MemoryBlock.Protection Prot { get; set; }
public ulong DiskStart { get; set; }
public ulong DiskSize { get; set; }
}
private readonly Dictionary<string, Section> _sectionsByName = new Dictionary<string, Section>();
private readonly List<Section> _sections = new List<Section>();
private Section _imports;
public string ModuleName { get; }
private readonly byte[] _fileData;
private readonly PeFile _pe;
private readonly byte[] _fileHash;
public ulong Size { get; }
public ulong Start { get; private set; }
public long LoadOffset { get; private set; }
public MemoryBlock Memory { get; private set; }
public IntPtr EntryPoint { get; private set; }
/// <summary>
/// for midipix-built PEs, pointer to the construtors to run during init
/// </summary>
public IntPtr CtorList { get; private set; }
/// <summary>
/// for midipix-build PEs, pointer to the destructors to run during fini
/// </summary>
public IntPtr DtorList { get; private set; }
// true if the imports have been set to readonly
private bool _importsSealed = false;
/*[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate bool DllEntry(IntPtr instance, int reason, IntPtr reserved);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate void ExeEntry();*/
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
using BizHawk.Emulation.Common;
using PeNet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace BizHawk.Emulation.Cores.Waterbox
{
/// <summary>
/// represents one PE file. used in PeRunner
/// </summary>
internal class PeWrapper : IImportResolver, IBinaryStateable, IDisposable
{
public Dictionary<int, IntPtr> ExportsByOrdinal { get; } = new Dictionary<int, IntPtr>();
/// <summary>
/// ordinal only exports will not show up in this list!
/// </summary>
public Dictionary<string, IntPtr> ExportsByName { get; } = new Dictionary<string, IntPtr>();
public Dictionary<string, Dictionary<string, IntPtr>> ImportsByModule { get; } =
new Dictionary<string, Dictionary<string, IntPtr>>();
private class Section
{
public string Name { get; set; }
public ulong Start { get; set; }
public ulong Size { get; set; }
public ulong SavedSize { get; set; }
public bool W { get; set; }
public bool R { get; set; }
public bool X { get; set; }
public MemoryBlock.Protection Prot { get; set; }
public ulong DiskStart { get; set; }
public ulong DiskSize { get; set; }
}
private readonly Dictionary<string, Section> _sectionsByName = new Dictionary<string, Section>();
private readonly List<Section> _sections = new List<Section>();
private Section _imports;
public string ModuleName { get; }
private readonly byte[] _fileData;
private readonly PeFile _pe;
private readonly byte[] _fileHash;
public ulong Size { get; }
public ulong Start { get; private set; }
public long LoadOffset { get; private set; }
public MemoryBlock Memory { get; private set; }
public IntPtr EntryPoint { get; private set; }
/// <summary>
/// for midipix-built PEs, pointer to the construtors to run during init
/// </summary>
public IntPtr CtorList { get; private set; }
/// <summary>
/// for midipix-build PEs, pointer to the destructors to run during fini
/// </summary>
public IntPtr DtorList { get; private set; }
// true if the imports have been set to readonly
private bool _importsSealed = false;
/*[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate bool DllEntry(IntPtr instance, int reason, IntPtr reserved);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate void ExeEntry();*/
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void GlobalCtor();
/*public bool RunDllEntry()
{
var entryThunk = (DllEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(DllEntry));
return entryThunk(Z.US(Start), 1, IntPtr.Zero); // DLL_PROCESS_ATTACH
}
public void RunExeEntry()
{
var entryThunk = (ExeEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(ExeEntry));
entryThunk();
/*public bool RunDllEntry()
{
var entryThunk = (DllEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(DllEntry));
return entryThunk(Z.US(Start), 1, IntPtr.Zero); // DLL_PROCESS_ATTACH
}
public void RunExeEntry()
{
var entryThunk = (ExeEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(ExeEntry));
entryThunk();
}*/
public unsafe void RunGlobalCtors()
{
int did = 0;
if (CtorList != IntPtr.Zero)
{
IntPtr* p = (IntPtr*)CtorList;
IntPtr f;
while ((f = *++p) != IntPtr.Zero) // skip 0th dummy pointer
{
var ctorThunk = (GlobalCtor)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(f, typeof(GlobalCtor));
//Console.WriteLine(f);
//System.Diagnostics.Debugger.Break();
ctorThunk();
did++;
}
}
if (did > 0)
{
Console.WriteLine($"Did {did} global ctors for {ModuleName}");
}
else
{
Console.WriteLine($"Warn: no global ctors for {ModuleName}; possibly no C++?");
}
}
public PeWrapper(string moduleName, byte[] fileData, ulong destAddress)
{
ModuleName = moduleName;
_fileData = fileData;
_pe = new PeFile(fileData);
Size = _pe.ImageNtHeaders.OptionalHeader.SizeOfImage;
Start = destAddress;
if (Size < _pe.ImageSectionHeaders.Max(s => (ulong)s.VirtualSize + s.VirtualAddress))
{
throw new InvalidOperationException("Image not Big Enough");
}
_fileHash = WaterboxUtils.Hash(fileData);
foreach (var s in _pe.ImageSectionHeaders)
{
ulong start = Start + s.VirtualAddress;
ulong length = s.VirtualSize;
MemoryBlock.Protection prot;
var r = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_READ) != 0;
var w = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_WRITE) != 0;
var x = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_EXECUTE) != 0;
if (w && x)
{
throw new InvalidOperationException("Write and Execute not allowed");
}
prot = x ? MemoryBlock.Protection.RX : w ? MemoryBlock.Protection.RW : MemoryBlock.Protection.R;
var section = new Section
{
// chop off possible null padding from name
Name = Encoding.ASCII.GetString(s.Name, 0,
(s.Name.Select((v, i) => new { v, i }).FirstOrDefault(a => a.v == 0) ?? new { v = (byte)0, i = s.Name.Length }).i),
Start = start,
Size = length,
R = r,
W = w,
X = x,
Prot = prot,
DiskStart = s.PointerToRawData,
DiskSize = s.SizeOfRawData
};
_sections.Add(section);
_sectionsByName.Add(section.Name, section);
}
_sectionsByName.TryGetValue(".idata", out _imports);
Mount();
}
/// <summary>
/// set memory protections.
/// </summary>
private void ProtectMemory()
{
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.R);
foreach (var s in _sections)
{
Memory.Protect(s.Start, s.Size, s.Prot);
}
}
/// <summary>
/// load the PE into memory
/// </summary>
/// <param name="org">start address</param>
private void Mount()
{
LoadOffset = (long)Start - (long)_pe.ImageNtHeaders.OptionalHeader.ImageBase;
Memory = new MemoryBlock(Start, Size);
Memory.Activate();
Memory.Protect(Start, Size, MemoryBlock.Protection.RW);
// copy headers
Marshal.Copy(_fileData, 0, Z.US(Start), (int)_pe.ImageNtHeaders.OptionalHeader.SizeOfHeaders);
// copy sections
foreach (var s in _sections)
{
ulong datalength = Math.Min(s.Size, s.DiskSize);
Marshal.Copy(_fileData, (int)s.DiskStart, Z.US(s.Start), (int)datalength);
WaterboxUtils.ZeroMemory(Z.US(s.Start + datalength), (long)(s.Size - datalength));
}
// apply relocations
var n32 = 0;
var n64 = 0;
foreach (var rel in _pe.ImageRelocationDirectory)
{
foreach (var to in rel.TypeOffsets)
{
ulong address = Start + rel.VirtualAddress + to.Offset;
switch (to.Type)
{
// there are many other types of relocation specified,
// but the only that are used is 0 (does nothing), 3 (32 bit standard), 10 (64 bit standard)
case 3: // IMAGE_REL_BASED_HIGHLOW
{
byte[] tmp = new byte[4];
Marshal.Copy(Z.US(address), tmp, 0, 4);
uint val = BitConverter.ToUInt32(tmp, 0);
tmp = BitConverter.GetBytes((uint)(val + LoadOffset));
Marshal.Copy(tmp, 0, Z.US(address), 4);
n32++;
break;
}
case 10: // IMAGE_REL_BASED_DIR64
{
byte[] tmp = new byte[8];
Marshal.Copy(Z.US(address), tmp, 0, 8);
long val = BitConverter.ToInt64(tmp, 0);
tmp = BitConverter.GetBytes(val + LoadOffset);
Marshal.Copy(tmp, 0, Z.US(address), 8);
n64++;
break;
}
}
}
}
if (IntPtr.Size == 8 && n32 > 0)
{
// check mcmodel, etc
throw new InvalidOperationException("32 bit relocations found in 64 bit dll! This will fail.");
}
Console.WriteLine($"Processed {n32} 32 bit and {n64} 64 bit relocations");
ProtectMemory();
// publish exports
EntryPoint = Z.US(Start + _pe.ImageNtHeaders.OptionalHeader.AddressOfEntryPoint);
foreach (var export in _pe.ExportedFunctions)
{
if (export.Name != null)
ExportsByName.Add(export.Name, Z.US(Start + export.Address));
ExportsByOrdinal.Add(export.Ordinal, Z.US(Start + export.Address));
}
// collect information about imports
// NB: Hints are not the same as Ordinals
foreach (var import in _pe.ImportedFunctions)
{
Dictionary<string, IntPtr> module;
if (!ImportsByModule.TryGetValue(import.DLL, out module))
{
module = new Dictionary<string, IntPtr>();
ImportsByModule.Add(import.DLL, module);
}
var dest = Start + import.Thunk;
if (_imports == null || dest >= _imports.Start + _imports.Size || dest < _imports.Start)
throw new InvalidOperationException("Import record outside of .idata!");
module.Add(import.Name, Z.US(dest));
}
Section midipix;
if (_sectionsByName.TryGetValue(".midipix", out midipix))
{
var dataOffset = midipix.DiskStart;
CtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x30)) + LoadOffset);
DtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x38)) + LoadOffset);
}
Console.WriteLine($"Mounted `{ModuleName}` @{Start:x16}");
foreach (var s in _sections.OrderBy(s => s.Start))
{
Console.WriteLine(" @{0:x16} {1}{2}{3} `{4}` {5} bytes",
s.Start,
s.R ? "R" : " ",
s.W ? "W" : " ",
s.X ? "X" : " ",
s.Name,
s.Size);
}
Console.WriteLine("GDB Symbol Load:");
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)
{
IntPtr ret;
ExportsByName.TryGetValue(entryPoint, out ret);
return ret;
}
public void ConnectImports(string moduleName, IImportResolver module)
{
// this is called once internally when bootstrapping, and externally
// when we need to restore a savestate from another run. so imports might or might not be sealed
if (_importsSealed && _imports != null)
Memory.Protect(_imports.Start, _imports.Size, MemoryBlock.Protection.RW);
Dictionary<string, IntPtr> imports;
if (ImportsByModule.TryGetValue(moduleName, out imports))
{
foreach (var kvp in imports)
{
var valueArray = new IntPtr[] { module.SafeResolve(kvp.Key) };
Marshal.Copy(valueArray, 0, kvp.Value, 1);
}
}
if (_importsSealed && _imports != null)
Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
}
public void SealImportsAndTakeXorSnapshot()
{
if (_importsSealed)
throw new InvalidOperationException("Imports already sealed!");
// save import values, then zero them all (for hash purposes), then take our snapshot, then load them again,
// then set the .idata area to read only
if (_imports != null)
{
var data = new byte[_imports.Size];
Marshal.Copy(Z.US(_imports.Start), data, 0, (int)_imports.Size);
WaterboxUtils.ZeroMemory(Z.US(_imports.Start), (long)_imports.Size);
Memory.SaveXorSnapshot();
Marshal.Copy(data, 0, Z.US(_imports.Start), (int)_imports.Size);
_imports.W = false;
Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
}
else
{
Memory.SaveXorSnapshot();
}
_importsSealed = true;
}
private bool _disposed = false;
public void Dispose()
{
if (!_disposed)
{
Memory.Dispose();
Memory = null;
_disposed = true;
}
}
const ulong MAGIC = 0x420cccb1a2e17420;
public void SaveStateBinary(BinaryWriter bw)
{
if (!_importsSealed)
throw new InvalidOperationException(".idata sections must be closed before saving state");
bw.Write(MAGIC);
bw.Write(_fileHash);
bw.Write(Memory.XorHash);
bw.Write(Start);
foreach (var s in _sections)
{
if (!s.W)
continue;
var ms = Memory.GetXorStream(s.Start, s.Size, false);
bw.Write(s.Size);
ms.CopyTo(bw.BaseStream);
}
}
public void LoadStateBinary(BinaryReader br)
{
if (!_importsSealed)
// operations happening in the wrong order. probable cause: internal logic error. make sure frontend calls Seal
throw new InvalidOperationException(".idata sections must be closed before loading state");
if (br.ReadUInt64() != MAGIC)
// file id is missing. probable cause: garbage savestate
throw new InvalidOperationException("Savestate corrupted!");
if (!br.ReadBytes(_fileHash.Length).SequenceEqual(_fileHash))
// 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?");
if (!br.ReadBytes(Memory.XorHash.Length).SequenceEqual(Memory.XorHash))
// the post-Seal memory state is different. probable cause: different rom or different version of rom,
// different syncsettings
throw new InvalidOperationException("Memory consistency check failed. Is this savestate from different SyncSettings?");
if (br.ReadUInt64() != Start)
// dll loaded somewhere else. probable cause: internal logic error.
// unlikely to get this far if the previous checks pssed
throw new InvalidOperationException("Trickys elves moved on you!");
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
foreach (var s in _sections)
{
if (!s.W)
continue;
if (br.ReadUInt64() != s.Size)
throw new InvalidOperationException("Unexpected section size for " + s.Name);
var ms = Memory.GetXorStream(s.Start, s.Size, true);
WaterboxUtils.CopySome(br.BaseStream, ms, (long)s.Size);
}
ProtectMemory();
}
}
}
public unsafe void RunGlobalCtors()
{
int did = 0;
if (CtorList != IntPtr.Zero)
{
IntPtr* p = (IntPtr*)CtorList;
IntPtr f;
while ((f = *++p) != IntPtr.Zero) // skip 0th dummy pointer
{
var ctorThunk = (GlobalCtor)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(f, typeof(GlobalCtor));
//Console.WriteLine(f);
//System.Diagnostics.Debugger.Break();
ctorThunk();
did++;
}
}
if (did > 0)
{
Console.WriteLine($"Did {did} global ctors for {ModuleName}");
}
else
{
Console.WriteLine($"Warn: no global ctors for {ModuleName}; possibly no C++?");
}
}
public PeWrapper(string moduleName, byte[] fileData, ulong destAddress)
{
ModuleName = moduleName;
_fileData = fileData;
_pe = new PeFile(fileData);
Size = _pe.ImageNtHeaders.OptionalHeader.SizeOfImage;
Start = destAddress;
if (Size < _pe.ImageSectionHeaders.Max(s => (ulong)s.VirtualSize + s.VirtualAddress))
{
throw new InvalidOperationException("Image not Big Enough");
}
_fileHash = WaterboxUtils.Hash(fileData);
foreach (var s in _pe.ImageSectionHeaders)
{
ulong start = Start + s.VirtualAddress;
ulong length = s.VirtualSize;
MemoryBlock.Protection prot;
var r = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_READ) != 0;
var w = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_WRITE) != 0;
var x = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_EXECUTE) != 0;
if (w && x)
{
throw new InvalidOperationException("Write and Execute not allowed");
}
prot = x ? MemoryBlock.Protection.RX : w ? MemoryBlock.Protection.RW : MemoryBlock.Protection.R;
var section = new Section
{
// chop off possible null padding from name
Name = Encoding.ASCII.GetString(s.Name, 0,
(s.Name.Select((v, i) => new { v, i }).FirstOrDefault(a => a.v == 0) ?? new { v = (byte)0, i = s.Name.Length }).i),
Start = start,
Size = length,
SavedSize = WaterboxUtils.AlignUp(length),
R = r,
W = w,
X = x,
Prot = prot,
DiskStart = s.PointerToRawData,
DiskSize = s.SizeOfRawData
};
_sections.Add(section);
_sectionsByName.Add(section.Name, section);
}
_sectionsByName.TryGetValue(".idata", out _imports);
Mount();
}
/// <summary>
/// set memory protections.
/// </summary>
private void ProtectMemory()
{
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.R);
foreach (var s in _sections)
{
Memory.Protect(s.Start, s.Size, s.Prot);
}
}
/// <summary>
/// load the PE into memory
/// </summary>
/// <param name="org">start address</param>
private void Mount()
{
LoadOffset = (long)Start - (long)_pe.ImageNtHeaders.OptionalHeader.ImageBase;
Memory = new MemoryBlock(Start, Size);
Memory.Activate();
Memory.Protect(Start, Size, MemoryBlock.Protection.RW);
// copy headers
Marshal.Copy(_fileData, 0, Z.US(Start), (int)_pe.ImageNtHeaders.OptionalHeader.SizeOfHeaders);
// copy sections
foreach (var s in _sections)
{
ulong datalength = Math.Min(s.Size, s.DiskSize);
Marshal.Copy(_fileData, (int)s.DiskStart, Z.US(s.Start), (int)datalength);
WaterboxUtils.ZeroMemory(Z.US(s.Start + datalength), (long)(s.SavedSize - datalength));
}
// apply relocations
var n32 = 0;
var n64 = 0;
foreach (var rel in _pe.ImageRelocationDirectory)
{
foreach (var to in rel.TypeOffsets)
{
ulong address = Start + rel.VirtualAddress + to.Offset;
switch (to.Type)
{
// there are many other types of relocation specified,
// but the only that are used is 0 (does nothing), 3 (32 bit standard), 10 (64 bit standard)
case 3: // IMAGE_REL_BASED_HIGHLOW
{
byte[] tmp = new byte[4];
Marshal.Copy(Z.US(address), tmp, 0, 4);
uint val = BitConverter.ToUInt32(tmp, 0);
tmp = BitConverter.GetBytes((uint)(val + LoadOffset));
Marshal.Copy(tmp, 0, Z.US(address), 4);
n32++;
break;
}
case 10: // IMAGE_REL_BASED_DIR64
{
byte[] tmp = new byte[8];
Marshal.Copy(Z.US(address), tmp, 0, 8);
long val = BitConverter.ToInt64(tmp, 0);
tmp = BitConverter.GetBytes(val + LoadOffset);
Marshal.Copy(tmp, 0, Z.US(address), 8);
n64++;
break;
}
}
}
}
if (IntPtr.Size == 8 && n32 > 0)
{
// check mcmodel, etc
throw new InvalidOperationException("32 bit relocations found in 64 bit dll! This will fail.");
}
Console.WriteLine($"Processed {n32} 32 bit and {n64} 64 bit relocations");
ProtectMemory();
// publish exports
EntryPoint = Z.US(Start + _pe.ImageNtHeaders.OptionalHeader.AddressOfEntryPoint);
foreach (var export in _pe.ExportedFunctions)
{
if (export.Name != null)
ExportsByName.Add(export.Name, Z.US(Start + export.Address));
ExportsByOrdinal.Add(export.Ordinal, Z.US(Start + export.Address));
}
// collect information about imports
// NB: Hints are not the same as Ordinals
foreach (var import in _pe.ImportedFunctions)
{
Dictionary<string, IntPtr> module;
if (!ImportsByModule.TryGetValue(import.DLL, out module))
{
module = new Dictionary<string, IntPtr>();
ImportsByModule.Add(import.DLL, module);
}
var dest = Start + import.Thunk;
if (_imports == null || dest >= _imports.Start + _imports.Size || dest < _imports.Start)
throw new InvalidOperationException("Import record outside of .idata!");
module.Add(import.Name, Z.US(dest));
}
Section midipix;
if (_sectionsByName.TryGetValue(".midipix", out midipix))
{
var dataOffset = midipix.DiskStart;
CtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x30)) + LoadOffset);
DtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x38)) + LoadOffset);
}
Console.WriteLine($"Mounted `{ModuleName}` @{Start:x16}");
foreach (var s in _sections.OrderBy(s => s.Start))
{
Console.WriteLine(" @{0:x16} {1}{2}{3} `{4}` {5} bytes",
s.Start,
s.R ? "R" : " ",
s.W ? "W" : " ",
s.X ? "X" : " ",
s.Name,
s.Size);
}
Console.WriteLine("GDB Symbol Load:");
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)
{
IntPtr ret;
ExportsByName.TryGetValue(entryPoint, out ret);
return ret;
}
public void ConnectImports(string moduleName, IImportResolver module)
{
// this is called once internally when bootstrapping, and externally
// when we need to restore a savestate from another run. so imports might or might not be sealed
if (_importsSealed && _imports != null)
Memory.Protect(_imports.Start, _imports.Size, MemoryBlock.Protection.RW);
Dictionary<string, IntPtr> imports;
if (ImportsByModule.TryGetValue(moduleName, out imports))
{
foreach (var kvp in imports)
{
var valueArray = new IntPtr[] { module.SafeResolve(kvp.Key) };
Marshal.Copy(valueArray, 0, kvp.Value, 1);
}
}
if (_importsSealed && _imports != null)
Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
}
public void SealImportsAndTakeXorSnapshot()
{
if (_importsSealed)
throw new InvalidOperationException("Imports already sealed!");
// save import values, then zero them all (for hash purposes), then take our snapshot, then load them again,
// then set the .idata area to read only
if (_imports != null)
{
var data = new byte[_imports.Size];
Marshal.Copy(Z.US(_imports.Start), data, 0, (int)_imports.Size);
WaterboxUtils.ZeroMemory(Z.US(_imports.Start), (long)_imports.Size);
Memory.SaveXorSnapshot();
Marshal.Copy(data, 0, Z.US(_imports.Start), (int)_imports.Size);
_imports.W = false;
Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
}
else
{
Memory.SaveXorSnapshot();
}
_importsSealed = true;
}
private bool _disposed = false;
public void Dispose()
{
if (!_disposed)
{
Memory.Dispose();
Memory = null;
_disposed = true;
}
}
const ulong MAGIC = 0x420cccb1a2e17420;
public void SaveStateBinary(BinaryWriter bw)
{
if (!_importsSealed)
throw new InvalidOperationException(".idata sections must be closed before saving state");
bw.Write(MAGIC);
bw.Write(_fileHash);
bw.Write(Memory.XorHash);
bw.Write(Start);
foreach (var s in _sections)
{
if (!s.W)
continue;
var ms = Memory.GetXorStream(s.Start, s.SavedSize, false);
bw.Write(s.SavedSize);
ms.CopyTo(bw.BaseStream);
}
}
public void LoadStateBinary(BinaryReader br)
{
if (!_importsSealed)
// operations happening in the wrong order. probable cause: internal logic error. make sure frontend calls Seal
throw new InvalidOperationException(".idata sections must be closed before loading state");
if (br.ReadUInt64() != MAGIC)
// file id is missing. probable cause: garbage savestate
throw new InvalidOperationException("Savestate corrupted!");
if (!br.ReadBytes(_fileHash.Length).SequenceEqual(_fileHash))
// 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?");
if (!br.ReadBytes(Memory.XorHash.Length).SequenceEqual(Memory.XorHash))
// the post-Seal memory state is different. probable cause: different rom or different version of rom,
// different syncsettings
throw new InvalidOperationException("Memory consistency check failed. Is this savestate from different SyncSettings?");
if (br.ReadUInt64() != Start)
// dll loaded somewhere else. probable cause: internal logic error.
// unlikely to get this far if the previous checks pssed
throw new InvalidOperationException("Trickys elves moved on you!");
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
foreach (var s in _sections)
{
if (!s.W)
continue;
if (br.ReadUInt64() != s.SavedSize)
throw new InvalidOperationException("Unexpected section size for " + s.Name);
var ms = Memory.GetXorStream(s.Start, s.SavedSize, true);
WaterboxUtils.CopySome(br.BaseStream, ms, (long)s.SavedSize);
}
ProtectMemory();
}
}
}

View File

@ -1,5 +1,6 @@
using BizHawk.Common;
using BizHawk.Common.BizInvoke;
using BizHawk.Common.BufferExtensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -30,9 +31,15 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// </summary>
private List<MemoryBlock> _memoryBlocks = new List<MemoryBlock>();
protected void AddMemoryBlock(MemoryBlock block)
/// <summary>
/// an informative name for each memory block: used for debugging purposes
/// </summary>
private List<string> _memoryBlockNames = new List<string>();
protected void AddMemoryBlock(MemoryBlock block, string name)
{
_memoryBlocks.Add(block);
_memoryBlockNames.Add(name);
}
protected void PurgeMemoryBlocks()
@ -130,6 +137,17 @@ namespace BizHawk.Emulation.Cores.Waterbox
foreach (var m in _memoryBlocks)
m.Activate();
}
public void PrintDebuggingInfo()
{
using (this.EnterExit())
{
foreach (var a in _memoryBlocks.Zip(_memoryBlockNames, (m, s) => new { m, s }))
{
Console.WriteLine($"{a.m.FullHash().BytesToHexString()}: {a.s}");
}
}
}
private bool _disposed = false;