• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Cpp2IL / 14016638949

23 Mar 2025 07:11AM UTC coverage: 27.127%. Remained the same
14016638949

Pull #427

github

web-flow
Merge 53d7b369d into 4409a1bb4
Pull Request #427: Process addresses in the Mach-O export trie as ULEB128 instead of LEB128

1270 of 6490 branches covered (19.57%)

Branch coverage included in aggregate %.

0 of 15 new or added lines in 3 files covered. (0.0%)

2 existing lines in 2 files now uncovered.

3379 of 10648 relevant lines covered (31.73%)

122354.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/LibCpp2IL/MachO/MachOFile.cs
1
using System.IO;
2
using System.Collections.Generic;
3
using System.Linq;
4
using LibCpp2IL.Logging;
5
using System.Diagnostics.CodeAnalysis;
6

7
namespace LibCpp2IL.MachO;
8

9
public class MachOFile : Il2CppBinary
10
{
11
    private byte[] _raw;
12

13
    private readonly MachOHeader _header;
14
    private readonly MachOLoadCommand[] _loadCommands;
15

16
    private readonly MachOSegmentCommand[] Segments64;
17
    private readonly MachOSection[] Sections64;
18
    private readonly Dictionary<string, ulong> _exportAddressesDict;
19
    private readonly Dictionary<ulong, string> _exportNamesDict;
20

21
    public MachOFile(MemoryStream input) : base(input)
×
22
    {
23
        LibLogger.VerboseNewline("Reading Mach-O file...");
×
24
        _raw = input.GetBuffer();
×
25

26
        LibLogger.Verbose("\tReading Mach-O header...");
×
27
        _header = ReadReadable<MachOHeader>();
×
28

29
        switch (_header.Magic)
×
30
        {
31
            case MachOHeader.MAGIC_32_BIT:
32
                LibLogger.Verbose("Mach-O is 32-bit...");
×
33
                is32Bit = true;
×
34
                break;
×
35
            case MachOHeader.MAGIC_64_BIT:
36
                LibLogger.Verbose("Mach-O is 64-bit...");
×
37
                is32Bit = false;
×
38
                break;
×
39
            default:
40
                throw new($"Unknown Mach-O Magic: {_header.Magic}");
×
41
        }
42

43
        switch (_header.CpuType)
×
44
        {
45
            case MachOCpuType.CPU_TYPE_I386:
46
                LibLogger.VerboseNewline("Mach-O contains x86_32 instructions.");
×
47
                InstructionSetId = DefaultInstructionSets.X86_32;
×
48
                break;
×
49
            case MachOCpuType.CPU_TYPE_X86_64:
50
                LibLogger.VerboseNewline("Mach-O contains x86_64 instructions.");
×
51
                InstructionSetId = DefaultInstructionSets.X86_64;
×
52
                break;
×
53
            case MachOCpuType.CPU_TYPE_ARM:
54
                LibLogger.VerboseNewline("Mach-O contains ARM (32-bit) instructions.");
×
55
                InstructionSetId = DefaultInstructionSets.ARM_V7;
×
56
                break;
×
57
            case MachOCpuType.CPU_TYPE_ARM64:
58
                LibLogger.VerboseNewline("Mach-O contains ARM64 instructions.");
×
59
                InstructionSetId = DefaultInstructionSets.ARM_V8;
×
60
                break;
×
61
            default:
62
                throw new($"Don't know how to handle a Mach-O CPU Type of {_header.CpuType}");
×
63
        }
64

65
        if (_header.Magic == MachOHeader.MAGIC_32_BIT)
×
66
            LibLogger.ErrorNewline("32-bit MACH-O files have not been tested! Please report any issues.");
×
67
        else
68
            LibLogger.WarnNewline("Mach-O Support is experimental. Please open an issue if anything seems incorrect.");
×
69

70
        LibLogger.Verbose("\tReading Mach-O load commands...");
×
71
        _loadCommands = ReadReadableArrayAtRawAddr<MachOLoadCommand>(-1, _header.NumLoadCommands);
×
72
        LibLogger.VerboseNewline($"Read {_loadCommands.Length} load commands.");
×
73

74
        Segments64 = _loadCommands.Where(c => c.Command == LoadCommandId.LC_SEGMENT_64).Select(c => c.CommandData).Cast<MachOSegmentCommand>().ToArray();
×
75
        Sections64 = Segments64.SelectMany(s => s.Sections).ToArray();
×
76

77
        var dyldData = _loadCommands.FirstOrDefault(c => c.Command is LoadCommandId.LC_DYLD_INFO or LoadCommandId.LC_DYLD_INFO_ONLY)?.CommandData as MachODynamicLinkerCommand;
×
78
        var exports = dyldData?.Exports ?? [];
×
79
        
80
        _exportAddressesDict = exports.ToDictionary(e => e.Name[1..], e => e.Address); //Skip the first character, which is a leading underscore inserted by the compiler
×
NEW
81
        _exportNamesDict = new Dictionary<ulong, string>();
×
82
        foreach (var export in exports) // there may be duplicate names
×
83
        {
84
            _exportNamesDict[export.Address] = export.Name[1..];
×
85
        }
86

87
        LibLogger.VerboseNewline($"\tFound {_exportAddressesDict.Count} exports in the DYLD info load command.");
×
88
        
89
        var chainedFixups = _loadCommands.FirstOrDefault(c => c.Command == LoadCommandId.LC_DYLD_CHAINED_FIXUPS)?.CommandData as MachOLinkEditDataCommand;
×
90
        if (chainedFixups != null) 
×
91
            ApplyChainedFixups(chainedFixups);
×
92

93
        LibLogger.VerboseNewline($"\tMach-O contains {Segments64.Length} segments, split into {Sections64.Length} sections.");
×
94
        
95
        LibLogger.VerboseNewline("Mach-O file read successfully.");
×
96
    }
×
97

98
    public override long RawLength => _raw.Length;
×
99
    public override byte GetByteAtRawAddress(ulong addr) => _raw[addr];
×
100

101
    public override long MapVirtualAddressToRaw(ulong uiAddr, bool throwOnError = true)
102
    {
103
        var sec = Sections64.FirstOrDefault(s => s.Address <= uiAddr && uiAddr < s.Address + s.Size);
×
104

105
        if (sec == null)
×
106
            if (throwOnError)
×
107
                throw new($"Could not find section for virtual address 0x{uiAddr:X}. Lowest section address is 0x{Sections64.Min(s => s.Address):X}, highest section address is 0x{Sections64.Max(s => s.Address + s.Size):X}");
×
108
            else
109
                return VirtToRawInvalidNoMatch;
×
110

111
        return (long)(sec.Offset + (uiAddr - sec.Address));
×
112
    }
113

114
    public override ulong MapRawAddressToVirtual(uint offset)
115
    {
116
        var sec = Sections64.FirstOrDefault(s => s.Offset <= offset && offset < s.Offset + s.Size);
×
117

118
        if (sec == null)
×
119
            throw new($"Could not find section for raw address 0x{offset:X}");
×
120

121
        return sec.Address + (offset - sec.Offset);
×
122
    }
123

124
    public override ulong GetRva(ulong pointer)
125
    {
126
        return pointer; //TODO?
×
127
    }
128

129
    public override byte[] GetRawBinaryContent() => _raw;
×
130

131
    public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind)
132
    {
133
        if (!_exportAddressesDict.TryGetValue(toFind, out var addr))
×
134
            return 0;
×
135

NEW
136
        return addr;
×
137
    }
138

NEW
139
    public override bool IsExportedFunction(ulong addr) => _exportNamesDict.ContainsKey(addr);
×
140

141
    public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name)
142
    {
NEW
143
        return _exportNamesDict.TryGetValue(addr, out name);
×
144
    }
145

146
    public override IEnumerable<KeyValuePair<string, ulong>> GetExportedFunctions()
147
    {
NEW
148
        return _exportAddressesDict.Select(pair => new KeyValuePair<string, ulong>(pair.Key, pair.Value));
×
149
    }
150

151
    private MachOSection GetTextSection64()
152
    {
153
        var textSection = Sections64.FirstOrDefault(s => s.SectionName == "__text");
×
154

155
        if (textSection == null)
×
156
            throw new("Could not find __text section");
×
157

158
        return textSection;
×
159
    }
160

161
    public override byte[] GetEntirePrimaryExecutableSection()
162
    {
163
        var textSection = GetTextSection64();
×
164

165
        return _raw.SubArray((int)textSection.Offset, (int)textSection.Size);
×
166
    }
167

168
    public override ulong GetVirtualAddressOfPrimaryExecutableSection() => GetTextSection64().Address;
×
169
    
170
    //Thanks to LukeFZ for this
171
    private void ApplyChainedFixups(MachOLinkEditDataCommand cmd)
172
    {
173
        LibLogger.Verbose("\tApplying chained fixups...");
×
174
        var chainedFixupsHeader = ReadReadable<MachODyldChainedFixupsHeader>(cmd.Offset);
×
175
        if (chainedFixupsHeader.FixupsVersion != MachODyldChainedFixupsHeader.SupportedFixupsVersion)
×
176
        {
177
            LibLogger.ErrorNewline($"Mach-O: Unsupported fixups version {chainedFixupsHeader.FixupsVersion}, expecting {MachODyldChainedFixupsHeader.SupportedFixupsVersion}");
×
178
            return;
×
179
        }
180

181
        if (chainedFixupsHeader.ImportsFormat != MachODyldChainedFixupsHeader.SupportedImportsFormat)
×
182
        {
183
            LibLogger.ErrorNewline($"Mach-O: Unsupported imports format {chainedFixupsHeader.ImportsFormat}, expecting {MachODyldChainedFixupsHeader.SupportedImportsFormat}");
×
184
            return;
×
185
        }
186

187
        var posBack = Position;
×
188
        
189
        var startsBase = cmd.Offset + chainedFixupsHeader.StartsOffset;
×
190
        
191
        Position = startsBase;
×
192
        var segmentCount = ReadUInt32();
×
193
        var segmentStartOffsets = ReadClassArrayAtRawAddr<uint>(startsBase + 4, segmentCount);
×
194

195
        Position = posBack;
×
196

197
        var count = 0;
×
198
        foreach (var startOffset in segmentStartOffsets)
×
199
        {
200
            if (startOffset == 0)
×
201
                continue;
202
            
203
            var startsInfo = ReadReadable<MachODyldChainedStartsInSegment>(startsBase + startOffset);
×
204
            if (startsInfo.SegmentOffset == 0)
×
205
                continue;
206
            
207
            var pointerFormat = (MachODyldChainedPtr)startsInfo.PointerFormat;
×
208
            var pages = ReadClassArrayAtRawAddr<ushort>(startsBase + startOffset + MachODyldChainedStartsInSegment.Size, startsInfo.PageCount);
×
209
            for (var i = 0; i < pages.Length; i++)
×
210
            {
211
                var page = pages[i];
×
212
                if (page == MachODyldChainedStartsInSegment.DYLD_CHAINED_PTR_START_NONE)
×
213
                    continue;
214
                var chainOffset = startsInfo.SegmentOffset + (ulong)(i * startsInfo.PageSize) + page;
×
215
                while (true)
×
216
                {
217
                    var currentEntry = ReadReadable<MachODyldChainedPtr64Rebase>((long)chainOffset);
×
218
                    var fixedValue = 0ul;
×
219
                    if (currentEntry.Bind)
×
220
                    {
221
                        //TODO: Bind.
222
                    }
223
                    else
224
                    {
225
                        fixedValue = pointerFormat switch
×
226
                        {
×
227
                            MachODyldChainedPtr.DYLD_CHAINED_PTR_64 or MachODyldChainedPtr.DYLD_CHAINED_PTR_64_OFFSET => currentEntry.High8 << 56 | currentEntry.Target,
×
228
                            _ => fixedValue
×
229
                        };
×
230
                        WriteWord((int)chainOffset, fixedValue);
×
231
                        count++;
×
232
                    }
233

234
                    if (currentEntry.Next == 0)
×
235
                        break;
236
                    chainOffset += currentEntry.Next * 4;
×
237
                }
238
            }
239
        }
240
        
241
        LibLogger.VerboseNewline($"Applied {count} chained fixups.");
×
242
    }
×
243
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc