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

SamboyCoding / Cpp2IL / 12074790339

28 Nov 2024 07:54PM UTC coverage: 28.371% (-0.02%) from 28.392%
12074790339

push

github

web-flow
Add a plugin for outputting Windows PDB files (#382)

1265 of 6204 branches covered (20.39%)

Branch coverage included in aggregate %.

2 of 53 new or added lines in 7 files covered. (3.77%)

3 existing lines in 3 files now uncovered.

3383 of 10179 relevant lines covered (33.24%)

126063.94 hits per line

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

54.77
/LibCpp2IL/Elf/ElfFile.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics.CodeAnalysis;
4
using System.IO;
5
using System.Linq;
6
using LibCpp2IL.Logging;
7
using LibCpp2IL.PE;
8

9
namespace LibCpp2IL.Elf;
10

11
public sealed class ElfFile : Il2CppBinary
12
{
13
    private byte[] _raw;
14
    private List<IElfProgramHeaderEntry> _elfProgramHeaderEntries;
15
    private readonly List<ElfSectionHeaderEntry> _elfSectionHeaderEntries;
16
    private ElfFileIdent? _elfFileIdent;
17
    private ElfFileHeader? _elfHeader;
18
    private readonly List<ElfDynamicEntry> _dynamicSection = [];
2✔
19
    private readonly List<ElfSymbolTableEntry> _symbolTable = [];
2✔
20
    private readonly Dictionary<string, ElfSymbolTableEntry> _exportNameTable = new();
2✔
21
    private readonly Dictionary<ulong, ElfSymbolTableEntry> _exportAddressTable = new();
2✔
22
    private List<long>? _initializerPointers;
23

24
    private readonly List<(ulong start, ulong end)> relocationBlocks = [];
2✔
25

26
    private long _globalOffset;
27

28
    public ElfFile(MemoryStream input) : base(input)
2✔
29
    {
30
        _raw = input.GetBuffer();
2✔
31

32
        LibLogger.Verbose("\tReading Elf File Ident...");
2✔
33
        var start = DateTime.Now;
2✔
34

35
        ReadAndValidateIdent();
2✔
36

37
        var isBigEndian = _elfFileIdent!.Endianness == 2;
2✔
38

39
        LibLogger.VerboseNewline($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
2✔
40
        LibLogger.VerboseNewline($"\tBinary is {(is32Bit ? "32-bit" : "64-bit")} and {(isBigEndian ? "big-endian" : "little-endian")}.");
2!
41

42
        if (isBigEndian)
2!
43
            SetBigEndian();
×
44

45
        LibLogger.Verbose("\tReading and validating full ELF header...");
2✔
46
        start = DateTime.Now;
2✔
47

48
        ReadHeader();
2✔
49

50
        LibLogger.VerboseNewline($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
2✔
51
        LibLogger.VerboseNewline($"\tElf File contains instructions of type {InstructionSetId}");
2✔
52

53
        LibLogger.Verbose("\tReading ELF program header table...");
2✔
54
        start = DateTime.Now;
2✔
55

56
        ReadProgramHeaderTable();
2✔
57

58
        LibLogger.VerboseNewline($"Read {_elfProgramHeaderEntries!.Count} OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
2✔
59

60
        LibLogger.VerboseNewline("\tReading ELF section header table and names...");
2✔
61
        start = DateTime.Now;
2✔
62

63
        //Non-null assertion reason: The elf header has already been checked while reading the program header.
64
        try
65
        {
66
            _elfSectionHeaderEntries = ReadReadableArrayAtRawAddr<ElfSectionHeaderEntry>(_elfHeader!.pSectionHeader, _elfHeader.SectionHeaderEntryCount).ToList();
2✔
67
        }
2✔
68
        catch (Exception)
×
69
        {
70
            _elfSectionHeaderEntries = [];
×
71
        }
×
72

73
        if (_elfHeader!.SectionNameSectionOffset >= 0 && _elfHeader.SectionNameSectionOffset < _elfSectionHeaderEntries.Count)
2✔
74
        {
75
            var pSectionHeaderStringTable = _elfSectionHeaderEntries[_elfHeader.SectionNameSectionOffset].RawAddress;
2✔
76

77
            foreach (var section in _elfSectionHeaderEntries)
118✔
78
            {
79
                section.Name = ReadStringToNull(pSectionHeaderStringTable + section.NameOffset);
57✔
80
                LibLogger.VerboseNewline($"\t\t-Name for section at 0x{section.RawAddress:X} is {section.Name}");
57✔
81
            }
82
        }
83

84
        LibLogger.VerboseNewline($"\tRead {_elfSectionHeaderEntries.Count} OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
2✔
85

86
        if (_elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text") is { } textSection)
26!
87
        {
88
            _globalOffset = (long)textSection.VirtualAddress - (long)textSection.RawAddress;
2✔
89
        }
90
        else
91
        {
92
            var execSegment = _elfProgramHeaderEntries!.First(p => (p.Flags & ElfProgramHeaderFlags.PF_X) != 0);
×
93
            _globalOffset = (long)execSegment.VirtualAddress - (long)execSegment.RawAddress;
×
94
        }
95

96
        LibLogger.VerboseNewline($"\tELF global offset is 0x{_globalOffset:X}");
2✔
97

98
        //Get dynamic section.
99
        if (GetProgramHeaderOfType(ElfProgramEntryType.PT_DYNAMIC) is { } dynamicSegment)
2✔
100
            _dynamicSection = ReadReadableArrayAtRawAddr<ElfDynamicEntry>((long)dynamicSegment.RawAddress, (int)dynamicSegment.RawSize / (is32Bit ? 8 : 16)).ToList();
2✔
101

102
        LibLogger.VerboseNewline("\tFinding Relocations...");
2✔
103
        start = DateTime.Now;
2✔
104

105
        ProcessRelocations();
2✔
106

107
        LibLogger.VerboseNewline($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
2✔
108

109
        LibLogger.VerboseNewline("\tProcessing Symbols...");
2✔
110
        start = DateTime.Now;
2✔
111

112
        ProcessSymbols();
2✔
113

114
        LibLogger.VerboseNewline($"\tOK ({(DateTime.Now - start).TotalMilliseconds} ms)");
2✔
115

116
        LibLogger.Verbose("\tProcessing Initializers...");
2✔
117
        start = DateTime.Now;
2✔
118

119
        ProcessInitializers();
2✔
120

121
        LibLogger.VerboseNewline($"Got {_initializerPointers!.Count} OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
2✔
122
    }
2✔
123

124
    private void ReadAndValidateIdent()
125
    {
126
        _elfFileIdent = ReadReadable<ElfFileIdent>(0);
2✔
127

128
        if (_elfFileIdent.Magic != 0x464c457f) //Magic number
2!
129
            throw new FormatException("ERROR: Magic number mismatch.");
×
130

131
        if (_elfFileIdent.Architecture == 1)
2✔
132
            is32Bit = true;
1✔
133
        else if (_elfFileIdent.Architecture != 2)
1!
134
            throw new FormatException($"Invalid arch number (expecting 1 or 2): {_elfFileIdent.Architecture}");
×
135

136
        if (_elfFileIdent.Version != 1)
2!
137
            throw new FormatException($"ELF Version is not 1? File header has version {_elfFileIdent.Version}");
×
138
    }
2✔
139

140
    private void ReadHeader()
141
    {
142
        _elfHeader = ReadReadable<ElfFileHeader>(0x10);
2✔
143

144
        InstructionSetId = _elfHeader.Machine switch
2!
145
        {
2✔
146
            0x03 => DefaultInstructionSets.X86_32,
×
147
            0x3E => DefaultInstructionSets.X86_64,
×
148
            0x28 => DefaultInstructionSets.ARM_V7,
1✔
149
            0xB7 => DefaultInstructionSets.ARM_V8,
1✔
150
            _ => throw new NotImplementedException($"ELF Machine {_elfHeader.Machine} not implemented")
×
151
        };
2✔
152

153
        if (_elfHeader.Version != 1)
2!
154
            throw new FormatException($"Full ELF header specifies version {_elfHeader.Version}, only supported version is 1.");
×
155
    }
2✔
156

157
    private void ReadProgramHeaderTable()
158
    {
159
        _elfProgramHeaderEntries = is32Bit
2✔
160
            ? ReadReadableArrayAtRawAddr<ElfProgramHeaderEntry32>(_elfHeader!.pProgramHeader, _elfHeader.ProgramHeaderEntryCount).Cast<IElfProgramHeaderEntry>().ToList()
2✔
161
            : ReadReadableArrayAtRawAddr<ElfProgramHeaderEntry64>(_elfHeader!.pProgramHeader, _elfHeader.ProgramHeaderEntryCount).Cast<IElfProgramHeaderEntry>().ToList();
2✔
162
    }
2✔
163

164
    private IElfProgramHeaderEntry? GetProgramHeaderOfType(ElfProgramEntryType type) => _elfProgramHeaderEntries.FirstOrDefault(p => p.Type == type);
9✔
165

166
    private IEnumerable<ElfSectionHeaderEntry> GetSections(ElfSectionEntryType type) => _elfSectionHeaderEntries.Where(s => s.Type == type);
199✔
167

168
    private ElfSectionHeaderEntry? GetSingleSection(ElfSectionEntryType type) => GetSections(type).FirstOrDefault();
6✔
169

170
    private ElfDynamicEntry? GetDynamicEntryOfType(ElfDynamicType type) => _dynamicSection.FirstOrDefault(d => d.Tag == type);
306✔
171

172
    private void ProcessRelocations()
173
    {
174
        try
175
        {
176
            var rels = new HashSet<ElfRelocation>();
2✔
177

178
            var relSectionStarts = new HashSet<ulong>();
2✔
179

180
            //REL tables
181
            foreach (var section in GetSections(ElfSectionEntryType.SHT_REL))
8✔
182
            {
183
                //Get related section pointer
184
                var relatedTablePointer = _elfSectionHeaderEntries[section.LinkedSectionIndex].RawAddress;
2✔
185

186
                //Read rel table
187
                var table = ReadReadableArrayAtRawAddr<ElfRelEntry>((long)section.RawAddress, (long)(section.Size / (ulong)section.EntrySize));
2✔
188

189
                LibLogger.VerboseNewline($"\t\t-Got {table.Length} from REL section {section.Name}");
2✔
190

191
                relocationBlocks.Add((section.RawAddress, section.RawAddress + section.Size));
2✔
192
                relSectionStarts.Add(section.RawAddress);
2✔
193

194
                //Insert into rels list.
195
                rels.UnionWith(table.Select(r => new ElfRelocation(this, r, relatedTablePointer)));
494,478✔
196
            }
197

198
            //RELA tables
199
            foreach (var section in GetSections(ElfSectionEntryType.SHT_RELA))
8✔
200
            {
201
                if (relSectionStarts.Contains(section.RawAddress))
2!
202
                {
203
                    LibLogger.VerboseNewline($"\t\t-Ignoring RELA section starting at 0x{section.RawAddress} because it's already been processed.");
×
204
                    continue;
×
205
                }
206

207
                //Get related section pointer
208
                var relatedTablePointer = _elfSectionHeaderEntries[section.LinkedSectionIndex].RawAddress;
2✔
209

210
                //Read rela table
211
                var table = ReadReadableArrayAtRawAddr<ElfRelaEntry>((long)section.RawAddress, (long)(section.Size / (ulong)section.EntrySize));
2✔
212

213
                LibLogger.VerboseNewline($"\t\t-Got {table.Length} from RELA section {section.Name} at 0x{section.RawAddress}");
2✔
214

215
                relocationBlocks.Add((section.RawAddress, section.RawAddress + section.Size));
2✔
216
                relSectionStarts.Add(section.RawAddress);
2✔
217

218
                //Insert into rels list.
219
                rels.UnionWith(table.Select(r => new ElfRelocation(this, r, relatedTablePointer)));
439,911✔
220
            }
221

222
            //Dynamic Rel Table
223
            if (GetDynamicEntryOfType(ElfDynamicType.DT_REL) is { } dt_rel && (uint)MapVirtualAddressToRaw(dt_rel.Value) is { } dtRelStartAddr)
2✔
224
            {
225
                if (!relSectionStarts.Contains(dtRelStartAddr))
1!
226
                {
227
                    //Null-assertion reason: We must have both a RELSZ and a RELENT or this is an error.
228
                    var relocationSectionSize = GetDynamicEntryOfType(ElfDynamicType.DT_RELSZ)!.Value;
×
229
                    var relCount = (int)(relocationSectionSize / GetDynamicEntryOfType(ElfDynamicType.DT_RELENT)!.Value);
×
230
                    var entries = ReadReadableArrayAtRawAddr<ElfRelEntry>(dtRelStartAddr, relCount);
×
231

232
                    LibLogger.VerboseNewline($"\t\t-Got {entries.Length} from dynamic REL section at 0x{dtRelStartAddr}");
×
233

234
                    //Null-assertion reason: We must have a DT_SYMTAB if we have a DT_REL
235
                    var pSymTab = GetDynamicEntryOfType(ElfDynamicType.DT_SYMTAB)!.Value;
×
236

237
                    relocationBlocks.Add((dtRelStartAddr, dtRelStartAddr + relocationSectionSize));
×
238

239
                    rels.UnionWith(entries.Select(r => new ElfRelocation(this, r, pSymTab)));
×
240
                }
241
                else
242
                {
243
                    LibLogger.VerboseNewline($"\t\t-Ignoring dynamic REL section starting at 0x{dtRelStartAddr} because it's already been processed.");
1✔
244
                }
245
            }
246

247
            //Dynamic Rela Table
248
            if (GetDynamicEntryOfType(ElfDynamicType.DT_RELA) is { } dt_rela)
2✔
249
            {
250
                //Null-assertion reason: We must have both a RELSZ and a RELENT or this is an error.
251
                var relocationSectionSize = GetDynamicEntryOfType(ElfDynamicType.DT_RELASZ)!.Value;
1✔
252
                var relCount = (int)(relocationSectionSize / GetDynamicEntryOfType(ElfDynamicType.DT_RELAENT)!.Value);
1✔
253
                var startAddr = (uint)MapVirtualAddressToRaw(dt_rela.Value);
1✔
254

255
                if (!relSectionStarts.Contains(startAddr))
1!
256
                {
257
                    var entries = ReadReadableArrayAtRawAddr<ElfRelaEntry>(startAddr, relCount);
×
258

259
                    LibLogger.VerboseNewline($"\t\t-Got {entries.Length} from dynamic RELA section at 0x{startAddr}");
×
260

261
                    //Null-assertion reason: We must have a DT_SYMTAB if we have a DT_RELA
262
                    var pSymTab = GetDynamicEntryOfType(ElfDynamicType.DT_SYMTAB)!.Value;
×
263

264
                    relocationBlocks.Add((startAddr, startAddr + relocationSectionSize));
×
265

266
                    rels.UnionWith(entries.Select(r => new ElfRelocation(this, r, pSymTab)));
×
267
                }
268
                else
269
                {
270
                    LibLogger.VerboseNewline($"\t\t-Ignoring dynamic RELA section starting at 0x{startAddr} because it's already been processed.");
1✔
271
                }
272
            }
273

274
            var sizeOfRelocationStruct = (ulong)(is32Bit ? ElfDynamicSymbol32.StructSize : ElfDynamicSymbol64.StructSize);
2✔
275

276
            LibLogger.Verbose($"\t-Now Processing {rels.Count} relocations...");
2✔
277

278
            foreach (var rel in rels)
1,868,774✔
279
            {
280
                var pointer = rel.pRelatedSymbolTable + rel.IndexInSymbolTable * sizeOfRelocationStruct;
934,385✔
281
                ulong symValue;
282
                try
283
                {
284
                    symValue = ((IElfDynamicSymbol)(is32Bit ? ReadReadable<ElfDynamicSymbol32>((long)pointer) : ReadReadable<ElfDynamicSymbol64>((long)pointer))).Value;
934,385✔
285
                }
934,385✔
286
                catch
×
287
                {
288
                    LibLogger.ErrorNewline($"Exception reading dynamic symbol for rel of type {rel.Type} at pointer 0x{pointer:X} (length of file is 0x{RawLength:X}, pointer - length is 0x{pointer - (ulong)RawLength:X})");
×
289
                    throw;
×
290
                }
291

292
                long targetLocation;
293
                try
294
                {
295
                    targetLocation = MapVirtualAddressToRaw(rel.Offset);
934,385✔
296
                }
934,385✔
297
                catch (InvalidOperationException)
×
298
                {
299
                    continue; //Ignore this rel.
×
300
                }
301

302
                //Read one word.
303
                ulong addend;
304
                if (rel.Addend.HasValue)
934,385✔
305
                    addend = rel.Addend.Value;
439,909✔
306
                else
307
                {
308
                    Position = targetLocation;
494,476✔
309
                    addend = ReadUInt64();
494,476✔
310
                }
311

312
                //Adapted from Il2CppInspector. Thanks to djKaty.
313

314
                ulong newValue;
315
                bool recognized;
316
                if (InstructionSetId == DefaultInstructionSets.ARM_V7)
934,385✔
317
                    (newValue, recognized) = rel.Type switch
494,476!
318
                    {
494,476✔
319
                        ElfRelocationType.R_ARM_ABS32 => (symValue + addend, true), // S + A
1,579✔
320
                        ElfRelocationType.R_ARM_REL32 => (symValue + rel.Offset - addend, true), // S - P + A
×
321
                        ElfRelocationType.R_ARM_COPY => (symValue, true), // S
×
322
                        _ => (0UL, false)
492,897✔
323
                    };
494,476✔
324
                else if (InstructionSetId == DefaultInstructionSets.ARM_V8)
439,909!
325
                    (newValue, recognized) = rel.Type switch
439,909!
326
                    {
439,909✔
327
                        ElfRelocationType.R_AARCH64_ABS64 => (symValue + addend, true), // S + A
1,550✔
328
                        ElfRelocationType.R_AARCH64_PREL64 => (symValue + addend - rel.Offset, true), // S + A - P
×
329
                        ElfRelocationType.R_AARCH64_GLOB_DAT => (symValue + addend, true), // S + A
153✔
330
                        ElfRelocationType.R_AARCH64_JUMP_SLOT => (symValue + addend, true), // S + A
540✔
331
                        ElfRelocationType.R_AARCH64_RELATIVE => (symValue + addend, true), // Delta(S) + A
437,666✔
332
                        _ => (0UL, false)
×
333
                    };
439,909✔
334
                else if (InstructionSetId == DefaultInstructionSets.X86_32)
×
335
                    (newValue, recognized) = rel.Type switch
×
336
                    {
×
337
                        ElfRelocationType.R_386_32 => (symValue + addend, true), // S + A
×
338
                        ElfRelocationType.R_386_PC32 => (symValue + addend - rel.Offset, true), // S + A - P
×
339
                        ElfRelocationType.R_386_GLOB_DAT => (symValue, true), // S
×
340
                        ElfRelocationType.R_386_JMP_SLOT => (symValue, true), // S
×
341
                        _ => (0UL, false)
×
342
                    };
×
343
                else if (InstructionSetId == DefaultInstructionSets.X86_64)
×
344
                    (newValue, recognized) = rel.Type switch
×
345
                    {
×
346
                        ElfRelocationType.R_AMD64_64 => (symValue + addend, true), // S + A
×
347
                        ElfRelocationType.R_AMD64_RELATIVE => (addend, true), //Base address + A
×
348

×
349
                        _ => (0UL, false)
×
350
                    };
×
351
                else
352
                    (newValue, recognized) = (0UL, false);
×
353

354
                if (recognized)
934,385✔
355
                {
356
                    WriteWord((int)targetLocation, newValue);
441,488✔
357
                }
358
            }
359
        }
2✔
360
        catch
×
361
        {
362
            LibLogger.Info("Exception during relocation mapping!");
×
363
            throw;
×
364
        }
365
    }
2✔
366

367
    private void ProcessSymbols()
368
    {
369
        var symbolTables = new List<(ulong offset, ulong count, ulong strings)>();
2✔
370

371
        //Look for .strtab
372
        if (GetSingleSection(ElfSectionEntryType.SHT_STRTAB) is { } strTab)
2✔
373
        {
374
            //Look for .symtab
375
            if (GetSingleSection(ElfSectionEntryType.SHT_SYMTAB) is { } symtab)
2!
376
            {
377
                LibLogger.VerboseNewline($"\t\t-Found .symtab at 0x{symtab.RawAddress:X}");
×
378
                symbolTables.Add((symtab.RawAddress, symtab.Size / (ulong)symtab.EntrySize, strTab.RawAddress));
×
379
            }
380

381
            //Look for .dynsym
382
            if (GetSingleSection(ElfSectionEntryType.SHT_DYNSYM) is { } dynsym)
2✔
383
            {
384
                LibLogger.VerboseNewline($"\t\t-Found .dynsym at 0x{dynsym.RawAddress:X}");
2✔
385
                symbolTables.Add((dynsym.RawAddress, dynsym.Size / (ulong)dynsym.EntrySize, strTab.RawAddress));
2✔
386
            }
387
        }
388

389
        //Look for Dynamic String table
390
        if (GetDynamicEntryOfType(ElfDynamicType.DT_STRTAB) is { } dynamicStrTab)
2✔
391
        {
392
            if (GetDynamicEntryOfType(ElfDynamicType.DT_SYMTAB) is { } dynamicSymTab)
2✔
393
            {
394
                var end = _dynamicSection.Where(x => x.Value > dynamicSymTab.Value).OrderBy(x => x.Value).First().Value;
96✔
395
                var dynSymSize = is32Bit ? 18ul : 24ul;
2✔
396

397
                var address = (ulong)MapVirtualAddressToRaw(dynamicSymTab.Value);
2✔
398

399
                LibLogger.VerboseNewline($"\t\t-Found DT_SYMTAB at 0x{address:X}");
2✔
400

401
                symbolTables.Add((
2✔
402
                    address,
2✔
403
                    (end - dynamicSymTab.Value) / dynSymSize,
2✔
404
                    dynamicStrTab.Value
2✔
405
                ));
2✔
406
            }
407
        }
408

409
        _symbolTable.Clear();
2✔
410
        _exportNameTable.Clear();
2✔
411
        _exportAddressTable.Clear();
2✔
412

413
        //Unify symbol tables
414
        foreach (var (offset, count, stringTable) in symbolTables)
12✔
415
        {
416
            var symbols = is32Bit
4✔
417
                ? ReadReadableArrayAtRawAddr<ElfDynamicSymbol32>((long)offset, (long)count).Cast<IElfDynamicSymbol>().ToList()
4✔
418
                : ReadReadableArrayAtRawAddr<ElfDynamicSymbol64>((long)offset, (long)count).Cast<IElfDynamicSymbol>().ToList();
4✔
419

420
            LibLogger.VerboseNewline($"\t\t-Found {symbols.Count} symbols in table at 0x{offset:X}");
4✔
421

422
            foreach (var symbol in symbols)
15,182✔
423
            {
424
                string name;
425
                try
426
                {
427
                    name = ReadStringToNull(stringTable + symbol.NameOffset);
7,587✔
428
                }
7,587✔
429
                catch (ArgumentOutOfRangeException)
×
430
                {
431
                    // Stripped
432
                    continue;
×
433
                }
434

435
                var usefulType = symbol.Shndx == 0 ? ElfSymbolTableEntry.ElfSymbolEntryType.Import
7,587✔
436
                    : symbol.Type == ElfDynamicSymbolType.STT_FUNC ? ElfSymbolTableEntry.ElfSymbolEntryType.Function
7,587✔
437
                    : symbol.Type == ElfDynamicSymbolType.STT_OBJECT || symbol.Type == ElfDynamicSymbolType.STT_COMMON ? ElfSymbolTableEntry.ElfSymbolEntryType.Name
7,587✔
438
                    : ElfSymbolTableEntry.ElfSymbolEntryType.Unknown;
7,587✔
439

440
                var virtualAddress = symbol.Value;
7,587✔
441

442
                var entry = new ElfSymbolTableEntry { Name = name, Type = usefulType, VirtualAddress = virtualAddress };
7,587✔
443
                _symbolTable.Add(entry);
7,587✔
444

445
                if (symbol.Shndx != 0)
7,587✔
446
                {
447
                    _exportNameTable.TryAdd(name, entry);
6,749✔
448
                    _exportAddressTable.TryAdd(virtualAddress, entry);
6,749✔
449
                }
450
            }
451
        }
452
    }
2✔
453

454
    private void ProcessInitializers()
455
    {
456
        if (!(GetDynamicEntryOfType(ElfDynamicType.DT_INIT_ARRAY) is { } dtInitArray) || !(GetDynamicEntryOfType(ElfDynamicType.DT_INIT_ARRAYSZ) is { } dtInitArraySz))
2!
457
        {
458
            _initializerPointers = [];
×
459
            return;
×
460
        }
461

462
        var pInitArray = MapVirtualAddressToRaw(dtInitArray.Value);
2✔
463
        var count = (int)dtInitArraySz.Value / (is32Bit ? 4 : 8);
2✔
464

465
        var initArray = ReadNUintArrayAtRawAddress(pInitArray, count);
2✔
466

467
        if (GetDynamicEntryOfType(ElfDynamicType.DT_INIT) is { } dtInit)
2!
468
            initArray = initArray.Append(dtInit.Value).ToArray();
×
469

470
        _initializerPointers = initArray.Select(a => MapVirtualAddressToRaw(a)).ToList();
20✔
471
    }
2✔
472

473
    public override (ulong pCodeRegistration, ulong pMetadataRegistration) FindCodeAndMetadataReg(int methodCount, int typeDefinitionsCount)
474
    {
475
        //Let's just try and be cheap here and find them in the symbol table.
476

477
        LibLogger.Verbose("\tChecking ELF Symbol Table for code and/or meta reg...");
2✔
478
        ulong codeReg = 0;
2✔
479
        ulong metadataReg = 0;
2✔
480
        if (_symbolTable.FirstOrDefault(s => s.Name.Contains("g_CodeRegistration")) is { } codeRegSymbol)
7,589!
481
            codeReg = codeRegSymbol.VirtualAddress;
×
482

483
        if (_symbolTable.FirstOrDefault(s => s.Name.Contains("g_MetadataRegistration")) is { } metaRegSymbol)
7,589!
484
            metadataReg = metaRegSymbol.VirtualAddress;
×
485

486
        if (codeReg != 0 && metadataReg != 0)
2!
487
        {
488
            LibLogger.VerboseNewline("Found them.");
×
489
            return (codeReg, metadataReg);
×
490
        }
491

492
        LibLogger.VerboseNewline("Didn't find them, scanning binary...");
2✔
493

494
        //Well, that didn't work. Look for the specific initializer function which calls into Il2CppCodegenRegistration.
495
        if (InstructionSetId == DefaultInstructionSets.ARM_V7 && LibCpp2IlMain.MetadataVersion < 24.2f)
2!
496
        {
497
            var ret = FindCodeAndMetadataRegArm32();
×
498
            if (ret != (0, 0))
×
499
                return ret;
×
500
        }
501

502
        if (InstructionSetId == DefaultInstructionSets.ARM_V8 && LibCpp2IlMain.MetadataVersion < 24.2f)
2!
503
        {
504
            var ret = FindCodeAndMetadataRegArm64();
×
505
            if (ret != (0, 0))
×
506
                return ret;
×
507
        }
508

509
        return FindCodeAndMetadataRegDefaultBehavior(methodCount, typeDefinitionsCount);
2✔
510
    }
511

512
    private (ulong codeReg, ulong metaReg) FindCodeAndMetadataRegArm32()
513
    {
514
        //This is a little complicated, so:
515
        //All ARM instructions are four bytes.
516
        //We need to check for two out of a specific 6 instructions, so 24 (0x18) bytes.
517
        //And we need to do this for all initializer functions.
518

519
        //Specifically, we're looking for:
520
        //ADD r0, pc, r0 (00 00 8f e0)
521
        //ADD r1, pc, r1 (01 10 8f e0)
522
        var addSearchBytes = new byte[] { 0x00, 0x00, 0x8F, 0xE0, 0x01, 0x10, 0x8F, 0xE0 };
×
523

524
        //Also, the third instruction should be LDR R1, #x. But we don't know what x is, but it contains the pointer to the CodegenRegistration function.
525
        //So search for the bytes that *don't* specify what x is. There are three.
526
        var ldrSearchBytes = new byte[] { 0x10, 0x9F, 0xE5 };
×
527

528
        LibLogger.VerboseNewline($"\tARM-32 MODE: Checking {_initializerPointers!.Count} initializer pointers...");
×
529
        foreach (var initializerPointer in _initializerPointers!) //Not-null asserted because it's initialized in the constructor.
×
530
        {
531
            //So, read 0x18 bytes.
532
            var instructionBytes = ReadByteArrayAtRawAddress(initializerPointer, 0x18);
×
533

534
            //We only want the last two instructions, so skip the first 16 bytes.
535
            if (!addSearchBytes.SequenceEqual(instructionBytes.Skip(0x10)))
×
536
                continue;
537

538
            //Check last three bytes of third instruction (so skip 9 bytes, read 3)
539
            if (!ldrSearchBytes.SequenceEqual(instructionBytes.Skip(9).Take(3)))
×
540
                continue;
541

542
            //Take the 8th byte, which contains our 'x' value, which specifies where the codereg function is.
543
            //Add the current PC value. ARM is weird in that the PC points to two instructions *after* the currently executing one.
544
            //This instruction is offset 0x8, each instruction is 4 bytes, so two instructions below is 0x10 into the function.
545
            //So add 0x10, to the function address, to the value in byte 8.
546
            var pointerToPointerToCodegenRegFunction = instructionBytes[8] + initializerPointer + 0x10;
×
547

548
            //Now we know where the function pointer is. Or, the important part of it.
549
            //Read 4 bytes there.
550
            Position = pointerToPointerToCodegenRegFunction;
×
551
            var pointerToCodegenRegFunction = ReadUInt32();
×
552

553
            //Pointer is relative, so add on address of function + offset of pointer table (?) in function (0x1C).
554
            pointerToCodegenRegFunction += (uint)initializerPointer + 0x1C;
×
555

556
            //Read 7 instructions + 3 pointers which should hopefully make up Il2CppCodegenRegistration.
557
            //functionBody[0] through [6] are instructions, [7] through [9] are pointers.
558
            var functionBody = ReadClassArrayAtRawAddr<uint>(pointerToCodegenRegFunction, 10);
×
559

560
            //Check the last instruction is an unconditional branch
561
            if (functionBody[6].Bits(24, 8) != 0b_1110_1010)
×
562
                continue;
563

564
            //Read the three register-value pairs for the first 3 LDRs in the function.
565
            var registerOffsets = new uint[3];
×
566

567
            var fail = false;
×
568
            for (var i = 0u; i <= 2u && !fail; i++)
×
569
            {
570
                var (registerNum, immediate) = ArmUtils.GetOperandsForLiteralLdr(functionBody[i]);
×
571
                if (registerNum > 2 || immediate == 0) //Immediate = 0 is a fail, register > 2 indicates wrong function.
×
572
                    fail = true;
×
573
                else
574
                    registerOffsets[registerNum] = immediate + i * 4 + 8; //PC is +8, i*4 is 4 bytes per instruction.
×
575
            }
576

577
            if (fail)
×
578
                continue;
579

580
            //Instructions 3-5 (4, 5, 6) load the actual data values. They can be LDR or ADD, where:
581
            //LDR indicates we have a pointer-to-pointer and have to read the struct pointer from elsewhere in the binary.
582
            //ADD indicates we have a relative pointer to the data and just resolve that to the struct pointer.
583

584
            var pointers = new uint[3];
×
585

586
            for (var i = 3u; i <= 5 && !fail; i++)
×
587
            {
588
                var (addFirstReg, addSecondReg, addThirdReg) = ArmUtils.GetOperandsForRegisterAdd(functionBody[i]);
×
589
                var (ldrFirstReg, ldrSecondReg, ldrThirdReg) = ArmUtils.GetOperandsForRegisterLdr(functionBody[i]);
×
590

591
                if (addSecondReg == ArmUtils.PC_REG && addFirstReg == addThirdReg && addFirstReg <= 2)
×
592
                    //Valid ADD
593
                    pointers[addFirstReg] = pointerToCodegenRegFunction + i * 4 + functionBody[registerOffsets[addFirstReg] / 4] + 8;
×
594
                else if (ldrSecondReg == ArmUtils.PC_REG && ldrFirstReg == ldrThirdReg && ldrFirstReg <= 2)
×
595
                {
596
                    //Valid LDR.
597
                    var p = pointerToCodegenRegFunction + i * 4 + functionBody[registerOffsets[ldrFirstReg] / 4] + 8;
×
598
                    //VIRTUAL address
599
                    //We're a 32-bit binary if we're here, so we can just read the pointer as a 32-bit value.
600
                    pointers[ldrFirstReg] = (uint)ReadPointerAtVirtualAddress(p);
×
601
                }
602
                else
603
                    fail = true;
×
604
            }
605

606
            if (fail)
×
607
            {
608
                LibLogger.VerboseNewline($"\t\tInitializer function at 0x{initializerPointer:X} is probably NOT the il2cpp initializer.");
×
609
                continue;
×
610
            }
611

612
            LibLogger.VerboseNewline($"\t\tFound valid sequence of bytes for il2cpp initializer function at 0x{initializerPointer:X}.");
×
613

614
            return (pointers[0], pointers[1]);
×
615
        }
616

617
        return (0, 0);
×
618
    }
×
619

620
    private (ulong codeReg, ulong metaReg) FindCodeAndMetadataRegArm64()
621
    {
622
        LibLogger.VerboseNewline($"\tARM-64 MODE: Checking {_initializerPointers!.Count} initializer pointers...");
×
623
        foreach (var initializerPointer in _initializerPointers)
×
624
        {
625
            //In most cases we don't need more than the first 7 instructions
626
            var func = MiniArm64Decompiler.ReadFunctionAtRawAddress(this, (uint)initializerPointer, 7);
×
627

628
            //Don't accept anything longer than 7 instructions
629
            //I.e. if it doesn't end with a jump we don't want it
630
            if (!MiniArm64Decompiler.IsB(func[^1]))
×
631
                continue;
632

633
            var registers = MiniArm64Decompiler.GetAddressesLoadedIntoRegisters(func, (ulong)(_globalOffset + initializerPointer), this);
×
634

635
            //Did we find the initializer defined in Il2CppCodeRegistration.cpp?
636
            //It will have only x0 and x1 set.
637
            if (registers.Count == 2 && registers.ContainsKey(0) && registers.TryGetValue(1, out var x1))
×
638
            {
639
                //Load the function whose address is in X1
640
                var secondFunc = MiniArm64Decompiler.ReadFunctionAtRawAddress(this, (uint)MapVirtualAddressToRaw(x1), 7);
×
641

642
                if (!MiniArm64Decompiler.IsB(secondFunc[^1]))
×
643
                    continue;
644

645
                registers = MiniArm64Decompiler.GetAddressesLoadedIntoRegisters(secondFunc, x1, this);
×
646
            }
647

648
            //Do we have Il2CppCodegenRegistration?
649
            //In v21 and later - which is the only range we support - we have X0 through X2 and only those.
650
            //We want what's in x0 and x1. x2 is irrelevant.
651
            if (registers.Count == 3 && registers.TryGetValue(0, out var x0) && registers.TryGetValue(1, out x1) && registers.ContainsKey(2))
×
652
            {
653
                LibLogger.VerboseNewline($"\t\tFound valid sequence of bytes for il2cpp initializer function at 0x{initializerPointer:X}.");
×
654
                return (x0, x1);
×
655
            }
656

657
            //Fail, move on.
658

659
            LibLogger.VerboseNewline($"\t\tInitializer function at 0x{initializerPointer:X} is probably NOT the il2cpp initializer - got {registers.Count} register values with keys {string.Join(", ", registers.Keys)}.");
×
660
        }
661

662
        return (0, 0);
×
663
    }
×
664

665
    private (ulong codeReg, ulong metaReg) FindCodeAndMetadataRegDefaultBehavior(int methodCount, int typeDefinitionsCount)
666
    {
667
        LibLogger.VerboseNewline("Searching for il2cpp structures in an ELF binary using non-arch-specific method...");
2✔
668
        var searcher = new BinarySearcher(this, methodCount, typeDefinitionsCount);
2✔
669

670
        LibLogger.VerboseNewline("\tLooking for code reg (this might take a while)...");
2✔
671
        var codeReg = LibCpp2IlMain.MetadataVersion >= 24.2f ? searcher.FindCodeRegistrationPost2019() : searcher.FindCodeRegistrationPre2019();
2!
672
        LibLogger.VerboseNewline($"\tGot code reg 0x{codeReg:X}");
2✔
673

674
        LibLogger.VerboseNewline($"\tLooking for meta reg ({(LibCpp2IlMain.MetadataVersion >= 27f ? "post-27" : "pre-27")})...");
2✔
675
        var metaReg = LibCpp2IlMain.MetadataVersion >= 27f ? searcher.FindMetadataRegistrationPost24_5() : searcher.FindMetadataRegistrationPre24_5();
2✔
676
        LibLogger.VerboseNewline($"\tGot meta reg 0x{metaReg:x}");
2✔
677

678
        return (codeReg, metaReg);
2✔
679
    }
680

681
    public override long RawLength => _raw.Length;
×
682

683
    public override long MapVirtualAddressToRaw(ulong addr, bool throwOnError = true)
684
    {
685
        var section = _elfProgramHeaderEntries.FirstOrDefault(x => addr >= x.VirtualAddress && addr < x.VirtualAddress + x.VirtualSize);
3,678,093!
686

687
        if (section == null)
1,045,571!
688
            if (throwOnError)
×
689
                throw new InvalidOperationException($"No entry in the Elf PHT contains virtual address 0x{addr:X}");
×
690
            else
691
                return VirtToRawInvalidNoMatch;
×
692

693
        if (addr >= section.VirtualAddress + section.RawSize)
1,045,571✔
694
            if (throwOnError)
2!
695
                throw new InvalidOperationException(
2✔
696
                    $"Virtual address {section.VirtualAddress:X} is located outside of the file-backed portion of Elf PHT section at 0x{section.VirtualAddress:X}");
2✔
697
            else
698
                return VirtToRawInvalidOutOfBounds;
×
699

700
        return (long)(addr - (section.VirtualAddress - section.RawAddress));
1,045,569✔
701
    }
702

703
    public override ulong MapRawAddressToVirtual(uint offset)
704
    {
705
        if (relocationBlocks.Any(b => b.start <= offset && b.end > offset))
75✔
706
            throw new InvalidOperationException("Attempt to map a relocation block to a virtual address");
15✔
707

708
        var section = _elfProgramHeaderEntries.First(x => offset >= x.RawAddress && offset < x.RawAddress + x.RawSize);
49!
709

710
        return section.VirtualAddress + offset - section.RawAddress;
15✔
711
    }
712

713
    public override byte GetByteAtRawAddress(ulong addr) => _raw[addr];
×
714

715
    public override ulong GetRva(ulong pointer) => (ulong)((long)pointer - _globalOffset);
×
716

717
    public override byte[] GetRawBinaryContent() => _raw;
2✔
718

719
    public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind)
720
    {
721
        if (!_exportNameTable.TryGetValue(toFind, out var exportedSymbol))
×
722
            return 0;
×
723

724
        return exportedSymbol.VirtualAddress;
×
725
    }
726

727
    public override bool IsExportedFunction(ulong addr) => _exportAddressTable.ContainsKey(addr);
×
728

729
    public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name)
730
    {
731
        if (_exportAddressTable.TryGetValue(addr, out var symbol))
×
732
        {
733
            name = symbol.Name;
×
734
            return true;
×
735
        }
736
        else
737
        {
738
            return base.TryGetExportedFunctionName(addr, out name);
×
739
        }
740
    }
741

742
    public override IEnumerable<KeyValuePair<string, ulong>> GetExportedFunctions()
743
    {
NEW
744
        return _exportNameTable.Select(kv => new KeyValuePair<string, ulong>(kv.Key, kv.Value.VirtualAddress));
×
745
    }
746

UNCOV
747
    public override ulong GetVirtualAddressOfPrimaryExecutableSection() => _elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text")?.VirtualAddress ?? 0;
×
748

749
    public override byte[] GetEntirePrimaryExecutableSection()
750
    {
751
        var primarySection = _elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text");
×
752

753
        if (primarySection == null)
×
754
            return [];
×
755

756
        return GetRawBinaryContent().SubArray((int)primarySection.RawAddress, (int)primarySection.Size);
×
757
    }
758
}
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

© 2026 Coveralls, Inc