Skip to content

Commit 9e22967

Browse files
committed
Lib: Support mach-o exports
1 parent 24c9c0f commit 9e22967

8 files changed

+254
-6
lines changed

LibCpp2IL/ClassReadingBinaryReader.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,6 @@ private object ReadAndConvertPrimitive(bool overrideArchCheck, Type type)
248248

249249
public virtual string ReadStringToNull(long offset)
250250
{
251-
var builder = new List<byte>();
252-
253251
GetLockOrThrow();
254252

255253
try
@@ -284,6 +282,9 @@ internal string ReadStringToNullNoLock(long offset)
284282
}
285283
}
286284

285+
public string ReadStringToNullAtCurrentPos()
286+
=> ReadStringToNullNoLock(-1);
287+
287288
public byte[] ReadByteArrayAtRawAddress(long offset, int count)
288289
{
289290
GetLockOrThrow();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
3+
namespace LibCpp2IL.MachO
4+
{
5+
public class MachODynamicLinkerCommand : ReadableClass
6+
{
7+
public int RebaseOffset;
8+
public int RebaseSize;
9+
public int BindOffset;
10+
public int BindSize;
11+
public int WeakBindOffset;
12+
public int WeakBindSize;
13+
public int LazyBindOffset;
14+
public int LazyBindSize;
15+
public int ExportOffset;
16+
public int ExportSize;
17+
18+
public MachOExportEntry[] Exports = Array.Empty<MachOExportEntry>();
19+
20+
public override void Read(ClassReadingBinaryReader reader)
21+
{
22+
RebaseOffset = reader.ReadInt32();
23+
RebaseSize = reader.ReadInt32();
24+
BindOffset = reader.ReadInt32();
25+
BindSize = reader.ReadInt32();
26+
WeakBindOffset = reader.ReadInt32();
27+
WeakBindSize = reader.ReadInt32();
28+
LazyBindOffset = reader.ReadInt32();
29+
LazyBindSize = reader.ReadInt32();
30+
ExportOffset = reader.ReadInt32();
31+
ExportSize = reader.ReadInt32();
32+
33+
var returnTo = reader.BaseStream.Position;
34+
35+
reader.BaseStream.Position = ExportOffset;
36+
37+
var exports = new MachOExportTrie(reader);
38+
Exports = exports.Entries.ToArray();
39+
40+
reader.BaseStream.Position = returnTo;
41+
}
42+
}
43+
}

LibCpp2IL/MachO/MachOExportEntry.cs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace LibCpp2IL.MachO
2+
{
3+
public class MachOExportEntry
4+
{
5+
public string Name;
6+
public long Address;
7+
public long Flags;
8+
public long Other;
9+
public string? ImportName;
10+
11+
public MachOExportEntry(string name, long address, long flags, long other, string? importName)
12+
{
13+
Name = name;
14+
Address = address;
15+
Flags = flags;
16+
Other = other;
17+
ImportName = importName;
18+
}
19+
}
20+
}

LibCpp2IL/MachO/MachOExportTrie.cs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace LibCpp2IL.MachO
5+
{
6+
public class MachOExportTrie
7+
{
8+
public List<MachOExportEntry> Entries = new();
9+
10+
private ClassReadingBinaryReader _reader;
11+
private long _basePtr;
12+
13+
public MachOExportTrie(ClassReadingBinaryReader reader)
14+
{
15+
_reader = reader;
16+
_basePtr = reader.BaseStream.Position;
17+
18+
var children = ParseNode("", 0);
19+
while (children.Count > 0)
20+
{
21+
var current = children[0];
22+
children.RemoveAt(0);
23+
children.AddRange(ParseNode(current.Name, current.Offset));
24+
}
25+
}
26+
27+
private List<Node> ParseNode(string name, int offset)
28+
{
29+
var children = new List<Node>();
30+
_reader.BaseStream.Position = _basePtr + offset;
31+
32+
var terminalSize = _reader.BaseStream.ReadLEB128Unsigned();
33+
var childrenIndex = _reader.BaseStream.Position + (long)terminalSize;
34+
if (terminalSize != 0)
35+
{
36+
var flags = (ExportFlags) _reader.BaseStream.ReadLEB128Unsigned();
37+
var address = 0L;
38+
var other = 0L;
39+
string? importName = null;
40+
41+
if ((flags & ExportFlags.ReExport) != 0)
42+
{
43+
other = _reader.BaseStream.ReadLEB128Signed();
44+
importName = _reader.ReadStringToNullAtCurrentPos();
45+
}
46+
else
47+
{
48+
address = _reader.BaseStream.ReadLEB128Signed();
49+
if ((flags & ExportFlags.StubAndResolver) != 0)
50+
other = _reader.BaseStream.ReadLEB128Signed();
51+
}
52+
53+
Entries.Add(new(name, address, (long) flags, other, importName));
54+
}
55+
56+
_reader.BaseStream.Position = childrenIndex;
57+
var numChildren = _reader.BaseStream.ReadLEB128Unsigned();
58+
for (var i = 0ul; i < numChildren; i++)
59+
{
60+
var childName = _reader.ReadStringToNullAtCurrentPos();
61+
var childOffset = _reader.BaseStream.ReadLEB128Unsigned();
62+
children.Add(new Node {Name = name + childName, Offset = (int) childOffset});
63+
}
64+
65+
return children;
66+
}
67+
68+
[Flags]
69+
private enum ExportFlags
70+
{
71+
KindRegular = 0,
72+
KindThreadLocal = 1,
73+
KindAbsolute = 2,
74+
WeakDefinition = 4,
75+
ReExport = 8,
76+
StubAndResolver = 0x10
77+
}
78+
79+
private struct Node
80+
{
81+
public string Name;
82+
public int Offset;
83+
}
84+
}
85+
}

LibCpp2IL/MachO/MachOFile.cs

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.IO;
1+
using System.IO;
2+
using System;
3+
using System.Collections.Generic;
24
using System.Linq;
35
using LibCpp2IL.Logging;
46

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

1416
private readonly MachOSegmentCommand[] Segments64;
1517
private readonly MachOSection[] Sections64;
18+
private Dictionary<string, long> _exportsDict;
1619

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

75+
var dyldData = _loadCommands.FirstOrDefault(c => c.Command is LoadCommandId.LC_DYLD_INFO or LoadCommandId.LC_DYLD_INFO_ONLY)?.CommandData as MachODynamicLinkerCommand;
76+
var exports = dyldData?.Exports ?? Array.Empty<MachOExportEntry>();
77+
_exportsDict = exports.ToDictionary(e => e.Name[1..], e => e.Address); //Skip the first character, which is a leading underscore inserted by the compiler
78+
79+
LibLogger.VerboseNewline($"Found {_exportsDict.Count} exports in the DYLD info load command.");
80+
7281
LibLogger.VerboseNewline($"\tMach-O contains {Segments64.Length} segments, split into {Sections64.Length} sections.");
7382
}
7483

@@ -107,7 +116,10 @@ public override ulong GetRva(ulong pointer)
107116

108117
public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind)
109118
{
110-
return 0; //TODO?
119+
if (!_exportsDict.TryGetValue(toFind, out var addr))
120+
return 0;
121+
122+
return (ulong) addr;
111123
}
112124

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

130142
public override ulong GetVirtualAddressOfPrimaryExecutableSection() => GetTextSection64().Address;
131143
}
132-
}
144+
}

LibCpp2IL/MachO/MachOLoadCommand.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,25 @@ public override void Read(ClassReadingBinaryReader reader)
2323
{
2424
case LoadCommandId.LC_SEGMENT:
2525
case LoadCommandId.LC_SEGMENT_64:
26+
{
2627
CommandData = reader.ReadReadableHereNoLock<MachOSegmentCommand>();
2728
break;
29+
}
30+
case LoadCommandId.LC_SYMTAB:
31+
{
32+
CommandData = reader.ReadReadableHereNoLock<MachOSymtabCommand>();
33+
break;
34+
}
35+
case LoadCommandId.LC_DYLD_INFO:
36+
case LoadCommandId.LC_DYLD_INFO_ONLY:
37+
{
38+
CommandData = reader.ReadReadableHereNoLock<MachODynamicLinkerCommand>();
39+
break;
40+
}
2841
default:
2942
UnknownCommandData = reader.ReadByteArrayAtRawAddressNoLock(-1, (int) CommandSize - 8); // -8 because we've already read the 8 bytes of the header
3043
break;
3144
}
3245
}
3346
}
34-
}
47+
}

LibCpp2IL/MachO/MachOSymtabCommand.cs

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace LibCpp2IL.MachO
5+
{
6+
public class MachOSymtabCommand : ReadableClass
7+
{
8+
public uint SymbolTableOffset;
9+
public uint NumSymbols;
10+
public uint StringTableOffset;
11+
public uint StringTableSize;
12+
13+
public MachOSymtabEntry[] Symbols = Array.Empty<MachOSymtabEntry>();
14+
15+
public override void Read(ClassReadingBinaryReader reader)
16+
{
17+
SymbolTableOffset = reader.ReadUInt32();
18+
NumSymbols = reader.ReadUInt32();
19+
StringTableOffset = reader.ReadUInt32();
20+
StringTableSize = reader.ReadUInt32();
21+
22+
var returnTo = reader.BaseStream.Position;
23+
24+
reader.BaseStream.Position = SymbolTableOffset;
25+
26+
Symbols = new MachOSymtabEntry[NumSymbols];
27+
for (var i = 0; i < NumSymbols; i++)
28+
{
29+
Symbols[i] = new();
30+
Symbols[i].Read(reader, this);
31+
}
32+
33+
reader.BaseStream.Position = returnTo;
34+
}
35+
}
36+
}

LibCpp2IL/MachO/MachOSymtabEntry.cs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace LibCpp2IL.MachO
2+
{
3+
public class MachOSymtabEntry
4+
{
5+
public uint NameOffset;
6+
public byte Type;
7+
public byte Section;
8+
public ushort Description;
9+
public ulong Value; // Architecture sized
10+
11+
public string Name;
12+
13+
public bool IsExternal => (Type & 0b1) == 0b1;
14+
public bool IsSymbolicDebugging => (Type & 0b1110_0000) != 0;
15+
public bool IsPrivateExternal => (Type & 0b0001_0000) == 0b0001_0000;
16+
17+
private byte TypeBits => (byte) (Type & 0b1110);
18+
19+
public bool IsTypeUndefined => Section == 0 && TypeBits == 0b0000;
20+
public bool IsTypeAbsolute => Section == 0 && TypeBits == 0b0010;
21+
public bool IsTypePreboundUndefined => Section == 0 && TypeBits == 0b1100;
22+
public bool IsTypeIndirect => Section == 0 && TypeBits == 0b1010;
23+
public bool IsTypeSection => TypeBits == 0b1110;
24+
25+
public void Read(ClassReadingBinaryReader reader, MachOSymtabCommand machOSymtabCommand)
26+
{
27+
NameOffset = reader.ReadUInt32();
28+
Type = reader.ReadByte();
29+
Section = reader.ReadByte();
30+
Description = reader.ReadUInt16();
31+
Value = reader.ReadNUint();
32+
33+
var returnTo = reader.BaseStream.Position;
34+
Name = reader.ReadStringToNullNoLock(machOSymtabCommand.StringTableOffset + NameOffset);
35+
reader.BaseStream.Position = returnTo;
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)