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; namespace BizHawk.Emulation.Cores { public sealed class ElfRunner : IDisposable { // TODO: a lot of things only work with our elves and aren't fully generalized private ELF _elf; private MemoryBlock _base; private long _loadoffset; private Dictionary> _symdict; private List> _symlist; public ElfRunner(string filename) { // 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); _base = new MemoryBlock(UIntPtr.Zero, orig_end - orig_start); _loadoffset = (long)_base.Start - orig_start; try { _base.Set(_base.Start, _base.Size, MemoryBlock.Protection.RW); foreach (var seg in loadsegs) { var data = seg.GetContents(); Marshal.Copy(data, 0, (IntPtr)(seg.Address + _loadoffset), data.Length); } RegisterSymbols(); ProcessRelocations(); _base.Set(_base.Start, _base.Size, MemoryBlock.Protection.R); foreach (var sec in _elf.Sections.Where(s => s.Flags.HasFlag(SectionFlags.Allocatable))) { if (sec.Flags.HasFlag(SectionFlags.Executable)) _base.Set((UIntPtr)(sec.LoadAddress + _loadoffset), sec.Size, MemoryBlock.Protection.RX); else if (sec.Flags.HasFlag(SectionFlags.Writable)) _base.Set((UIntPtr)(sec.LoadAddress + _loadoffset), sec.Size, MemoryBlock.Protection.RW); } //FixupGOT(); ConnectAllClibPatches(); Console.WriteLine("Loaded {0}@{1:X16}", filename, (long)_base.Start); } catch { _base.Dispose(); throw; } } 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(); } } } // elfsharp does not read relocation tables, so there private void ProcessRelocations() { 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 PopulateInterface(object o) { var fields = o.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public) .Where(fi => typeof(Delegate).IsAssignableFrom(fi.FieldType)) .Where(fi => fi.FieldType.GetCustomAttributes(typeof(UnmanagedFunctionPointerAttribute), false).Length > 0); foreach (var fi in fields) { var sym = _symdict[fi.Name]; // TODO: allow some sort of EntryPoint attribute if (sym.Type != SymbolType.Function) throw new InvalidOperationException("Unexpected symbol type for alleged function!"); IntPtr ptr = (IntPtr)(sym.Value + _loadoffset); fi.SetValue(o, Marshal.GetDelegateForFunctionPointer(ptr, fi.FieldType)); } } public void Dispose() { Dispose(true); //GC.SuppressFinalize(this); } //~ElfRunner() //{ // Dispose(false); //} private void Dispose(bool disposing) { if (disposing) { if (_base != null) { _base.Dispose(); _base = null; } if (_heaps != null) { foreach (var b in _heaps.Values) { b.Dispose(); } _heaps = null; } } } #region clib monkeypatches // our clib expects a few function pointers to be defined for it /// /// heap free callback /// /// ptr /// bytesize [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void FreeMem_D(IntPtr p, IntPtr size); /// /// heap alloc callback /// /// bytesize /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr AllocMem_D(IntPtr size); /// /// exit() callback /// /// desired exit code [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void Exit_D(int status); [CLibPatch("_ZZFree")] private void FreeMem(IntPtr p, IntPtr size) { MemoryBlock block; if (_heaps.TryGetValue(p, out block)) { _heaps.Remove(p); block.Dispose(); } } [CLibPatch("_ZZAlloc")] private IntPtr AllocMem(IntPtr size) { var block = new MemoryBlock((long)size); var p = (IntPtr)(long)block.Start; _heaps[p] = block; block.Set(block.Start, block.Size, MemoryBlock.Protection.RW); Console.WriteLine("AllocMem: {0:X8}@{1:X16}", (long)size, (long)block.Start); return p; } [CLibPatch("_ZZExit")] private void Exit(int status) { throw new InvalidOperationException("Client code called exit()"); } /// /// list of memoryblocks that need to be cleaned up /// private Dictionary _heaps = new Dictionary(); /// /// list of delegates that need to not be GCed /// private List _delegates = new List(); private void ConnectAllClibPatches() { 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!"); Marshal.Copy(new[] { ptr }, 0, (IntPtr)(sym.Value + _loadoffset), 1); } } [AttributeUsage(AttributeTargets.Method)] private class CLibPatchAttribute : Attribute { public string NativeName { get; private set; } public CLibPatchAttribute(string nativeName) { NativeName = nativeName; } } #endregion } }