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;