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

SamboyCoding / Cpp2IL / 30771032007

02 Aug 2026 10:51PM UTC coverage: 34.288% (+0.1%) from 34.169%
30771032007

push

github

web-flow
optimize performance of simplifier and dominator calculation (#594)

2911 of 9942 branches covered (29.28%)

Branch coverage included in aggregate %.

141 of 155 new or added lines in 3 files covered. (90.97%)

1 existing line in 1 file now uncovered.

5347 of 14142 relevant lines covered (37.81%)

156323.88 hits per line

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

0.0
/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.Metadata;
8
using LibCpp2IL.PE;
9

10
namespace LibCpp2IL.Elf;
11

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

25
    private readonly List<(ulong start, ulong end)> relocationBlocks = [];
×
26

27
    private long _globalOffset;
28

29
    public ElfFile(MemoryStream input) : base(input)
×
30
    {
31
        _raw = input.GetBuffer();
×
32

33
        LibLogger.Verbose("\tReading Elf File Ident...");
×
34
        var start = DateTime.Now;
×
35

36
        ReadAndValidateIdent();
×
37

38
        var isBigEndian = _elfFileIdent!.Endianness == 2;
×
39

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

43
        if (isBigEndian)
×
44
            SetBigEndian();
×
45

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

49
        ReadHeader();
×
50

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

54
        LibLogger.Verbose("\tReading ELF program header table...");
×
55
        start = DateTime.Now;
×
56

57
        ReadProgramHeaderTable();
×
58

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

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

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

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

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

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

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

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

99
        //Get dynamic section.
100
        if (GetProgramHeaderOfType(ElfProgramEntryType.PT_DYNAMIC) is { } dynamicSegment)
×
101
        {
102
            // NOTE: Do not use .RawAddress here, as it is not guaranteed to point to the actual dynamic table being used.
103
            // The dynamic table should be mapped by one of the preceding PT_LOAD entries.
104
            // Source: phdr_table_get_dynamic_section, https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker_phdr.cpp
105
            _dynamicSection = ReadReadableArrayAtVirtualAddress<ElfDynamicEntry>(dynamicSegment.VirtualAddress, (int)dynamicSegment.RawSize / (is32Bit ? 8 : 16)).ToList();
×
106
        }
107

108
        LibLogger.VerboseNewline("\tFinding Relocations...");
×
109
        start = DateTime.Now;
×
110

111
        ProcessRelocations();
×
112

113
        LibLogger.VerboseNewline($"OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
×
114

115
        LibLogger.VerboseNewline("\tProcessing Symbols...");
×
116
        start = DateTime.Now;
×
117

118
        try
119
        {
120
            ProcessSymbols();
×
121

122
            LibLogger.VerboseNewline($"\tOK ({(DateTime.Now - start).TotalMilliseconds} ms)");
×
123
        }
×
124
        catch (Exception e)
×
125
        {
126
            LibLogger.ErrorNewline($"\tCaught {e.GetType().Name} processing symbols! Attempting to continue without symbol information (no exports, for example)...");
×
127
#if DEBUG
128
            LibLogger.ErrorNewline(e.ToString());
129
#endif
130
        }
×
131

132
        LibLogger.Verbose("\tProcessing Initializers...");
×
133
        start = DateTime.Now;
×
134

135
        ProcessInitializers();
×
136

137
        LibLogger.VerboseNewline($"Got {_initializerPointers!.Count} OK ({(DateTime.Now - start).TotalMilliseconds} ms)");
×
138
    }
×
139

140
    private void ReadAndValidateIdent()
141
    {
142
        _elfFileIdent = ReadReadable<ElfFileIdent>(0);
×
143

144
        if (_elfFileIdent.Magic != 0x464c457f) //Magic number
×
145
            throw new FormatException("ERROR: Magic number mismatch.");
×
146

147
        if (_elfFileIdent.Architecture == 1)
×
148
            is32Bit = true;
×
149
        else if (_elfFileIdent.Architecture != 2)
×
150
            throw new FormatException($"Invalid arch number (expecting 1 or 2): {_elfFileIdent.Architecture}");
×
151

152
        if (_elfFileIdent.Version != 1)
×
153
            throw new FormatException($"ELF Version is not 1? File header has version {_elfFileIdent.Version}");
×
154
    }
×
155

156
    private void ReadHeader()
157
    {
158
        _elfHeader = ReadReadable<ElfFileHeader>(0x10);
×
159

160
        InstructionSetId = _elfHeader.Machine switch
×
161
        {
×
162
            0x03 => DefaultInstructionSets.X86_32,
×
163
            0x3E => DefaultInstructionSets.X86_64,
×
164
            0x28 => DefaultInstructionSets.ARM_V7,
×
165
            0xB7 => DefaultInstructionSets.ARM_V8,
×
166
            _ => throw new NotImplementedException($"ELF Machine {_elfHeader.Machine} not implemented")
×
167
        };
×
168

169
        if (_elfHeader.Version != 1)
×
170
            throw new FormatException($"Full ELF header specifies version {_elfHeader.Version}, only supported version is 1.");
×
171
    }
×
172

173
    private void ReadProgramHeaderTable()
174
    {
175
        _elfProgramHeaderEntries = is32Bit
×
176
            ? ReadReadableArrayAtRawAddr<ElfProgramHeaderEntry32>(_elfHeader!.pProgramHeader, _elfHeader.ProgramHeaderEntryCount).Cast<IElfProgramHeaderEntry>().ToList()
×
177
            : ReadReadableArrayAtRawAddr<ElfProgramHeaderEntry64>(_elfHeader!.pProgramHeader, _elfHeader.ProgramHeaderEntryCount).Cast<IElfProgramHeaderEntry>().ToList();
×
178
    }
×
179

180
    private IElfProgramHeaderEntry? GetProgramHeaderOfType(ElfProgramEntryType type) => _elfProgramHeaderEntries.FirstOrDefault(p => p.Type == type);
×
181

182
    private IEnumerable<ElfSectionHeaderEntry> GetSections(ElfSectionEntryType type) => _elfSectionHeaderEntries.Where(s => s.Type == type);
×
183

184
    private ElfSectionHeaderEntry? GetSingleSection(ElfSectionEntryType type) => GetSections(type).FirstOrDefault();
×
185

186
    private ElfDynamicEntry? GetDynamicEntryOfType(ElfDynamicType type) => _dynamicSection.FirstOrDefault(d => d.Tag == type);
×
187

188
    private void ProcessRelocations()
189
    {
190
        try
191
        {
192
            var rels = new HashSet<ElfRelocation>();
×
193

194
            var relSectionStarts = new HashSet<ulong>();
×
195

196
            //REL tables
197
            foreach (var section in GetSections(ElfSectionEntryType.SHT_REL))
×
198
            {
199
                //Get related section pointer
200
                var relatedTablePointer = _elfSectionHeaderEntries[section.LinkedSectionIndex].RawAddress;
×
201

202
                //Read rel table
203
                var table = ReadReadableArrayAtRawAddr<ElfRelEntry>((long)section.RawAddress, (long)(section.Size / (ulong)section.EntrySize));
×
204

205
                LibLogger.VerboseNewline($"\t\t-Got {table.Length} from REL section {section.Name}");
×
206

207
                relocationBlocks.Add((section.RawAddress, section.RawAddress + section.Size));
×
208
                relSectionStarts.Add(section.RawAddress);
×
209

210
                //Insert into rels list.
211
                rels.UnionWith(table.Select(r => new ElfRelocation(this, r, relatedTablePointer)));
×
212
            }
213

214
            //RELA tables
215
            foreach (var section in GetSections(ElfSectionEntryType.SHT_RELA))
×
216
            {
217
                if (relSectionStarts.Contains(section.RawAddress))
×
218
                {
219
                    LibLogger.VerboseNewline($"\t\t-Ignoring RELA section starting at 0x{section.RawAddress} because it's already been processed.");
×
220
                    continue;
×
221
                }
222

223
                //Get related section pointer
224
                var relatedTablePointer = _elfSectionHeaderEntries[section.LinkedSectionIndex].RawAddress;
×
225

226
                //Read rela table
227
                var table = ReadReadableArrayAtRawAddr<ElfRelaEntry>((long)section.RawAddress, (long)(section.Size / (ulong)section.EntrySize));
×
228

229
                LibLogger.VerboseNewline($"\t\t-Got {table.Length} from RELA section {section.Name} at 0x{section.RawAddress}");
×
230

231
                relocationBlocks.Add((section.RawAddress, section.RawAddress + section.Size));
×
232
                relSectionStarts.Add(section.RawAddress);
×
233

234
                //Insert into rels list.
235
                rels.UnionWith(table.Select(r => new ElfRelocation(this, r, relatedTablePointer)));
×
236
            }
237

238
            //Dynamic Rel Table
239
            if (GetDynamicEntryOfType(ElfDynamicType.DT_REL) is { } dt_rel && (uint)MapVirtualAddressToRaw(dt_rel.Value) is { } dtRelStartAddr)
×
240
            {
241
                if (!relSectionStarts.Contains(dtRelStartAddr))
×
242
                {
243
                    //Null-assertion reason: We must have both a RELSZ and a RELENT or this is an error.
244
                    var relocationSectionSize = GetDynamicEntryOfType(ElfDynamicType.DT_RELSZ)!.Value;
×
245
                    var relCount = (int)(relocationSectionSize / GetDynamicEntryOfType(ElfDynamicType.DT_RELENT)!.Value);
×
246
                    var entries = ReadReadableArrayAtRawAddr<ElfRelEntry>(dtRelStartAddr, relCount);
×
247

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

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

253
                    relocationBlocks.Add((dtRelStartAddr, dtRelStartAddr + relocationSectionSize));
×
254

255
                    rels.UnionWith(entries.Select(r => new ElfRelocation(this, r, pSymTab)));
×
256
                }
257
                else
258
                {
259
                    LibLogger.VerboseNewline($"\t\t-Ignoring dynamic REL section starting at 0x{dtRelStartAddr} because it's already been processed.");
×
260
                }
261
            }
262

263
            //Dynamic Rela Table
264
            if (GetDynamicEntryOfType(ElfDynamicType.DT_RELA) is { } dt_rela)
×
265
            {
266
                //Null-assertion reason: We must have both a RELSZ and a RELENT or this is an error.
267
                var relocationSectionSize = GetDynamicEntryOfType(ElfDynamicType.DT_RELASZ)!.Value;
×
268
                var relCount = (int)(relocationSectionSize / GetDynamicEntryOfType(ElfDynamicType.DT_RELAENT)!.Value);
×
269
                var startAddr = (uint)MapVirtualAddressToRaw(dt_rela.Value);
×
270

271
                if (!relSectionStarts.Contains(startAddr))
×
272
                {
273
                    var entries = ReadReadableArrayAtRawAddr<ElfRelaEntry>(startAddr, relCount);
×
274

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

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

280
                    relocationBlocks.Add((startAddr, startAddr + relocationSectionSize));
×
281

282
                    rels.UnionWith(entries.Select(r => new ElfRelocation(this, r, pSymTab)));
×
283
                }
284
                else
285
                {
286
                    LibLogger.VerboseNewline($"\t\t-Ignoring dynamic RELA section starting at 0x{startAddr} because it's already been processed.");
×
287
                }
288
            }
289

290
            var sizeOfRelocationStruct = (ulong)(is32Bit ? ElfDynamicSymbol32.StructSize : ElfDynamicSymbol64.StructSize);
×
291

292
            LibLogger.Verbose($"\t-Now Processing {rels.Count} relocations...");
×
293

294
            foreach (var rel in rels)
×
295
            {
296
                var pointer = rel.pRelatedSymbolTable + rel.IndexInSymbolTable * sizeOfRelocationStruct;
×
297
                ulong symValue;
298
                try
299
                {
300
                    symValue = ((IElfDynamicSymbol)(is32Bit ? ReadReadable<ElfDynamicSymbol32>((long)pointer) : ReadReadable<ElfDynamicSymbol64>((long)pointer))).Value;
×
301
                }
×
302
                catch
×
303
                {
304
                    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})");
×
305
                    throw;
×
306
                }
307

308
                long targetLocation;
309
                try
310
                {
311
                    targetLocation = MapVirtualAddressToRaw(rel.Offset);
×
312
                }
×
313
                catch (InvalidOperationException)
×
314
                {
315
                    continue; //Ignore this rel.
×
316
                }
317

318
                //Read one word.
319
                ulong addend;
320
                if (rel.Addend.HasValue)
×
321
                    addend = rel.Addend.Value;
×
322
                else
323
                {
324
                    Position = targetLocation;
×
325
                    addend = ReadUInt64();
×
326
                }
327

328
                //Adapted from Il2CppInspector. Thanks to djKaty.
329

330
                ulong newValue;
331
                bool recognized;
332
                if (InstructionSetId == DefaultInstructionSets.ARM_V7)
×
333
                    (newValue, recognized) = rel.Type switch
×
334
                    {
×
335
                        ElfRelocationType.R_ARM_ABS32 => (symValue + addend, true), // S + A
×
336
                        ElfRelocationType.R_ARM_REL32 => (symValue + rel.Offset - addend, true), // S - P + A
×
337
                        ElfRelocationType.R_ARM_COPY => (symValue, true), // S
×
338
                        _ => (0UL, false)
×
339
                    };
×
340
                else if (InstructionSetId == DefaultInstructionSets.ARM_V8)
×
341
                    (newValue, recognized) = rel.Type switch
×
342
                    {
×
343
                        ElfRelocationType.R_AARCH64_ABS64 => (symValue + addend, true), // S + A
×
344
                        ElfRelocationType.R_AARCH64_PREL64 => (symValue + addend - rel.Offset, true), // S + A - P
×
345
                        ElfRelocationType.R_AARCH64_GLOB_DAT => (symValue + addend, true), // S + A
×
346
                        ElfRelocationType.R_AARCH64_JUMP_SLOT => (symValue + addend, true), // S + A
×
347
                        ElfRelocationType.R_AARCH64_RELATIVE => (symValue + addend, true), // Delta(S) + A
×
348
                        _ => (0UL, false)
×
349
                    };
×
350
                else if (InstructionSetId == DefaultInstructionSets.X86_32)
×
351
                    (newValue, recognized) = rel.Type switch
×
352
                    {
×
353
                        ElfRelocationType.R_386_32 => (symValue + addend, true), // S + A
×
354
                        ElfRelocationType.R_386_PC32 => (symValue + addend - rel.Offset, true), // S + A - P
×
355
                        ElfRelocationType.R_386_GLOB_DAT => (symValue, true), // S
×
356
                        ElfRelocationType.R_386_JMP_SLOT => (symValue, true), // S
×
357
                        _ => (0UL, false)
×
358
                    };
×
359
                else if (InstructionSetId == DefaultInstructionSets.X86_64)
×
360
                    (newValue, recognized) = rel.Type switch
×
361
                    {
×
362
                        ElfRelocationType.R_AMD64_64 => (symValue + addend, true), // S + A
×
363
                        ElfRelocationType.R_AMD64_RELATIVE => (addend, true), //Base address + A
×
364

×
365
                        _ => (0UL, false)
×
366
                    };
×
367
                else
368
                    (newValue, recognized) = (0UL, false);
×
369

370
                if (recognized)
×
371
                {
372
                    WriteWord((int)targetLocation, newValue);
×
373
                }
374
            }
375
        }
×
376
        catch
×
377
        {
378
            LibLogger.Info("Exception during relocation mapping!");
×
379
            throw;
×
380
        }
381
    }
×
382

383
    private void ProcessSymbols()
384
    {
385
        var symbolTables = new List<(ulong offset, ulong count, ulong strings)>();
×
386

387
        //Look for .strtab
388
        if (GetSingleSection(ElfSectionEntryType.SHT_STRTAB) is { } strTab)
×
389
        {
390
            //Look for .symtab
391
            if (GetSingleSection(ElfSectionEntryType.SHT_SYMTAB) is { } symtab)
×
392
            {
393
                LibLogger.VerboseNewline($"\t\t-Found .symtab at 0x{symtab.RawAddress:X}");
×
394
                symbolTables.Add((symtab.RawAddress, symtab.Size / (ulong)symtab.EntrySize, strTab.RawAddress));
×
395
            }
396

397
            //Look for .dynsym
398
            if (GetSingleSection(ElfSectionEntryType.SHT_DYNSYM) is { } dynsym)
×
399
            {
400
                LibLogger.VerboseNewline($"\t\t-Found .dynsym at 0x{dynsym.RawAddress:X}");
×
401
                symbolTables.Add((dynsym.RawAddress, dynsym.Size / (ulong)dynsym.EntrySize, strTab.RawAddress));
×
402
            }
403
        }
404

405
        //Look for Dynamic String table
406
        if (GetDynamicEntryOfType(ElfDynamicType.DT_STRTAB) is { } dynamicStrTab)
×
407
        {
408
            if (GetDynamicEntryOfType(ElfDynamicType.DT_SYMTAB) is { } dynamicSymTab)
×
409
            {
410
                var endSection = _dynamicSection.Where(x => x.Value > dynamicSymTab.Value).OrderBy(x => x.Value).FirstOrDefault();
×
411
                ulong end;
412
                if(endSection != null)
×
413
                    end = endSection.Value;
×
414
                else
415
                    end = GetProgramHeaderOfType(ElfProgramEntryType.PT_DYNAMIC) is {} dynamicSegment ? dynamicSegment.VirtualAddress + dynamicSegment.RawSize : (ulong)RawLength;
×
416
                var dynSymSize = is32Bit ? 18ul : 24ul;
×
417

418
                var address = (ulong)MapVirtualAddressToRaw(dynamicSymTab.Value);
×
419

420
                LibLogger.VerboseNewline($"\t\t-Found DT_SYMTAB at 0x{address:X}");
×
421

422
                symbolTables.Add((
×
423
                    address,
×
424
                    (end - dynamicSymTab.Value) / dynSymSize,
×
425
                    dynamicStrTab.Value
×
426
                ));
×
427
            }
428
        }
429

430
        _symbolTable.Clear();
×
431
        _exportNameTable.Clear();
×
432
        _exportAddressTable.Clear();
×
433

434
        //Unify symbol tables
435
        foreach (var (offset, count, stringTable) in symbolTables)
×
436
        {
437
            var symbols = is32Bit
×
438
                ? ReadReadableArrayAtRawAddr<ElfDynamicSymbol32>((long)offset, (long)count).Cast<IElfDynamicSymbol>().ToList()
×
439
                : ReadReadableArrayAtRawAddr<ElfDynamicSymbol64>((long)offset, (long)count).Cast<IElfDynamicSymbol>().ToList();
×
440

441
            LibLogger.VerboseNewline($"\t\t-Found {symbols.Count} symbols in table at 0x{offset:X}");
×
442

443
            foreach (var symbol in symbols)
×
444
            {
445
                string name;
446
                try
447
                {
448
                    name = ReadStringToNull(stringTable + symbol.NameOffset);
×
449
                }
×
450
                catch (ArgumentOutOfRangeException)
×
451
                {
452
                    // Stripped
453
                    continue;
×
454
                }
455

456
                var usefulType = symbol.Shndx == 0 ? ElfSymbolTableEntry.ElfSymbolEntryType.Import
×
457
                    : symbol.Type == ElfDynamicSymbolType.STT_FUNC ? ElfSymbolTableEntry.ElfSymbolEntryType.Function
×
458
                    : symbol.Type == ElfDynamicSymbolType.STT_OBJECT || symbol.Type == ElfDynamicSymbolType.STT_COMMON ? ElfSymbolTableEntry.ElfSymbolEntryType.Name
×
459
                    : ElfSymbolTableEntry.ElfSymbolEntryType.Unknown;
×
460

461
                var virtualAddress = symbol.Value;
×
462

463
                var entry = new ElfSymbolTableEntry { Name = name, Type = usefulType, VirtualAddress = virtualAddress };
×
464
                _symbolTable.Add(entry);
×
465

466
                if (symbol.Shndx != 0)
×
467
                {
468
                    _exportNameTable.TryAdd(name, entry);
×
469
                    _exportAddressTable.TryAdd(virtualAddress, entry);
×
470
                }
471
            }
472
        }
473
    }
×
474

475
    private void ProcessInitializers()
476
    {
477
        if (!(GetDynamicEntryOfType(ElfDynamicType.DT_INIT_ARRAY) is { } dtInitArray) || !(GetDynamicEntryOfType(ElfDynamicType.DT_INIT_ARRAYSZ) is { } dtInitArraySz))
×
478
        {
479
            _initializerPointers = [];
×
480
            return;
×
481
        }
482

483
        var pInitArray = MapVirtualAddressToRaw(dtInitArray.Value);
×
484
        var count = (int)dtInitArraySz.Value / (is32Bit ? 4 : 8);
×
485

486
        var initArray = ReadNUintArrayAtRawAddress(pInitArray, count);
×
487

488
        if (GetDynamicEntryOfType(ElfDynamicType.DT_INIT) is { } dtInit)
×
489
            initArray = initArray.Append(dtInit.Value).ToArray();
×
490

491
        _initializerPointers = initArray.Select(a => MapVirtualAddressToRaw(a)).ToList();
×
492
    }
×
493

494
    public override (ulong pCodeRegistration, ulong pMetadataRegistration) FindCodeAndMetadataReg(Il2CppMetadata metadata)
495
    {
496
        //Let's just try and be cheap here and find them in the symbol table.
497

498
        LibLogger.Verbose("\tChecking ELF Symbol Table for code and/or meta reg...");
×
499
        ulong codeReg = 0;
×
500
        ulong metadataReg = 0;
×
501
        if (_symbolTable.FirstOrDefault(s => s.Name.Contains("g_CodeRegistration")) is { } codeRegSymbol)
×
502
            codeReg = codeRegSymbol.VirtualAddress;
×
503

504
        if (_symbolTable.FirstOrDefault(s => s.Name.Contains("g_MetadataRegistration")) is { } metaRegSymbol)
×
505
            metadataReg = metaRegSymbol.VirtualAddress;
×
506

507
        if (codeReg != 0 && metadataReg != 0)
×
508
        {
509
            LibLogger.VerboseNewline("Found them.");
×
510
            return (codeReg, metadataReg);
×
511
        }
512

513
        LibLogger.VerboseNewline("Didn't find them, scanning binary...");
×
514

515
        //Well, that didn't work. Look for the specific initializer function which calls into Il2CppCodegenRegistration.
516
        if (InstructionSetId == DefaultInstructionSets.ARM_V7 && metadata.MetadataVersion < 24.2f)
×
517
        {
518
            var ret = FindCodeAndMetadataRegArm32();
×
519
            if (ret != (0, 0))
×
520
                return ret;
×
521
        }
522

523
        if (InstructionSetId == DefaultInstructionSets.ARM_V8 && metadata.MetadataVersion < 24.2f)
×
524
        {
525
            var ret = FindCodeAndMetadataRegArm64();
×
526
            if (ret != (0, 0))
×
527
                return ret;
×
528
        }
529

530
        return FindCodeAndMetadataRegDefaultBehavior(metadata);
×
531
    }
532

533
    private (ulong codeReg, ulong metaReg) FindCodeAndMetadataRegArm32()
534
    {
535
        //This is a little complicated, so:
536
        //All ARM instructions are four bytes.
537
        //We need to check for two out of a specific 6 instructions, so 24 (0x18) bytes.
538
        //And we need to do this for all initializer functions.
539

540
        //Specifically, we're looking for:
541
        //ADD r0, pc, r0 (00 00 8f e0)
542
        //ADD r1, pc, r1 (01 10 8f e0)
543
        var addSearchBytes = new byte[] { 0x00, 0x00, 0x8F, 0xE0, 0x01, 0x10, 0x8F, 0xE0 };
×
544

545
        //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.
546
        //So search for the bytes that *don't* specify what x is. There are three.
547
        var ldrSearchBytes = new byte[] { 0x10, 0x9F, 0xE5 };
×
548

549
        LibLogger.VerboseNewline($"\tARM-32 MODE: Checking {_initializerPointers!.Count} initializer pointers...");
×
550
        foreach (var initializerPointer in _initializerPointers!) //Not-null asserted because it's initialized in the constructor.
×
551
        {
552
            //So, read 0x18 bytes.
553
            var instructionBytes = ReadByteArrayAtRawAddress(initializerPointer, 0x18);
×
554

555
            //We only want the last two instructions, so skip the first 16 bytes.
556
            if (!addSearchBytes.SequenceEqual(instructionBytes.Skip(0x10)))
×
557
                continue;
558

559
            //Check last three bytes of third instruction (so skip 9 bytes, read 3)
560
            if (!ldrSearchBytes.SequenceEqual(instructionBytes.Skip(9).Take(3)))
×
561
                continue;
562

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

569
            //Now we know where the function pointer is. Or, the important part of it.
570
            //Read 4 bytes there.
571
            Position = pointerToPointerToCodegenRegFunction;
×
572
            var pointerToCodegenRegFunction = ReadUInt32();
×
573

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

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

581
            //Check the last instruction is an unconditional branch
582
            if (functionBody[6].Bits(24, 8) != 0b_1110_1010)
×
583
                continue;
584

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

588
            var fail = false;
×
589
            for (var i = 0u; i <= 2u && !fail; i++)
×
590
            {
591
                var (registerNum, immediate) = ArmUtils.GetOperandsForLiteralLdr(functionBody[i]);
×
592
                if (registerNum > 2 || immediate == 0) //Immediate = 0 is a fail, register > 2 indicates wrong function.
×
593
                    fail = true;
×
594
                else
595
                    registerOffsets[registerNum] = immediate + i * 4 + 8; //PC is +8, i*4 is 4 bytes per instruction.
×
596
            }
597

598
            if (fail)
×
599
                continue;
600

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

605
            var pointers = new uint[3];
×
606

607
            for (var i = 3u; i <= 5 && !fail; i++)
×
608
            {
609
                var (addFirstReg, addSecondReg, addThirdReg) = ArmUtils.GetOperandsForRegisterAdd(functionBody[i]);
×
610
                var (ldrFirstReg, ldrSecondReg, ldrThirdReg) = ArmUtils.GetOperandsForRegisterLdr(functionBody[i]);
×
611

612
                if (addSecondReg == ArmUtils.PC_REG && addFirstReg == addThirdReg && addFirstReg <= 2)
×
613
                    //Valid ADD
614
                    pointers[addFirstReg] = pointerToCodegenRegFunction + i * 4 + functionBody[registerOffsets[addFirstReg] / 4] + 8;
×
615
                else if (ldrSecondReg == ArmUtils.PC_REG && ldrFirstReg == ldrThirdReg && ldrFirstReg <= 2)
×
616
                {
617
                    //Valid LDR.
618
                    var p = pointerToCodegenRegFunction + i * 4 + functionBody[registerOffsets[ldrFirstReg] / 4] + 8;
×
619
                    //VIRTUAL address
620
                    //We're a 32-bit binary if we're here, so we can just read the pointer as a 32-bit value.
621
                    pointers[ldrFirstReg] = (uint)ReadPointerAtVirtualAddress(p);
×
622
                }
623
                else
624
                    fail = true;
×
625
            }
626

627
            if (fail)
×
628
            {
629
                LibLogger.VerboseNewline($"\t\tInitializer function at 0x{initializerPointer:X} is probably NOT the il2cpp initializer.");
×
630
                continue;
×
631
            }
632

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

635
            return (pointers[0], pointers[1]);
×
636
        }
637

638
        return (0, 0);
×
639
    }
×
640

641
    private (ulong codeReg, ulong metaReg) FindCodeAndMetadataRegArm64()
642
    {
643
        LibLogger.VerboseNewline($"\tARM-64 MODE: Checking {_initializerPointers!.Count} initializer pointers...");
×
644
        foreach (var initializerPointer in _initializerPointers)
×
645
        {
646
            //In most cases we don't need more than the first 7 instructions
647
            var func = MiniArm64Decompiler.ReadFunctionAtRawAddress(this, (uint)initializerPointer, 7);
×
648

649
            //Don't accept anything longer than 7 instructions
650
            //I.e. if it doesn't end with a jump we don't want it
651
            if (!MiniArm64Decompiler.IsB(func[^1]))
×
652
                continue;
653

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

656
            //Did we find the initializer defined in Il2CppCodeRegistration.cpp?
657
            //It will have only x0 and x1 set.
658
            if (registers.Count == 2 && registers.ContainsKey(0) && registers.TryGetValue(1, out var x1))
×
659
            {
660
                //Load the function whose address is in X1
661
                var secondFunc = MiniArm64Decompiler.ReadFunctionAtRawAddress(this, (uint)MapVirtualAddressToRaw(x1), 7);
×
662

663
                if (!MiniArm64Decompiler.IsB(secondFunc[^1]))
×
664
                    continue;
665

666
                registers = MiniArm64Decompiler.GetAddressesLoadedIntoRegisters(secondFunc, x1, this);
×
667
            }
668

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

678
            //Fail, move on.
679

680
            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)}.");
×
681
        }
682

683
        return (0, 0);
×
684
    }
×
685

686
    private (ulong codeReg, ulong metaReg) FindCodeAndMetadataRegDefaultBehavior(Il2CppMetadata metadata)
687
    {
688
        var methodCount = metadata.methodDefs.Count(x => x.methodIndex >= 0);
×
689
        var typeDefinitionsCount = metadata.TypeDefinitionCount;
×
690
        
691
        LibLogger.VerboseNewline("Searching for il2cpp structures in an ELF binary using non-arch-specific method...");
×
692
        var searcher = new BinarySearcher(this, metadata, methodCount, typeDefinitionsCount);
×
693

694
        LibLogger.VerboseNewline("\tLooking for code reg (this might take a while)...");
×
695
        var codeReg = metadata.MetadataVersion >= 24.2f ? searcher.FindCodeRegistrationPost2019() : searcher.FindCodeRegistrationPre2019();
×
696
        LibLogger.VerboseNewline($"\tGot code reg 0x{codeReg:X}");
×
697

698
        LibLogger.VerboseNewline($"\tLooking for meta reg ({(metadata.MetadataVersion >= 27f ? "post-27" : "pre-27")})...");
×
699
        var metaReg = metadata.MetadataVersion >= 27f ? searcher.FindMetadataRegistrationPost24_5() : searcher.FindMetadataRegistrationPre24_5();
×
700
        LibLogger.VerboseNewline($"\tGot meta reg 0x{metaReg:x}");
×
701

702
        return (codeReg, metaReg);
×
703
    }
704

705
    public override long RawLength => _raw.Length;
×
706

707
    public override long MapVirtualAddressToRaw(ulong addr, bool throwOnError = true)
708
    {
709
        var section = _elfProgramHeaderEntries.FirstOrDefault(x => addr >= x.VirtualAddress && addr < x.VirtualAddress + x.VirtualSize);
×
710

711
        if (section == null)
×
712
            if (throwOnError)
×
713
                throw new InvalidOperationException($"No entry in the Elf PHT contains virtual address 0x{addr:X}");
×
714
            else
715
                return VirtToRawInvalidNoMatch;
×
716

717
        if (addr >= section.VirtualAddress + section.RawSize)
×
718
            if (throwOnError)
×
719
                throw new InvalidOperationException(
×
NEW
720
                    $"Virtual address {addr:X} is located outside of the file-backed portion of Elf PHT section at 0x{section.VirtualAddress:X}");
×
721
            else
722
                return VirtToRawInvalidOutOfBounds;
×
723

724
        return (long)(addr - (section.VirtualAddress - section.RawAddress));
×
725
    }
726

727
    public override ulong MapRawAddressToVirtual(uint offset, bool throwOnError = true)
728
    {
729
        if (relocationBlocks.Any(b => b.start <= offset && b.end > offset))
×
730
            if (throwOnError)
×
731
                throw new InvalidOperationException("Attempt to map a relocation block to a virtual address");
×
732
            else
733
                return 0;
×
734

735
        var section = _elfProgramHeaderEntries.FirstOrDefault(x => offset >= x.RawAddress && offset < x.RawAddress + x.RawSize);
×
736
        if (section == null)
×
737
            if (throwOnError)
×
738
                throw new InvalidOperationException($"No entry in the Elf PHT contains raw address 0x{offset:X}");
×
739
            else
740
                return 0;
×
741

742
        return section.VirtualAddress + offset - section.RawAddress;
×
743
    }
744

745
    public override byte GetByteAtRawAddress(ulong addr) => _raw[addr];
×
746

747
    public override ulong GetRva(ulong pointer) => (ulong)((long)pointer - _globalOffset);
×
748

749
    public override ReadOnlySpan<byte> GetRawBinaryContent() => _raw;
×
750

751
    public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind)
752
    {
753
        if (!_exportNameTable.TryGetValue(toFind, out var exportedSymbol))
×
754
            return 0;
×
755

756
        return exportedSymbol.VirtualAddress;
×
757
    }
758

759
    public override bool IsExportedFunction(ulong addr) => _exportAddressTable.ContainsKey(addr);
×
760

761
    public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name)
762
    {
763
        if (_exportAddressTable.TryGetValue(addr, out var symbol))
×
764
        {
765
            name = symbol.Name;
×
766
            return true;
×
767
        }
768
        else
769
        {
770
            return base.TryGetExportedFunctionName(addr, out name);
×
771
        }
772
    }
773

774
    public override IEnumerable<KeyValuePair<string, ulong>> GetExportedFunctions()
775
    {
776
        return _exportNameTable.Select(kv => new KeyValuePair<string, ulong>(kv.Key, kv.Value.VirtualAddress));
×
777
    }
778

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

781
    public override ReadOnlySpan<byte> GetEntirePrimaryExecutableSection()
782
    {
783
        var primarySection = _elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text");
×
784

785
        if (primarySection == null)
×
786
            return ReadOnlySpan<byte>.Empty;
×
787

788
        return GetRawBinaryContent().Slice((int)primarySection.RawAddress, (int)primarySection.Size);
×
789
    }
790
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc