using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using ELFSharp.ELF; using ELFSharp.ELF.Sections; using ELFSharp.ELF.Segments; using System.Reflection; using BizHawk.Common; using System.Security.Cryptography; using System.IO; using System.Collections.Concurrent; using System.Threading; namespace BizHawk.Emulation.Cores { public sealed class ElfRunner : IImportResolver, IDisposable, IMonitor { // TODO: a lot of things only work with our elves and aren't fully generalized private ELF _elf; private byte[] _elfhash; /// /// executable is loaded here /// private MemoryBlock _base; /// /// standard malloc() heap /// private Heap _heap; /// /// sealed heap (writable only during init) /// private Heap _sealedheap; /// /// invisible heap (not savestated, use with care) /// private Heap _invisibleheap; /// /// _base.Start, or 0 if we were relocated and so don't need to be swapped /// private ulong _lockkey; private long _loadoffset; private Dictionary> _symdict; private List> _symlist; /// /// everything to clean up at dispose time /// private List _disposeList = new List(); /// /// everything to swap in for context switches /// private List _memoryBlocks = new List(); private ulong GetHeapStart(ulong prevend) { // if relocatable, we won't have constant pointers, so put the heap anywhere // otherwise, put the heap at a canonical location aligned 1MB from the end of the elf, then incremented 16MB ulong heapstart = HasRelocations() ? 0 : ((prevend - 1) | 0xfffff) + 0x1000001; return heapstart; } public ElfRunner(string filename, long heapsize, long sealedheapsize, long invisibleheapsize) { using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) { _elfhash = Hash(fs); } // todo: hack up this baby to take Streams _elf = ELFReader.Load(filename); var loadsegs = _elf.Segments.Where(s => s.Type == SegmentType.Load); long orig_start = loadsegs.Min(s => s.Address); orig_start &= ~(Environment.SystemPageSize - 1); long orig_end = loadsegs.Max(s => s.Address + s.Size); if (HasRelocations()) { _base = new MemoryBlock((ulong)(orig_end - orig_start)); _loadoffset = (long)_base.Start - orig_start; _lockkey = 0; } else { _lockkey = (ulong)orig_start; _base = new MemoryBlock(_lockkey, (ulong)(orig_end - orig_start)); _loadoffset = 0; Enter(); } try { _disposeList.Add(_base); _memoryBlocks.Add(_base); _base.Activate(); _base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.RW); foreach (var seg in loadsegs) { var data = seg.GetContents(); Marshal.Copy(data, 0, Z.SS(seg.Address + _loadoffset), data.Length); } RegisterSymbols(); ProcessRelocations(); _base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.R); foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Allocatable) != 0)) { if ((sec.Flags & SectionFlags.Executable) != 0) _base.Protect((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, MemoryBlock.Protection.RX); else if ((sec.Flags & SectionFlags.Writable) != 0) _base.Protect((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, MemoryBlock.Protection.RW); } ulong end = _base.End; if (heapsize > 0) { _heap = new Heap(GetHeapStart(end), (ulong)heapsize, "sbrk-heap"); _heap.Memory.Activate(); end = _heap.Memory.End; _disposeList.Add(_heap); _memoryBlocks.Add(_heap.Memory); } if (sealedheapsize > 0) { _sealedheap = new Heap(GetHeapStart(end), (ulong)sealedheapsize, "sealed-heap"); _sealedheap.Memory.Activate(); end = _sealedheap.Memory.End; _disposeList.Add(_sealedheap); _memoryBlocks.Add(_sealedheap.Memory); } if (invisibleheapsize > 0) { _invisibleheap = new Heap(GetHeapStart(end), (ulong)invisibleheapsize, "invisible-heap"); _invisibleheap.Memory.Activate(); end = _invisibleheap.Memory.End; _disposeList.Add(_invisibleheap); _memoryBlocks.Add(_invisibleheap.Memory); } ConnectAllClibPatches(); Console.WriteLine("Loaded {0}@{1:X16}", filename, _base.Start); foreach (var sec in _elf.Sections.Where(s => s.LoadAddress != 0)) { Console.WriteLine(" {0}@{1:X16}, size {2}", sec.Name.PadLeft(20), sec.LoadAddress + _loadoffset, sec.Size.ToString().PadLeft(12)); } PrintTopSavableSymbols(); } catch { Dispose(); throw; } finally { Exit(); } } private void PrintTopSavableSymbols() { Console.WriteLine("Top savestate symbols:"); foreach (var text in _symlist .Where(s => s.PointedSection != null && (s.PointedSection.Flags & SectionFlags.Writable) != 0) .OrderByDescending(s => s.Size) .Take(30) .Select(s => string.Format("{0} size {1}", s.Name, s.Size))) { Console.WriteLine(text); } } private class Elf32_Rel { public long Address; public byte Type; public int SymbolIdx; public long Addend; public Elf32_Rel(byte[] data, int start, int len) { if (len == 8 || len == 12) { Address = BitConverter.ToInt32(data, start); Type = data[start + 4]; SymbolIdx = (int)(BitConverter.ToUInt32(data, start + 4) >> 8); Addend = data.Length == 12 ? BitConverter.ToInt32(data, start + 8) : 0; } else { throw new InvalidOperationException(); } } } private bool HasRelocations() { return _elf.Sections.Any(s => s.Name.StartsWith(".rel")); } // elfsharp does not read relocation tables, so there private void ProcessRelocations() { // todo: amd64 foreach (var rel in _elf.Sections.Where(s => s.Name.StartsWith(".rel"))) { byte[] data = rel.GetContents(); var symbols = Enumerable.Range(0, data.Length / 8) .Select(i => new Elf32_Rel(data, i * 8, 8)); foreach (var symbol in symbols) { ApplyRelocation(symbol); } } } private void ApplyRelocation(Elf32_Rel rel) { // http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf // this is probably mostly wrong long val = 0; long A = rel.Addend; // since all symbols were moved by the same amount, just add _loadoffset here long S = _symlist[rel.SymbolIdx].Value + _loadoffset; long B = _loadoffset; switch (rel.Type) { case 0: val = 0; break; case 1: val = S + A; break; case 2: throw new NotImplementedException(); case 3: throw new NotImplementedException(); case 4: throw new NotImplementedException(); case 5: val = 0; break; case 6: val = S; break; case 7: val = S; break; case 8: val = B + A; break; case 9: throw new NotImplementedException(); case 10: throw new NotImplementedException(); default: throw new InvalidOperationException(); } byte[] tmp = new byte[4]; Marshal.Copy((IntPtr)(rel.Address + _loadoffset), tmp, 0, 4); long currentVal = BitConverter.ToUInt32(tmp, 0); tmp = BitConverter.GetBytes((uint)(currentVal + val)); Marshal.Copy(tmp, 0, (IntPtr)(rel.Address + _loadoffset), 4); } private void RegisterSymbols() { var symbols = ((ISymbolTable)_elf.GetSection(".symtab")) .Entries .Cast>(); // when there are duplicate names, don't register either in the dictionary _symdict = symbols .GroupBy(e => e.Name) .Where(g => g.Count() == 1) .ToDictionary(g => g.Key, g => g.First()); _symlist = symbols.ToList(); } public void Dispose() { // we don't need to activate to dispose Dispose(true); //GC.SuppressFinalize(this); } public void Seal() { Enter(); try { _sealedheap.Seal(); } finally { Exit(); } } //~ElfRunner() //{ // Dispose(false); //} private void Dispose(bool disposing) { if (disposing) { foreach (var d in _disposeList) d.Dispose(); _disposeList.Clear(); _memoryBlocks.Clear(); _base = null; _heap = null; _sealedheap = null; _invisibleheap = null; } } #region clib monkeypatches // our clib expects a few function pointers to be defined for it /// /// abort() / other abnormal situation /// /// desired exit code [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void Trap_D(); /// /// expand heap /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr Sbrk_D(UIntPtr n); /// /// output a string /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void DebugPuts_D(string s); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SbrkSealed_D(UIntPtr n); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr SbrkInvisible_D(UIntPtr n); [CLibPatch("_ecl_trap")] private void Trap() { throw new InvalidOperationException("Waterbox code trapped!"); } [CLibPatch("_ecl_sbrk")] private IntPtr Sbrk(UIntPtr n) { return Z.US(_heap.Allocate((ulong)n, 1)); } [CLibPatch("_ecl_debug_puts")] private void DebugPuts(string s) { Console.WriteLine("Waterbox debug puts: {0}", s); } [CLibPatch("_ecl_sbrk_sealed")] private IntPtr SbrkSealed(UIntPtr n) { return Z.US(_sealedheap.Allocate((ulong)n, 16)); } [CLibPatch("_ecl_sbrk_invisible")] private IntPtr SbrkInvisible(UIntPtr n) { return Z.US(_invisibleheap.Allocate((ulong)n, 16)); } /// /// list of delegates that need to not be GCed /// private List _delegates = new List(); private void ConnectAllClibPatches() { _delegates.Clear(); // in case we're reconnecting var methods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(mi => mi.GetCustomAttributes(typeof(CLibPatchAttribute), false).Length > 0); foreach (var mi in methods) { var delegateType = GetType().GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic) .Single(t => t.Name == mi.Name + "_D"); var del = Delegate.CreateDelegate(delegateType, this, mi); IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); _delegates.Add(del); var sym = _symdict[((CLibPatchAttribute)mi.GetCustomAttributes(typeof(CLibPatchAttribute), false)[0]).NativeName]; if (sym.Size != IntPtr.Size) throw new InvalidOperationException("Unexpected function pointer size patching clib!"); IntPtr dest = Z.SS(sym.Value + _loadoffset); Marshal.Copy(new[] { ptr }, 0, dest, 1); } } [AttributeUsage(AttributeTargets.Method)] private class CLibPatchAttribute : Attribute { public string NativeName { get; private set; } public CLibPatchAttribute(string nativeName) { NativeName = nativeName; } } #endregion public IntPtr Resolve(string entryPoint) { SymbolEntry sym; if (_symdict.TryGetValue(entryPoint, out sym)) { return Z.SS(sym.Value + _loadoffset); } else { return IntPtr.Zero; } } /// /// true if the IMonitor should be used for native calls /// public bool ShouldMonitor { get { return _lockkey != 0; } } // any ElfRunner is assumed to conflict with any other ElfRunner at the same base address, // but not any other starting address. so don't put them too close together! private class LockInfo { public object Sync; public ElfRunner Loaded; } private static readonly ConcurrentDictionary LockInfos = new ConcurrentDictionary(); static ElfRunner() { LockInfos.GetOrAdd(0, new LockInfo()); // any errant attempt to lock when ShouldMonitor == false will result in NRE } /// /// acquire lock and swap this into memory /// public void Enter() { var li = LockInfos.GetOrAdd(_lockkey, new LockInfo { Sync = new object() }); Monitor.Enter(li.Sync); if (li.Loaded != this) { if (li.Loaded != null) li.Loaded.DeactivateInternal(); li.Loaded = null; ActivateInternal(); li.Loaded = this; } } /// /// release lock /// public void Exit() { var li = LockInfos.GetOrAdd(_lockkey, new LockInfo { Sync = new object() }); Monitor.Exit(li.Sync); } private void DeactivateInternal() { Console.WriteLine("ElfRunner DeactivateInternal {0}", GetHashCode()); foreach (var m in _memoryBlocks) m.Deactivate(); } private void ActivateInternal() { Console.WriteLine("ElfRunner ActivateInternal {0}", GetHashCode()); foreach (var m in _memoryBlocks) m.Activate(); } #region state const ulong MAGIC = 0xb00b1e5b00b1e569; public void SaveStateBinary(BinaryWriter bw) { Enter(); try { bw.Write(MAGIC); bw.Write(_elfhash); bw.Write(_loadoffset); foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Writable) != 0)) { var ms = _base.GetStream((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, false); bw.Write(sec.Size); ms.CopyTo(bw.BaseStream); } if (_heap != null) _heap.SaveStateBinary(bw); if (_sealedheap != null) _sealedheap.SaveStateBinary(bw); bw.Write(MAGIC); } finally { Exit(); } } public void LoadStateBinary(BinaryReader br) { Enter(); try { if (br.ReadUInt64() != MAGIC) throw new InvalidOperationException("Magic not magic enough!"); if (!br.ReadBytes(_elfhash.Length).SequenceEqual(_elfhash)) throw new InvalidOperationException("Elf changed disguise!"); if (br.ReadInt64() != _loadoffset) throw new InvalidOperationException("Trickys elves moved on you!"); foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Writable) != 0)) { var len = br.ReadInt64(); if (sec.Size != len) throw new InvalidOperationException("Unexpected section size for " + sec.Name); var ms = _base.GetStream((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, true); CopySome(br.BaseStream, ms, len); } if (_heap != null) _heap.LoadStateBinary(br); if (_sealedheap != null) _sealedheap.LoadStateBinary(br); if (br.ReadUInt64() != MAGIC) throw new InvalidOperationException("Magic not magic enough!"); // the syscall trampolines were overwritten in loadstate (they're in .bss), and if we're cross-session, // are no longer valid. cores must similiarly resend any external pointers they gave the core. ConnectAllClibPatches(); } finally { Exit(); } } #endregion #region utils private static void CopySome(Stream src, Stream dst, long len) { var buff = new byte[4096]; while (len > 0) { int r = src.Read(buff, 0, (int)Math.Min(len, 4096)); dst.Write(buff, 0, r); len -= r; } } private static byte[] Hash(byte[] data) { using (var h = SHA1.Create()) { return h.ComputeHash(data); } } private static byte[] Hash(Stream s) { using (var h = SHA1.Create()) { return h.ComputeHash(s); } } private byte[] HashSection(ulong ptr, ulong len) { using (var h = SHA1.Create()) { var ms = _base.GetStream(ptr, len, false); return h.ComputeHash(ms); } } /// /// a simple grow-only fixed max size heap /// private sealed class Heap : IDisposable { public MemoryBlock Memory { get; private set; } /// /// name, used in identifying errors /// public string Name { get; private set; } /// /// total number of bytes used /// public ulong Used { get; private set; } /// /// true if the heap has been sealed, preventing further changes /// public bool Sealed { get; private set; } private byte[] _hash; public Heap(ulong start, ulong size, string name) { Memory = new MemoryBlock(start, size); Used = 0; Name = name; } private void EnsureAlignment(int align) { if (align > 1) { ulong newused = ((Used - 1) | (ulong)(align - 1)) + 1; if (newused > Memory.Size) { throw new InvalidOperationException(string.Format("Failed to meet alignment {0} on heap {1}", align, Name)); } Used = newused; } } public ulong Allocate(ulong size, int align) { if (Sealed) throw new InvalidOperationException(string.Format("Attempt made to allocate from sealed heap {0}", Name)); EnsureAlignment(align); ulong newused = Used + size; if (newused > Memory.Size) { throw new InvalidOperationException(string.Format("Failed to allocate {0} bytes from heap {1}", size, Name)); } ulong ret = Memory.Start + Used; Memory.Protect(ret, newused - Used, MemoryBlock.Protection.RW); Used = newused; Console.WriteLine("Allocated {0} bytes on {1}", size, Name); return ret; } public void Seal() { if (!Sealed) { Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.R); _hash = Hash(Memory.GetStream(Memory.Start, Used, false)); Sealed = true; } else { throw new InvalidOperationException(string.Format("Attempt to reseal heap {0}", Name)); } } public void SaveStateBinary(BinaryWriter bw) { bw.Write(Name); bw.Write(Used); if (!Sealed) { var ms = Memory.GetStream(Memory.Start, Used, false); ms.CopyTo(bw.BaseStream); } else { bw.Write(_hash); } } public void LoadStateBinary(BinaryReader br) { var name = br.ReadString(); if (name != Name) throw new InvalidOperationException(string.Format("Name did not match for heap {0}", Name)); var used = br.ReadUInt64(); if (used > Memory.Size) throw new InvalidOperationException(string.Format("Heap {0} used {1} larger than available {2}", Name, used, Memory.Size)); if (!Sealed) { Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.None); Memory.Protect(Memory.Start, used, MemoryBlock.Protection.RW); var ms = Memory.GetStream(Memory.Start, used, true); CopySome(br.BaseStream, ms, (long)used); Used = used; } else { var hash = br.ReadBytes(_hash.Length); if (!hash.SequenceEqual(_hash)) { throw new InvalidOperationException(string.Format("Hash did not match for heap {0}. Is this the same rom?")); } } } public void Dispose() { if (Memory != null) { Memory.Dispose(); Memory = null; } } } #endregion } }