diff --git a/Assets/libwaterboxhost.so b/Assets/libwaterboxhost.so new file mode 100644 index 0000000000..4280de9941 Binary files /dev/null and b/Assets/libwaterboxhost.so differ diff --git a/Assets/linguard.so b/Assets/linguard.so deleted file mode 100644 index e0a5910687..0000000000 Binary files a/Assets/linguard.so and /dev/null differ diff --git a/output/dll/faust.wbx.gz b/output/dll/faust.wbx.gz index c773a19f5e..38e803ad30 100644 Binary files a/output/dll/faust.wbx.gz and b/output/dll/faust.wbx.gz differ diff --git a/output/dll/gpgx.wbx.gz b/output/dll/gpgx.wbx.gz index 9045c3e27a..d9948452bc 100644 Binary files a/output/dll/gpgx.wbx.gz and b/output/dll/gpgx.wbx.gz differ diff --git a/output/dll/hyper.wbx.gz b/output/dll/hyper.wbx.gz index ab6e5535b5..5b8488f2c7 100644 Binary files a/output/dll/hyper.wbx.gz and b/output/dll/hyper.wbx.gz differ diff --git a/output/dll/libsnes.wbx.gz b/output/dll/libsnes.wbx.gz index ef9981987a..6ff964e964 100644 Binary files a/output/dll/libsnes.wbx.gz and b/output/dll/libsnes.wbx.gz differ diff --git a/output/dll/ngp.wbx.gz b/output/dll/ngp.wbx.gz index 808ba55158..3694b42566 100644 Binary files a/output/dll/ngp.wbx.gz and b/output/dll/ngp.wbx.gz differ diff --git a/output/dll/pcfx.wbx.gz b/output/dll/pcfx.wbx.gz index 6a44c52204..e2ad8e43cc 100644 Binary files a/output/dll/pcfx.wbx.gz and b/output/dll/pcfx.wbx.gz differ diff --git a/output/dll/picodrive.wbx.gz b/output/dll/picodrive.wbx.gz index eb42b96194..370fb7c196 100644 Binary files a/output/dll/picodrive.wbx.gz and b/output/dll/picodrive.wbx.gz differ diff --git a/output/dll/sameboy.wbx.gz b/output/dll/sameboy.wbx.gz index f468365cb9..ab490d3f7c 100644 Binary files a/output/dll/sameboy.wbx.gz and b/output/dll/sameboy.wbx.gz differ diff --git a/output/dll/snes9x.wbx.gz b/output/dll/snes9x.wbx.gz index 7b3b478e4a..9a59367c7c 100644 Binary files a/output/dll/snes9x.wbx.gz and b/output/dll/snes9x.wbx.gz differ diff --git a/output/dll/ss.wbx.gz b/output/dll/ss.wbx.gz index 9773f935cd..17afdf50e7 100644 Binary files a/output/dll/ss.wbx.gz and b/output/dll/ss.wbx.gz differ diff --git a/output/dll/turbo.wbx.gz b/output/dll/turbo.wbx.gz index 3cca121f4b..31ca93022e 100644 Binary files a/output/dll/turbo.wbx.gz and b/output/dll/turbo.wbx.gz differ diff --git a/output/dll/uzem.wbx.gz b/output/dll/uzem.wbx.gz index 9aee14e6e5..806d67ae9a 100644 Binary files a/output/dll/uzem.wbx.gz and b/output/dll/uzem.wbx.gz differ diff --git a/output/dll/vb.wbx.gz b/output/dll/vb.wbx.gz index 0aeedbce20..af0846810b 100644 Binary files a/output/dll/vb.wbx.gz and b/output/dll/vb.wbx.gz differ diff --git a/output/dll/waterboxhost.dll b/output/dll/waterboxhost.dll new file mode 100644 index 0000000000..7e58ad2053 Binary files /dev/null and b/output/dll/waterboxhost.dll differ diff --git a/output/dll/winguard.dll b/output/dll/winguard.dll deleted file mode 100644 index dabf30b3ba..0000000000 Binary files a/output/dll/winguard.dll and /dev/null differ diff --git a/src/BizHawk.BizInvoke/BizInvoker.cs b/src/BizHawk.BizInvoke/BizInvoker.cs index 592a3494c1..0d3718362e 100644 --- a/src/BizHawk.BizInvoke/BizInvoker.cs +++ b/src/BizHawk.BizInvoke/BizInvoker.cs @@ -558,7 +558,7 @@ namespace BizHawk.BizInvoke var strlenbytes = il.DeclareLocal(typeof(int), false); il.Emit(OpCodes.Ldloc, encoding); il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Call, typeof(Encoding).GetMethod("GetByteCount", new[] { typeof(string) })); + il.EmitCall(OpCodes.Callvirt, typeof(Encoding).GetMethod("GetByteCount", new[] { typeof(string) }), Type.EmptyTypes); il.Emit(OpCodes.Stloc, strlenbytes); var strval = il.DeclareLocal(typeof(string), true); // pin! @@ -590,7 +590,7 @@ namespace BizHawk.BizInvoke // bytelength il.Emit(OpCodes.Ldloc, strlenbytes); // call - il.Emit(OpCodes.Call, typeof(Encoding).GetMethod("GetBytes", new[] { typeof(char*), typeof(int), typeof(byte*), typeof(int) })); + il.EmitCall(OpCodes.Callvirt, typeof(Encoding).GetMethod("GetBytes", new[] { typeof(char*), typeof(int), typeof(byte*), typeof(int) }), Type.EmptyTypes); // unused ret il.Emit(OpCodes.Pop); diff --git a/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs b/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs index ce843b79ae..31585a1194 100644 --- a/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs +++ b/src/BizHawk.Client.Common/rewind/ZwinderBuffer.cs @@ -252,7 +252,7 @@ namespace BizHawk.Client.Common _nextStateIndex = reader.ReadInt32(); } - private class SaveStateStream : Stream + private class SaveStateStream : Stream, ISpanStream { /// /// @@ -296,6 +296,7 @@ namespace BizHawk.Client.Common public override int Read(byte[] buffer, int offset, int count) => throw new IOException(); public override long Seek(long offset, SeekOrigin origin) => throw new IOException(); public override void SetLength(long value) => throw new IOException(); + public int Read(Span buffer) => throw new IOException(); public override void Write(byte[] buffer, int offset, int count) { @@ -324,6 +325,40 @@ namespace BizHawk.Client.Common } } + public void Write(ReadOnlySpan buffer) + { + long requestedSize = _position + buffer.Length; + while (requestedSize > _notifySize) + _notifySize = _notifySizeReached(); + long n = buffer.Length; + if (n > 0) + { + var start = (_position + _offset) & _mask; + var end = (start + n) & _mask; + if (end < start) + { + long m = _buffer.LongLength - start; + + // Array.Copy(buffer, offset, _buffer, start, m); + buffer.Slice(0, (int)m).CopyTo(new Span(_buffer, (int)start, (int)m)); + + // offset += (int)m; + buffer = buffer.Slice((int)m); + + n -= m; + _position += m; + start = 0; + } + if (n > 0) + { + // Array.Copy(buffer, offset, _buffer, start, n); + buffer.CopyTo(new Span(_buffer, (int)start, (int)n)); + + _position += n; + } + } + } + public override void WriteByte(byte value) { long requestedSize = _position + 1; @@ -333,7 +368,7 @@ namespace BizHawk.Client.Common } } - private class LoadStateStream : Stream + private class LoadStateStream : Stream, ISpanStream { public LoadStateStream(byte[] buffer, long offset, long size, long mask) { @@ -387,6 +422,38 @@ namespace BizHawk.Client.Common return ret; } + public unsafe int Read(Span buffer) + { + long n = Math.Min(_size - _position, buffer.Length); + int ret = (int)n; + if (n > 0) + { + var start = (_position + _offset) & _mask; + var end = (start + n) & _mask; + if (end < start) + { + long m = _buffer.LongLength - start; + + // Array.Copy(_buffer, start, buffer, offset, m); + new ReadOnlySpan(_buffer, (int)start, (int)m).CopyTo(buffer); + + // offset += (int)m; + buffer = buffer.Slice((int)m); + + n -= m; + _position += m; + start = 0; + } + if (n > 0) + { + // Array.Copy(_buffer, start, buffer, offset, n); + new ReadOnlySpan(_buffer, (int)start, (int)n).CopyTo(buffer); + _position += n; + } + } + return ret; + } + public override int ReadByte() { return _position < _size @@ -397,6 +464,8 @@ namespace BizHawk.Client.Common public override long Seek(long offset, SeekOrigin origin) => throw new IOException(); public override void SetLength(long value) => throw new IOException(); public override void Write(byte[] buffer, int offset, int count) => throw new IOException(); + + public void Write(ReadOnlySpan buffer) => throw new IOException(); } } } diff --git a/src/BizHawk.Common/BizHawk.Common.csproj b/src/BizHawk.Common/BizHawk.Common.csproj index bd0b106d94..061aaf80c6 100644 --- a/src/BizHawk.Common/BizHawk.Common.csproj +++ b/src/BizHawk.Common/BizHawk.Common.csproj @@ -7,6 +7,7 @@ + diff --git a/src/BizHawk.Common/SpanStream.cs b/src/BizHawk.Common/SpanStream.cs new file mode 100644 index 0000000000..fea93f817c --- /dev/null +++ b/src/BizHawk.Common/SpanStream.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace BizHawk.Common +{ + /// + /// TODO: Switch to dotnet core and remove this junkus + /// + public interface ISpanStream + { + void Write (ReadOnlySpan buffer); + int Read (Span buffer); + } + public class SpanStream + { + /// + /// Returns a stream in spanstream mode, or creates a wrapper that provides that functionality + /// + /// + /// + public static ISpanStream GetOrBuild(Stream s) + { + return s as ISpanStream + ?? new SpanStreamAdapter(s); + } + private class SpanStreamAdapter : ISpanStream + { + public SpanStreamAdapter(Stream stream) + { + _stream = stream; + } + private byte[] _buffer = new byte[0]; + private readonly Stream _stream; + public unsafe int Read(Span buffer) + { + if (buffer.Length > _buffer.Length) + { + _buffer = new byte[buffer.Length]; + } + var n = _stream.Read(_buffer, 0, buffer.Length); + fixed(byte* p = buffer) + { + Marshal.Copy(_buffer, 0, (IntPtr)p, n); + } + return n; + } + + public unsafe void Write(ReadOnlySpan buffer) + { + if (buffer.Length > _buffer.Length) + { + _buffer = new byte[buffer.Length]; + } + fixed(byte* p = buffer) + { + Marshal.Copy((IntPtr)p, _buffer, 0, buffer.Length); + } + _stream.Write(_buffer, 0, buffer.Length); + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 6e04f0764b..a4e7df44a4 100644 --- a/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -6,6 +6,7 @@ + diff --git a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs index f85ec136fc..846dd165b0 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs @@ -29,11 +29,13 @@ namespace BizHawk.Emulation.Cores.Consoles.NEC.PCE NymaSettings settings, NymaSyncSettings syncSettings, bool deterministic) : base(comm, "PCE", "PC Engine Controller", settings, syncSettings) { - var firmwares = new Dictionary - { - { "FIRMWARE:syscard3.pce", ("PCECD", "Bios") }, - { "FIRMWARE:gecard.pce", ("PCECD", "GE-Bios") }, - }; + var ids = discs.Select(d => new DiscIdentifier(d).DetectDiscType()) + .ToList(); + var firmwares = new Dictionary(); + if (ids.Contains(DiscType.TurboCD)) + firmwares.Add("FIRMWARE:syscard3.pce", ("PCECD", "Bios")); + if (ids.Contains(DiscType.TurboGECD)) + firmwares.Add("FIRMWARE:gecard.pce", ("PCECD", "GE-Bios")); _turboNyma = DoInit(game, null, discs, "turbo.wbx", null, deterministic, firmwares); } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index 1aecf3b228..90cd7909f8 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -168,7 +168,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy public new byte[] CloneSaveRam() { - _exe.AddTransientFile(null, "save.ram"); + _exe.AddTransientFile(new byte[0], "save.ram"); _core.GetSaveRam(); return _exe.RemoveTransientFile("save.ram"); } diff --git a/src/BizHawk.Emulation.Cores/Waterbox/EmuLibc.cs b/src/BizHawk.Emulation.Cores/Waterbox/EmuLibc.cs deleted file mode 100644 index 367609f5ef..0000000000 --- a/src/BizHawk.Emulation.Cores/Waterbox/EmuLibc.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using BizHawk.BizInvoke; -using BizHawk.Common; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - /// - /// implementation for special functions defined in emulibc.h - /// - internal class EmuLibc - { - private readonly WaterboxHost _parent; - public EmuLibc(WaterboxHost parent) - { - _parent = parent; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__walloc_sealed")] - public IntPtr AllocSealed(UIntPtr size) - { - return Z.US(_parent._sealedheap.Allocate((ulong)size, 16)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__walloc_invisible")] - public IntPtr AllocInvisible(UIntPtr size) - { - return Z.US(_parent._invisibleheap.Allocate((ulong)size, 16)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__walloc_plain")] - public IntPtr AllocPlain(UIntPtr size) - { - return Z.US(_parent._plainheap.Allocate((ulong)size, 16)); - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__w_debug_puts")] - public void DebugPuts(IntPtr s) - { - Console.WriteLine(Mershul.PtrToStringUtf8(s)); - } - } -} diff --git a/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs b/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs index 58500a3348..78aac0709a 100644 --- a/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs +++ b/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs @@ -59,22 +59,37 @@ namespace BizHawk.Emulation.Cores.Waterbox var filesToRemove = new List(); if (firmwares != null) { - _exe.MissingFileCallback = s => + foreach (var kvp in firmwares) { - if (firmwares.TryGetValue(s, out var tt)) + var s = kvp.Key; + var tt = kvp.Value; + var data = CoreComm.CoreFileProvider.GetFirmware(tt.SystemID, tt.FirmwareID, false, + "Firmware files are usually required and may stop your game from loading"); + if (data != null) { - var data = CoreComm.CoreFileProvider.GetFirmware(tt.SystemID, tt.FirmwareID, false, - "Firmware files are usually required and may stop your game from loading"); - if (data != null) - { - _exe.AddReadonlyFile(data, s); - filesToRemove.Add(s); - return true; - } + _exe.AddReadonlyFile(data, kvp.Key); + filesToRemove.Add(s); } - return false; - }; + } } + // if (firmwares != null) + // { + // _exe.MissingFileCallback = s => + // { + // if (firmwares.TryGetValue(s, out var tt)) + // { + // var data = CoreComm.CoreFileProvider.GetFirmware(tt.SystemID, tt.FirmwareID, false, + // "Firmware files are usually required and may stop your game from loading"); + // if (data != null) + // { + // _exe.AddReadonlyFile(data, s); + // filesToRemove.Add(s); + // return true; + // } + // } + // return false; + // }; + // } if (discs?.Length > 0) { _disks = discs; @@ -104,14 +119,14 @@ namespace BizHawk.Emulation.Cores.Waterbox _exe.RemoveReadonlyFile(fn); } - if (firmwares != null) - { + // if (firmwares != null) + // { foreach (var s in filesToRemove) { _exe.RemoveReadonlyFile(s); } - _exe.MissingFileCallback = null; - } + // _exe.MissingFileCallback = null; + // } var info = *_nyma.GetSystemInfo(); _videoBuffer = new int[Math.Max(info.MaxWidth * info.MaxHeight, info.LcmWidth * info.LcmHeight)]; diff --git a/src/BizHawk.Emulation.Cores/Waterbox/Swappable.cs b/src/BizHawk.Emulation.Cores/Waterbox/Swappable.cs deleted file mode 100644 index 64c9aa9a4e..0000000000 --- a/src/BizHawk.Emulation.Cores/Waterbox/Swappable.cs +++ /dev/null @@ -1,169 +0,0 @@ -using BizHawk.Common; -using BizHawk.BizInvoke; -using BizHawk.Common.BufferExtensions; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - /// - /// represents an object that can be swapped in and out of memory to compete with other objects in the same memory - /// not suited for general purpose stuff - /// - public abstract class Swappable : IMonitor, IDisposable - { - /// - /// start address - /// - private uint _lockkey; - - /// - /// the the relevant lockinfo for this core - /// - private LockInfo _currentLockInfo; - - /// - /// everything to swap in for context switches - /// - private List _memoryBlocks = new List(); - - /// - /// 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() - { - _memoryBlocks = null; - } - - protected void Initialize(ulong startAddress) - { - // any Swappables in the same 4G range are assumed to conflict - var lockkey = (uint)(startAddress >> 32); - - _lockkey = lockkey; - if (lockkey == 0) - throw new NullReferenceException(); - _currentLockInfo = LockInfos.GetOrAdd(_lockkey, new LockInfo { Sync = new object() }); - } - - private class LockInfo - { - public object Sync; - private WeakReference LoadedRef = new WeakReference(null); -#if DEBUG - /// - /// recursive lock count - /// - public int LockCount; -#endif - public Swappable Loaded - { - // if somehow an object died without being disposed, - // the MemoryBlock finalizer will have unloaded the memory - // and so we can treat it as if no Swappable was attached - get => (Swappable)LoadedRef.Target; - set => LoadedRef.Target = value; - } - } - - private static readonly ConcurrentDictionary LockInfos = new ConcurrentDictionary(); - - /// - /// acquire lock and swap this into memory - /// - public void Enter() - { - Monitor.Enter(_currentLockInfo.Sync); -#if DEBUG - if (_currentLockInfo.LockCount++ != 0 && _currentLockInfo.Loaded != this) - throw new InvalidOperationException("Woops!"); -#endif - if (_currentLockInfo.Loaded != this) - { - _currentLockInfo.Loaded?.DeactivateInternal(); - _currentLockInfo.Loaded = null; - ActivateInternal(); - _currentLockInfo.Loaded = this; - } - } - - /// - /// release lock - /// - public void Exit() - { -#if DEBUG - // when debugging, if we're releasing the lock then deactivate - if (_currentLockInfo.LockCount-- == 1) - { - if (_currentLockInfo.Loaded != this) - throw new InvalidOperationException("Woops!"); - DeactivateInternal(); - _currentLockInfo.Loaded = null; - } -#endif - Monitor.Exit(_currentLockInfo.Sync); - } - - private void DeactivateInternal() - { - foreach (var m in _memoryBlocks) - m.Deactivate(); - } - - private void ActivateInternal() - { - 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; - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - lock (_currentLockInfo.Sync) - { - if (_currentLockInfo.Loaded == this) - { - DeactivateInternal(); - _currentLockInfo.Loaded = null; - } - _currentLockInfo = null; - } - } - _disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - } - } -} diff --git a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Errno.cs b/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Errno.cs deleted file mode 100644 index 1aea091957..0000000000 --- a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Errno.cs +++ /dev/null @@ -1,140 +0,0 @@ -namespace BizHawk.Emulation.Cores.Waterbox -{ - partial class Syscalls - { - internal const int EPERM = 1; - internal const int ENOENT = 2; - internal const int ESRCH = 3; - internal const int EINTR = 4; - internal const int EIO = 5; - internal const int ENXIO = 6; - internal const int E2BIG = 7; - internal const int ENOEXEC = 8; - internal const int EBADF = 9; - internal const int ECHILD = 10; - internal const int EAGAIN = 11; - internal const int ENOMEM = 12; - internal const int EACCES = 13; - internal const int EFAULT = 14; - internal const int ENOTBLK = 15; - internal const int EBUSY = 16; - internal const int EEXIST = 17; - internal const int EXDEV = 18; - internal const int ENODEV = 19; - internal const int ENOTDIR = 20; - internal const int EISDIR = 21; - internal const int EINVAL = 22; - internal const int ENFILE = 23; - internal const int EMFILE = 24; - internal const int ENOTTY = 25; - internal const int ETXTBSY = 26; - internal const int EFBIG = 27; - internal const int ENOSPC = 28; - internal const int ESPIPE = 29; - internal const int EROFS = 30; - internal const int EMLINK = 31; - internal const int EPIPE = 32; - internal const int EDOM = 33; - internal const int ERANGE = 34; - internal const int EDEADLK = 35; - internal const int ENAMETOOLONG = 36; - internal const int ENOLCK = 37; - internal const int ENOSYS = 38; - internal const int ENOTEMPTY = 39; - internal const int ELOOP = 40; - internal const int EWOULDBLOCK = EAGAIN; - internal const int ENOMSG = 42; - internal const int EIDRM = 43; - internal const int ECHRNG = 44; - internal const int EL2NSYNC = 45; - internal const int EL3HLT = 46; - internal const int EL3RST = 47; - internal const int ELNRNG = 48; - internal const int EUNATCH = 49; - internal const int ENOCSI = 50; - internal const int EL2HLT = 51; - internal const int EBADE = 52; - internal const int EBADR = 53; - internal const int EXFULL = 54; - internal const int ENOANO = 55; - internal const int EBADRQC = 56; - internal const int EBADSLT = 57; - internal const int EDEADLOCK = EDEADLK; - internal const int EBFONT = 59; - internal const int ENOSTR = 60; - internal const int ENODATA = 61; - internal const int ETIME = 62; - internal const int ENOSR = 63; - internal const int ENONET = 64; - internal const int ENOPKG = 65; - internal const int EREMOTE = 66; - internal const int ENOLINK = 67; - internal const int EADV = 68; - internal const int ESRMNT = 69; - internal const int ECOMM = 70; - internal const int EPROTO = 71; - internal const int EMULTIHOP = 72; - internal const int EDOTDOT = 73; - internal const int EBADMSG = 74; - internal const int EOVERFLOW = 75; - internal const int ENOTUNIQ = 76; - internal const int EBADFD = 77; - internal const int EREMCHG = 78; - internal const int ELIBACC = 79; - internal const int ELIBBAD = 80; - internal const int ELIBSCN = 81; - internal const int ELIBMAX = 82; - internal const int ELIBEXEC = 83; - internal const int EILSEQ = 84; - internal const int ERESTART = 85; - internal const int ESTRPIPE = 86; - internal const int EUSERS = 87; - internal const int ENOTSOCK = 88; - internal const int EDESTADDRREQ = 89; - internal const int EMSGSIZE = 90; - internal const int EPROTOTYPE = 91; - internal const int ENOPROTOOPT = 92; - internal const int EPROTONOSUPPORT = 93; - internal const int ESOCKTNOSUPPORT = 94; - internal const int EOPNOTSUPP = 95; - internal const int ENOTSUP = EOPNOTSUPP; - internal const int EPFNOSUPPORT = 96; - internal const int EAFNOSUPPORT = 97; - internal const int EADDRINUSE = 98; - internal const int EADDRNOTAVAIL = 99; - internal const int ENETDOWN = 100; - internal const int ENETUNREACH = 101; - internal const int ENETRESET = 102; - internal const int ECONNABORTED = 103; - internal const int ECONNRESET = 104; - internal const int ENOBUFS = 105; - internal const int EISCONN = 106; - internal const int ENOTCONN = 107; - internal const int ESHUTDOWN = 108; - internal const int ETOOMANYREFS = 109; - internal const int ETIMEDOUT = 110; - internal const int ECONNREFUSED = 111; - internal const int EHOSTDOWN = 112; - internal const int EHOSTUNREACH = 113; - internal const int EALREADY = 114; - internal const int EINPROGRESS = 115; - internal const int ESTALE = 116; - internal const int EUCLEAN = 117; - internal const int ENOTNAM = 118; - internal const int ENAVAIL = 119; - internal const int EISNAM = 120; - internal const int EREMOTEIO = 121; - internal const int EDQUOT = 122; - internal const int ENOMEDIUM = 123; - internal const int EMEDIUMTYPE = 124; - internal const int ECANCELED = 125; - internal const int ENOKEY = 126; - internal const int EKEYEXPIRED = 127; - internal const int EKEYREVOKED = 128; - internal const int EKEYREJECTED = 129; - internal const int EOWNERDEAD = 130; - internal const int ENOTRECOVERABLE = 131; - internal const int ERFKILL = 132; - internal const int EHWPOISON = 133; - } -} diff --git a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/MMan.cs b/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/MMan.cs deleted file mode 100644 index 433734528b..0000000000 --- a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/MMan.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using BizHawk.BizInvoke; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - partial class Syscalls - { - internal const long MAP_FAILED = -1; - - internal const ulong MAP_SHARED = 0x01; - internal const ulong MAP_PRIVATE = 0x02; - internal const ulong MAP_SHARED_VALIDATE = 0x03; - internal const ulong MAP_TYPE = 0x0f; - internal const ulong MAP_FIXED = 0x10; - internal const ulong MAP_ANON = 0x20; - internal const ulong MAP_ANONYMOUS = MAP_ANON; - internal const ulong MAP_NORESERVE = 0x4000; - internal const ulong MAP_GROWSDOWN = 0x0100; - internal const ulong MAP_DENYWRITE = 0x0800; - internal const ulong MAP_EXECUTABLE = 0x1000; - internal const ulong MAP_LOCKED = 0x2000; - internal const ulong MAP_POPULATE = 0x8000; - internal const ulong MAP_NONBLOCK = 0x10000; - internal const ulong MAP_STACK = 0x20000; - internal const ulong MAP_HUGETLB = 0x40000; - internal const ulong MAP_SYNC = 0x80000; - internal const ulong MAP_FIXED_NOREPLACE = 0x100000; - internal const ulong MAP_FILE = 0; - - internal const ulong MAP_HUGE_SHIFT = 26; - internal const ulong MAP_HUGE_MASK = 0x3f; - internal const ulong MAP_HUGE_64KB = (16 << 26); - internal const ulong MAP_HUGE_512KB = (19 << 26); - internal const ulong MAP_HUGE_1MB = (20 << 26); - internal const ulong MAP_HUGE_2MB = (21 << 26); - internal const ulong MAP_HUGE_8MB = (23 << 26); - internal const ulong MAP_HUGE_16MB = (24 << 26); - internal const ulong MAP_HUGE_32MB = (25 << 26); - internal const ulong MAP_HUGE_256MB = (28 << 26); - internal const ulong MAP_HUGE_512MB = (29 << 26); - internal const ulong MAP_HUGE_1GB = (30 << 26); - internal const ulong MAP_HUGE_2GB = (31 << 26); - internal const ulong MAP_HUGE_16GB = (34U << 26); - - internal const ulong PROT_NONE = 0; - internal const ulong PROT_READ = 1; - internal const ulong PROT_WRITE = 2; - internal const ulong PROT_EXEC = 4; - internal const ulong PROT_GROWSDOWN = 0x01000000; - internal const ulong PROT_GROWSUP = 0x02000000; - - internal const ulong MS_ASYNC = 1; - internal const ulong MS_INVALIDATE = 2; - internal const ulong MS_SYNC = 4; - - internal const ulong MCL_CURRENT = 1; - internal const ulong MCL_FUTURE = 2; - internal const ulong MCL_ONFAULT = 4; - - internal const ulong POSIX_MADV_NORMAL = 0; - internal const ulong POSIX_MADV_RANDOM = 1; - internal const ulong POSIX_MADV_SEQUENTIAL = 2; - internal const ulong POSIX_MADV_WILLNEED = 3; - internal const ulong POSIX_MADV_DONTNEED = 4; - - internal const ulong MADV_NORMAL = 0; - internal const ulong MADV_RANDOM = 1; - internal const ulong MADV_SEQUENTIAL = 2; - internal const ulong MADV_WILLNEED = 3; - internal const ulong MADV_DONTNEED = 4; - internal const ulong MADV_FREE = 8; - internal const ulong MADV_REMOVE = 9; - internal const ulong MADV_DONTFORK = 10; - internal const ulong MADV_DOFORK = 11; - internal const ulong MADV_MERGEABLE = 12; - internal const ulong MADV_UNMERGEABLE = 13; - internal const ulong MADV_HUGEPAGE = 14; - internal const ulong MADV_NOHUGEPAGE = 15; - internal const ulong MADV_DONTDUMP = 16; - internal const ulong MADV_DODUMP = 17; - internal const ulong MADV_WIPEONFORK = 18; - internal const ulong MADV_KEEPONFORK = 19; - internal const ulong MADV_COLD = 20; - internal const ulong MADV_PAGEOUT = 21; - internal const ulong MADV_HWPOISON = 100; - internal const ulong MADV_SOFT_OFFLINE = 101; - - internal const ulong MREMAP_MAYMOVE = 1; - internal const ulong MREMAP_FIXED = 2; - - internal const ulong MLOCK_ONFAULT = 0x01; - - internal const ulong MFD_CLOEXEC = 0x0001U; - internal const ulong MFD_ALLOW_SEALING = 0x0002U; - internal const ulong MFD_HUGETLB = 0x0004U; - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[9]")] - public IntPtr MMap(IntPtr address, UIntPtr size, ulong prot, ulong flags, int fd, IntPtr offs) - { - if (address != IntPtr.Zero) - { - // waterbox cores generally don't know about hardcoded addresses - // we could support this, so long as the address is in our heap's range - return Z.SS(MAP_FAILED); - } - MemoryBlock.Protection mprot; - switch (prot) - { - case PROT_NONE: - mprot = MemoryBlock.Protection.None; - break; - default: - case PROT_WRITE | PROT_EXEC: // W^X - case PROT_READ | PROT_WRITE | PROT_EXEC: // W^X - case PROT_EXEC: // exec only???? - case PROT_WRITE: - return Z.SS(MAP_FAILED); // write only???? - case PROT_READ | PROT_WRITE: - mprot = (flags & MAP_STACK) != 0 ? MemoryBlock.Protection.RW_Stack : MemoryBlock.Protection.RW; - break; - case PROT_READ: - mprot = MemoryBlock.Protection.R; - break; - case PROT_READ | PROT_EXEC: - mprot = MemoryBlock.Protection.RX; - break; - } - if ((flags & MAP_ANONYMOUS) == 0) - { - // anonymous + private is easy - // anonymous by itself is hard - // nothing needs either right now - return Z.SS(MAP_FAILED); - } - if ((flags & 0xf00) != 0) - { - // various unsupported flags - return Z.SS(MAP_FAILED); - } - - var ret = _parent._mmapheap.Map((ulong)size, mprot); - return ret == 0 ? Z.SS(MAP_FAILED) : Z.US(ret); - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[25]")] - public IntPtr MRemap(UIntPtr oldAddress, UIntPtr oldSize, - UIntPtr newSize, ulong flags) - { - if ((flags & MREMAP_FIXED) != 0) - { - // just like mmap. - // waterbox cores generally don't know about hardcoded addresses - // we could support this, so long as the address is in our heap's range - return Z.SS(MAP_FAILED); - } - var ret = _parent._mmapheap.Remap((ulong)oldAddress, (ulong)oldSize, (ulong)newSize, - (flags & MREMAP_MAYMOVE) != 0); - return ret == 0 ? Z.SS(MAP_FAILED) : Z.US(ret); - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[11]")] - public long MUnmap(UIntPtr address, UIntPtr size) - { - return _parent._mmapheap.Unmap((ulong)address, (ulong)size) ? 0 : MAP_FAILED; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[10]")] - public long MProtect(UIntPtr address, UIntPtr size, ulong prot) - { - MemoryBlock.Protection mprot; - switch (prot) - { - case PROT_NONE: - mprot = MemoryBlock.Protection.None; - break; - default: - case PROT_WRITE | PROT_EXEC: // W^X - case PROT_READ | PROT_WRITE | PROT_EXEC: // W^X - case PROT_EXEC: // exec only???? - case PROT_WRITE: - return MAP_FAILED; // write only???? - case PROT_READ | PROT_WRITE: - mprot = MemoryBlock.Protection.RW; - break; - case PROT_READ: - mprot = MemoryBlock.Protection.R; - break; - case PROT_READ | PROT_EXEC: - mprot = MemoryBlock.Protection.RX; - break; - } - return _parent._mmapheap.Protect((ulong)address, (ulong)size, mprot) ? 0 : MAP_FAILED; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[28]")] - public long MAdvise(UIntPtr address, UIntPtr size, ulong advice) - { - // malloc will call this with DONTNEED in a few situations. Ignore for now. - // TODO: This could be implemented along with munmap returning pages to non-dirty - return 0; - } - } -} diff --git a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/NotImplementedSyscalls.cs b/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/NotImplementedSyscalls.cs deleted file mode 100644 index 1d82a3338c..0000000000 --- a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/NotImplementedSyscalls.cs +++ /dev/null @@ -1,422 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using BizHawk.BizInvoke; -using BizHawk.Common; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - /// - /// Provides useful traps for any syscalls that are not implemented by libc - /// - internal class NotImplementedSyscalls : IImportResolver - { - private static readonly Dictionary SyscallNames = new Dictionary() - { - { 0, "read" }, - { 1, "write" }, - { 2, "open" }, - { 3, "close" }, - { 4, "stat" }, - { 5, "fstat" }, - { 6, "lstat" }, - { 7, "poll" }, - { 8, "lseek" }, - { 9, "mmap" }, - { 10, "mprotect" }, - { 11, "munmap" }, - { 12, "brk" }, - { 13, "rt_sigaction" }, - { 14, "rt_sigprocmask" }, - { 15, "rt_sigreturn" }, - { 16, "ioctl" }, - { 17, "pread64" }, - { 18, "pwrite64" }, - { 19, "readv" }, - { 20, "writev" }, - { 21, "access" }, - { 22, "pipe" }, - { 23, "select" }, - { 24, "sched_yield" }, - { 25, "mremap" }, - { 26, "msync" }, - { 27, "mincore" }, - { 28, "madvise" }, - { 29, "shmget" }, - { 30, "shmat" }, - { 31, "shmctl" }, - { 32, "dup" }, - { 33, "dup2" }, - { 34, "pause" }, - { 35, "nanosleep" }, - { 36, "getitimer" }, - { 37, "alarm" }, - { 38, "setitimer" }, - { 39, "getpid" }, - { 40, "sendfile" }, - { 41, "socket" }, - { 42, "connect" }, - { 43, "accept" }, - { 44, "sendto" }, - { 45, "recvfrom" }, - { 46, "sendmsg" }, - { 47, "recvmsg" }, - { 48, "shutdown" }, - { 49, "bind" }, - { 50, "listen" }, - { 51, "getsockname" }, - { 52, "getpeername" }, - { 53, "socketpair" }, - { 54, "setsockopt" }, - { 55, "getsockopt" }, - { 56, "clone" }, - { 57, "fork" }, - { 58, "vfork" }, - { 59, "execve" }, - { 60, "exit" }, - { 61, "wait4" }, - { 62, "kill" }, - { 63, "uname" }, - { 64, "semget" }, - { 65, "semop" }, - { 66, "semctl" }, - { 67, "shmdt" }, - { 68, "msgget" }, - { 69, "msgsnd" }, - { 70, "msgrcv" }, - { 71, "msgctl" }, - { 72, "fcntl" }, - { 73, "flock" }, - { 74, "fsync" }, - { 75, "fdatasync" }, - { 76, "truncate" }, - { 77, "ftruncate" }, - { 78, "getdents" }, - { 79, "getcwd" }, - { 80, "chdir" }, - { 81, "fchdir" }, - { 82, "rename" }, - { 83, "mkdir" }, - { 84, "rmdir" }, - { 85, "creat" }, - { 86, "link" }, - { 87, "unlink" }, - { 88, "symlink" }, - { 89, "readlink" }, - { 90, "chmod" }, - { 91, "fchmod" }, - { 92, "chown" }, - { 93, "fchown" }, - { 94, "lchown" }, - { 95, "umask" }, - { 96, "gettimeofday" }, - { 97, "getrlimit" }, - { 98, "getrusage" }, - { 99, "sysinfo" }, - { 100, "times" }, - { 101, "ptrace" }, - { 102, "getuid" }, - { 103, "syslog" }, - { 104, "getgid" }, - { 105, "setuid" }, - { 106, "setgid" }, - { 107, "geteuid" }, - { 108, "getegid" }, - { 109, "setpgid" }, - { 110, "getppid" }, - { 111, "getpgrp" }, - { 112, "setsid" }, - { 113, "setreuid" }, - { 114, "setregid" }, - { 115, "getgroups" }, - { 116, "setgroups" }, - { 117, "setresuid" }, - { 118, "getresuid" }, - { 119, "setresgid" }, - { 120, "getresgid" }, - { 121, "getpgid" }, - { 122, "setfsuid" }, - { 123, "setfsgid" }, - { 124, "getsid" }, - { 125, "capget" }, - { 126, "capset" }, - { 127, "rt_sigpending" }, - { 128, "rt_sigtimedwait" }, - { 129, "rt_sigqueueinfo" }, - { 130, "rt_sigsuspend" }, - { 131, "sigaltstack" }, - { 132, "utime" }, - { 133, "mknod" }, - { 134, "uselib" }, - { 135, "personality" }, - { 136, "ustat" }, - { 137, "statfs" }, - { 138, "fstatfs" }, - { 139, "sysfs" }, - { 140, "getpriority" }, - { 141, "setpriority" }, - { 142, "sched_setparam" }, - { 143, "sched_getparam" }, - { 144, "sched_setscheduler" }, - { 145, "sched_getscheduler" }, - { 146, "sched_get_priority_max" }, - { 147, "sched_get_priority_min" }, - { 148, "sched_rr_get_interval" }, - { 149, "mlock" }, - { 150, "munlock" }, - { 151, "mlockall" }, - { 152, "munlockall" }, - { 153, "vhangup" }, - { 154, "modify_ldt" }, - { 155, "pivot_root" }, - { 156, "_sysctl" }, - { 157, "prctl" }, - { 158, "arch_prctl" }, - { 159, "adjtimex" }, - { 160, "setrlimit" }, - { 161, "chroot" }, - { 162, "sync" }, - { 163, "acct" }, - { 164, "settimeofday" }, - { 165, "mount" }, - { 166, "umount2" }, - { 167, "swapon" }, - { 168, "swapoff" }, - { 169, "reboot" }, - { 170, "sethostname" }, - { 171, "setdomainname" }, - { 172, "iopl" }, - { 173, "ioperm" }, - { 174, "create_module" }, - { 175, "init_module" }, - { 176, "delete_module" }, - { 177, "get_kernel_syms" }, - { 178, "query_module" }, - { 179, "quotactl" }, - { 180, "nfsservctl" }, - { 181, "getpmsg" }, - { 182, "putpmsg" }, - { 183, "afs_syscall" }, - { 184, "tuxcall" }, - { 185, "security" }, - { 186, "gettid" }, - { 187, "readahead" }, - { 188, "setxattr" }, - { 189, "lsetxattr" }, - { 190, "fsetxattr" }, - { 191, "getxattr" }, - { 192, "lgetxattr" }, - { 193, "fgetxattr" }, - { 194, "listxattr" }, - { 195, "llistxattr" }, - { 196, "flistxattr" }, - { 197, "removexattr" }, - { 198, "lremovexattr" }, - { 199, "fremovexattr" }, - { 200, "tkill" }, - { 201, "time" }, - { 202, "futex" }, - { 203, "sched_setaffinity" }, - { 204, "sched_getaffinity" }, - { 205, "set_thread_area" }, - { 206, "io_setup" }, - { 207, "io_destroy" }, - { 208, "io_getevents" }, - { 209, "io_submit" }, - { 210, "io_cancel" }, - { 211, "get_thread_area" }, - { 212, "lookup_dcookie" }, - { 213, "epoll_create" }, - { 214, "epoll_ctl_old" }, - { 215, "epoll_wait_old" }, - { 216, "remap_file_pages" }, - { 217, "getdents64" }, - { 218, "set_tid_address" }, - { 219, "restart_syscall" }, - { 220, "semtimedop" }, - { 221, "fadvise64" }, - { 222, "timer_create" }, - { 223, "timer_settime" }, - { 224, "timer_gettime" }, - { 225, "timer_getoverrun" }, - { 226, "timer_delete" }, - { 227, "clock_settime" }, - { 228, "clock_gettime" }, - { 229, "clock_getres" }, - { 230, "clock_nanosleep" }, - { 231, "exit_group" }, - { 232, "epoll_wait" }, - { 233, "epoll_ctl" }, - { 234, "tgkill" }, - { 235, "utimes" }, - { 236, "vserver" }, - { 237, "mbind" }, - { 238, "set_mempolicy" }, - { 239, "get_mempolicy" }, - { 240, "mq_open" }, - { 241, "mq_unlink" }, - { 242, "mq_timedsend" }, - { 243, "mq_timedreceive" }, - { 244, "mq_notify" }, - { 245, "mq_getsetattr" }, - { 246, "kexec_load" }, - { 247, "waitid" }, - { 248, "add_key" }, - { 249, "request_key" }, - { 250, "keyctl" }, - { 251, "ioprio_set" }, - { 252, "ioprio_get" }, - { 253, "inotify_init" }, - { 254, "inotify_add_watch" }, - { 255, "inotify_rm_watch" }, - { 256, "migrate_pages" }, - { 257, "openat" }, - { 258, "mkdirat" }, - { 259, "mknodat" }, - { 260, "fchownat" }, - { 261, "futimesat" }, - { 262, "newfstatat" }, - { 263, "unlinkat" }, - { 264, "renameat" }, - { 265, "linkat" }, - { 266, "symlinkat" }, - { 267, "readlinkat" }, - { 268, "fchmodat" }, - { 269, "faccessat" }, - { 270, "pselect6" }, - { 271, "ppoll" }, - { 272, "unshare" }, - { 273, "set_robust_list" }, - { 274, "get_robust_list" }, - { 275, "splice" }, - { 276, "tee" }, - { 277, "sync_file_range" }, - { 278, "vmsplice" }, - { 279, "move_pages" }, - { 280, "utimensat" }, - { 281, "epoll_pwait" }, - { 282, "signalfd" }, - { 283, "timerfd_create" }, - { 284, "eventfd" }, - { 285, "fallocate" }, - { 286, "timerfd_settime" }, - { 287, "timerfd_gettime" }, - { 288, "accept4" }, - { 289, "signalfd4" }, - { 290, "eventfd2" }, - { 291, "epoll_create1" }, - { 292, "dup3" }, - { 293, "pipe2" }, - { 294, "inotify_init1" }, - { 295, "preadv" }, - { 296, "pwritev" }, - { 297, "rt_tgsigqueueinfo" }, - { 298, "perf_event_open" }, - { 299, "recvmmsg" }, - { 300, "fanotify_init" }, - { 301, "fanotify_mark" }, - { 302, "prlimit64" }, - { 303, "name_to_handle_at" }, - { 304, "open_by_handle_at" }, - { 305, "clock_adjtime" }, - { 306, "syncfs" }, - { 307, "sendmmsg" }, - { 308, "setns" }, - { 309, "getcpu" }, - { 310, "process_vm_readv" }, - { 311, "process_vm_writev" }, - { 312, "kcmp" }, - { 313, "finit_module" }, - { 314, "sched_setattr" }, - { 315, "sched_getattr" }, - { 316, "renameat2" }, - { 317, "seccomp" }, - { 318, "getrandom" }, - { 319, "memfd_create" }, - { 320, "kexec_file_load" }, - { 321, "bpf" }, - { 322, "execveat" }, - { 323, "userfaultfd" }, - { 324, "membarrier" }, - { 325, "mlock2" }, - { 326, "copy_file_range" }, - { 327, "preadv2" }, - { 328, "pwritev2" }, - { 329, "pkey_mprotect" }, - { 330, "pkey_alloc" }, - { 331, "pkey_free" }, - { 332, "statx" }, - { 333, "io_pgetevents" }, - { 334, "rseq" }, - { 424, "pidfd_send_signal" }, - { 425, "io_uring_setup" }, - { 426, "io_uring_enter" }, - { 427, "io_uring_register" }, - { 428, "open_tree" }, - { 429, "move_mount" }, - { 430, "fsopen" }, - { 431, "fsconfig" }, - { 432, "fsmount" }, - { 433, "fspick" }, - { 434, "pidfd_open" }, - { 435, "clone3" }, - }; - - private class Trap - { - private readonly int _index; - private readonly string _message; - private readonly IImportResolver _resolver; - public Trap(int index) - { - _index = index; - _resolver = BizExvoker.GetExvoker(this, CallingConventionAdapters.Waterbox); - if (!SyscallNames.TryGetValue(_index, out var name)) - name = "???"; - _message = $"Trapped on unimplemented syscall {name} (#{_index})"; - } - [BizExport(CallingConvention.Cdecl, EntryPoint="@@")] - public void RunTrap() - { - System.Diagnostics.Debug.WriteLine(_message); - // TODO: this unwind never works, so debugbrk instead - System.Diagnostics.Debugger.Break(); - throw new InvalidOperationException(_message); - } - public IntPtr FunctionPointer => _resolver.GetProcAddrOrThrow("@@"); - } - private readonly List _traps; - private NotImplementedSyscalls() - { - _traps = Enumerable.Range(0, 512) - .Select(i => new Trap(i)) - .ToList(); - } - - private static readonly Regex ExportRegex = new Regex("__wsyscalltab\\[(\\d+)\\]"); - - public IntPtr GetProcAddrOrZero(string entryPoint) - { - var m = ExportRegex.Match(entryPoint); - if (m.Success) - { - return _traps[int.Parse(m.Groups[1].Value)].FunctionPointer; - } - return IntPtr.Zero; - } - - public IntPtr GetProcAddrOrThrow(string entryPoint) - { - var m = ExportRegex.Match(entryPoint); - if (m.Success) - { - return _traps[int.Parse(m.Groups[1].Value)].FunctionPointer; - } - throw new InvalidOperationException($"{entryPoint} was not of the format __wsyscalltab[#]"); - } - - public static NotImplementedSyscalls Instance { get; } = new NotImplementedSyscalls(); - } -} diff --git a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Stat.cs b/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Stat.cs deleted file mode 100644 index a2f4e22802..0000000000 --- a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Stat.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using BizHawk.BizInvoke; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - unsafe partial class Syscalls - { - internal const uint S_IFMT = 61440; - - internal const uint S_IFDIR = 16384; - internal const uint S_IFCHR = 8192; - internal const uint S_IFBLK = 24576; - internal const uint S_IFREG = 32768; - internal const uint S_IFIFO = 4096; - internal const uint S_IFLNK = 40960; - internal const uint S_IFSOCK = 49152; - - internal const uint S_ISUID = 2048; - internal const uint S_ISGID = 1024; - internal const uint S_ISVTX = 512; - internal const uint S_IRUSR = 0400; - internal const uint S_IWUSR = 256; - internal const uint S_IXUSR = 64; - internal const uint S_IRWXU = 448; - internal const uint S_IRGRP = 32; - internal const uint S_IWGRP = 16; - internal const uint S_IXGRP = 8; - internal const uint S_IRWXG = 56; - internal const uint S_IROTH = 4; - internal const uint S_IWOTH = 2; - internal const uint S_IXOTH = 1; - internal const uint S_IRWXO = 7; - - - [StructLayout(LayoutKind.Sequential)] - public struct KStat - { - public ulong st_dev; - public ulong st_ino; - public ulong st_nlink; - - public uint st_mode; - public uint st_uid; - public uint st_gid; - public uint __pad0; - public ulong st_rdev; - public long st_size; - public long st_blksize; - public long st_blocks; - - public long st_atime_sec; - public long st_atime_nsec; - public long st_mtime_sec; - public long st_mtime_nsec; - public long st_ctime_sec; - public long st_ctime_nsec; - public long __unused0; - public long __unused1; - public long __unused2; - } - - private void StatInternal(KStat* s, IFileObject o) - { - s->st_dev = 1; - s->st_ino = 1; - s->st_nlink = 0; - - uint flags = 0; - if (o.Stream.CanRead) - flags |= S_IRUSR | S_IRGRP | S_IROTH; - if (o.Stream.CanWrite) - flags |= S_IWUSR | S_IWGRP | S_IWOTH; - if (o.Stream.CanSeek) - flags |= S_IFREG; - else - flags |= S_IFIFO; - s->st_mode = flags; - s->st_uid = 0; - s->st_gid = 0; - s->__pad0 = 0; - s->st_rdev = 0; - if (o.Stream.CanSeek) - s->st_size = o.Stream.Length; - else - s->st_size = 0; - s->st_blksize = 4096; - s->st_blocks = (s->st_size + 511) / 512; - - s->st_atime_sec = 1262304000000; - s->st_atime_nsec = 1000000000 / 2; - s->st_mtime_sec = 1262304000000; - s->st_mtime_nsec = 1000000000 / 2; - s->st_ctime_sec = 1262304000000; - s->st_ctime_nsec = 1000000000 / 2; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[4]")] - public long Stat(string path, KStat* statbuf) - { - if (!_availableFiles.TryGetValue(path, out var o)) - return -ENOENT; - - StatInternal(statbuf, o); - return 0; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[5]")] - public long Fstat(int fd, KStat* statbuf) - { - if (fd < 0 || fd >= _openFiles.Count) - return -EBADF; - var o = _openFiles[fd]; - if (o == null) - return -EBADF; - StatInternal(statbuf, o); - return 0; - } - } -} diff --git a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Syscalls.cs b/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Syscalls.cs deleted file mode 100644 index 3c8d174c37..0000000000 --- a/src/BizHawk.Emulation.Cores/Waterbox/Syscalls/Syscalls.cs +++ /dev/null @@ -1,498 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using BizHawk.BizInvoke; -using BizHawk.Common; -using BizHawk.Emulation.Common; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - /// - /// syscall emulation layer - /// - internal partial class Syscalls : IBinaryStateable - { - /// - /// Can be set by the frontend and will be called if the core attempts to open a missing file. - /// The callee may add additional files to the waterbox during the callback and return `true` to indicate - /// that the right file was added and the scan should be rerun. The callee may return `false` to indicate - /// that the file should be reported as missing. Do not call other things during this callback. - /// Can be called at any time by the core, so you may want to remove your callback entirely after init - /// if it was for firmware only. - /// - public Func MissingFileCallback { get; set; } - public interface IFileObject : IBinaryStateable - { - bool Open(FileAccess access); - bool Close(); - Stream Stream { get; } - string Name { get; } - } - - private class SpecialFile : IFileObject - { - // stdin, stdout, stderr - public string Name { get; } - public Stream Stream { get; } - public bool Close() => false; - public bool Open(FileAccess access) => false; - - public void SaveStateBinary(BinaryWriter writer) { } - public void LoadStateBinary(BinaryReader reader) { } - - public SpecialFile(Stream stream, string name) - { - Stream = stream; - Name = name; - } - } - - private class ReadonlyFirmware : IFileObject - { - private readonly byte[] _data; - private readonly byte[] _hash; - - public string Name { get; } - public Stream Stream { get; private set; } - public bool Close() - { - if (Stream == null) - return false; - Stream = null; - return true; - } - - public bool Open(FileAccess access) - { - if (Stream != null || access != FileAccess.Read) - return false; - Stream = new MemoryStream(_data, false); - return true; - } - - public void LoadStateBinary(BinaryReader br) - { - if (!br.ReadBytes(_hash.Length).SequenceEqual(_hash)) - throw new InvalidOperationException("Savestate internal firmware mismatch"); - var pos = br.ReadInt64(); - if (pos == -1) - { - Stream = null; - } - else - { - if (Stream == null) - Open(FileAccess.Read); - Stream.Position = pos; - } - } - - public void SaveStateBinary(BinaryWriter bw) - { - bw.Write(_hash); - bw.Write(Stream != null ? Stream.Position : -1); - } - - public ReadonlyFirmware(byte[] data, string name) - { - _data = data; - _hash = WaterboxUtils.Hash(data); - Name = name; - } - } - - private class TransientFile : IFileObject - { - private bool _inUse = false; - public string Name { get; } - public Stream Stream { get; } - public bool Close() - { - if (_inUse) - { - _inUse = false; - return true; - } - else - { - return false; - } - } - - public bool Open(FileAccess access) - { - if (_inUse) - { - return false; - } - else - { - // TODO: if access != RW, the resultant handle lets you do those all anyway - _inUse = true; - Stream.Position = 0; - return true; - } - } - - public void LoadStateBinary(BinaryReader br) - { - throw new InvalidOperationException("Internal savestate error!"); - } - - public void SaveStateBinary(BinaryWriter bw) - { - throw new InvalidOperationException("Transient files cannot be savestated!"); - } - - public TransientFile(byte[] data, string name) - { - Stream = new MemoryStream(); - Name = name; - if (data != null) - { - Stream.Write(data, 0, data.Length); - Stream.Position = 0; - } - } - - public byte[] GetContents() - { - if (_inUse) - throw new InvalidOperationException(); - return ((MemoryStream)Stream).ToArray(); - } - } - - private readonly List _openFiles = new List(); - private readonly Dictionary _availableFiles = new Dictionary(); - - private readonly WaterboxHost _parent; - public Syscalls(WaterboxHost parent) - { - _parent = parent; - var stdin = new SpecialFile(Stream.Null, "___stdin"); - var stdout = new SpecialFile(Console.OpenStandardOutput(), "___stdout"); - var stderr = new SpecialFile(Console.OpenStandardError(), "___stderr"); - - _openFiles = new List - { - stdin, - stdout, - stderr - }; - _availableFiles = new Dictionary - { - [stdin.Name] = stdin, - [stdout.Name] = stdout, - [stderr.Name] = stderr - }; - } - - private Stream StreamForFd(int fd) - { - if (fd >= 0 && fd < _openFiles.Count) - return _openFiles[fd].Stream; - else - return null; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[12]")] - public UIntPtr Brk(UIntPtr _p) - { - var heap = _parent._heap; - - var start = heap.Memory.Start; - var currentEnd = start + heap.Used; - var maxEnd = heap.Memory.EndExclusive; - - var p = (ulong)_p; - - if (p < start || p > maxEnd) - { - // failure: return current break - return Z.UU(currentEnd); - } - else if (p > currentEnd) - { - // increase size of heap - heap.Allocate(p - currentEnd, 1); - return Z.UU(p); - } - else if (p < currentEnd) - { - throw new InvalidOperationException("We don't support shrinking heaps"); - } - else - { - // no change - return Z.UU(currentEnd); - } - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[16]")] - 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 = "__wsyscalltab[0]")] - public long Read(int fd, IntPtr buff, ulong count) - { - var s = StreamForFd(fd); - if (s == null || !s.CanRead) - return -1; - var tmp = new byte[count]; - var ret = s.Read(tmp, 0, (int)count); - Marshal.Copy(tmp, 0, buff, ret); - return ret; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[1]")] - public long Write(int fd, IntPtr buff, ulong count) - { - var s = StreamForFd(fd); - if (s == null || !s.CanWrite) - return -1; - var tmp = new byte[count]; - Marshal.Copy(buff, tmp, 0, (int)count); - s.Write(tmp, 0, tmp.Length); - return (long)count; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[19]")] - public unsafe long Readv(int fd, Iovec* iov, int iovcnt) - { - long ret = 0; - for (int i = 0; i < iovcnt; i++) - { - var len = Read(fd, iov[i].Base, iov[i].Length); - if (len < 0) - return len; - ret += len; - if (len != (long)iov[i].Length) - break; - } - return ret; - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[20]")] - public unsafe long Writev(int fd, Iovec* iov, int iovcnt) - { - long ret = 0; - for (int i = 0; i < iovcnt; i++) - { - if (iov[i].Base != IntPtr.Zero) - ret += Write(fd, iov[i].Base, iov[i].Length); - } - return ret; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[2]")] - public long Open(string path, int flags, int mode) - { - if (!_availableFiles.TryGetValue(path, out var o)) - { - var retry = MissingFileCallback?.Invoke(path) == true; - if (retry) - { - if (!_availableFiles.TryGetValue(path, out o)) - return -ENOENT; - } - else - { - return -ENOENT; - } - } - if (_openFiles.Contains(o)) - return -EACCES; - FileAccess access; - switch (flags & 3) - { - case 0: - access = FileAccess.Read; - break; - case 1: - access = FileAccess.Write; - break; - case 2: - access = FileAccess.ReadWrite; - break; - default: - return -EINVAL; - } - if (!o.Open(access)) - return -EACCES; - int fd; - for (fd = 0; fd < _openFiles.Count; fd++) - if (_openFiles[fd] == null) - break; - if (fd == _openFiles.Count) - _openFiles.Add(o); - else - _openFiles[fd] = o; - return fd; - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[3]")] - public long Close(int fd) - { - if (fd < 0 || fd >= _openFiles.Count) - return -EBADF; - var o = _openFiles[fd]; - if (o == null) - return -EBADF; - if (!o.Close()) - return -EIO; - _openFiles[fd] = null; - return 0; - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[8]")] - public long Seek(int fd, long offset, int type) - { - var s = StreamForFd(fd); - if (s == null) - return -EBADF; - if (!s.CanSeek) - return -ESPIPE; - SeekOrigin o; - switch (type) - { - case 0: - o = SeekOrigin.Begin; - break; - case 1: - o = SeekOrigin.Current; - break; - case 2: - o = SeekOrigin.End; - break; - default: - return -EINVAL; - } - try - { - return s.Seek(offset, o); - } - catch (IOException) - { - // usually means bad offset, since we already filtered out on CanSeek - return -EINVAL; - } - } - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[77]")] - public long FTruncate(int fd, long length) - { - var s = StreamForFd(fd); - if (s == null) - return -EBADF; - if (!s.CanWrite) - return -ESPIPE; - if (!s.CanSeek) - return -ESPIPE; - s.SetLength(length); - return 0; - } - - // TODO: Remove this entirely once everything is compiled against the new libc - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[205]")] - public long SetThreadArea(IntPtr uinfo) - { - return 38; // ENOSYS - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[218]")] - public long SetTidAddress(IntPtr address) - { - // pretend we succeeded - return 8675309; // arbitrary thread id - } - - [StructLayout(LayoutKind.Sequential)] - public struct TimeSpec - { - public long Seconds; - public long NanoSeconds; - } - - [BizExport(CallingConvention.Cdecl, EntryPoint = "__wsyscalltab[228]")] - public unsafe int SysClockGetTime(int which, TimeSpec* time) - { - time->Seconds = 1495889068; - time->NanoSeconds = 0; - return 0; - } - - public void SaveStateBinary(BinaryWriter bw) - { - bw.Write(_availableFiles.Count); - foreach (var f in _availableFiles.Values.OrderBy(f => f.Name)) - { - bw.Write(f.Name); - f.SaveStateBinary(bw); - } - bw.Write(_openFiles.Count); - foreach (var f in _openFiles) - { - bw.Write(f != null); - if (f != null) - bw.Write(f.Name); - } - } - - public void LoadStateBinary(BinaryReader br) - { - if (_availableFiles.Count != br.ReadInt32()) - throw new InvalidOperationException("Internal savestate error: Filelist change"); - foreach (var f in _availableFiles.Values.OrderBy(f => f.Name)) - { - if (br.ReadString() != f.Name) - throw new InvalidOperationException("Internal savestate error: Filelist change"); - f.LoadStateBinary(br); - } - var c = br.ReadInt32(); - _openFiles.Clear(); - for (int i = 0; i < c; i++) - { - _openFiles.Add(br.ReadBoolean() ? _availableFiles[br.ReadString()] : null); - } - } - - private T RemoveFileInternal(string name) - where T : IFileObject - { - if (!_availableFiles.TryGetValue(name, out var o)) - throw new InvalidOperationException("File was never registered!"); - if (o.GetType() != typeof(T)) - throw new InvalidOperationException("Object was not a the right kind of file"); - if (_openFiles.Contains(o)) - throw new InvalidOperationException("Core never closed the file!"); - _availableFiles.Remove(name); - return (T)o; - } - - public void AddReadonlyFile(byte[] data, string name) - { - _availableFiles.Add(name, new ReadonlyFirmware(data, name)); - } - - public void RemoveReadonlyFile(string name) - { - RemoveFileInternal(name); - } - - public void AddTransientFile(byte[] data, string name) - { - _availableFiles.Add(name, new TransientFile(data, name)); - } - public byte[] RemoveTransientFile(string name) - { - return RemoveFileInternal(name).GetContents(); - } - } -} diff --git a/src/BizHawk.Emulation.Cores/Waterbox/WaterboxHost.cs b/src/BizHawk.Emulation.Cores/Waterbox/WaterboxHost.cs index 497021b016..8292cdf529 100644 --- a/src/BizHawk.Emulation.Cores/Waterbox/WaterboxHost.cs +++ b/src/BizHawk.Emulation.Cores/Waterbox/WaterboxHost.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; +using static BizHawk.Emulation.Cores.Waterbox.WaterboxHostNative; namespace BizHawk.Emulation.Cores.Waterbox { @@ -53,11 +54,6 @@ namespace BizHawk.Emulation.Cores.Waterbox /// public uint MmapHeapSizeKB { get; set; } - /// - /// start address in memory - /// - public ulong StartAddress { get; set; } = WaterboxHost.CanonicalStart; - /// /// Skips the check that the wbx file and other associated dlls match from state save to state load. /// DO NOT SET THIS TO TRUE. A different executable most likely means different meanings for memory locations, @@ -72,222 +68,84 @@ namespace BizHawk.Emulation.Cores.Waterbox public bool SkipMemoryConsistencyCheck { get; set; } = false; } - public class WaterboxHost : Swappable, IImportResolver, IBinaryStateable + public unsafe class WaterboxHost : IMonitor, IImportResolver, IBinaryStateable, IDisposable { + private IntPtr _nativeHost; + private IntPtr _activatedNativeHost; + private int _enterCount; + private object _keepAliveDelegate; + + private static readonly WaterboxHostNative NativeImpl; static WaterboxHost() { - if (OSTailoredCode.IsUnixHost) - { - WaterboxLibcoLinuxStartup.Setup(); - } + NativeImpl = BizInvoker.GetInvoker( + new DynamicLibraryImportResolver(OSTailoredCode.IsUnixHost ? "libwaterboxhost.so" : "waterboxhost.dll", eternal: true), + CallingConventionAdapters.Native); + #if !DEBUG + NativeImpl.wbx_set_always_evict_blocks(false); + #endif } - /// - /// usual starting point for the executable - /// - public const ulong CanonicalStart = 0x0000036f00000000; - - /// - /// 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) + private ReadCallback Reader(Stream s) { - _nextStart += size; - // align to 1MB, then increment 16MB - _nextStart = ((_nextStart - 1) | 0xfffff) + 0x1000001; + var ret = MakeCallbackForReader(s); + _keepAliveDelegate = s; + return ret; } - - /// - /// standard malloc() heap - /// - internal Heap _heap; - - /// - /// sealed heap (writable only during init) - /// - internal Heap _sealedheap; - - /// - /// invisible heap (not savestated, use with care) - /// - internal Heap _invisibleheap; - - /// - /// extra savestated heap - /// - internal Heap _plainheap; - - /// - /// memory map emulation - /// - internal MapHeap _mmapheap; - - /// - /// the loaded elf file - /// - private ElfLoader _module; - - /// - /// 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(); - - private readonly EmuLibc _emu; - private readonly Syscalls _syscalls; - - /// - /// the set of functions made available for the elf module - /// - private readonly IImportResolver _imports; - - /// - /// 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) + private WriteCallback Writer(Stream s) { - 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; - } + var ret = MakeCallbackForWriter(s); + _keepAliveDelegate = s; + return ret; } public WaterboxHost(WaterboxOptions opt) { - _nextStart = opt.StartAddress; - Initialize(_nextStart); - using (this.EnterExit()) + var nativeOpts = new MemoryLayoutTemplate { - _emu = new EmuLibc(this); - _syscalls = new Syscalls(this); + sbrk_size = Z.UU(opt.SbrkHeapSizeKB * 1024), + sealed_size = Z.UU(opt.SealedHeapSizeKB * 1024), + invis_size = Z.UU(opt.InvisibleHeapSizeKB * 1024), + plain_size = Z.UU(opt.PlainHeapSizeKB * 1024), + mmap_size = Z.UU(opt.MmapHeapSizeKB * 1024), + }; - _imports = new PatchImportResolver( - NotImplementedSyscalls.Instance, - BizExvoker.GetExvoker(_emu, CallingConventionAdapters.Waterbox), - BizExvoker.GetExvoker(_syscalls, CallingConventionAdapters.Waterbox) - ); + var moduleName = opt.Filename; - if (true) - { - var moduleName = opt.Filename; - - 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); - data = Util.DecompressGzipFile(fs); - } - else - { - data = File.ReadAllBytes(path); - } - - _module = new ElfLoader(moduleName, data, _nextStart, opt.SkipCoreConsistencyCheck, opt.SkipMemoryConsistencyCheck); - - ComputeNextStart(_module.Memory.Size); - AddMemoryBlock(_module.Memory, moduleName); - _savestateComponents.Add(_module); - _disposeList.Add(_module); - } - - 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); - } - - // TODO: This debugger stuff doesn't work on nix? - System.Diagnostics.Debug.WriteLine($"About to enter unmanaged code for {opt.Filename}"); - if (OSTailoredCode.IsUnixHost) - { - if (System.Diagnostics.Debugger.IsAttached) - System.Diagnostics.Debugger.Break(); - } - else - { - if (!System.Diagnostics.Debugger.IsAttached && Win32Imports.IsDebuggerPresent()) - { - // 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(); - } - } - _module.RunNativeInit(); + 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); + data = Util.DecompressGzipFile(fs); } + else + { + data = File.ReadAllBytes(path); + } + + var retobj = new ReturnData(); + NativeImpl.wbx_create_host(nativeOpts, opt.Filename, Reader(new MemoryStream(data, false)), IntPtr.Zero, retobj); + _nativeHost = retobj.GetDataOrThrow(); } public IntPtr GetProcAddrOrZero(string entryPoint) { - var addr = _module.GetProcAddrOrZero(entryPoint); - if (addr != IntPtr.Zero) + using (this.EnterExit()) { - var exclude = _imports.GetProcAddrOrZero(entryPoint); - if (exclude != IntPtr.Zero) - { - // don't reexport anything that's part of waterbox internals - return IntPtr.Zero; - } + var retobj = new ReturnData(); + NativeImpl.wbx_get_proc_addr(_activatedNativeHost, entryPoint, retobj); + return retobj.GetDataOrThrow(); } - return addr; } public IntPtr GetProcAddrOrThrow(string entryPoint) { - var addr = _module.GetProcAddrOrZero(entryPoint); + var addr = GetProcAddrOrZero(entryPoint); if (addr != IntPtr.Zero) { - var exclude = _imports.GetProcAddrOrZero(entryPoint); - if (exclude != IntPtr.Zero) - { - // don't reexport anything that's part of waterbox internals - throw new InvalidOperationException($"Tried to resolve {entryPoint}, but it should not be exported"); - } - else - { - return addr; - } + return addr; } else { @@ -299,38 +157,13 @@ namespace BizHawk.Emulation.Cores.Waterbox { 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... - - // TODO: MAKE SURE THIS STILL WORKS - IntPtr co_clean; - if ((co_clean = _module.GetProcAddrOrZero("co_clean")) != IntPtr.Zero) - { - Console.WriteLine("Calling co_clean()."); - CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(co_clean)(); - } - - _sealedheap.Seal(); - foreach (var h in _heaps) - { - if (h != _invisibleheap && h != _sealedheap) // TODO: if we have more non-savestated heaps, refine this hack - h.Memory.Seal(); - } - _module.SealImportsAndTakeXorSnapshot(); - _mmapheap?.Memory.Seal(); + var retobj = new ReturnData(); + NativeImpl.wbx_seal(_activatedNativeHost, retobj); + retobj.GetDataOrThrow(); } Console.WriteLine("WaterboxHost Sealed!"); } - private void ConnectAllImports() - { - _module.ConnectSyscalls(_imports); - } - /// /// Adds a file that will appear to the waterbox core's libc. the file will be read only. /// All savestates must have the same file list, so either leave it up forever or remove it during init! @@ -338,7 +171,12 @@ namespace BizHawk.Emulation.Cores.Waterbox /// the filename that the unmanaged core will access the file by public void AddReadonlyFile(byte[] data, string name) { - _syscalls.AddReadonlyFile((byte[])data.Clone(), name); + using (this.EnterExit()) + { + var retobj = new ReturnData(); + NativeImpl.wbx_mount_file(_activatedNativeHost, name, Reader(new MemoryStream(data, false)), IntPtr.Zero, false, retobj); + retobj.GetDataOrThrow(); + } } /// @@ -347,7 +185,12 @@ namespace BizHawk.Emulation.Cores.Waterbox /// public void RemoveReadonlyFile(string name) { - _syscalls.RemoveReadonlyFile(name); + using (this.EnterExit()) + { + var retobj = new ReturnData(); + NativeImpl.wbx_unmount_file(_activatedNativeHost, name, null, IntPtr.Zero, retobj); + retobj.GetDataOrThrow(); + } } /// @@ -356,7 +199,12 @@ namespace BizHawk.Emulation.Cores.Waterbox /// public void AddTransientFile(byte[] data, string name) { - _syscalls.AddTransientFile(data, name); // don't need to clone data, as it's used at init only + using (this.EnterExit()) + { + var retobj = new ReturnData(); + NativeImpl.wbx_mount_file(_activatedNativeHost, name, Reader(new MemoryStream(data, false)), IntPtr.Zero, true, retobj); + retobj.GetDataOrThrow(); + } } /// @@ -365,108 +213,123 @@ namespace BizHawk.Emulation.Cores.Waterbox /// The state of the file when it was removed public byte[] RemoveTransientFile(string name) { - return _syscalls.RemoveTransientFile(name); + using (this.EnterExit()) + { + var retobj = new ReturnData(); + var ms = new MemoryStream(); + NativeImpl.wbx_unmount_file(_activatedNativeHost, name, Writer(ms), IntPtr.Zero, retobj); + retobj.GetDataOrThrow(); + return ms.ToArray(); + } } + // public class MissingFileResult + // { + // public byte[] data; + // public bool writable; + // } + /// /// Can be set by the frontend and will be called if the core attempts to open a missing file. - /// The callee may add additional files to the waterbox during the callback and return `true` to indicate - /// that the right file was added and the scan should be rerun. The callee may return `false` to indicate - /// that the file should be reported as missing. Do not call other things during this callback. + /// The callee returns a result object, either null to indicate that the file should be reported as missing, + /// or data and writable status for a file to be just in time mounted. + /// Do not call anything on the waterbox things during this callback. /// Can be called at any time by the core, so you may want to remove your callback entirely after init /// if it was for firmware only. + /// writable == false is equivalent to AddReadonlyFile, writable == true is equivalent to AddTransientFile /// - public Func MissingFileCallback - { - get => _syscalls.MissingFileCallback; - set => _syscalls.MissingFileCallback = value; - } + // public Func MissingFileCallback + // { + // set + // { + // // TODO + // using (this.EnterExit()) + // { + // var mfc_o = value == null ? null : new WaterboxHostNative.MissingFileCallback + // { + // callback = (_unused, name) => + // { + // var res = value(name); + // } + // }; - private const ulong MAGIC = 0x736b776162727477; - private const ulong WATERBOXSTATEVERSION = 2; + // NativeImpl.wbx_set_missing_file_callback(_activatedNativeHost, value == null + // ? null + // : ) + // } + // } + // get => _syscalls.MissingFileCallback; + // set => _syscalls.MissingFileCallback = value; + // } public void SaveStateBinary(BinaryWriter bw) { - bw.Write(MAGIC); - bw.Write(WATERBOXSTATEVERSION); - bw.Write(_createstamp); - bw.Write(_savestateComponents.Count); using (this.EnterExit()) { - foreach (var c in _savestateComponents) - { - c.SaveStateBinary(bw); - } + var retobj = new ReturnData(); + NativeImpl.wbx_save_state(_activatedNativeHost, Writer(bw.BaseStream), IntPtr.Zero, retobj); + retobj.GetDataOrThrow(); } } public void LoadStateBinary(BinaryReader br) { - if (br.ReadUInt64() != MAGIC) - throw new InvalidOperationException("Internal savestate error"); - if (br.ReadUInt64() != WATERBOXSTATEVERSION) - throw new InvalidOperationException("Waterbox savestate version mismatch"); - 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 {nameof(WaterboxHost)} state from a different core..."); - ConnectAllImports(); - } + var retobj = new ReturnData(); + NativeImpl.wbx_load_state(_activatedNativeHost, Reader(br.BaseStream), IntPtr.Zero, retobj); + retobj.GetDataOrThrow(); } } - protected override void Dispose(bool disposing) + public void Enter() { - base.Dispose(disposing); - if (disposing) + if (_enterCount == 0) { - foreach (var d in _disposeList) - d.Dispose(); - _disposeList.Clear(); - PurgeMemoryBlocks(); - _module = null; - _heap = null; - _sealedheap = null; - _invisibleheap = null; - _plainheap = null; - _mmapheap = null; + var retobj = new ReturnData(); + NativeImpl.wbx_activate_host(_nativeHost, retobj); + _activatedNativeHost = retobj.GetDataOrThrow(); + } + _enterCount++; + } + + public void Exit() + { + if (_enterCount <= 0) + { + throw new InvalidOperationException(); + } + else if (_enterCount == 1) + { + var retobj = new ReturnData(); + NativeImpl.wbx_deactivate_host(_activatedNativeHost, retobj); + retobj.GetDataOrThrow(); + _activatedNativeHost = IntPtr.Zero; + } + _enterCount--; + } + + public void Dispose() + { + if (_nativeHost != IntPtr.Zero) + { + var retobj = new ReturnData(); + if (_activatedNativeHost != IntPtr.Zero) + { + NativeImpl.wbx_deactivate_host(_activatedNativeHost, retobj); + Console.Error.WriteLine("Warn: Disposed of WaterboxHost which was active"); + _activatedNativeHost = IntPtr.Zero; + } + NativeImpl.wbx_destroy_host(_nativeHost, retobj); + _enterCount = 0; + _nativeHost = IntPtr.Zero; + GC.SuppressFinalize(this); } } - } - internal static class WaterboxLibcoLinuxStartup - { - // TODO: This is a giant mess and get rid of it entirely - internal static void Setup() + ~WaterboxHost() { - // Our libco implementation plays around with stuff in the TEB block. This is bad for a few reasons: - // 1. It's windows specific - // 2. It's only doing this to satisfy some msvs __stkchk code that should never run - // 3. That code is only running because memory allocations in a cothread still send you back - // to managed land where things like the .NET jit might run on the costack, oof. - - // We need to stop #3 from happening, probably by making the waterboxhost unmanaged code. Then if we - // still need "GS fiddling" we can have it be part of our syscall layer without having a managed transition - - // Until then, just fake a TEB block for linux -- nothing else uses GS anyway. - var ptr = Marshal.AllocHGlobal(0x40); - WaterboxUtils.ZeroMemory(ptr, 0x40); - Marshal.WriteIntPtr(ptr, 0x30, ptr); - if (ArchPrCtl(0x1001 /* SET_GS */, Z.SU((long)ptr)) != 0) - throw new InvalidOperationException("ArchPrCtl failed!"); + Dispose(); } - - [DllImport("libc.so.6", EntryPoint = "arch_prctl")] - private static extern int ArchPrCtl(int code, UIntPtr addr); } } diff --git a/src/BizHawk.Emulation.Cores/Waterbox/WaterboxHostNative.cs b/src/BizHawk.Emulation.Cores/Waterbox/WaterboxHostNative.cs new file mode 100644 index 0000000000..460946dcb0 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Waterbox/WaterboxHostNative.cs @@ -0,0 +1,157 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using BizHawk.BizInvoke; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Waterbox +{ + public unsafe abstract class WaterboxHostNative + { + [StructLayout(LayoutKind.Explicit)] + public class ReturnData + { + [FieldOffset(0)] + public byte ErrorMessageStart; + [FieldOffset(1024)] + public IntPtr Data; + + public unsafe IntPtr GetDataOrThrow() + { + if (ErrorMessageStart != 0) + { + fixed(byte* p = &ErrorMessageStart) + throw new InvalidOperationException(Mershul.PtrToStringUtf8((IntPtr)p)); + } + return Data; + } + } + + [StructLayout(LayoutKind.Sequential)] + public class MemoryLayoutTemplate + { + /// Memory space to serve brk(2) + public UIntPtr sbrk_size; + /// Memory space to serve alloc_sealed(3) + public UIntPtr sealed_size; + /// Memory space to serve alloc_invisible(3) + public UIntPtr invis_size; + /// Memory space to serve alloc_plain(3) + public UIntPtr plain_size; + /// Memory space to serve mmap(2) and friends. + /// Calls without MAP_FIXED or MREMAP_FIXED will be placed in this area. + /// TODO: Are we allowing fixed calls to happen anywhere in the block? + public UIntPtr mmap_size; + } + /// Read bytes into the buffer. Return number of bytes read on success, or < 0 on failure. + /// permitted to read less than the provided buffer size, but must always read at least 1 + /// byte if EOF is not reached. If EOF is reached, should return 0. + public delegate IntPtr ReadCallback(IntPtr userdata, IntPtr /*byte**/ data, UIntPtr size); + /// write bytes. Return 0 on success, or < 0 on failure. + /// Must write all provided bytes in one call or fail, not permitted to write less (unlike reader). + public delegate int WriteCallback(IntPtr userdata, IntPtr /*byte**/ data, UIntPtr size); + // public delegate UIntPtr /*MissingFileResult*/ FileCallback(IntPtr userdata, UIntPtr /*string*/ name); + + public static WriteCallback MakeCallbackForWriter(Stream s) + { + var ss = SpanStream.GetOrBuild(s); + return (_unused, data, size) => + { + try + { + var count = (int)size; + ss.Write(new ReadOnlySpan((void*)data, (int)size)); + return 0; + } + catch + { + return -1; + } + }; + } + public static ReadCallback MakeCallbackForReader(Stream s) + { + var ss = SpanStream.GetOrBuild(s); + return (_unused, data, size) => + { + try + { + var count = (int)size; + var n = ss.Read(new Span((void*)data, count)); + return Z.SS(n); + } + catch + { + return Z.SS(-1); + } + }; + } + // [StructLayout(LayoutKind.Sequential)] + // public class MissingFileCallback + // { + // public UIntPtr userdata; + // public FileCallback callback; + // } + // [StructLayout(LayoutKind.Sequential)] + // public class MissingFileResult : CReader + // { + // public bool writable; + // } + + /// Given a guest executable and a memory layout, create a new host environment. All data will be immediately consumed from the reader, + /// which will not be used after this call. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_create_host(MemoryLayoutTemplate layout, string moduleName, ReadCallback wbx, IntPtr userdata, ReturnData /*WaterboxHost*/ ret); + /// Tear down a host environment. May not be called while the environment is active. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_destroy_host(IntPtr /*WaterboxHost*/ obj, ReturnData /*void*/ ret); + /// Activate a host environment. This swaps it into memory and makes it available for use. + /// Pointers to inside the environment are only valid while active. Uses a mutex internally + /// so as to not stomp over other host environments in the same 4GiB slice. + /// Returns a pointer to the activated object, used to do most other functions. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_activate_host(IntPtr /*WaterboxHost*/ obj, ReturnData /*ActivatedWaterboxHost*/ ret); + /// Deactivates a host environment, and releases the mutex. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_deactivate_host(IntPtr /*ActivatedWaterboxHost*/ obj, ReturnData /*void*/ ret); + /// Returns the address of an exported function from the guest executable. This pointer is only valid + /// while the host is active. A missing proc is not an error and simply returns 0. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_get_proc_addr(IntPtr /*ActivatedWaterboxHost*/ obj, string name, ReturnData /*UIntPtr*/ ret); + /// Calls the seal operation, which is a one time action that prepares the host to save states. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_seal(IntPtr /*ActivatedWaterboxHost*/ obj, ReturnData /*void*/ ret); + /// Mounts a file in the environment. All data will be immediately consumed from the reader, which will not be used after this call. + /// To prevent nondeterminism, adding and removing files is very limited WRT savestates. If a file is writable, it must never exist + /// when save_state is called, and can only be used for transient operations. If a file is readable, it can appear in savestates, + /// but it must exist in every savestate and the exact sequence of add_file calls must be consistent from savestate to savestate. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_mount_file(IntPtr /*ActivatedWaterboxHost*/ obj, string name, ReadCallback reader, IntPtr userdata, bool writable, ReturnData /*void*/ ret); + /// Remove a file previously added. Writer is optional; if provided, the contents of the file at time of removal will be dumped to it. + /// It is an error to remove a file which is currently open in the guest. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_unmount_file(IntPtr /*ActivatedWaterboxHost*/ obj, string name, WriteCallback writer, IntPtr userdata, ReturnData /*void*/ ret); + /// Set (or clear, with None) a callback to be called whenever the guest tries to load a nonexistant file. + /// The callback will be provided with the name of the requested load, and can either return null to signal the waterbox + /// to return ENOENT to the guest, or a struct to immediately load that file. You may not call any wbx methods + /// in the callback. If the MissingFileResult is provided, it will be consumed immediately and will have the same effect + /// as wbx_mount_file(). You may free resources associated with the MissingFileResult whenever control next returns to your code. + // [BizImport(CallingConvention.Cdecl)] + // public abstract void wbx_set_missing_file_callback(IntPtr /*ActivatedWaterboxHost*/ obj, MissingFileCallback mfc_o); + /// Save state. Must not be called before seal. Must not be called with any writable files mounted. + /// Must always be called with the same sequence and contents of readonly files. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_save_state(IntPtr /*ActivatedWaterboxHost*/ obj, WriteCallback writer, IntPtr userdata, ReturnData /*void*/ ret); + /// Load state. Must not be called before seal. Must not be called with any writable files mounted. + /// Must always be called with the same sequence and contents of readonly files that were in the save state. + /// Must be called with the same wbx executable and memory layout as in the savestate. + /// Errors generally poison the environment; sorry! + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_load_state(IntPtr /*ActivatedWaterboxHost*/ obj, ReadCallback reader, IntPtr userdata, ReturnData /*void*/ ret); + /// Control whether the host automatically evicts blocks from memory when they are not active. For the best performance, + /// this should be set to false. Set to true to help catch dangling pointer issues. Will be ignored (and forced to true) + /// if waterboxhost was built in debug mode. This is a single global setting. + [BizImport(CallingConvention.Cdecl)] + public abstract void wbx_set_always_evict_blocks(bool val); + } +} diff --git a/waterbox/Notes on Debugging.md b/waterbox/Notes on Debugging.md new file mode 100644 index 0000000000..d06ee4bb54 --- /dev/null +++ b/waterbox/Notes on Debugging.md @@ -0,0 +1,87 @@ +# How to Debug Waterbox and Cores + +Bring lots of tools, and lots of self loathing. + +## Windows + +### gdb + +* Usually comes from mingw, or something +* Example script to attach to a running BizHawk instance: + ``` + #!/bin/bash + PSLINE=$(eval "ps -W | grep EmuHawk") + + if [[ $PSLINE =~ [0-9]+[[:space:]]+[0-9]+[[:space:]]+[0-9]+[[:space:]]+([0-9]+) ]]; then + gdb --pid=${BASH_REMATCH[1]} + fi + ``` +* Thinks we're in x86-32 mode because of the PE header on EmuHawk.exe. We're not. + You can put this in `~/.gdbinit` (or maybe add it to the startgdb script?) + ``` + set arch i386:x86-64 + ``` +* The waterbox files have DWARF information, which gdb can read. If you build a waterbox core in debug mode, + you even get full source level debugging. With the new rust based waterboxhost, these symbol files should automatically + be registered for you as waterboxhost hits a gdb hook. + * The gdb hook is experimental, so be ready with `add-sym foobar.wbx` if needed. +* Has no way to understand first chance vs second chance exceptions. Since lazystates was added, the cores now + emit lots of benign SIGSEGVs as the waterboxhost discovers what memory space they use. You can suppress these exceptions: + ``` + han SIGSEGV nos nopr + ``` + But if the real exception you're trying to break on is a SIGSEGV, this leaves you defenseless. +* Also understands symbols for waterboxhost.dll, since that was actually built with MINGW. +* `b rust_panic` to examine rust unwinds before they explode your computer. +* Breakpoints on symbols in the wbx file just don't work a lot of the time. + * This is the single worst part of modern waterbox debugging. I have no idea what gdb is doing wrong. It sees the wbx + symbols and can print stack information and tell you what function you're in, and print globals, but `b some_emu_core_function` + just doesn't get hit. I think it might have something to do with how we map memory. This worked in some previous + waterbox editions, but I never got to the bottom. + * Recompile cores with lots of `__asm__("int3")` in them? Heh. + +### windbg + +* `!address`, `!vprot` are useful for examining VirtualQuery information. +* Can't read any symbols, which makes it mostly useless until you've narrowed things down heavily. +* Has more useful stack traces than gdb, as gdb usually can't see the stack outside DWARF land. +* Understands first and second chance exceptions. + * `sxd av` will do exactly what we need for lazystate page mapping; break only when our handled decides not to handle it. +* Can give reasonable information on random WIN32 exceptions with `!analyze` + +### OmniSharp + +* Great visibility into C# exceptions, pretty worthless otherwise. + +### General unwinding hazards + +* Within the guest, DWARF unwinding information is available and libunwind is used. Guest C++ programs can freely use exceptions, + and Mednafen does so without issue. + * Unwinding through any stack that is not guest content will likely explode. +* Within the host, rust unwinds usually make some SEH explosion that crashes the application. + * None of them were meant to be recoverable anyway. +* C# exceptions that occur when there is guest code on the stack usually cause a complete termination. + * So a callback to managed from within a waterbox core, etc. + * I never figured this one out. Guest execution is fully done on vanilla host stacks, and we should be doing nothing + that would make this fail. But it does. + * Since the rust waterboxhost, this now will only happen from emulator core specific callbacks, since the rust waterboxhost + does not call out to C# from within a syscall handler, ever. +* Any unwinding on a libco cothread will likely make me laugh. + +## Linux + +### gdb + +* Only game in town here. +* Mono seems to use a lot of custom signals for... something. + * This script will start gdb, ignore those signals, and start EmuHawk: + ``` + #!/bin/bash + gdb -iex "han SIG35 nos" -iex "han SIG36 nos" --args mono ./EmuHawk.exe "$@" + ``` +* Because you're actually in linux, beware function names: `b mmap` can mean the host libc's mmap, the rust implementation of the guest mmap, + or the guest libc's mmap. +* Same general problem with intermittently functional guest breakpoints as Windows. Heh. +* Same lazystate SIGSEGV problem (and same solutions) as Windows. +* Can see some mono symbols, which is sometimes useful. +* If you're looking for a pure core bug, this might be a better environment to test it on than Windows. It depends. diff --git a/waterbox/emulibc/emulibc.c b/waterbox/emulibc/emulibc.c index 6a9799354f..4c71a13f03 100644 --- a/waterbox/emulibc/emulibc.c +++ b/waterbox/emulibc/emulibc.c @@ -1,27 +1,94 @@ #include "emulibc.h" +#include +#include -#define __WBXSYSCALL __attribute__((section(".wbxsyscall"))) __attribute__((visibility("default"))) +// Keep this in sync with "syscall.h"!! +struct __AddressRange { + unsigned long start; + unsigned long size; +}; +struct __WbxSysLayout { + struct __AddressRange elf; + struct __AddressRange sbrk; + struct __AddressRange sealed; + struct __AddressRange invis; + struct __AddressRange plain; + struct __AddressRange mmap; +}; +struct __WbxSysSyscall { + long ud; + void* syscall; +}; +struct __WbxSysArea { + struct __WbxSysLayout layout; + struct __WbxSysSyscall syscall; +}; +extern struct __WbxSysArea __wbxsysarea; -__WBXSYSCALL void *(*__walloc_sealed)(size_t); -void *alloc_sealed(size_t size) +void* alloc_helper(size_t size, const struct __AddressRange* range, unsigned long* current, const char* name) { - return __walloc_sealed(size); + if (!*current) + { + printf("Initializing heap %s at %p:%p\n", name, (void*)range->start, (void*)(range->start + range->size)); + *current = range->start; + } + + unsigned long start = *current; + unsigned long end = start + size; + end = (end + 15) & ~15ul; + + if (end < start || end > range->start + range->size) + { + fprintf(stderr, "Failed to satisfy allocation of %lu bytes on %s heap\n", size, name); + return NULL; + } + else + { + unsigned long pstart = (start + 0xfff) & ~0xffful; + unsigned long pend = (end + 0xfff) & ~0xffful; + if (pstart < pend) + { + if (mmap((void*)pstart, pend - pstart, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_FIXED_NOREPLACE, -1, 0) == MAP_FAILED) + { + fprintf(stderr, "VERY STRANGE: mmap() failed to satisfy allocation of %lu bytes on %s heap\n", size, name); + return NULL; + } + } + printf("Allocated %lu bytes on %s heap, usage %lu/%lu\n", size, name, end - range->start, range->size); + *current = end; + return (void*)start; + } } -__WBXSYSCALL void *(*__walloc_invisible)(size_t); -void *alloc_invisible(size_t size) +static unsigned long __sealed_current; +void* alloc_sealed(size_t size) { - return __walloc_invisible(size); + return alloc_helper(size, &__wbxsysarea.layout.sealed, &__sealed_current, "sealed"); } -__WBXSYSCALL void *(*__walloc_plain)(size_t); -void *alloc_plain(size_t size) +static unsigned long __invisible_current; +void* alloc_invisible(size_t size) { - return __walloc_plain(size); + return alloc_helper(size, &__wbxsysarea.layout.invis, &__invisible_current, "invisible"); } -__WBXSYSCALL void (*__w_debug_puts)(const char *); +static unsigned long __plain_current; +void* alloc_plain(size_t size) +{ + return alloc_helper(size, &__wbxsysarea.layout.plain, &__plain_current, "plain"); +} + +// TODO: This existed before we even had stdio support. Retire? void _debug_puts(const char *s) { - __w_debug_puts(s); + fprintf(stderr, "%s\n", s); +} + +ECL_EXPORT void ecl_seal() +{ + if (__sealed_current) + { + if (mprotect((void*)__wbxsysarea.layout.sealed.start, (__sealed_current - __wbxsysarea.layout.sealed.start + 0xfff) & ~0xffful, PROT_READ) != 0) + __asm__("int3"); + } } diff --git a/waterbox/libco/amd64.c b/waterbox/libco/amd64.c index 8a708453bc..235d04145b 100644 --- a/waterbox/libco/amd64.c +++ b/waterbox/libco/amd64.c @@ -13,13 +13,14 @@ #include #include -// allocations are 16k larger than asked for, -// and include guard space between the stack and the storage +// allocations are 16k larger than asked for, which is all used as guard space +#define GUARD_SIZE 0x4000 typedef struct { // used by coswap.s, has to be at the beginning of the struct uint64_t jmp_buf[32]; // points to the lowest address in the stack + // NB: because of guard space, this is not valid stack void* stack; // length of the stack that we allocated in bytes uint64_t stack_size; @@ -30,6 +31,13 @@ static cothread_impl co_host_buffer; // what cothread are we in right now static cothread_impl* co_active_handle; +static void free_thread(cothread_impl* co) +{ + if (munmap(co->stack, co->stack_size) != 0) + abort(); + free(co); +} + static cothread_impl* alloc_thread(uint64_t size) { cothread_impl* co = calloc(1, sizeof(*co)); @@ -37,27 +45,28 @@ static cothread_impl* alloc_thread(uint64_t size) return NULL; // align up to 4k - size = (size + 4095) & ~4095; + size = (size + 4095) & ~4095ul; + size += GUARD_SIZE; co->stack = mmap(NULL, size, - PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_STACK, -1, 0); + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); if (co->stack == (void*)(-1)) { free(co); return NULL; } + + if (mprotect(co->stack, GUARD_SIZE, PROT_NONE) != 0) + { + free_thread(co); + return NULL; + } + co->stack_size = size; return co; } -static void free_thread(cothread_impl* co) -{ - if (munmap(co->stack, co->stack_size) != 0) - abort(); - free(co); -} - extern void co_swap(cothread_impl*, cothread_impl*); static void crash(void) @@ -99,39 +108,39 @@ void co_delete(cothread_t handle) free_thread(handle); } -static uint64_t hoststart; -static uint64_t hostend; +// static uint64_t hoststart; +// static uint64_t hostend; void co_switch(cothread_t handle) { cothread_impl* co = handle; - uint64_t start; - uint64_t end; - if (co_active_handle == &co_host_buffer) - { - // migrating off of real thread; save stack params - __asm__("movq %%gs:0x08, %0": "=r"(end)); - __asm__("movq %%gs:0x10, %0": "=r"(start)); - hoststart = start; - hostend = end; - } - if (handle == &co_host_buffer) - { - // migrating onto real thread; load stack params - start = hoststart; - end = hostend; - hoststart = 0; - hostend = 0; - } - else - { - // migrating onto cothread; compute its extents we allocated them - start = (uintptr_t)co->stack; - end = start + co->stack_size; - } - __asm__("movq %0, %%gs:0x08":: "r"(end)); - __asm__("movq %0, %%gs:0x10":: "r"(start)); + // uint64_t start; + // uint64_t end; + // if (co_active_handle == &co_host_buffer) + // { + // // migrating off of real thread; save stack params + // __asm__("movq %%gs:0x08, %0": "=r"(end)); + // __asm__("movq %%gs:0x10, %0": "=r"(start)); + // hoststart = start; + // hostend = end; + // } + // if (handle == &co_host_buffer) + // { + // // migrating onto real thread; load stack params + // start = hoststart; + // end = hostend; + // hoststart = 0; + // hostend = 0; + // } + // else + // { + // // migrating onto cothread; compute its extents we allocated them + // start = (uintptr_t)co->stack; + // end = start + co->stack_size; + // } + // __asm__("movq %0, %%gs:0x08":: "r"(end)); + // __asm__("movq %0, %%gs:0x10":: "r"(start)); register cothread_impl* co_previous_handle = co_active_handle; co_swap(co_active_handle = co, co_previous_handle); diff --git a/waterbox/linguard/Makefile b/waterbox/linguard/Makefile deleted file mode 100644 index 6c48b41f08..0000000000 --- a/waterbox/linguard/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -ifeq (,$(findstring Linux,$(shell uname))) -$(error This must be built with linux) -endif - -linguard.so: linguard.c - gcc -Wall -O2 -s -o $@ $< -shared -fPIC - -install: linguard.so - cp $< ../../output -clean: - rm linguard.so - -.PHONY: install clean diff --git a/waterbox/linguard/linguard.c b/waterbox/linguard/linguard.c deleted file mode 100644 index cd7fb647df..0000000000 --- a/waterbox/linguard/linguard.c +++ /dev/null @@ -1,279 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX_TRIPS 64 - -typedef struct { - uintptr_t start; - uintptr_t length; - uint8_t tripped[0]; -} tripwire_t; - -static tripwire_t* Trips[MAX_TRIPS]; -static int HandlerInstalled; - -static char altstack[SIGSTKSZ]; - -static struct sigaction sa_old; - -static void SignalHandler(int sig, siginfo_t* info, void* ucontext) -{ - uintptr_t faultAddress = (uintptr_t)info->si_addr; - for (int i = 0; i < MAX_TRIPS; i++) - { - if (Trips[i] && faultAddress >= Trips[i]->start && faultAddress < Trips[i]->start + Trips[i]->length) - { - uintptr_t page = (faultAddress - Trips[i]->start) >> 12; - if (Trips[i]->tripped[page] & 1) // should change - { - if (mprotect((void*)(faultAddress & ~0xffful), 0x1000, PROT_READ | PROT_WRITE) != 0) - { - abort(); - while (1) - ; - } - - Trips[i]->tripped[page] = 3; // did change - return; - } - else - { - break; - } - } - } - - if (sa_old.sa_flags & SA_SIGINFO) - sa_old.sa_sigaction(sig, info, ucontext); - else - sa_old.sa_handler(sig); -} - -static int InstallHandler() -{ - stack_t ss; - ss.ss_flags = 0; - ss.ss_sp = altstack; - ss.ss_size = sizeof(altstack); - - if (sigaltstack(&ss, NULL) != 0) - { - fprintf(stderr, "sigaltstack: %i\n", errno); - return 0; - } - - struct sigaction sa; - sa.sa_sigaction = SignalHandler; - sa.sa_flags = SA_ONSTACK | SA_SIGINFO; - sigfillset(&sa.sa_mask); - - if (sigaction(SIGSEGV, &sa, &sa_old) != 0) - { - fprintf(stderr, "sigaction: %i\n", errno); - return 0; - } - return 1; -} - -uint8_t* AddTripGuard(uintptr_t start, uintptr_t length) -{ - if (!HandlerInstalled) - { - if (!InstallHandler()) - return NULL; - HandlerInstalled = 1; - } - - uintptr_t npage = length >> 12; - for (int i = 0; i < MAX_TRIPS; i++) - { - if (!Trips[i]) - { - Trips[i] = calloc(1, sizeof(*Trips[i]) + npage); - if (!Trips[i]) - return NULL; - Trips[i]->start = start; - Trips[i]->length = length; - return &Trips[i]->tripped[0]; - } - } - return NULL; -} - -int64_t RemoveTripGuard(uintptr_t start, uintptr_t length) -{ - for (int i = 0; i < MAX_TRIPS; i++) - { - if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length) - { - free(Trips[i]); - Trips[i] = NULL; - return 1; - } - } - return 0; -} - -uint8_t* ExamineTripGuard(uintptr_t start, uintptr_t length) -{ - for (int i = 0; i < MAX_TRIPS; i++) - { - if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length) - return &Trips[i]->tripped[0]; - } - return NULL; -} - - -/* - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - struct uffdio_writeprotect wp; - wp.range.start = start; - wp.range.len = length; - wp.mode = 0; - if (ioctl(fd, UFFDIO_WRITEPROTECT, &wp) == -1) - { - free(Trips[i]); - Trips[i] = NULL; - return NULL; - } - -static void v(const char* msg, int value) -{ - if (v < 0) - { - printf("ERROR: %s %i\n", msg, errno); - exit(1); - } -} - -const uint64_t addr = 0x36f00000000ul; -const uint64_t size = 0x100000ul; - -static void* threadproc(void* arg) -{ - int wpfd = (int)(long)arg; - // should be able to register once for a large range, - // then wp smaller ranges - struct uffdio_register uffdio_register; - uffdio_register.range.start = addr; - uffdio_register.range.len = size; - - uffdio_register.mode = UFFDIO_REGISTER_MODE_WP; - - v("ioctl:UFFDIO_REGISTER", ioctl(wpfd, UFFDIO_REGISTER, &uffdio_register)); - v("uffdio_register.ioctls", (uffdio_register.ioctls & UFFD_API_RANGE_IOCTLS) == UFFD_API_RANGE_IOCTLS ? 0 : -1); - - // wp - { - struct uffdio_writeprotect wp; - wp.range.start = addr; - wp.range.len = size; - wp.mode = UFFDIO_WRITEPROTECT_MODE_WP; - v("ioctl:UFFDIO_WRITEPROTECT", ioctl(wpfd, UFFDIO_WRITEPROTECT, &wp)); - } - - while (1) - { - struct uffd_msg msg; - - int nb = read(wpfd, &msg, sizeof(msg)); - if (nb == -1) - { - if (errno == EAGAIN) - continue; - v("read", errno); - } - if (nb != sizeof(msg)) - v("sizeof(msg)", -1); - if (msg.event & UFFD_EVENT_PAGEFAULT) - { - printf("==> Event is pagefault on %p flags 0x%llx write? 0x%llx wp? 0x%llx\n" - , (void *)msg.arg.pagefault.address - , msg.arg.pagefault.flags - , msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WRITE - , msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP - ); - } - if (msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) - { - // send write unlock - struct uffdio_writeprotect wp; - wp.range.start = addr; - wp.range.len = size; - wp.mode = 0; - printf("sending !UFFDIO_WRITEPROTECT event to userfaultfd\n"); - v("ioctl:UFFDIO_WRITEPROTECT", ioctl(wpfd, UFFDIO_WRITEPROTECT, &wp)); - } - } - return NULL; -} - -int main(void) -{ - int fd = memfd_create("pewps", MFD_CLOEXEC); - v("memfd_create", fd); - printf("fd: %i\n", fd); - v("ftruncate", ftruncate(fd, size)); - char* ptr = mmap((void*)addr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED, fd, 0); - if (ptr == MAP_FAILED || ptr != (void*)addr) - v("mmap", (int)(long)ptr); - v("mprotect", mprotect(ptr, size, PROT_READ | PROT_WRITE)); - - int wpfd = syscall(SYS_userfaultfd, O_CLOEXEC); - v("SYS_userfaultfd", wpfd); - - struct uffdio_api uffdio_api; - uffdio_api.api = UFFD_API; - uffdio_api.features = 0; - v("ioctl:UFFDIO_API", ioctl(wpfd, UFFDIO_API, &uffdio_api)); - - v("uffdio_api.api", uffdio_api.api == UFFD_API ? 0 : -1); - - - pthread_t thr; - v("pthread_create", pthread_create(&thr, NULL, threadproc, (void*)(long)wpfd)); - - for (uint64_t a = addr; a < addr + size; a += 4096) - { - sleep(1); - printf("Gonna read"); - if (*(uint64_t*)a == 77777) - { - printf("Lucky!"); - } - sleep(1); - printf("Gonna write"); - strcpy((void*)a, "string in mem area"); - printf("%s\n", (char*)a); - } -} - -*/ diff --git a/waterbox/linguard/linguard.so b/waterbox/linguard/linguard.so deleted file mode 100644 index e0a5910687..0000000000 Binary files a/waterbox/linguard/linguard.so and /dev/null differ diff --git a/waterbox/musl b/waterbox/musl index 1020370fb2..fc0fd09fa3 160000 --- a/waterbox/musl +++ b/waterbox/musl @@ -1 +1 @@ -Subproject commit 1020370fb2850b33c5bb8c016dfe822551faf4c6 +Subproject commit fc0fd09fa3aad2b9a30d5825caab0259304c5a45 diff --git a/waterbox/waterboxhost/Cargo.toml b/waterbox/waterboxhost/Cargo.toml index 3780e0e49f..c66226745d 100644 --- a/waterbox/waterboxhost/Cargo.toml +++ b/waterbox/waterboxhost/Cargo.toml @@ -8,6 +8,9 @@ rust = "nightly" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[profile.release] +lto = true + [dependencies] bitflags = "1.2.1" page_size = "0.4.2" diff --git a/waterbox/waterboxhost/README.md b/waterbox/waterboxhost/README.md new file mode 100644 index 0000000000..2ffe472e77 --- /dev/null +++ b/waterbox/waterboxhost/README.md @@ -0,0 +1,37 @@ +# Waterboxhost + +This is the native support code for Waterbox. It's intended to be consumed as a shared library from the host environment +with a C api. For most work with Waterbox cores, you don't need to get into this at all. + +## API +The public api is mostly all in `src/cinterface.rs` and has basic documentation on it. Bare minimum sequence of calls to +get going: + +0. (Optional) In a release environment, turn off certain checks to speed things up + `wbx_set_always_evict_blocks()` +1. Create an environment, and load the ELF into it + `wbx_create_host()` + `wbx_activate_host()` +2. Connect exports from the guest executable to your host system + `wbx_get_proc_addr()` +3. Run the guest system's init, using function pointers it exposed through wbx_get_proc_addr() +4. Get ready to take savestates + `wbx_seal()` +5. Run emulation, using frameadvance or other advance functions exposed by the guest through wbx_get_proc_addr() +6. Save and load states as needed + `wbx_save_state()` + `wbx_load_state()` +7. Tear down the environment when done with it. (One shot processes that are about to exit can skip this; the OS will clean everything up) + `wbx_deactivate_host()` + `wbx_destroy_host()` + +If you're keeping around multiple hosts that may compete for the same address space, use `wbx_activate_host` and `wbx_deactivate_host` +to switch between them. If you'd like to expose files to the virtual filesystem, see `wbx_mount_file` and `wbx_unmount_file` + +## Building + +Standard rust build infrastructure is used and can be installed with `rustup`. At the moment, we're using the `nightly-x86_64-pc-windows-gnu` +chain on Windows, and the `nightly-x86_64-unknown-linux-gnu` chain on linux. I don't know much about crosspiling, but presumably that will work. +The linux chain works fine in WSL, anyway. When used in a Windows environment with the right default chain, `build-release.bat` will build +waterboxhost.dll and copy it to the right place. When used in a Linux (or WSL) environment with the right default chain, `build-release.sh` +will build libwaterboxhost.so and copy it to the right place. diff --git a/waterbox/waterboxhost/build-debug.bat b/waterbox/waterboxhost/build-debug.bat new file mode 100644 index 0000000000..5b144c9240 --- /dev/null +++ b/waterbox/waterboxhost/build-debug.bat @@ -0,0 +1,2 @@ +@cargo b +@copy target\debug\waterboxhost.dll ..\..\output\dll diff --git a/waterbox/waterboxhost/build-debug.sh b/waterbox/waterboxhost/build-debug.sh new file mode 100644 index 0000000000..262069ddd6 --- /dev/null +++ b/waterbox/waterboxhost/build-debug.sh @@ -0,0 +1,3 @@ +#!/bin/sh +cargo b +cp target/debug/libwaterboxhost.so ../../Assets diff --git a/waterbox/waterboxhost/build-release.bat b/waterbox/waterboxhost/build-release.bat new file mode 100644 index 0000000000..e3e9bfd4cd --- /dev/null +++ b/waterbox/waterboxhost/build-release.bat @@ -0,0 +1,2 @@ +@cargo b --release +@copy target\release\waterboxhost.dll ..\..\output\dll diff --git a/waterbox/waterboxhost/build-release.sh b/waterbox/waterboxhost/build-release.sh new file mode 100644 index 0000000000..d85272b6bd --- /dev/null +++ b/waterbox/waterboxhost/build-release.sh @@ -0,0 +1,3 @@ +#!/bin/sh +cargo b --release +cp target/release/libwaterboxhost.so ../../Assets diff --git a/waterbox/waterboxhost/src/cinterface.rs b/waterbox/waterboxhost/src/cinterface.rs index 90f9b1c763..806e527763 100644 --- a/waterbox/waterboxhost/src/cinterface.rs +++ b/waterbox/waterboxhost/src/cinterface.rs @@ -1,17 +1,12 @@ use crate::*; use host::{ActivatedWaterboxHost, WaterboxHost}; -use std::{os::raw::c_char, ffi::CStr}; +use std::{os::raw::c_char, io, ffi::{/*CString, */CStr}}; /// The memory template for a WaterboxHost. Don't worry about /// making every size as small as possible, since the savestater handles sparse regions /// well enough. All values should be PAGESIZE aligned. #[repr(C)] pub struct MemoryLayoutTemplate { - /// Absolute pointer to the start of the mapped space - pub start: usize, - /// Memory space for the elf executable. The elf must be non-relocatable and - /// all loaded segments must fit within [start..start + elf_size] - pub elf_size: usize, /// Memory space to serve brk(2) pub sbrk_size: usize, /// Memory space to serve alloc_sealed(3) @@ -27,19 +22,14 @@ pub struct MemoryLayoutTemplate { } impl MemoryLayoutTemplate { /// checks a memory layout for validity - pub fn make_layout(&self) -> anyhow::Result { - let start = align_down(self.start); - let elf_size = align_up(self.elf_size); + pub fn make_layout(&self, elf_addr: AddressRange) -> anyhow::Result { let sbrk_size = align_up(self.sbrk_size); let sealed_size = align_up(self.sealed_size); let invis_size = align_up(self.invis_size); let plain_size = align_up(self.plain_size); let mmap_size = align_up(self.mmap_size); let mut res = unsafe { std::mem::zeroed::() }; - res.elf = AddressRange { - start, - size: elf_size - }; + res.elf = elf_addr.align_expand(); res.sbrk = AddressRange { start: res.elf.end(), size: sbrk_size @@ -57,10 +47,10 @@ impl MemoryLayoutTemplate { size: plain_size }; res.mmap = AddressRange { - start: res.invis.end(), + start: res.plain.end(), size: mmap_size }; - if start >> 32 != (res.mmap.end() - 1) >> 32 { + if res.elf.start >> 32 != (res.mmap.end() - 1) >> 32 { Err(anyhow!("HostMemoryLayout must fit into a single 4GiB region!")) } else { Ok(res) @@ -94,50 +84,63 @@ impl Return { } } -/// stream writer -#[repr(C)] -pub struct CWriter { +/// write bytes. Return 0 on success, or < 0 on failure. +/// Must write all provided bytes in one call or fail, not permitted to write less (unlike reader). +pub type WriteCallback = extern fn(userdata: usize, data: *const u8, size: usize) -> i32; +struct CWriter { /// will be passed to callback pub userdata: usize, - /// write bytes. Return number of bytes written on success, or < 0 on failure. - /// Permitted to write less than the provided number of bytes. - pub callback: extern fn(userdata: usize, data: *const u8, size: usize) -> isize, + pub callback: WriteCallback, } impl Write for CWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result { + fn write(&mut self, buf: &[u8]) -> io::Result { let res = (self.callback)(self.userdata, buf.as_ptr(), buf.len()); if res < 0 { - Err(std::io::Error::new(std::io::ErrorKind::Other, "Callback signaled abnormal failure")) + Err(io::Error::new(io::ErrorKind::Other, "Callback signaled abnormal failure")) } else { - Ok(res as usize) + Ok(buf.len()) } } - fn flush(&mut self) -> std::io::Result<()> { + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.write(buf)?; + Ok(()) + } + fn flush(&mut self) -> io::Result<()> { Ok(()) } } -/// stream reader -#[repr(C)] -pub struct CReader { - /// will be passed to callback +/// Read bytes into the buffer. Return number of bytes read on success, or < 0 on failure. +/// permitted to read less than the provided buffer size, but must always read at least 1 +/// byte if EOF is not reached. If EOF is reached, should return 0. +pub type ReadCallback = extern fn(userdata: usize, data: *mut u8, size: usize) -> isize; +struct CReader { pub userdata: usize, - /// Read bytes into the buffer. Return number of bytes read on success, or < 0 on failure. - /// permitted to read less than the provided buffer size, but must always read at least 1 - /// byte if EOF is not reached. If EOF is reached, should return 0. - pub callback: extern fn(userdata: usize, data: *mut u8, size: usize) -> isize, + pub callback: ReadCallback, } impl Read for CReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + fn read(&mut self, buf: &mut [u8]) -> io::Result { let res = (self.callback)(self.userdata, buf.as_mut_ptr(), buf.len()); if res < 0 { - Err(std::io::Error::new(std::io::ErrorKind::Other, "Callback signaled abnormal failure")) + Err(io::Error::new(io::ErrorKind::Other, "Callback signaled abnormal failure")) } else { Ok(res as usize) } } } +// #[repr(C)] +// pub struct MissingFileCallback { +// pub userdata: usize, +// pub callback: extern fn(userdata: usize, name: *const c_char) -> *mut MissingFileResult, +// } + +// #[repr(C)] +// pub struct MissingFileResult { +// pub reader: CReader, +// pub writable: bool, +// } + fn arg_to_str(arg: *const c_char) -> anyhow::Result { let cs = unsafe { CStr::from_ptr(arg as *const c_char) }; match cs.to_str() { @@ -148,17 +151,21 @@ fn arg_to_str(arg: *const c_char) -> anyhow::Result { fn read_whole_file(reader: &mut CReader) -> anyhow::Result> { let mut res = Vec::::new(); - std::io::copy(reader, &mut res)?; + io::copy(reader, &mut res)?; Ok(res) } /// Given a guest executable and a memory layout, create a new host environment. All data will be immediately consumed from the reader, /// which will not be used after this call. #[no_mangle] -pub extern fn wbx_create_host(layout: &MemoryLayoutTemplate, module_name: *const c_char, wbx: &mut CReader, ret: &mut Return<*mut WaterboxHost>) { +pub extern fn wbx_create_host(layout: &MemoryLayoutTemplate, module_name: *const c_char, callback: ReadCallback, userdata: usize, ret: &mut Return<*mut WaterboxHost>) { + let mut reader = CReader { + userdata, + callback + }; let res = (|| { - let data = read_whole_file(wbx)?; - WaterboxHost::new(&data[..], &arg_to_str(module_name)?[..], layout) + let data = read_whole_file(&mut reader)?; + WaterboxHost::new(data, &arg_to_str(module_name)?[..], layout) })(); ret.put(res.map(|boxed| Box::into_raw(boxed))); } @@ -227,9 +234,13 @@ pub extern fn wbx_seal(obj: &mut ActivatedWaterboxHost, ret: &mut Return<()>) { /// when save_state is called, and can only be used for transient operations. If a file is readable, it can appear in savestates, /// but it must exist in every savestate and the exact sequence of add_file calls must be consistent from savestate to savestate. #[no_mangle] -pub extern fn wbx_mount_file(obj: &mut ActivatedWaterboxHost, name: *const c_char, reader: &mut CReader, writable: bool, ret: &mut Return<()>) { +pub extern fn wbx_mount_file(obj: &mut ActivatedWaterboxHost, name: *const c_char, callback: ReadCallback, userdata: usize, writable: bool, ret: &mut Return<()>) { + let mut reader = CReader { + userdata, + callback + }; let res: anyhow::Result<()> = (|| { - obj.mount_file(arg_to_str(name)?, read_whole_file(reader)?, writable)?; + obj.mount_file(arg_to_str(name)?, read_whole_file(&mut reader)?, writable)?; Ok(()) })(); ret.put(res); @@ -238,28 +249,88 @@ pub extern fn wbx_mount_file(obj: &mut ActivatedWaterboxHost, name: *const c_cha /// Remove a file previously added. Writer is optional; if provided, the contents of the file at time of removal will be dumped to it. /// It is an error to remove a file which is currently open in the guest. #[no_mangle] -pub extern fn wbx_unmount_file(obj: &mut ActivatedWaterboxHost, name: *const c_char, writer: Option<&mut CWriter>, ret: &mut Return<()>) { +pub extern fn wbx_unmount_file(obj: &mut ActivatedWaterboxHost, name: *const c_char, callback_opt: Option, userdata: usize, ret: &mut Return<()>) { let res: anyhow::Result<()> = (|| { let data = obj.unmount_file(&arg_to_str(name)?)?; - if let Some(w) = writer { - std::io::copy(&mut &data[..], w)?; + if let Some(callback) = callback_opt { + let mut writer = CWriter { + userdata, + callback + }; + io::copy(&mut &data[..], &mut writer)?; } Ok(()) })(); ret.put(res); } +/// Set (or clear, with None) a callback to be called whenever the guest tries to load a nonexistant file. +/// The callback will be provided with the name of the requested load, and can either return null to signal the waterbox +/// to return ENOENT to the guest, or a struct to immediately load that file. You may not call any wbx methods +/// in the callback. If the MissingFileResult is provided, it will be consumed immediately and will have the same effect +/// as wbx_mount_file(). You may free resources associated with the MissingFileResult whenever control next returns to your code. +// #[no_mangle] +// pub extern fn wbx_set_missing_file_callback(obj: &mut ActivatedWaterboxHost, mfc_o: Option<&MissingFileCallback>) { +// match mfc_o { +// None => obj.set_missing_file_callback(None), +// Some(mfc) => { +// let userdata = mfc.userdata; +// let callback = mfc.callback; +// obj.set_missing_file_callback(Some(Box::new(move |name| { +// let namestr = CString::new(name).unwrap(); +// let mfr = callback(userdata, namestr.as_ptr() as *const c_char); +// if mfr == 0 as *mut MissingFileResult { +// return None +// } +// unsafe { +// let data = read_whole_file(&mut (*mfr).reader); +// match data { +// Ok(d) => Some(fs::MissingFileResult { +// data: d, +// writable: (*mfr).writable +// }), +// Err(_) => None, +// } +// } +// }))); +// } +// } +// } + /// Save state. Must not be called before seal. Must not be called with any writable files mounted. /// Must always be called with the same sequence and contents of readonly files. #[no_mangle] -pub extern fn wbx_save_state(obj: &mut ActivatedWaterboxHost, writer: &mut CWriter, ret: &mut Return<()>) { - ret.put(obj.save_state(writer)); +pub extern fn wbx_save_state(obj: &mut ActivatedWaterboxHost, callback: WriteCallback, userdata: usize, ret: &mut Return<()>) { + let mut writer = CWriter { + userdata, + callback + }; + let res: anyhow::Result<()> = (|| { + obj.save_state(&mut writer)?; + Ok(()) + })(); + ret.put(res); } /// Load state. Must not be called before seal. Must not be called with any writable files mounted. /// Must always be called with the same sequence and contents of readonly files that were in the save state. /// Must be called with the same wbx executable and memory layout as in the savestate. /// Errors generally poison the environment; sorry! #[no_mangle] -pub extern fn wbx_load_state(obj: &mut ActivatedWaterboxHost, reader: &mut CReader, ret: &mut Return<()>) { - ret.put(obj.load_state(reader)); +pub extern fn wbx_load_state(obj: &mut ActivatedWaterboxHost, callback: ReadCallback, userdata: usize, ret: &mut Return<()>) { + let mut reader = CReader { + userdata, + callback + }; + ret.put(obj.load_state(&mut reader)); +} + +/// Control whether the host automatically evicts blocks from memory when they are not active. For the best performance, +/// this should be set to false. Set to true to help catch dangling pointer issues. Will be ignored (and forced to true) +/// if waterboxhost was built in debug mode. This is a single global setting. +#[no_mangle] +pub extern fn wbx_set_always_evict_blocks(_val: bool) { + #[cfg(not(debug_assertions))] + { + unsafe { ALWAYS_EVICT_BLOCKS = _val; } + } } diff --git a/waterbox/waterboxhost/src/elf.rs b/waterbox/waterboxhost/src/elf.rs index 8d6c04d541..8c2889770f 100644 --- a/waterbox/waterboxhost/src/elf.rs +++ b/waterbox/waterboxhost/src/elf.rs @@ -1,5 +1,5 @@ use goblin; -use goblin::elf64::{sym::*, section_header::*}; +use goblin::{elf::Elf, elf64::{sym::*, section_header::*}}; use crate::*; use crate::memory_block::ActivatedMemoryBlock; use crate::memory_block::Protection; @@ -30,26 +30,26 @@ pub struct ElfLoader { import_area: AddressRange, } impl ElfLoader { - pub fn new(data: &[u8], - module_name: &str, - layout: &WbxSysLayout, - b: &mut ActivatedMemoryBlock - ) -> anyhow::Result { - let wbx = goblin::elf::Elf::parse(data)?; - + pub fn elf_addr(wbx: &Elf) -> AddressRange { let start = wbx.program_headers.iter() + .filter(|x| x.p_vaddr != 0) .map(|x| x.vm_range().start) .min() .unwrap(); let end = wbx.program_headers.iter() + .filter(|x| x.p_vaddr != 0) .map(|x| x.vm_range().end) .max() .unwrap(); - if start < layout.elf.start || end > layout.elf.end() { - return Err(anyhow!("{} from {}..{} did not fit in the provided region", module_name, start, end)) - } - - println!("Mouting `{}` @{:x}", module_name, start); + return AddressRange { start, size: end - start }; + } + pub fn new(wbx: &Elf, data: &[u8], + module_name: &str, + layout: &WbxSysLayout, + b: &mut ActivatedMemoryBlock + ) -> anyhow::Result { + println!("Mouting `{}` @{:x}", module_name, layout.elf.start); + println!(" Sections:"); let mut sections = Vec::new(); @@ -58,8 +58,9 @@ impl ElfLoader { Some(Ok(s)) => s, _ => "" }; - println!(" @{:x} {}{}{} `{}` {} bytes", + println!(" @{:x}:{:x} {}{}{} `{}` {} bytes", section.sh_addr, + section.sh_addr + section.sh_size, if section.sh_flags & (SHF_ALLOC as u64) != 0 { "R" } else { " " }, if section.sh_flags & (SHF_WRITE as u64) != 0 { "W" } else { " " }, if section.sh_flags & (SHF_EXECINSTR as u64) != 0 { "X" } else { " " }, @@ -112,10 +113,20 @@ impl ElfLoader { { let invis_opt = sections.iter().find(|x| x.name == ".invis"); if let Some(invis) = invis_opt { - let any_below = sections.iter().any(|x| x.addr.align_expand().end() > invis.addr.align_expand().start); - let any_above = sections.iter().any(|x| x.addr.align_expand().start < invis.addr.align_expand().end()); - if any_below || any_above { - return Err(anyhow!("Overlap between .invis and other sections -- check linkscript.")); + for s in sections.iter() { + if s.addr.align_expand().start < invis.addr.align_expand().start { + if s.addr.align_expand().end() > invis.addr.align_expand().start { + return Err(anyhow!("When aligned, {} partially overlaps .invis from below -- check linkscript.", s.name)) + } + } else if s.addr.align_expand().start > invis.addr.align_expand().start { + if invis.addr.align_expand().end() > s.addr.align_expand().start { + return Err(anyhow!("When aligned, {} partially overlaps .invis from above -- check linkscript.", s.name)) + } + } else { + if s.name != ".invis" { + return Err(anyhow!("When aligned, {} partially overlays .invis -- check linkscript", s.name)) + } + } } b.mark_invisible(invis.addr.align_expand())?; } @@ -123,7 +134,8 @@ impl ElfLoader { b.mark_invisible(layout.invis)?; - for segment in wbx.program_headers.iter() { + println!(" Segments:"); + for segment in wbx.program_headers.iter().filter(|x| x.p_vaddr != 0) { let addr = AddressRange { start: segment.vm_range().start, size: segment.vm_range().end - segment.vm_range().start @@ -136,12 +148,22 @@ impl ElfLoader { (_, true, false) => Protection::RW, (_, true, true) => Protection::RWX }; - b.mmap_fixed(prot_addr, prot)?; + println!(" %{:x}:{:x} {}{}{} {} bytes", + addr.start, + addr.end(), + if segment.is_read() { "R" } else { " " }, + if segment.is_write() { "W" } else { " " }, + if segment.is_executable() { "X" } else { " " }, + addr.size + ); + // TODO: Using no_replace false here because the linker puts eh_frame_hdr in a separate segment that overlaps the other RO segment??? + b.mmap_fixed(prot_addr, Protection::RW, false)?; unsafe { let src = &data[segment.file_range()]; let dst = AddressRange { start: addr.start, size: segment.file_range().end - segment.file_range().start }.slice_mut(); dst.copy_from_slice(src); } + b.mprotect(prot_addr, prot)?; } Ok(ElfLoader { @@ -152,18 +174,21 @@ impl ElfLoader { import_area }) } - pub fn seal(&self, b: &mut ActivatedMemoryBlock) { + pub fn pre_seal(&mut self, b: &mut ActivatedMemoryBlock) { + self.run_proc(b, "co_clean"); + self.run_proc(b, "ecl_seal"); for section in self.sections.iter() { if section_name_is_readonly(section.name.as_str()) { - b.mprotect(section.addr, Protection::R).unwrap(); + b.mprotect(section.addr.align_expand(), Protection::R).unwrap(); } } + self.clear_syscalls(b); } pub fn connect_syscalls(&mut self, _b: &mut ActivatedMemoryBlock, sys: &WbxSysArea) { let addr = self.import_area; unsafe { *(addr.start as *mut WbxSysArea) = *sys; } } - pub fn clear_syscalls(&mut self, _b: &mut ActivatedMemoryBlock) { + fn clear_syscalls(&mut self, _b: &mut ActivatedMemoryBlock) { let addr = self.import_area; unsafe { addr.zero(); } } @@ -173,11 +198,11 @@ impl ElfLoader { std::mem::transmute:: ()>(self.entry_point)(); } } - pub fn co_clean(&mut self, _b: &mut ActivatedMemoryBlock) { - match self.get_proc_addr("co_clean") { + fn run_proc(&mut self, _b: &mut ActivatedMemoryBlock, name: &str) { + match self.get_proc_addr(name) { 0 => (), ptr => { - println!("Calling co_clean()"); + println!("Calling {}()", name); unsafe { std::mem::transmute:: ()>(ptr)(); } diff --git a/waterbox/waterboxhost/src/fs/mod.rs b/waterbox/waterboxhost/src/fs/mod.rs index 2a47bdb8c9..d0e9bf32e6 100644 --- a/waterbox/waterboxhost/src/fs/mod.rs +++ b/waterbox/waterboxhost/src/fs/mod.rs @@ -90,8 +90,15 @@ impl IStateable for MountedFile { } } +// pub type MissingFileCallback = Box Option>; +// pub struct MissingFileResult { +// pub data: Vec, +// pub writable: bool, +// } + pub struct FileSystem { files: Vec, + // missing_file_callback: Option, } impl FileSystem { pub fn new() -> FileSystem { @@ -113,8 +120,13 @@ impl FileSystem { obj: Box::new(SysOutObj { host_handle: Box::new(std::io::stderr()) }) }, ], + // missing_file_callback: None, } } + /// Set (or clear, with None) a callback to be called whenever the guest tries to load a nonexistant file + // pub fn set_missing_file_callback(&mut self, cb: Option) { + // self.missing_file_callback = cb; + // } /// Accept a file from the outside world. Writable files may never appear in a savestate, /// and readonly files must not be added or removed from savestate to savestate, so all uses /// are either transient or read only resources that last for the life of emulation. @@ -145,9 +157,15 @@ impl FileSystem { } Ok(self.files.remove(idx).obj.unmount()) } + + // fn try_missing_file_cb(&mut self, name: &str) -> Option<()> { + // self.missing_file_callback + // .as_mut() + // .and_then(|cb| cb(name)) + // .map(|res| self.mount(name.to_string(), res.data, res.writable).unwrap()) + // } /// Implements a subset of open(2) pub fn open(&mut self, name: &str, flags: i32, _mode: i32) -> Result { - // TODO: Missing file callback let fd = { let mut i = 0; loop { @@ -157,7 +175,26 @@ impl FileSystem { i += 1; } }; - let file = match self.files.iter_mut().find(|f| f.name == name) { + let file_opt = { + // let mut did_cb = false; + loop { + match self.files.iter_mut().find(|f| f.name == name) { + Some(f) => break Some(f), + // None if !did_cb => { + // match self.try_missing_file_cb(name) { + // Some(()) => { + // did_cb = true; + // continue + // }, + // None => break None, + // } + // } + _ => break None, + } + } + }; + + let file = match file_opt { Some(f) => f, None => return Err(ENOENT) }; diff --git a/waterbox/waterboxhost/src/gdb.rs b/waterbox/waterboxhost/src/gdb.rs index 67cb4eb4b1..3492a5f5d2 100644 --- a/waterbox/waterboxhost/src/gdb.rs +++ b/waterbox/waterboxhost/src/gdb.rs @@ -1 +1,94 @@ -// TODO https://sourceware.org/gdb/current/onlinedocs/gdb/Declarations.html#Declarations +// https://sourceware.org/gdb/current/onlinedocs/gdb/Declarations.html#Declarations +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] + +use std::{sync::Mutex, ptr::null_mut}; +use lazy_static::lazy_static; + +const JIT_NOACTION: u32 = 0; +const JIT_REGISTER_FN: u32 = 1; +const JIT_UNREGISTER_FN: u32 = 2; + +#[repr(C)] +struct jit_code_entry { + next_entry: *mut jit_code_entry, + prev_entry: *mut jit_code_entry, + symfile_addr: *const u8, + symfile_size: u64 +} +unsafe impl Send for jit_code_entry {} +unsafe impl Sync for jit_code_entry {} + +#[repr(C)] +struct jit_descriptor { + version: u32, + action_flag: u32, + relevant_entry: *mut jit_code_entry, + first_entry: *mut jit_code_entry, +} +unsafe impl Send for jit_descriptor {} +unsafe impl Sync for jit_descriptor {} + +#[no_mangle] +#[inline(never)] +extern fn __jit_debug_register_code() {} + +#[no_mangle] +static mut __jit_debug_descriptor: jit_descriptor = jit_descriptor { + version: 1, + action_flag: JIT_NOACTION, + relevant_entry: null_mut(), // 0 as *mut jit_code_entry, + first_entry: null_mut(), // 0 as *mut jit_code_entry +}; + + +lazy_static! { + static ref LOCK: Mutex<()> = Mutex::new(()); +} + +/// unsafe: the data should be valid until a matching unregister call +pub unsafe fn register(data: &[u8]) { + let _guard = LOCK.lock().unwrap(); + let entry = Box::into_raw(Box::new(jit_code_entry { + next_entry: null_mut(), + prev_entry: null_mut(), + symfile_addr: &data[0], + symfile_size: data.len() as u64 + })); + if __jit_debug_descriptor.first_entry == null_mut() { + __jit_debug_descriptor.first_entry = entry; + } else { + let mut tail = __jit_debug_descriptor.first_entry; + while (*tail).next_entry != null_mut() { + tail = (*tail).next_entry; + } + (*tail).next_entry = entry; + (*entry).prev_entry = tail; + } + __jit_debug_descriptor.relevant_entry = entry; + __jit_debug_descriptor.action_flag = JIT_REGISTER_FN; + __jit_debug_register_code(); +} + +/// unsafe: undefined if not exactly matching a register call +pub unsafe fn deregister(data: &[u8]) { + let _guard = LOCK.lock().unwrap(); + + let mut entry = __jit_debug_descriptor.first_entry; + while (*entry).symfile_addr != &data[0] { + entry = (*entry).next_entry; + } + if (*entry).next_entry != null_mut() { + (*(*entry).next_entry).prev_entry = (*entry).prev_entry; + } + if (*entry).prev_entry != null_mut() { + (*(*entry).prev_entry).next_entry = (*entry).next_entry; + } else { + __jit_debug_descriptor.first_entry = (*entry).next_entry; + } + __jit_debug_descriptor.relevant_entry = entry; + __jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN; + __jit_debug_register_code(); + + Box::from_raw(entry); +} diff --git a/waterbox/waterboxhost/src/host.rs b/waterbox/waterboxhost/src/host.rs index 952e77e4f3..890c1fd5c1 100644 --- a/waterbox/waterboxhost/src/host.rs +++ b/waterbox/waterboxhost/src/host.rs @@ -2,9 +2,10 @@ use crate::*; use crate::{memory_block::ActivatedMemoryBlock, syscall_defs::*}; use memory_block::{MemoryBlock, Protection}; use std::{os::raw::c_char, ffi::CStr}; -use fs::{FileDescriptor, FileSystem}; +use fs::{FileDescriptor, FileSystem/*, MissingFileCallback*/}; use elf::ElfLoader; use cinterface::MemoryLayoutTemplate; +use goblin::elf::Elf; pub struct WaterboxHost { fs: FileSystem, @@ -14,15 +15,19 @@ pub struct WaterboxHost { memory_block: Box, active: bool, sealed: bool, + image_file: Vec, } impl WaterboxHost { - pub fn new(wbx: &[u8], module_name: &str, layout_template: &MemoryLayoutTemplate) -> anyhow::Result> { - let layout = layout_template.make_layout()?; + pub fn new(image_file: Vec, module_name: &str, layout_template: &MemoryLayoutTemplate) -> anyhow::Result> { + let wbx = Elf::parse(&image_file[..])?; + let elf_addr = ElfLoader::elf_addr(&wbx); + let layout = layout_template.make_layout(elf_addr)?; let mut memory_block = MemoryBlock::new(layout.all()); let mut b = memory_block.enter(); - let elf = ElfLoader::new(wbx, module_name, &layout, &mut b)?; + let elf = ElfLoader::new(&wbx, &image_file[..], module_name, &layout, &mut b)?; let fs = FileSystem::new(); drop(b); + unsafe { gdb::register(&image_file[..]) } let mut res = Box::new(WaterboxHost { fs, program_break: layout.sbrk.start, @@ -31,10 +36,10 @@ impl WaterboxHost { memory_block, active: false, sealed: false, + image_file, }); let mut active = res.activate(); - active.h.elf.connect_syscalls(&mut active.b, &mut active.sys); active.h.elf.native_init(&mut active.b); drop(active); @@ -62,10 +67,16 @@ impl WaterboxHost { sys }); res.sys.syscall.ud = res.as_mut() as *mut ActivatedWaterboxHost as usize; + res.h.elf.connect_syscalls(&mut res.b, &res.sys); res.h.active = true; res } } +impl Drop for WaterboxHost { + fn drop(&mut self) { + unsafe { gdb::deregister(&self.image_file[..]) } + } +} const TAG: u64 = 0xd01487803948acff; pub struct ActivatedWaterboxHost<'a> { @@ -95,11 +106,9 @@ impl<'a> ActivatedWaterboxHost<'a> { if self.h.sealed { return Err(anyhow!("Already sealed!")) } - self.h.elf.clear_syscalls(&mut self.b); - self.h.elf.seal(&mut self.b); - self.h.elf.connect_syscalls(&mut self.b, &self.sys); - self.h.elf.co_clean(&mut self.b); + self.h.elf.pre_seal(&mut self.b); self.b.seal(); + self.h.elf.connect_syscalls(&mut self.b, &self.sys); self.h.sealed = true; Ok(()) } @@ -109,6 +118,9 @@ impl<'a> ActivatedWaterboxHost<'a> { pub fn unmount_file(&mut self, name: &str) -> anyhow::Result> { self.h.fs.unmount(name) } + // pub fn set_missing_file_callback(&mut self, cb: Option) { + // self.h.fs.set_missing_file_callback(cb); + // } } const SAVE_START_MAGIC: &str = "ActivatedWaterboxHost_v1"; @@ -192,10 +204,10 @@ fn arg_to_statbuff<'a>(arg: usize) -> &'a mut KStat { } pub extern "win64" fn syscall(nr: SyscallNumber, ud: usize, a1: usize, a2: usize, a3: usize, a4: usize, _a5: usize, _a6: usize) -> SyscallReturn { - let mut h = gethost(ud); + let h = gethost(ud); match nr { NR_MMAP => { - let prot = arg_to_prot(a3)?; + let mut prot = arg_to_prot(a3)?; let flags = a4; if flags & MAP_ANONYMOUS == 0 { // anonymous + private is easy @@ -207,8 +219,16 @@ pub extern "win64" fn syscall(nr: SyscallNumber, ud: usize, a1: usize, a2: usize // various unsupported flags return syscall_err(EOPNOTSUPP) } + if flags & MAP_STACK != 0 { + if prot == Protection::RW { + prot = Protection::RWStack; + } else { + return syscall_err(EINVAL) // stacks must be readable and writable + } + } + let no_replace = flags & MAP_FIXED_NOREPLACE != 0; let arena_addr = h.sys.layout.mmap; - let res = h.b.mmap(AddressRange { start: a1, size: a2 }, prot, arena_addr)?; + let res = h.b.mmap(AddressRange { start: a1, size: a2 }, prot, arena_addr, no_replace)?; syscall_ok(res) }, NR_MREMAP => { @@ -297,10 +317,17 @@ pub extern "win64" fn syscall(nr: SyscallNumber, ud: usize, a1: usize, a2: usize let old = h.h.program_break; let res = if a1 != align_down(a1) { old - } else if a1 < addr.start || a1 > addr.end() { + } else if a1 < addr.start { + if a1 == 0 { + println!("Initializing heap sbrk at {:x}:{:x}", addr.start, addr.end()); + } old + } else if a1 > addr.end() { + eprintln!("Failed to satisfy allocation of {} bytes on sbrk heap", a1 - old); + old } else if a1 > old { - h.b.mmap_fixed(AddressRange { start: old, size: a1 - old }, Protection::RW).unwrap(); + h.b.mmap_fixed(AddressRange { start: old, size: a1 - old }, Protection::RW, true).unwrap(); + println!("Allocated {} bytes on sbrk heap, usage {}/{}", a1 - old, a1 - addr.start, addr.size); a1 } else { old diff --git a/waterbox/waterboxhost/src/lib.rs b/waterbox/waterboxhost/src/lib.rs index eb7510f853..b57922a768 100644 --- a/waterbox/waterboxhost/src/lib.rs +++ b/waterbox/waterboxhost/src/lib.rs @@ -20,6 +20,7 @@ mod elf; mod fs; mod host; mod cinterface; +mod gdb; pub trait IStateable { fn save_state(&mut self, stream: &mut dyn Write) -> anyhow::Result<()>; @@ -76,7 +77,7 @@ fn align_down(p: usize) -> usize { p & !PAGEMASK } fn align_up(p: usize) -> usize { - ((p - 1) | PAGEMASK) + 1 + ((p.wrapping_sub(1)) | PAGEMASK).wrapping_add(1) } /// Information about memory layout injected into the guest application @@ -115,6 +116,10 @@ pub struct WbxSysArea { pub syscall: WbxSysSyscall, } +/// Always remove memoryblocks from active ram when possible, to help debug dangling pointers. +/// Severe performance consequences. +static mut ALWAYS_EVICT_BLOCKS: bool = true; + #[cfg(test)] mod tests { #[test] diff --git a/waterbox/waterboxhost/src/memory_block/mod.rs b/waterbox/waterboxhost/src/memory_block/mod.rs index b75cbf425b..14e1caf17d 100644 --- a/waterbox/waterboxhost/src/memory_block/mod.rs +++ b/waterbox/waterboxhost/src/memory_block/mod.rs @@ -316,8 +316,7 @@ impl MemoryBlock { unsafe fn deactivate(&mut self, mut guard: BlockGuard) { // self.trace("deactivate"); assert!(self.active); - #[cfg(debug_assertions)] - { + if ALWAYS_EVICT_BLOCKS { // in debug mode, forcibly evict to catch dangling pointers let other_opt = guard.deref_mut(); match *other_opt { @@ -533,10 +532,9 @@ impl<'block> ActivatedMemoryBlock<'block> { } /// implements a subset of mmap(2) for anonymous, fixed address mappings - pub fn mmap_fixed(&mut self, addr: AddressRange, prot: Protection) -> SyscallResult { + pub fn mmap_fixed(&mut self, addr: AddressRange, prot: Protection, no_replace: bool) -> SyscallResult { let mut range = self.b.validate_range(addr)?; - if range.iter().any(|p| p.status != PageAllocation::Free) { - // assume MAP_FIXED_NOREPLACE at all times + if no_replace && range.iter().any(|p| p.status != PageAllocation::Free) { return Err(EEXIST) } MemoryBlock::set_protections(&mut range, PageAllocation::Allocated(prot)); @@ -639,14 +637,14 @@ impl<'block> ActivatedMemoryBlock<'block> { self.munmap_impl(addr, false) } - pub fn mmap(&mut self, addr: AddressRange, prot: Protection, arena_addr: AddressRange) -> Result { + pub fn mmap(&mut self, addr: AddressRange, prot: Protection, arena_addr: AddressRange, no_replace: bool) -> Result { if addr.size == 0 { return Err(EINVAL) } if addr.start == 0 { self.mmap_movable(addr.size, prot, arena_addr) } else { - self.mmap_fixed(addr, prot)?; + self.mmap_fixed(addr, prot, no_replace)?; Ok(addr.start) } } @@ -757,10 +755,23 @@ impl<'block> IStateable for ActivatedMemoryBlock<'block> { self.b.get_stack_dirty(); self.b.addr.save_state(stream)?; + unsafe { + let mut statii = Vec::new(); + let mut dirtii = Vec::new(); + statii.reserve_exact(self.b.pages.len()); + dirtii.reserve_exact(self.b.pages.len()); + for p in self.b.pages.iter() { + statii.push(p.status); + dirtii.push(p.dirty); + } + stream.write_all(std::mem::transmute(&statii[..]))?; + stream.write_all(std::mem::transmute(&dirtii[..]))?; + } + for (paddr, p) in self.b.page_range().iter_with_addr() { - bin::write(stream, &p.status)?; + // bin::write(stream, &p.status)?; if !p.invisible { - bin::write(stream, &p.dirty)?; + // bin::write(stream, &p.dirty)?; if p.dirty { unsafe { if !p.status.readable() { @@ -795,10 +806,18 @@ impl<'block> IStateable for ActivatedMemoryBlock<'block> { unsafe { pal::protect(self.b.addr, Protection::RW); + let mut statii = vec![PageAllocation::Free; self.b.pages.len()]; + let mut dirtii = vec![false; self.b.pages.len()]; + stream.read_exact(std::mem::transmute(&mut statii[..]))?; + stream.read_exact(std::mem::transmute(&mut dirtii[..]))?; + + let mut index = 0usize; for (paddr, p) in self.b.page_range().iter_mut_with_addr() { - let status = bin::readval::(stream)?; + let status = statii[index]; + // let status = bin::readval::(stream)?; if !p.invisible { - let dirty = bin::readval::(stream)?; + let dirty = dirtii[index]; + // let dirty = bin::readval::(stream)?; match (p.dirty, dirty) { (false, false) => (), (false, true) => { @@ -821,6 +840,7 @@ impl<'block> IStateable for ActivatedMemoryBlock<'block> { p.dirty = dirty; } p.status = status; + index += 1; } self.b.refresh_all_protections(); diff --git a/waterbox/waterboxhost/src/memory_block/tests.rs b/waterbox/waterboxhost/src/memory_block/tests.rs index d511368821..6cee61b4d4 100644 --- a/waterbox/waterboxhost/src/memory_block/tests.rs +++ b/waterbox/waterboxhost/src/memory_block/tests.rs @@ -31,7 +31,7 @@ fn test_dirty() -> TestResult { let addr = AddressRange { start: 0x36f00000000, size: 0x10000 }; let mut b = MemoryBlock::new(addr); let mut g = b.enter(); - g.mmap_fixed(addr, Protection::RW)?; + g.mmap_fixed(addr, Protection::RW, true)?; let ptr = g.b.addr.slice_mut(); ptr[0x2003] = 5; assert!(g.b.pages[2].dirty); @@ -46,7 +46,7 @@ fn test_offset() -> TestResult { let addr = AddressRange { start: 0x36f00000000, size: 0x20000 }; let mut b = MemoryBlock::new(addr); let mut g = b.enter(); - g.mmap_fixed(AddressRange { start: 0x36f00003000, size: 0x1000 }, Protection::RW)?; + g.mmap_fixed(AddressRange { start: 0x36f00003000, size: 0x1000 }, Protection::RW, true)?; let ptr = g.b.addr.slice_mut(); ptr[0x3663] = 12; assert!(g.b.pages[3].dirty); @@ -61,14 +61,19 @@ fn test_stk_norm() -> TestResult { let addr = AddressRange { start: 0x36200000000, size: 0x10000 }; let mut b = MemoryBlock::new(addr); let mut g = b.enter(); - g.mmap_fixed(addr, Protection::RWStack)?; + g.mmap_fixed(addr, Protection::RWStack, true)?; let ptr = g.b.addr.slice_mut(); ptr[0xeeee] = 0xee; ptr[0x44] = 0x44; + + g.b.get_stack_dirty(); + assert!(g.b.pages[0].dirty); assert!(g.b.pages[14].dirty); assert_eq!(ptr[0x8000], 0); + g.b.get_stack_dirty(); + // This is an unfair test, but it's just documenting the current limitations of the system. // Ideally, page 8 would be clean because we read from it but did not write to it. // Due to limitations of RWStack tracking on windows, it is dirty. @@ -89,7 +94,7 @@ fn test_stack() -> TestResult { let addr = AddressRange { start: 0x36f00000000, size: 0x10000 }; let mut b = MemoryBlock::new(addr); let mut g = b.enter(); - g.mmap_fixed(addr, Protection::RW)?; + g.mmap_fixed(addr, Protection::RW, true)?; let ptr = g.b.addr.slice_mut(); let mut i = 0; @@ -105,6 +110,9 @@ fn test_stack() -> TestResult { let tmp_rsp = addr.end(); let res = transmute:: u8>(addr.start)(tmp_rsp); assert_eq!(res, 42); + + g.b.get_stack_dirty(); + assert!(g.b.pages[0].dirty); assert!(!g.b.pages[1].dirty); assert!(!g.b.pages[14].dirty); @@ -124,7 +132,7 @@ fn test_state_basic() -> TestResult { let mut b = MemoryBlock::new(addr); let mut g = b.enter(); let ptr = g.b.addr.slice_mut(); - g.mmap_fixed(addr, Protection::RW)?; + g.mmap_fixed(addr, Protection::RW, true)?; ptr[0x0000] = 20; ptr[0x1000] = 40; ptr[0x2000] = 60; @@ -172,7 +180,7 @@ fn test_state_unreadable() -> TestResult { let mut b = MemoryBlock::new(addr); let mut g = b.enter(); let ptr = g.b.addr.slice_mut(); - g.mmap_fixed(addr, Protection::RW)?; + g.mmap_fixed(addr, Protection::RW, true)?; g.seal(); ptr[200] = 200; @@ -222,7 +230,7 @@ fn test_thready_stack() -> TestResult { let mut g = b.enter(); blocker.wait(); - g.mmap_fixed(addr, Protection::RWX)?; + g.mmap_fixed(addr, Protection::RWX, true)?; g.mprotect(AddressRange { start: addr.start + PAGESIZE, size: PAGESIZE }, Protection::RWStack)?; let ptr = g.b.addr.slice_mut(); @@ -236,12 +244,15 @@ fn test_thready_stack() -> TestResult { ptr[i] = 0xc3 ; // ret g.seal(); - + assert!(!g.b.pages[0].dirty); assert!(!g.b.pages[1].dirty); let tmp_rsp = addr.end(); let res = transmute:: u8>(addr.start)(tmp_rsp); assert_eq!(res, 42); + + g.b.get_stack_dirty(); + assert!(!g.b.pages[0].dirty); assert!(g.b.pages[1].dirty); @@ -266,7 +277,7 @@ fn test_state_invisible() -> TestResult { let mut b = MemoryBlock::new(addr); let mut g = b.enter(); let ptr = g.b.addr.slice_mut(); - g.mmap_fixed(addr, Protection::RW)?; + g.mmap_fixed(addr, Protection::RW, true)?; ptr[0x0055] = 11; ptr[0x1055] = 22; g.mark_invisible(AddressRange { start: 0x36400001000, size: 0x2000 })?; @@ -316,7 +327,7 @@ fn test_dontneed() -> TestResult { g.seal(); let ptr = g.b.addr.slice_mut(); - g.mmap_fixed(addr, Protection::RW)?; + g.mmap_fixed(addr, Protection::RW, true)?; for i in 0..addr.size { ptr[i] = i as u8; } @@ -341,7 +352,7 @@ fn test_remap_nomove() -> TestResult { let mut b = MemoryBlock::new(addr); let mut g = b.enter(); - g.mmap_fixed(AddressRange { start: addr.start, size: 0x4000 }, Protection::RWX)?; + g.mmap_fixed(AddressRange { start: addr.start, size: 0x4000 }, Protection::RWX, true)?; g.mremap_nomove(AddressRange { start: addr.start, size: 0x4000 }, 0x6000)?; assert_eq!(g.b.pages[3].status, PageAllocation::Allocated(Protection::RWX)); assert_eq!(g.b.pages[5].status, PageAllocation::Allocated(Protection::RWX)); @@ -379,12 +390,12 @@ fn test_mremap_move_expand() -> TestResult { let ptr = g.b.addr.slice_mut(); let initial_addr = AddressRange { start: 0x36800002000, size: 0x1000 }; - g.mmap_fixed(initial_addr, Protection::RW)?; + g.mmap_fixed(initial_addr, Protection::RW, true)?; ptr[0x2004] = 11; let p1 = g.mremap_maymove(initial_addr, 0x2000, addr)?; assert_eq!(p1, addr.start); assert_eq!(ptr[4], 11); - g.mmap_fixed(initial_addr, Protection::RW)?; + g.mmap_fixed(initial_addr, Protection::RW, true)?; assert_eq!(ptr[0x2004], 0); } Ok(()) @@ -399,12 +410,12 @@ fn test_mremap_move_shrink() -> TestResult { let ptr = g.b.addr.slice_mut(); let initial_addr = AddressRange { start: 0x36900001000, size: 0x3000 }; - g.mmap_fixed(initial_addr, Protection::RW)?; + g.mmap_fixed(initial_addr, Protection::RW, true)?; ptr[0x1004] = 11; let p1 = g.mremap_maymove(initial_addr, 0x1000, addr)?; assert_eq!(p1, addr.start); assert_eq!(ptr[4], 11); - g.mmap_fixed(initial_addr, Protection::RW)?; + g.mmap_fixed(initial_addr, Protection::RW, true)?; assert_eq!(ptr[0x1004], 0); } Ok(()) diff --git a/waterbox/waterboxhost/src/memory_block/tripguard.rs b/waterbox/waterboxhost/src/memory_block/tripguard.rs index 42447fc741..e6a2d61314 100644 --- a/waterbox/waterboxhost/src/memory_block/tripguard.rs +++ b/waterbox/waterboxhost/src/memory_block/tripguard.rs @@ -55,6 +55,7 @@ unsafe fn trip(addr: usize) -> TripResult { let page_start_addr = addr & !PAGEMASK; let page = &mut memory_block.pages[(addr - memory_block.addr.start) >> PAGESHIFT]; if !page.status.writable() { + std::intrinsics::breakpoint(); return TripResult::NotHandled } page.maybe_snapshot(page_start_addr); @@ -62,6 +63,7 @@ unsafe fn trip(addr: usize) -> TripResult { if pal::protect(AddressRange { start: page_start_addr, size: PAGESIZE }, page.native_prot()) { TripResult::Handled } else { + std::intrinsics::breakpoint(); std::process::abort(); } } @@ -79,7 +81,22 @@ mod trip_pal { let flags = p_record.ExceptionInformation[0]; match p_record.ExceptionCode { STATUS_ACCESS_VIOLATION if (flags & 1) != 0 => (), // write exception - STATUS_GUARD_PAGE_VIOLATION => (), // guard exception + STATUS_GUARD_PAGE_VIOLATION => { + // guard exception + + // If this is ours, it's from a cothread stack. If we're on a cothread + // stack right now, we need to return without taking our lock because + // the stack used in handler() and everything it calls might move onto + // another page and trip again which would deadlock us. (Alternatively, + // this might be the second trip in a row while already servicing another one.) + // This does not cause determinism issues because of get_stack_dirty() and the + // windows specific code in set_protections(). + + // The only problem here is that we might be swallowing a completely unrelated guard + // exception by returning before checking whether it was in a waterbox block; + // in which case we'll find out what we broke eventually. + return EXCEPTION_CONTINUE_EXECUTION + }, _ => return EXCEPTION_CONTINUE_SEARCH } let fault_address = p_record.ExceptionInformation[1] as usize; @@ -125,13 +142,20 @@ mod trip_pal { } } unsafe { + // TODO: sigaltstack is per thread, so this won't work + // At the same time, one seems to be set up automatically on each thread, so this isn't needed. + // let ss = stack_t { + // ss_flags: 0, + // ss_sp: Box::into_raw(Box::new(zeroed::<[u8; SIGSTKSZ]>())) as *mut c_void, + // ss_size: SIGSTKSZ + // }; + // let mut ss_old = stack_t { + // ss_flags: 0, + // ss_sp: 0 as *mut c_void, + // ss_size: 0 + // }; + // assert!(sigaltstack(&ss, &mut ss_old) == 0, "sigaltstack failed"); SA_OLD = Some(Box::new(zeroed())); - let ss = stack_t { - ss_flags: 0, - ss_sp: Box::into_raw(Box::new(zeroed::<[u8; SIGSTKSZ]>)) as *mut c_void, - ss_size: SIGSTKSZ - }; - assert!(sigaltstack(&ss, 0 as *mut stack_t) == 0, "sigaltstack failed"); let mut sa = sigaction { sa_mask: zeroed(), sa_sigaction: transmute::(handler), diff --git a/waterbox/waterboxhost/src/syscall_defs.rs b/waterbox/waterboxhost/src/syscall_defs.rs index 803570c0ac..285b3e387b 100644 --- a/waterbox/waterboxhost/src/syscall_defs.rs +++ b/waterbox/waterboxhost/src/syscall_defs.rs @@ -64,7 +64,7 @@ macro_rules! lookup { $(pub const $N: $T = $T($E);)+ pub fn $P(val: &$T) -> &'static str { match val { - $($T($E) => stringify!($E),)+ + $($T($E) => stringify!($N),)+ _ => "????" } } diff --git a/waterbox/winguard/Makefile b/waterbox/winguard/Makefile deleted file mode 100644 index 2fcd7e038d..0000000000 --- a/waterbox/winguard/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -ifeq (,$(findstring MINGW,$(shell uname))) -$(error This must be built with mingw) -endif - -winguard.dll: winguard.c - gcc -O2 -s -o $@ $< -shared - -install: winguard.dll - cp $< ../../output/dll -clean: - rm winguard.dll - -.PHONY: install clean diff --git a/waterbox/winguard/winguard.c b/waterbox/winguard/winguard.c deleted file mode 100644 index d258ed6abb..0000000000 --- a/waterbox/winguard/winguard.c +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include - -#define MAX_TRIPS 64 - -typedef struct { - uintptr_t start; - uintptr_t length; - uint8_t tripped[0]; -} tripwire_t; - -static tripwire_t* Trips[MAX_TRIPS]; -static int HandlerInstalled; - -static LONG VectoredHandler(struct _EXCEPTION_POINTERS* p_info) -{ - EXCEPTION_RECORD* p_record = p_info->ExceptionRecord; - - // CONTEXT* p_context = p_info->ContextRecord; - DWORD64 flags = p_record->ExceptionInformation[0]; - - if (p_record->ExceptionCode != STATUS_ACCESS_VIOLATION // only trigger on access violations... - || !(flags & 1)) // ...due to a write attempts - return EXCEPTION_CONTINUE_SEARCH; - - uintptr_t faultAddress = (uintptr_t)p_record->ExceptionInformation[1]; - for (int i = 0; i < MAX_TRIPS; i++) - { - if (Trips[i] && faultAddress >= Trips[i]->start && faultAddress < Trips[i]->start + Trips[i]->length) - { - uintptr_t page = (faultAddress - Trips[i]->start) >> 12; - if (Trips[i]->tripped[page] & 1) // should change - { - DWORD oldprot; - if (!VirtualProtect((void*)faultAddress, 1, PAGE_READWRITE, &oldprot)) - { - RaiseFailFastException(NULL, NULL, 0); - while (1) - ; - } - Trips[i]->tripped[page] = 3; // did change - return EXCEPTION_CONTINUE_EXECUTION; - } - else - { - return EXCEPTION_CONTINUE_SEARCH; - } - } - } - return EXCEPTION_CONTINUE_SEARCH; -} - -__declspec(dllexport) uint8_t* AddTripGuard(uintptr_t start, uintptr_t length) -{ - if (!HandlerInstalled) - { - if (!AddVectoredExceptionHandler(1 /* CALL_FIRST */, VectoredHandler)) - return NULL; - HandlerInstalled = 1; - } - - uintptr_t npage = length >> 12; - for (int i = 0; i < MAX_TRIPS; i++) - { - if (!Trips[i]) - { - Trips[i] = calloc(1, sizeof(*Trips[i]) + npage); - if (!Trips[i]) - return NULL; - Trips[i]->start = start; - Trips[i]->length = length; - return &Trips[i]->tripped[0]; - } - } - return NULL; -} - -__declspec(dllexport) int64_t RemoveTripGuard(uintptr_t start, uintptr_t length) -{ - for (int i = 0; i < MAX_TRIPS; i++) - { - if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length) - { - free(Trips[i]); - Trips[i] = NULL; - return 1; - } - } - return 0; -} - -__declspec(dllexport) uint8_t* ExamineTripGuard(uintptr_t start, uintptr_t length) -{ - for (int i = 0; i < MAX_TRIPS; i++) - { - if (Trips[i] && Trips[i]->start == start && Trips[i]->length == length) - return &Trips[i]->tripped[0]; - } - return NULL; -}