From a7f7583fcc7f6daaab079b6e2a1da374d3b966eb Mon Sep 17 00:00:00 2001 From: nattthebear Date: Tue, 20 Jun 2017 20:51:06 -0400 Subject: [PATCH] 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 --- BizHawk.Common/BizInvoke/MemoryBlock.cs | 17 + BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs | 8 +- BizHawk.Emulation.Cores/Waterbox/Heap.cs | 21 +- BizHawk.Emulation.Cores/Waterbox/PeRunner.cs | 1772 ++++++++--------- BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs | 860 ++++---- BizHawk.Emulation.Cores/Waterbox/Swappable.cs | 20 +- 6 files changed, 1368 insertions(+), 1330 deletions(-) diff --git a/BizHawk.Common/BizInvoke/MemoryBlock.cs b/BizHawk.Common/BizInvoke/MemoryBlock.cs index e0437898fc..3407960818 100644 --- a/BizHawk.Common/BizInvoke/MemoryBlock.cs +++ b/BizHawk.Common/BizInvoke/MemoryBlock.cs @@ -186,6 +186,23 @@ namespace BizHawk.Common.BizInvoke ProtectAll(); } + /// + /// take a hash of the current full contents of the block, including unreadable areas + /// + /// + 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; diff --git a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs index 5498331740..000d762995 100644 --- a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs +++ b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs @@ -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(); diff --git a/BizHawk.Emulation.Cores/Waterbox/Heap.cs b/BizHawk.Emulation.Cores/Waterbox/Heap.cs index 8d2d58b35d..26eb952972 100644 --- a/BizHawk.Emulation.Cores/Waterbox/Heap.cs +++ b/BizHawk.Emulation.Cores/Waterbox/Heap.cs @@ -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 diff --git a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs index 90d78b15ea..913312ccd3 100644 --- a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs +++ b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs @@ -1,359 +1,359 @@ -using BizHawk.Common; -using BizHawk.Common.BizInvoke; -using BizHawk.Emulation.Common; -using PeNet; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - public class PeRunnerOptions - { - // string directory, string filename, ulong heapsize, ulong sealedheapsize, ulong invisibleheapsize - /// - /// path which the main executable and all associated libraries should be found - /// - public string Path { get; set; } - - /// - /// filename of the main executable; expected to be in Path - /// - public string Filename { get; set; } - - /// - /// how large the normal heap should be. it services sbrk calls - /// can be 0, but sbrk calls will crash. - /// - public uint SbrkHeapSizeKB { get; set; } - - /// - /// 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, and any alloc_sealed() calls - /// - public uint SealedHeapSizeKB { get; set; } - - /// - /// 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, and any alloc_invisible() calls - /// - public uint InvisibleHeapSizeKB { get; set; } - - /// - /// how large the "plain" heap should be. it is savestated, and contains - /// Must be > 0 and at least large enough for the internal pthread structure, and any alloc_plain() calls - /// - public uint PlainHeapSizeKB { get; set; } - - /// - /// how large the mmap heap should be. it is savestated. - /// can be 0, but mmap calls will crash. - /// - public uint MmapHeapSizeKB { get; set; } - - /// - /// start address in memory - /// - public ulong StartAddress { get; set; } = PeRunner.CanonicalStart; - } - - public class PeRunner : Swappable, IImportResolver, IBinaryStateable - { - /// - /// serves as a standin for libpsxscl.so - /// - private class Psx - { - private readonly PeRunner _parent; - private readonly List _traps = new List(); - - public Psx(PeRunner parent) - { - _parent = parent; - } - - [StructLayout(LayoutKind.Sequential)] - public struct PsxContext - { - public int Size; - public int Options; - public IntPtr SyscallVtable; - public IntPtr LdsoVtable; - public IntPtr PsxVtable; - public uint SysIdx; - public uint LibcIdx; - public IntPtr PthreadSurrogate; - public IntPtr PthreadCreate; - public IntPtr DoGlobalCtors; - public IntPtr DoGlobalDtors; - } - - private void PopulateVtable(string moduleName, ICollection entries, IntPtr table) - { - var imports = _parent._exports[moduleName]; - var pointers = entries.Select(e => - { - var ptr = imports.Resolve(e); - if (ptr == IntPtr.Zero) - { - var s = string.Format("Trapped on unimplemented function {0}:{1}", moduleName, e); - Action del = () => - { - Console.WriteLine(s); - throw new InvalidOperationException(s); - }; - _traps.Add(del); - ptr = CallingConventionAdapters.Waterbox.GetFunctionPointerForDelegate(del); - } - return ptr; - }).ToArray(); - Marshal.Copy(pointers, 0, table, pointers.Length); - } - - /// - /// called by the PeRunner to reset pointers after a loadsave - /// - public void ReloadVtables() - { - _traps.Clear(); - - PopulateVtable("__syscalls", Enumerable.Range(0, 340).Select(i => "n" + i).ToList(), _syscallVtable); - PopulateVtable("__syscalls", new[] // ldso - { - "dladdr", "dlinfo", "dlsym", "dlopen", "dlclose", "dlerror", "reset_tls" - }, _ldsoVtable); - PopulateVtable("__syscalls", new[] // psx - { - "start_main", "convert_thread", "unmapself", "log_output", "pthread_self" - }, _psxVtable); - /*unsafe - { - var ptr = (IntPtr*)_psxVtable; - Console.WriteLine("AWESOMES: " + ptr[0]); - }*/ - } - - private IntPtr _syscallVtable; - private IntPtr _ldsoVtable; - private IntPtr _psxVtable; - - private IntPtr AllocVtable(int count) - { - return Z.US(_parent._invisibleheap.Allocate((ulong)(count * IntPtr.Size), 16)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__psx_init")] - public int PsxInit(ref int argc, ref IntPtr argv, ref IntPtr envp, [In, Out]ref PsxContext context) - { - { - // argc = 1, argv = ["foobar, NULL], envp = [NULL] - argc = 1; - var argArea = _parent._sealedheap.Allocate(32, 16); - argv = Z.US(argArea); - envp = Z.US(argArea + (uint)IntPtr.Size * 2); - Marshal.WriteIntPtr(Z.US(argArea), Z.US(argArea + 24)); - Marshal.WriteInt64(Z.US(argArea + 24), 0x7261626f6f66); - } - - context.SyscallVtable = _syscallVtable = AllocVtable(340); - context.LdsoVtable = _ldsoVtable = AllocVtable(7); - context.PsxVtable = _psxVtable = AllocVtable(5); - // ctx comes from the native stack, where it could have any garbage in uninited fields - context.SysIdx = 0; - context.LibcIdx = 0; - context.DoGlobalCtors = IntPtr.Zero; - context.DoGlobalDtors = IntPtr.Zero; - - ReloadVtables(); - - // TODO: we can't set these pointers 4 and preserve across session - // until we find out where they get saved to and add a way to reset them - /*var extraTable = CreateVtable("__syscalls", new[] - { - "pthread_surrogate", "pthread_create", "do_global_ctors", "do_global_dtors" - }); - var tmp = new IntPtr[4]; - Marshal.Copy(extraTable, tmp, 0, 4); - context.PthreadSurrogate = tmp[0]; - context.PthreadCreate = tmp[1]; - context.DoGlobalCtors = tmp[2]; - context.DoGlobalDtors = tmp[3];*/ - - return 0; // success - } - } - - /// - /// special emulator-functions - /// - private class Emu - { - private readonly PeRunner _parent; - public Emu(PeRunner parent) - { - _parent = parent; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_sealed")] - public IntPtr AllocSealed(UIntPtr size) - { - return Z.US(_parent._sealedheap.Allocate((ulong)size, 16)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_invisible")] - public IntPtr AllocInvisible(UIntPtr size) - { - return Z.US(_parent._invisibleheap.Allocate((ulong)size, 16)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_plain")] - public IntPtr AllocPlain(UIntPtr size) - { - return Z.US(_parent._plainheap.Allocate((ulong)size, 16)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "_debug_puts")] - public void DebugPuts(IntPtr s) - { - Console.WriteLine("_debug_puts:" + Marshal.PtrToStringAnsi(s)); - } - } - - /// - /// syscall emulation layer, as well as a bit of other stuff - /// - private class Syscalls - { - private readonly PeRunner _parent; - public Syscalls(PeRunner parent) - { - _parent = parent; - } - - private IntPtr _pthreadSelf; - - public void Init() - { - // as the inits are done in a defined order with a defined memory map, - // we don't need to savestate _pthreadSelf, only its contents - _pthreadSelf = Z.US(_parent._plainheap.Allocate(512, 1)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "log_output")] - public void DebugPuts(IntPtr s) - { - Console.WriteLine("_psx_log_output:" + Marshal.PtrToStringAnsi(s)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n12")] - public UIntPtr Brk(UIntPtr _p) - { - var heap = _parent._heap; - - var start = heap.Memory.Start; - var end = start + heap.Used; - var max = heap.Memory.End; - - var p = (ulong)_p; - - if (p < start || p > max) - { - // failure: return current break - return Z.UU(end); - } - else if (p > end) - { - // increase size of heap - heap.Allocate(p - end, 1); - return Z.UU(p); - } - else if (p < end) - { - throw new InvalidOperationException("We don't support shrinking heaps"); - } - else - { - // no change - return Z.UU(end); - } - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n16")] - public int IoCtl(int fd, ulong req) - { - return 0; // sure it worked, honest - } - - public struct Iovec - { - public IntPtr Base; - public ulong Length; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n0")] - public long Read(int fd, IntPtr buff, ulong count) - { - return 0; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n1")] - public long Write(int fd, IntPtr buff, ulong count) - { - return (long)count; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n19")] - public unsafe long Readv(int fd, Iovec* iov, int iovcnt) - { - return 0; - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "n20")] - public unsafe long Writev(int fd, Iovec* iov, int iovcnt) - { - long ret = 0; - for (int i = 0; i < iovcnt; i++) - { - ret += (long)iov[i].Length; - } - return ret; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n2")] - public int Open(string path, int flags, int mode) - { - return -1; - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "n3")] - public int Close(int fd) - { - return 0; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n4")] - public int Stat(string path, IntPtr statbuf) - { - return -1; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n5")] - public int Fstat(int fd, IntPtr statbuf) - { - return -1; - } - - //[UnmanagedFunctionPointer(CallingConvention.Cdecl)] - //private delegate int LibcStartMain(IntPtr main, int argc, IntPtr argv); - - //bool _firstTime = true; - - // aka __psx_init_frame (it's used elsewhere for thread setup) - // in midipix, this just sets up a SEH frame and then calls musl's start_main - [BizExport(CallingConvention.Cdecl, EntryPoint = "start_main")] - public unsafe int StartMain(IntPtr main, int argc, IntPtr argv, IntPtr libc_start_main) +using BizHawk.Common; +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Common; +using PeNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace BizHawk.Emulation.Cores.Waterbox +{ + public class PeRunnerOptions + { + // string directory, string filename, ulong heapsize, ulong sealedheapsize, ulong invisibleheapsize + /// + /// path which the main executable and all associated libraries should be found + /// + public string Path { get; set; } + + /// + /// filename of the main executable; expected to be in Path + /// + public string Filename { get; set; } + + /// + /// how large the normal heap should be. it services sbrk calls + /// can be 0, but sbrk calls will crash. + /// + public uint SbrkHeapSizeKB { get; set; } + + /// + /// 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, and any alloc_sealed() calls + /// + public uint SealedHeapSizeKB { get; set; } + + /// + /// 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, and any alloc_invisible() calls + /// + public uint InvisibleHeapSizeKB { get; set; } + + /// + /// how large the "plain" heap should be. it is savestated, and contains + /// Must be > 0 and at least large enough for the internal pthread structure, and any alloc_plain() calls + /// + public uint PlainHeapSizeKB { get; set; } + + /// + /// how large the mmap heap should be. it is savestated. + /// can be 0, but mmap calls will crash. + /// + public uint MmapHeapSizeKB { get; set; } + + /// + /// start address in memory + /// + public ulong StartAddress { get; set; } = PeRunner.CanonicalStart; + } + + public class PeRunner : Swappable, IImportResolver, IBinaryStateable + { + /// + /// serves as a standin for libpsxscl.so + /// + private class Psx + { + private readonly PeRunner _parent; + private readonly List _traps = new List(); + + public Psx(PeRunner parent) + { + _parent = parent; + } + + [StructLayout(LayoutKind.Sequential)] + public struct PsxContext + { + public int Size; + public int Options; + public IntPtr SyscallVtable; + public IntPtr LdsoVtable; + public IntPtr PsxVtable; + public uint SysIdx; + public uint LibcIdx; + public IntPtr PthreadSurrogate; + public IntPtr PthreadCreate; + public IntPtr DoGlobalCtors; + public IntPtr DoGlobalDtors; + } + + private void PopulateVtable(string moduleName, ICollection entries, IntPtr table) + { + var imports = _parent._exports[moduleName]; + var pointers = entries.Select(e => + { + var ptr = imports.Resolve(e); + if (ptr == IntPtr.Zero) + { + var s = string.Format("Trapped on unimplemented function {0}:{1}", moduleName, e); + Action del = () => + { + Console.WriteLine(s); + throw new InvalidOperationException(s); + }; + _traps.Add(del); + ptr = CallingConventionAdapters.Waterbox.GetFunctionPointerForDelegate(del); + } + return ptr; + }).ToArray(); + Marshal.Copy(pointers, 0, table, pointers.Length); + } + + /// + /// called by the PeRunner to reset pointers after a loadsave + /// + public void ReloadVtables() + { + _traps.Clear(); + + PopulateVtable("__syscalls", Enumerable.Range(0, 340).Select(i => "n" + i).ToList(), _syscallVtable); + PopulateVtable("__syscalls", new[] // ldso + { + "dladdr", "dlinfo", "dlsym", "dlopen", "dlclose", "dlerror", "reset_tls" + }, _ldsoVtable); + PopulateVtable("__syscalls", new[] // psx + { + "start_main", "convert_thread", "unmapself", "log_output", "pthread_self" + }, _psxVtable); + /*unsafe + { + var ptr = (IntPtr*)_psxVtable; + Console.WriteLine("AWESOMES: " + ptr[0]); + }*/ + } + + private IntPtr _syscallVtable; + private IntPtr _ldsoVtable; + private IntPtr _psxVtable; + + private IntPtr AllocVtable(int count) + { + return Z.US(_parent._invisibleheap.Allocate((ulong)(count * IntPtr.Size), 16)); + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "__psx_init")] + public int PsxInit(ref int argc, ref IntPtr argv, ref IntPtr envp, [In, Out]ref PsxContext context) + { + { + // argc = 1, argv = ["foobar, NULL], envp = [NULL] + argc = 1; + var argArea = _parent._sealedheap.Allocate(32, 16); + argv = Z.US(argArea); + envp = Z.US(argArea + (uint)IntPtr.Size * 2); + Marshal.WriteIntPtr(Z.US(argArea), Z.US(argArea + 24)); + Marshal.WriteInt64(Z.US(argArea + 24), 0x7261626f6f66); + } + + context.SyscallVtable = _syscallVtable = AllocVtable(340); + context.LdsoVtable = _ldsoVtable = AllocVtable(7); + context.PsxVtable = _psxVtable = AllocVtable(5); + // ctx comes from the native stack, where it could have any garbage in uninited fields + context.SysIdx = 0; + context.LibcIdx = 0; + context.DoGlobalCtors = IntPtr.Zero; + context.DoGlobalDtors = IntPtr.Zero; + + ReloadVtables(); + + // TODO: we can't set these pointers 4 and preserve across session + // until we find out where they get saved to and add a way to reset them + /*var extraTable = CreateVtable("__syscalls", new[] + { + "pthread_surrogate", "pthread_create", "do_global_ctors", "do_global_dtors" + }); + var tmp = new IntPtr[4]; + Marshal.Copy(extraTable, tmp, 0, 4); + context.PthreadSurrogate = tmp[0]; + context.PthreadCreate = tmp[1]; + context.DoGlobalCtors = tmp[2]; + context.DoGlobalDtors = tmp[3];*/ + + return 0; // success + } + } + + /// + /// special emulator-functions + /// + private class Emu + { + private readonly PeRunner _parent; + public Emu(PeRunner parent) + { + _parent = parent; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_sealed")] + public IntPtr AllocSealed(UIntPtr size) + { + return Z.US(_parent._sealedheap.Allocate((ulong)size, 16)); + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_invisible")] + public IntPtr AllocInvisible(UIntPtr size) + { + return Z.US(_parent._invisibleheap.Allocate((ulong)size, 16)); + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_plain")] + public IntPtr AllocPlain(UIntPtr size) + { + return Z.US(_parent._plainheap.Allocate((ulong)size, 16)); + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "_debug_puts")] + public void DebugPuts(IntPtr s) + { + Console.WriteLine("_debug_puts:" + Marshal.PtrToStringAnsi(s)); + } + } + + /// + /// syscall emulation layer, as well as a bit of other stuff + /// + private class Syscalls + { + private readonly PeRunner _parent; + public Syscalls(PeRunner parent) + { + _parent = parent; + } + + private IntPtr _pthreadSelf; + + public void Init() + { + // as the inits are done in a defined order with a defined memory map, + // we don't need to savestate _pthreadSelf, only its contents + _pthreadSelf = Z.US(_parent._plainheap.Allocate(512, 1)); + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "log_output")] + public void DebugPuts(IntPtr s) + { + Console.WriteLine("_psx_log_output:" + Marshal.PtrToStringAnsi(s)); + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n12")] + public UIntPtr Brk(UIntPtr _p) + { + var heap = _parent._heap; + + var start = heap.Memory.Start; + var end = start + heap.Used; + var max = heap.Memory.End; + + var p = (ulong)_p; + + if (p < start || p > max) + { + // failure: return current break + return Z.UU(end); + } + else if (p > end) + { + // increase size of heap + heap.Allocate(p - end, 1); + return Z.UU(p); + } + else if (p < end) + { + throw new InvalidOperationException("We don't support shrinking heaps"); + } + else + { + // no change + return Z.UU(end); + } + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n16")] + public int IoCtl(int fd, ulong req) + { + return 0; // sure it worked, honest + } + + public struct Iovec + { + public IntPtr Base; + public ulong Length; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n0")] + public long Read(int fd, IntPtr buff, ulong count) + { + return 0; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n1")] + public long Write(int fd, IntPtr buff, ulong count) + { + return (long)count; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n19")] + public unsafe long Readv(int fd, Iovec* iov, int iovcnt) + { + return 0; + } + [BizExport(CallingConvention.Cdecl, EntryPoint = "n20")] + public unsafe long Writev(int fd, Iovec* iov, int iovcnt) + { + long ret = 0; + for (int i = 0; i < iovcnt; i++) + { + ret += (long)iov[i].Length; + } + return ret; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n2")] + public int Open(string path, int flags, int mode) + { + return -1; + } + [BizExport(CallingConvention.Cdecl, EntryPoint = "n3")] + public int Close(int fd) + { + return 0; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n4")] + public int Stat(string path, IntPtr statbuf) + { + return -1; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n5")] + public int Fstat(int fd, IntPtr statbuf) + { + return -1; + } + + //[UnmanagedFunctionPointer(CallingConvention.Cdecl)] + //private delegate int LibcStartMain(IntPtr main, int argc, IntPtr argv); + + //bool _firstTime = true; + + // aka __psx_init_frame (it's used elsewhere for thread setup) + // in midipix, this just sets up a SEH frame and then calls musl's start_main + [BizExport(CallingConvention.Cdecl, EntryPoint = "start_main")] + public unsafe int StartMain(IntPtr main, int argc, IntPtr argv, IntPtr libc_start_main) { //var del = (LibcStartMain)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libc_start_main, typeof(LibcStartMain)); // this will init, and then call user main, and then call exit() @@ -365,533 +365,533 @@ namespace BizHawk.Emulation.Cores.Waterbox //if (_firstTime) //{ //_firstTime = false; - throw new InvalidOperationException("This shouldn't be called"); - //} - //else - //{ - // return 0; - //} - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_self")] - public IntPtr PthreadSelf() - { - return _pthreadSelf; - } - - /*[BizExport(CallingConvention.Cdecl, EntryPoint = "convert_thread")] - public void ConvertThread() - { - - }*/ - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n218")] - public long SetTidAddress(IntPtr address) - { - return 8675309; - } - - [StructLayout(LayoutKind.Sequential)] - public class TimeSpec - { - public long Seconds; - public long NanoSeconds; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n228")] - public int SysClockGetTime(int which, [In, Out] TimeSpec time) - { - time.Seconds = 1495889068; - 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; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "n10")] - public int MProtect(UIntPtr address, UIntPtr size, int prot) - { - 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 -1; // write only???? - case 3: mprot = MemoryBlock.Protection.RW; break; - case 1: mprot = MemoryBlock.Protection.R; break; - case 5: mprot = MemoryBlock.Protection.RX; break; - } - return _parent._mmapheap.Protect((ulong)address, (ulong)size, mprot) ? 0 : -1; - } - } - - /// - /// libc.so functions that we want to redirect - /// - private class LibcPatch - { - private readonly PeRunner _parent; - public LibcPatch(PeRunner parent) - { - _parent = parent; - } - - - private bool _didOnce = false; - private readonly Dictionary _specificKeys = new Dictionary(); - private uint _nextSpecificKey = 401; - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void PthreadCallback(); - - // pthread stuff: - // since we don't allow multiple threads (for now), this is all pretty simple - /* - // int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_create")] - public int PthreadKeyCreate(ref uint key, PthreadCallback destructor) - { - key = _nextSpecificKey++; - _specificKeys.Add(key, IntPtr.Zero); - return 0; - } - // int pthread_key_delete(pthread_key_t key); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_delete")] - public int PthreadKeyDelete(uint key) - { - _specificKeys.Remove(key); - return 0; - } - // int pthread_setspecific(pthread_key_t key, const void *value); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_setspecific")] - public int PthreadSetSpecific(uint key, IntPtr value) - { - _specificKeys[key] = value; - return 0; - } - // void *pthread_getspecific(pthread_key_t key); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_getspecific")] - public IntPtr PthreadGetSpecific(uint key) - { - IntPtr ret; - _specificKeys.TryGetValue(key, out ret); - return ret; - } - - // int pthread_once(pthread_once_t* once_control, void (*init_routine)(void)); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_once")] - public int PthreadOnce(IntPtr control, PthreadCallback init) - { - if (!_didOnce) - { - System.Diagnostics.Debugger.Break(); - _didOnce = true; - init(); - } - return 0; - } - - // int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t* attr); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_init")] - public int PthreadMutexInit(IntPtr mutex, IntPtr attr) { return 0; } - // int pthread_mutex_destroy(pthread_mutex_t* mutex); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_destroy")] - public int PthreadMutexDestroy(IntPtr mutex) { return 0; } - - // int pthread_mutex_lock(pthread_mutex_t* mutex); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_lock")] - public int PthreadMutexLock(IntPtr mutex) { return 0; } - // int pthread_mutex_trylock(pthread_mutex_t* mutex); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_trylock")] - public int PthreadMutexTryLock(IntPtr mutex) { return 0; } - // int pthread_mutex_unlock(pthread_mutex_t* mutex); - [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_unlock")] - public int PthreadMutexUnlock(IntPtr mutex) { return 0; }*/ - - - } - - /// - /// usual starting point for the executable - /// - public const ulong CanonicalStart = 0x0000036f00000000; - - public const ulong AlternateStart = 0x0000036e00000000; - - /// - /// the next place where we can put a module or heap - /// - private ulong _nextStart = CanonicalStart; - - /// - /// increment _nextStart after adding a module - /// - private void ComputeNextStart(ulong size) - { - _nextStart += size; - // align to 1MB, then increment 16MB - _nextStart = ((_nextStart - 1) | 0xfffff) + 0x1000001; - } - - /// - /// standard malloc() heap - /// - private Heap _heap; - - /// - /// sealed heap (writable only during init) - /// - private Heap _sealedheap; - - /// - /// invisible heap (not savestated, use with care) - /// - private Heap _invisibleheap; - - /// - /// extra savestated heap - /// - private Heap _plainheap; - - /// - /// memory map emulation - /// - private MapHeap _mmapheap; - - /// - /// all loaded PE files - /// - private readonly List _modules = new List(); - - /// - /// all loaded heaps - /// - private readonly List _heaps = new List(); - - /// - /// anything at all that needs to be disposed on finish - /// - private readonly List _disposeList = new List(); - - /// - /// anything at all that needs its state saved and loaded - /// - private readonly List _savestateComponents = new List(); - - /// - /// all of the exports, including real PeWrapper ones and fake ones - /// - private readonly Dictionary _exports = new Dictionary(); - - private Psx _psx; - private Emu _emu; - private Syscalls _syscalls; - private LibcPatch _libcpatch; - - /// - /// timestamp of creation acts as a sort of "object id" in the savestate - /// - private readonly long _createstamp = WaterboxUtils.Timestamp(); - - private Heap CreateHeapHelper(uint sizeKB, string name, bool saveStated) - { - 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)] - private delegate void LibcEntryRoutineD(IntPtr appMain, IntPtr psxInit, int options); - - public PeRunner(PeRunnerOptions opt) - { - _nextStart = opt.StartAddress; - Initialize(_nextStart); - using (this.EnterExit()) - { - // load any predefined exports - _psx = new Psx(this); - _exports.Add("libpsxscl.so", BizExvoker.GetExvoker(_psx, CallingConventionAdapters.Waterbox)); - _emu = new Emu(this); - _exports.Add("libemuhost.so", BizExvoker.GetExvoker(_emu, CallingConventionAdapters.Waterbox)); - _syscalls = new Syscalls(this); - _exports.Add("__syscalls", BizExvoker.GetExvoker(_syscalls, CallingConventionAdapters.Waterbox)); - - // load and connect all modules, starting with the executable - var todoModules = new Queue(); - todoModules.Enqueue(opt.Filename); - - while (todoModules.Count > 0) - { - var moduleName = todoModules.Dequeue(); - if (!_exports.ContainsKey(moduleName)) - { - var path = Path.Combine(opt.Path, moduleName); - var gzpath = path + ".gz"; - byte[] data; - if (File.Exists(gzpath)) - { - using (var fs = new FileStream(gzpath, FileMode.Open, FileAccess.Read)) - { - var tmp = new byte[4]; - fs.Seek(-4, SeekOrigin.End); - fs.Read(tmp, 0, 4); - int size = BitConverter.ToInt32(tmp, 0); - data = new byte[size]; - var ms = new MemoryStream(data); - fs.Seek(0, SeekOrigin.Begin); - using (var gs = new GZipStream(fs, CompressionMode.Decompress)) - gs.CopyTo(ms); - } - } - else - { - data = File.ReadAllBytes(path); - } - - var module = new PeWrapper(moduleName, data, _nextStart); - ComputeNextStart(module.Size); - AddMemoryBlock(module.Memory); - _savestateComponents.Add(module); - _disposeList.Add(module); - - _exports.Add(moduleName, module); - _modules.Add(module); - foreach (var name in module.ImportsByModule.Keys) - { - todoModules.Enqueue(name); - } - } - } - - _libcpatch = new LibcPatch(this); - _exports["libc.so"] = new PatchImportResolver(_exports["libc.so"], BizExvoker.GetExvoker(_libcpatch, CallingConventionAdapters.Waterbox)); - - ConnectAllImports(); - - // load all heaps - _heap = CreateHeapHelper(opt.SbrkHeapSizeKB, "brk-heap", true); - _sealedheap = CreateHeapHelper(opt.SealedHeapSizeKB, "sealed-heap", true); - _invisibleheap = CreateHeapHelper(opt.InvisibleHeapSizeKB, "invisible-heap", false); - _plainheap = CreateHeapHelper(opt.PlainHeapSizeKB, "plain-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(); - - Console.WriteLine("About to enter unmanaged code"); - if (Win32Hacks.IsDebuggerReallyPresent() && !System.Diagnostics.Debugger.IsAttached) - { - // this means that GDB or another unconventional debugger is attached. - // if that's the case, and it's observing this core, it probably wants a break - System.Diagnostics.Debugger.Break(); - } - - // run unmanaged init code - var libcEnter = _exports["libc.so"].SafeResolve("__libc_entry_routine"); - var psxInit = _exports["libpsxscl.so"].SafeResolve("__psx_init"); - - var del = (LibcEntryRoutineD)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libcEnter, typeof(LibcEntryRoutineD)); - // the current mmglue code doesn't use the main pointer at all, and this just returns - del(IntPtr.Zero, psxInit, 0); - - foreach (var m in _modules) - { - m.RunGlobalCtors(); - } - - /*try - { - _modules[0].RunExeEntry(); - //throw new InvalidOperationException("main() returned!"); - } - catch //(EndOfMainException) - { - } - _modules[0].RunGlobalCtors(); - foreach (var m in _modules.Skip(1)) - { - if (!m.RunDllEntry()) - throw new InvalidOperationException("DllMain() returned false"); - m.RunGlobalCtors(); - }*/ - } - } - - public IntPtr Resolve(string entryPoint) - { - // modules[0] is always the main module - return _modules[0].Resolve(entryPoint); - } - - public void Seal() - { - using (this.EnterExit()) - { - // if libco is used, the jmp_buf for the main cothread can have stack stuff in it. - // this isn't a problem, since we only savestate when the core is not running, and - // the next time it's run, that buf will be overridden again. - // but it breaks xor state verification, so when we seal, nuke it. - - // this could be the responsibility of something else other than the PeRunner; I am not sure yet... - IImportResolver libco; - if (_exports.TryGetValue("libco.so", out libco)) - { - Console.WriteLine("Calling co_clean()..."); - CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libco.SafeResolve("co_clean"))(); - } - - _sealedheap.Seal(); - foreach (var h in _heaps) - { - if (h != _invisibleheap) // TODO: if we have more non-savestated heaps, refine this hack - h.Memory.SaveXorSnapshot(); - } - foreach (var pe in _modules) - { - pe.SealImportsAndTakeXorSnapshot(); - } - if (_mmapheap != null) - _mmapheap.Memory.SaveXorSnapshot(); - } - } - - private void ConnectAllImports() - { - foreach (var module in _modules) - { - foreach (var name in module.ImportsByModule.Keys) - { - module.ConnectImports(name, _exports[name]); - } - } - } - - public void SaveStateBinary(BinaryWriter bw) - { - bw.Write(_createstamp); - bw.Write(_savestateComponents.Count); - using (this.EnterExit()) - { - foreach (var c in _savestateComponents) - { - c.SaveStateBinary(bw); - } - } - } - - public void LoadStateBinary(BinaryReader br) - { - var differentCore = br.ReadInt64() != _createstamp; // true if a different core instance created the state - if (br.ReadInt32() != _savestateComponents.Count) - throw new InvalidOperationException("Internal savestate error"); - using (this.EnterExit()) - { - foreach (var c in _savestateComponents) - { - c.LoadStateBinary(br); - } - if (differentCore) - { - // if a different runtime instance than this one saved the state, - // Exvoker imports need to be reconnected - Console.WriteLine("Restoring PeRunner state from a different core..."); - ConnectAllImports(); - _psx.ReloadVtables(); - } - } - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - foreach (var d in _disposeList) - d.Dispose(); - _disposeList.Clear(); - PurgeMemoryBlocks(); - _modules.Clear(); - _exports.Clear(); - _heap = null; - _sealedheap = null; - _invisibleheap = null; - _plainheap = null; - _mmapheap = null; - } - } - } -} + throw new InvalidOperationException("This shouldn't be called"); + //} + //else + //{ + // return 0; + //} + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_self")] + public IntPtr PthreadSelf() + { + return _pthreadSelf; + } + + /*[BizExport(CallingConvention.Cdecl, EntryPoint = "convert_thread")] + public void ConvertThread() + { + + }*/ + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n218")] + public long SetTidAddress(IntPtr address) + { + return 8675309; + } + + [StructLayout(LayoutKind.Sequential)] + public class TimeSpec + { + public long Seconds; + public long NanoSeconds; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n228")] + public int SysClockGetTime(int which, [In, Out] TimeSpec time) + { + time.Seconds = 1495889068; + 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; + } + + [BizExport(CallingConvention.Cdecl, EntryPoint = "n10")] + public int MProtect(UIntPtr address, UIntPtr size, int prot) + { + 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 -1; // write only???? + case 3: mprot = MemoryBlock.Protection.RW; break; + case 1: mprot = MemoryBlock.Protection.R; break; + case 5: mprot = MemoryBlock.Protection.RX; break; + } + return _parent._mmapheap.Protect((ulong)address, (ulong)size, mprot) ? 0 : -1; + } + } + + /// + /// libc.so functions that we want to redirect + /// + private class LibcPatch + { + private readonly PeRunner _parent; + public LibcPatch(PeRunner parent) + { + _parent = parent; + } + + + private bool _didOnce = false; + private readonly Dictionary _specificKeys = new Dictionary(); + private uint _nextSpecificKey = 401; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PthreadCallback(); + + // pthread stuff: + // since we don't allow multiple threads (for now), this is all pretty simple + /* + // int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_create")] + public int PthreadKeyCreate(ref uint key, PthreadCallback destructor) + { + key = _nextSpecificKey++; + _specificKeys.Add(key, IntPtr.Zero); + return 0; + } + // int pthread_key_delete(pthread_key_t key); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_delete")] + public int PthreadKeyDelete(uint key) + { + _specificKeys.Remove(key); + return 0; + } + // int pthread_setspecific(pthread_key_t key, const void *value); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_setspecific")] + public int PthreadSetSpecific(uint key, IntPtr value) + { + _specificKeys[key] = value; + return 0; + } + // void *pthread_getspecific(pthread_key_t key); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_getspecific")] + public IntPtr PthreadGetSpecific(uint key) + { + IntPtr ret; + _specificKeys.TryGetValue(key, out ret); + return ret; + } + + // int pthread_once(pthread_once_t* once_control, void (*init_routine)(void)); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_once")] + public int PthreadOnce(IntPtr control, PthreadCallback init) + { + if (!_didOnce) + { + System.Diagnostics.Debugger.Break(); + _didOnce = true; + init(); + } + return 0; + } + + // int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t* attr); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_init")] + public int PthreadMutexInit(IntPtr mutex, IntPtr attr) { return 0; } + // int pthread_mutex_destroy(pthread_mutex_t* mutex); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_destroy")] + public int PthreadMutexDestroy(IntPtr mutex) { return 0; } + + // int pthread_mutex_lock(pthread_mutex_t* mutex); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_lock")] + public int PthreadMutexLock(IntPtr mutex) { return 0; } + // int pthread_mutex_trylock(pthread_mutex_t* mutex); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_trylock")] + public int PthreadMutexTryLock(IntPtr mutex) { return 0; } + // int pthread_mutex_unlock(pthread_mutex_t* mutex); + [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_unlock")] + public int PthreadMutexUnlock(IntPtr mutex) { return 0; }*/ + + + } + + /// + /// usual starting point for the executable + /// + public const ulong CanonicalStart = 0x0000036f00000000; + + public const ulong AlternateStart = 0x0000036e00000000; + + /// + /// the next place where we can put a module or heap + /// + private ulong _nextStart = CanonicalStart; + + /// + /// increment _nextStart after adding a module + /// + private void ComputeNextStart(ulong size) + { + _nextStart += size; + // align to 1MB, then increment 16MB + _nextStart = ((_nextStart - 1) | 0xfffff) + 0x1000001; + } + + /// + /// standard malloc() heap + /// + private Heap _heap; + + /// + /// sealed heap (writable only during init) + /// + private Heap _sealedheap; + + /// + /// invisible heap (not savestated, use with care) + /// + private Heap _invisibleheap; + + /// + /// extra savestated heap + /// + private Heap _plainheap; + + /// + /// memory map emulation + /// + private MapHeap _mmapheap; + + /// + /// all loaded PE files + /// + private readonly List _modules = new List(); + + /// + /// all loaded heaps + /// + private readonly List _heaps = new List(); + + /// + /// anything at all that needs to be disposed on finish + /// + private readonly List _disposeList = new List(); + + /// + /// anything at all that needs its state saved and loaded + /// + private readonly List _savestateComponents = new List(); + + /// + /// all of the exports, including real PeWrapper ones and fake ones + /// + private readonly Dictionary _exports = new Dictionary(); + + private Psx _psx; + private Emu _emu; + private Syscalls _syscalls; + private LibcPatch _libcpatch; + + /// + /// timestamp of creation acts as a sort of "object id" in the savestate + /// + private readonly long _createstamp = WaterboxUtils.Timestamp(); + + private Heap CreateHeapHelper(uint sizeKB, string name, bool saveStated) + { + if (sizeKB != 0) + { + var heap = new Heap(_nextStart, sizeKB * 1024, name); + heap.Memory.Activate(); + ComputeNextStart(sizeKB * 1024); + AddMemoryBlock(heap.Memory, name); + if (saveStated) + _savestateComponents.Add(heap); + _disposeList.Add(heap); + _heaps.Add(heap); + return heap; + } + else + { + return null; + } + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void LibcEntryRoutineD(IntPtr appMain, IntPtr psxInit, int options); + + public PeRunner(PeRunnerOptions opt) + { + _nextStart = opt.StartAddress; + Initialize(_nextStart); + using (this.EnterExit()) + { + // load any predefined exports + _psx = new Psx(this); + _exports.Add("libpsxscl.so", BizExvoker.GetExvoker(_psx, CallingConventionAdapters.Waterbox)); + _emu = new Emu(this); + _exports.Add("libemuhost.so", BizExvoker.GetExvoker(_emu, CallingConventionAdapters.Waterbox)); + _syscalls = new Syscalls(this); + _exports.Add("__syscalls", BizExvoker.GetExvoker(_syscalls, CallingConventionAdapters.Waterbox)); + + // load and connect all modules, starting with the executable + var todoModules = new Queue(); + todoModules.Enqueue(opt.Filename); + + while (todoModules.Count > 0) + { + var moduleName = todoModules.Dequeue(); + if (!_exports.ContainsKey(moduleName)) + { + var path = Path.Combine(opt.Path, moduleName); + var gzpath = path + ".gz"; + byte[] data; + if (File.Exists(gzpath)) + { + using (var fs = new FileStream(gzpath, FileMode.Open, FileAccess.Read)) + { + var tmp = new byte[4]; + fs.Seek(-4, SeekOrigin.End); + fs.Read(tmp, 0, 4); + int size = BitConverter.ToInt32(tmp, 0); + data = new byte[size]; + var ms = new MemoryStream(data); + fs.Seek(0, SeekOrigin.Begin); + using (var gs = new GZipStream(fs, CompressionMode.Decompress)) + gs.CopyTo(ms); + } + } + else + { + data = File.ReadAllBytes(path); + } + + var module = new PeWrapper(moduleName, data, _nextStart); + ComputeNextStart(module.Size); + AddMemoryBlock(module.Memory, moduleName); + _savestateComponents.Add(module); + _disposeList.Add(module); + + _exports.Add(moduleName, module); + _modules.Add(module); + foreach (var name in module.ImportsByModule.Keys) + { + todoModules.Enqueue(name); + } + } + } + + _libcpatch = new LibcPatch(this); + _exports["libc.so"] = new PatchImportResolver(_exports["libc.so"], BizExvoker.GetExvoker(_libcpatch, CallingConventionAdapters.Waterbox)); + + ConnectAllImports(); + + // load all heaps + _heap = CreateHeapHelper(opt.SbrkHeapSizeKB, "brk-heap", true); + _sealedheap = CreateHeapHelper(opt.SealedHeapSizeKB, "sealed-heap", true); + _invisibleheap = CreateHeapHelper(opt.InvisibleHeapSizeKB, "invisible-heap", false); + _plainheap = CreateHeapHelper(opt.PlainHeapSizeKB, "plain-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, "mmap-heap"); + _savestateComponents.Add(_mmapheap); + _disposeList.Add(_mmapheap); + } + + _syscalls.Init(); + + Console.WriteLine("About to enter unmanaged code"); + if (Win32Hacks.IsDebuggerReallyPresent() && !System.Diagnostics.Debugger.IsAttached) + { + // this means that GDB or another unconventional debugger is attached. + // if that's the case, and it's observing this core, it probably wants a break + System.Diagnostics.Debugger.Break(); + } + + // run unmanaged init code + var libcEnter = _exports["libc.so"].SafeResolve("__libc_entry_routine"); + var psxInit = _exports["libpsxscl.so"].SafeResolve("__psx_init"); + + var del = (LibcEntryRoutineD)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libcEnter, typeof(LibcEntryRoutineD)); + // the current mmglue code doesn't use the main pointer at all, and this just returns + del(IntPtr.Zero, psxInit, 0); + + foreach (var m in _modules) + { + m.RunGlobalCtors(); + } + + /*try + { + _modules[0].RunExeEntry(); + //throw new InvalidOperationException("main() returned!"); + } + catch //(EndOfMainException) + { + } + _modules[0].RunGlobalCtors(); + foreach (var m in _modules.Skip(1)) + { + if (!m.RunDllEntry()) + throw new InvalidOperationException("DllMain() returned false"); + m.RunGlobalCtors(); + }*/ + } + } + + public IntPtr Resolve(string entryPoint) + { + // modules[0] is always the main module + return _modules[0].Resolve(entryPoint); + } + + public void Seal() + { + using (this.EnterExit()) + { + // if libco is used, the jmp_buf for the main cothread can have stack stuff in it. + // this isn't a problem, since we only savestate when the core is not running, and + // the next time it's run, that buf will be overridden again. + // but it breaks xor state verification, so when we seal, nuke it. + + // this could be the responsibility of something else other than the PeRunner; I am not sure yet... + IImportResolver libco; + if (_exports.TryGetValue("libco.so", out libco)) + { + Console.WriteLine("Calling co_clean()..."); + CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libco.SafeResolve("co_clean"))(); + } + + _sealedheap.Seal(); + foreach (var h in _heaps) + { + if (h != _invisibleheap) // TODO: if we have more non-savestated heaps, refine this hack + h.Memory.SaveXorSnapshot(); + } + foreach (var pe in _modules) + { + pe.SealImportsAndTakeXorSnapshot(); + } + if (_mmapheap != null) + _mmapheap.Memory.SaveXorSnapshot(); + } + } + + private void ConnectAllImports() + { + foreach (var module in _modules) + { + foreach (var name in module.ImportsByModule.Keys) + { + module.ConnectImports(name, _exports[name]); + } + } + } + + public void SaveStateBinary(BinaryWriter bw) + { + bw.Write(_createstamp); + bw.Write(_savestateComponents.Count); + using (this.EnterExit()) + { + foreach (var c in _savestateComponents) + { + c.SaveStateBinary(bw); + } + } + } + + public void LoadStateBinary(BinaryReader br) + { + var differentCore = br.ReadInt64() != _createstamp; // true if a different core instance created the state + if (br.ReadInt32() != _savestateComponents.Count) + throw new InvalidOperationException("Internal savestate error"); + using (this.EnterExit()) + { + foreach (var c in _savestateComponents) + { + c.LoadStateBinary(br); + } + if (differentCore) + { + // if a different runtime instance than this one saved the state, + // Exvoker imports need to be reconnected + Console.WriteLine("Restoring PeRunner state from a different core..."); + ConnectAllImports(); + _psx.ReloadVtables(); + } + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + foreach (var d in _disposeList) + d.Dispose(); + _disposeList.Clear(); + PurgeMemoryBlocks(); + _modules.Clear(); + _exports.Clear(); + _heap = null; + _sealedheap = null; + _invisibleheap = null; + _plainheap = null; + _mmapheap = null; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs b/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs index 07c057f2cd..bb7cb2e9fb 100644 --- a/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs +++ b/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs @@ -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 -{ - /// - /// represents one PE file. used in PeRunner - /// - internal class PeWrapper : IImportResolver, IBinaryStateable, IDisposable - { - public Dictionary ExportsByOrdinal { get; } = new Dictionary(); - /// - /// ordinal only exports will not show up in this list! - /// - public Dictionary ExportsByName { get; } = new Dictionary(); - - public Dictionary> ImportsByModule { get; } = - new Dictionary>(); - - 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 _sectionsByName = new Dictionary(); - private readonly List
_sections = new List
(); - 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; } - - /// - /// for midipix-built PEs, pointer to the construtors to run during init - /// - public IntPtr CtorList { get; private set; } - /// - /// for midipix-build PEs, pointer to the destructors to run during fini - /// - 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 +{ + /// + /// represents one PE file. used in PeRunner + /// + internal class PeWrapper : IImportResolver, IBinaryStateable, IDisposable + { + public Dictionary ExportsByOrdinal { get; } = new Dictionary(); + /// + /// ordinal only exports will not show up in this list! + /// + public Dictionary ExportsByName { get; } = new Dictionary(); + + public Dictionary> ImportsByModule { get; } = + new Dictionary>(); + + 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 _sectionsByName = new Dictionary(); + private readonly List
_sections = new List
(); + 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; } + + /// + /// for midipix-built PEs, pointer to the construtors to run during init + /// + public IntPtr CtorList { get; private set; } + /// + /// for midipix-build PEs, pointer to the destructors to run during fini + /// + 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(); - } - - /// - /// set memory protections. - /// - 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); - } - } - - /// - /// load the PE into memory - /// - /// start address - 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 module; - if (!ImportsByModule.TryGetValue(import.DLL, out module)) - { - module = new Dictionary(); - 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 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(); + } + + /// + /// set memory protections. + /// + 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); + } + } + + /// + /// load the PE into memory + /// + /// start address + 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 module; + if (!ImportsByModule.TryGetValue(import.DLL, out module)) + { + module = new Dictionary(); + 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 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(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs index bee1f6a0ab..32bc1bf0f5 100644 --- a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs +++ b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs @@ -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 /// private List _memoryBlocks = new List(); - protected void AddMemoryBlock(MemoryBlock block) + /// + /// an informative name for each memory block: used for debugging purposes + /// + private List _memoryBlockNames = new List(); + + 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;