Skip to content

Commit

Permalink
Lib: Support mach-o exports
Browse files Browse the repository at this point in the history
  • Loading branch information
SamboyCoding committed Feb 16, 2023
1 parent 24c9c0f commit 9e22967
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 6 deletions.
5 changes: 3 additions & 2 deletions LibCpp2IL/ClassReadingBinaryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,6 @@ private object ReadAndConvertPrimitive(bool overrideArchCheck, Type type)

public virtual string ReadStringToNull(long offset)
{
var builder = new List<byte>();

GetLockOrThrow();

try
Expand Down Expand Up @@ -284,6 +282,9 @@ internal string ReadStringToNullNoLock(long offset)
}
}

public string ReadStringToNullAtCurrentPos()
=> ReadStringToNullNoLock(-1);

public byte[] ReadByteArrayAtRawAddress(long offset, int count)
{
GetLockOrThrow();
Expand Down
43 changes: 43 additions & 0 deletions LibCpp2IL/MachO/MachODynamicLinkerCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;

namespace LibCpp2IL.MachO
{
public class MachODynamicLinkerCommand : ReadableClass
{
public int RebaseOffset;
public int RebaseSize;
public int BindOffset;
public int BindSize;
public int WeakBindOffset;
public int WeakBindSize;
public int LazyBindOffset;
public int LazyBindSize;
public int ExportOffset;
public int ExportSize;

public MachOExportEntry[] Exports = Array.Empty<MachOExportEntry>();

public override void Read(ClassReadingBinaryReader reader)
{
RebaseOffset = reader.ReadInt32();
RebaseSize = reader.ReadInt32();
BindOffset = reader.ReadInt32();
BindSize = reader.ReadInt32();
WeakBindOffset = reader.ReadInt32();
WeakBindSize = reader.ReadInt32();
LazyBindOffset = reader.ReadInt32();
LazyBindSize = reader.ReadInt32();
ExportOffset = reader.ReadInt32();
ExportSize = reader.ReadInt32();

var returnTo = reader.BaseStream.Position;

reader.BaseStream.Position = ExportOffset;

var exports = new MachOExportTrie(reader);
Exports = exports.Entries.ToArray();

reader.BaseStream.Position = returnTo;
}
}
}
20 changes: 20 additions & 0 deletions LibCpp2IL/MachO/MachOExportEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace LibCpp2IL.MachO
{
public class MachOExportEntry
{
public string Name;
public long Address;
public long Flags;
public long Other;
public string? ImportName;

public MachOExportEntry(string name, long address, long flags, long other, string? importName)
{
Name = name;
Address = address;
Flags = flags;
Other = other;
ImportName = importName;
}
}
}
85 changes: 85 additions & 0 deletions LibCpp2IL/MachO/MachOExportTrie.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;

namespace LibCpp2IL.MachO
{
public class MachOExportTrie
{
public List<MachOExportEntry> Entries = new();

private ClassReadingBinaryReader _reader;
private long _basePtr;

public MachOExportTrie(ClassReadingBinaryReader reader)
{
_reader = reader;
_basePtr = reader.BaseStream.Position;

var children = ParseNode("", 0);
while (children.Count > 0)
{
var current = children[0];
children.RemoveAt(0);
children.AddRange(ParseNode(current.Name, current.Offset));
}
}

private List<Node> ParseNode(string name, int offset)
{
var children = new List<Node>();
_reader.BaseStream.Position = _basePtr + offset;

var terminalSize = _reader.BaseStream.ReadLEB128Unsigned();
var childrenIndex = _reader.BaseStream.Position + (long)terminalSize;
if (terminalSize != 0)
{
var flags = (ExportFlags) _reader.BaseStream.ReadLEB128Unsigned();
var address = 0L;
var other = 0L;
string? importName = null;

if ((flags & ExportFlags.ReExport) != 0)
{
other = _reader.BaseStream.ReadLEB128Signed();
importName = _reader.ReadStringToNullAtCurrentPos();
}
else
{
address = _reader.BaseStream.ReadLEB128Signed();
if ((flags & ExportFlags.StubAndResolver) != 0)
other = _reader.BaseStream.ReadLEB128Signed();
}

Entries.Add(new(name, address, (long) flags, other, importName));
}

_reader.BaseStream.Position = childrenIndex;
var numChildren = _reader.BaseStream.ReadLEB128Unsigned();
for (var i = 0ul; i < numChildren; i++)
{
var childName = _reader.ReadStringToNullAtCurrentPos();
var childOffset = _reader.BaseStream.ReadLEB128Unsigned();
children.Add(new Node {Name = name + childName, Offset = (int) childOffset});
}

return children;
}

[Flags]
private enum ExportFlags
{
KindRegular = 0,
KindThreadLocal = 1,
KindAbsolute = 2,
WeakDefinition = 4,
ReExport = 8,
StubAndResolver = 0x10
}

private struct Node
{
public string Name;
public int Offset;
}
}
}
18 changes: 15 additions & 3 deletions LibCpp2IL/MachO/MachOFile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.IO;
using System.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using LibCpp2IL.Logging;

Expand All @@ -13,6 +15,7 @@ public class MachOFile : Il2CppBinary

private readonly MachOSegmentCommand[] Segments64;
private readonly MachOSection[] Sections64;
private Dictionary<string, long> _exportsDict;

public MachOFile(MemoryStream input) : base(input)
{
Expand Down Expand Up @@ -69,6 +72,12 @@ public MachOFile(MemoryStream input) : base(input)
Segments64 = _loadCommands.Where(c => c.Command == LoadCommandId.LC_SEGMENT_64).Select(c => c.CommandData).Cast<MachOSegmentCommand>().ToArray();
Sections64 = Segments64.SelectMany(s => s.Sections).ToArray();

var dyldData = _loadCommands.FirstOrDefault(c => c.Command is LoadCommandId.LC_DYLD_INFO or LoadCommandId.LC_DYLD_INFO_ONLY)?.CommandData as MachODynamicLinkerCommand;
var exports = dyldData?.Exports ?? Array.Empty<MachOExportEntry>();
_exportsDict = exports.ToDictionary(e => e.Name[1..], e => e.Address); //Skip the first character, which is a leading underscore inserted by the compiler

LibLogger.VerboseNewline($"Found {_exportsDict.Count} exports in the DYLD info load command.");

LibLogger.VerboseNewline($"\tMach-O contains {Segments64.Length} segments, split into {Sections64.Length} sections.");
}

Expand Down Expand Up @@ -107,7 +116,10 @@ public override ulong GetRva(ulong pointer)

public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind)
{
return 0; //TODO?
if (!_exportsDict.TryGetValue(toFind, out var addr))
return 0;

return (ulong) addr;
}

private MachOSection GetTextSection64()
Expand All @@ -129,4 +141,4 @@ public override byte[] GetEntirePrimaryExecutableSection()

public override ulong GetVirtualAddressOfPrimaryExecutableSection() => GetTextSection64().Address;
}
}
}
15 changes: 14 additions & 1 deletion LibCpp2IL/MachO/MachOLoadCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,25 @@ public override void Read(ClassReadingBinaryReader reader)
{
case LoadCommandId.LC_SEGMENT:
case LoadCommandId.LC_SEGMENT_64:
{
CommandData = reader.ReadReadableHereNoLock<MachOSegmentCommand>();
break;
}
case LoadCommandId.LC_SYMTAB:
{
CommandData = reader.ReadReadableHereNoLock<MachOSymtabCommand>();
break;
}
case LoadCommandId.LC_DYLD_INFO:
case LoadCommandId.LC_DYLD_INFO_ONLY:
{
CommandData = reader.ReadReadableHereNoLock<MachODynamicLinkerCommand>();
break;
}
default:
UnknownCommandData = reader.ReadByteArrayAtRawAddressNoLock(-1, (int) CommandSize - 8); // -8 because we've already read the 8 bytes of the header
break;
}
}
}
}
}
36 changes: 36 additions & 0 deletions LibCpp2IL/MachO/MachOSymtabCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;

namespace LibCpp2IL.MachO
{
public class MachOSymtabCommand : ReadableClass
{
public uint SymbolTableOffset;
public uint NumSymbols;
public uint StringTableOffset;
public uint StringTableSize;

public MachOSymtabEntry[] Symbols = Array.Empty<MachOSymtabEntry>();

public override void Read(ClassReadingBinaryReader reader)
{
SymbolTableOffset = reader.ReadUInt32();
NumSymbols = reader.ReadUInt32();
StringTableOffset = reader.ReadUInt32();
StringTableSize = reader.ReadUInt32();

var returnTo = reader.BaseStream.Position;

reader.BaseStream.Position = SymbolTableOffset;

Symbols = new MachOSymtabEntry[NumSymbols];
for (var i = 0; i < NumSymbols; i++)
{
Symbols[i] = new();
Symbols[i].Read(reader, this);
}

reader.BaseStream.Position = returnTo;
}
}
}
38 changes: 38 additions & 0 deletions LibCpp2IL/MachO/MachOSymtabEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace LibCpp2IL.MachO
{
public class MachOSymtabEntry
{
public uint NameOffset;
public byte Type;
public byte Section;
public ushort Description;
public ulong Value; // Architecture sized

public string Name;

public bool IsExternal => (Type & 0b1) == 0b1;
public bool IsSymbolicDebugging => (Type & 0b1110_0000) != 0;
public bool IsPrivateExternal => (Type & 0b0001_0000) == 0b0001_0000;

private byte TypeBits => (byte) (Type & 0b1110);

public bool IsTypeUndefined => Section == 0 && TypeBits == 0b0000;
public bool IsTypeAbsolute => Section == 0 && TypeBits == 0b0010;
public bool IsTypePreboundUndefined => Section == 0 && TypeBits == 0b1100;
public bool IsTypeIndirect => Section == 0 && TypeBits == 0b1010;
public bool IsTypeSection => TypeBits == 0b1110;

public void Read(ClassReadingBinaryReader reader, MachOSymtabCommand machOSymtabCommand)
{
NameOffset = reader.ReadUInt32();
Type = reader.ReadByte();
Section = reader.ReadByte();
Description = reader.ReadUInt16();
Value = reader.ReadNUint();

var returnTo = reader.BaseStream.Position;
Name = reader.ReadStringToNullNoLock(machOSymtabCommand.StringTableOffset + NameOffset);
reader.BaseStream.Position = returnTo;
}
}
}

0 comments on commit 9e22967

Please sign in to comment.