953b1a9b0b
Both the regular and deferred versions of this test contain up to six different test cases: All platforms: * ELF snapshot, using DWARF from the snapshot * ELF snapshot, using DWARF from the separate debugging information For platforms where the test can assemble snapshots: * assembled snapshot, using DWARF from the snapshot or the separate .dSYM package on MacOS * assembled snapshot, using DWARF from the separate debugging information For MacOS only: * creating a single-architecture universal binary from the separate .dSYM package and extracting DWARF information from it * creating a multi-architecture universal binary from the separate .dSYM package and extracting DWARF information from it Originally the tests were written using package:expect, performing program compilation and execution separately before each test and lazily reading DWARF information within the test itself. Since tests using package:expect stop the program immediately on a failure, a failing expectation keeps other independent test cases from being checked. However, it's useful to know if the failure is limited to only a subset of the test cases, since that helps point at which code is to blame for the test failure(s). Now the tests are refactored to first set up the tests by performing all program compilation and execution first, collecting all outputs and DWARF information as test state. Then the test cases are defined over the collected test state using package:test instead of package:expect. This way, as long as there is not a failure in the initial setup, all of the applicable test cases are run even if one or more of them fail. ----- In pkg/native_stack_traces, changes the return types of the reader retrieval methods in the DwarfContainer class and DwarfSnapshot.fromDwarfContainer to be nullable. If the shared object does not contain the expected DWARF information, these methods now return null instead of causing null check exceptions to be thrown. Issue: https://github.com/dart-lang/sdk/issues/55612 Change-Id: I253965a95894f455e51d021e32dbf2703d8b99cf Cq-Include-Trybots: luci.dart.try:vm-aot-dwarf-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-product-arm64-try,vm-aot-mac-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/375240 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
1347 lines
42 KiB
Dart
1347 lines
42 KiB
Dart
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// ignore_for_file: constant_identifier_names
|
|
|
|
import 'dart:typed_data';
|
|
|
|
import 'constants.dart' as constants;
|
|
import 'dwarf_container.dart';
|
|
import 'reader.dart';
|
|
|
|
int _readElfBytes(Reader reader, int bytes, int alignment) {
|
|
final alignOffset = reader.offset % alignment;
|
|
if (alignOffset != 0) {
|
|
// Move the reader to the next aligned position.
|
|
reader.seek(reader.offset - alignOffset + alignment);
|
|
}
|
|
return reader.readBytes(bytes);
|
|
}
|
|
|
|
// Reads an Elf{32,64}_Addr.
|
|
int _readElfAddress(Reader reader) =>
|
|
_readElfBytes(reader, reader.wordSize, reader.wordSize);
|
|
|
|
// Reads an Elf{32,64}_Off.
|
|
int _readElfOffset(Reader reader) =>
|
|
_readElfBytes(reader, reader.wordSize, reader.wordSize);
|
|
|
|
// Reads an Elf{32,64}_Half.
|
|
int _readElfHalf(Reader reader) => _readElfBytes(reader, 2, 2);
|
|
|
|
// Reads an Elf{32,64}_Word.
|
|
int _readElfWord(Reader reader) => _readElfBytes(reader, 4, 4);
|
|
|
|
// Reads an Elf64_Xword.
|
|
int _readElfXword(Reader reader) {
|
|
switch (reader.wordSize) {
|
|
case 4:
|
|
throw 'Internal reader error: reading Elf64_Xword in 32-bit ELF file';
|
|
case 8:
|
|
return _readElfBytes(reader, 8, 8);
|
|
default:
|
|
throw 'Unsupported word size ${reader.wordSize}';
|
|
}
|
|
}
|
|
|
|
// Reads an Elf{32,64}_Section.
|
|
int _readElfSection(Reader reader) => _readElfBytes(reader, 2, 2);
|
|
|
|
// Used in cases where the value read for a given field is Elf32_Word on 32-bit
|
|
// and Elf64_Xword on 64-bit.
|
|
int _readElfNative(Reader reader) {
|
|
switch (reader.wordSize) {
|
|
case 4:
|
|
return _readElfWord(reader);
|
|
case 8:
|
|
return _readElfXword(reader);
|
|
default:
|
|
throw 'Unsupported word size ${reader.wordSize}';
|
|
}
|
|
}
|
|
|
|
/// The identification block at the start of an ELF header, which includes
|
|
/// the magic bytes for file type identification, word size and endian
|
|
/// information, etc.
|
|
class ElfIdentification {
|
|
final int wordSize;
|
|
final Endian endian;
|
|
|
|
ElfIdentification._(this.wordSize, this.endian);
|
|
|
|
static ElfIdentification? fromReader(Reader reader) {
|
|
final start = reader.offset;
|
|
final bytes = Uint8List.sublistView(reader.readRawBytes(_EI_NIDENT));
|
|
// Reset reader in case of failures/null returns below.
|
|
reader.seek(start, absolute: true);
|
|
|
|
// Check magic bytes at start. Return null for a mismatch here.
|
|
if (bytes[_EI_MAG0] != _ELFMAG0) return null;
|
|
if (bytes[_EI_MAG1] != _ELFMAG1) return null;
|
|
if (bytes[_EI_MAG2] != _ELFMAG2) return null;
|
|
if (bytes[_EI_MAG3] != _ELFMAG3) return null;
|
|
|
|
// Check this first since it only has one good value currently.
|
|
if (bytes[_EI_VERSION] != _EV_CURRENT) {
|
|
throw FormatException('Unexpected e_ident[EI_VERSION] value');
|
|
}
|
|
|
|
int? wordSize;
|
|
switch (bytes[_EI_CLASS]) {
|
|
case _ELFCLASS32:
|
|
wordSize = 4;
|
|
break;
|
|
case _ELFCLASS64:
|
|
wordSize = 8;
|
|
break;
|
|
default:
|
|
throw FormatException('Unexpected e_ident[EI_CLASS] value');
|
|
}
|
|
|
|
Endian? endian;
|
|
switch (bytes[_EI_DATA]) {
|
|
case _ELFDATA2LSB:
|
|
endian = Endian.little;
|
|
break;
|
|
case _ELFDATA2MSB:
|
|
endian = Endian.big;
|
|
break;
|
|
default:
|
|
throw FormatException('Unexpected e_ident[EI_DATA] value');
|
|
}
|
|
|
|
// Successfully read, so position the reader after the identification block.
|
|
reader.seek(start + _EI_NIDENT, absolute: true);
|
|
return ElfIdentification._(wordSize, endian);
|
|
}
|
|
|
|
// Offsets into the identification block.
|
|
static const _EI_MAG0 = 0;
|
|
static const _EI_MAG1 = 1;
|
|
static const _EI_MAG2 = 2;
|
|
static const _EI_MAG3 = 3;
|
|
static const _EI_CLASS = 4;
|
|
static const _EI_DATA = 5;
|
|
static const _EI_VERSION = 6;
|
|
static const _EI_NIDENT = 16;
|
|
|
|
// Constants used within the ELF specification.
|
|
static const _ELFMAG0 = 0x7f;
|
|
static const _ELFMAG1 = 0x45; // E
|
|
static const _ELFMAG2 = 0x4c; // L
|
|
static const _ELFMAG3 = 0x46; // F
|
|
static const _ELFCLASS32 = 1;
|
|
static const _ELFCLASS64 = 2;
|
|
static const _ELFDATA2LSB = 1;
|
|
static const _ELFDATA2MSB = 2;
|
|
static const _EV_CURRENT = 1;
|
|
}
|
|
|
|
/// The header of the ELF file, which includes information necessary to parse
|
|
/// the rest of the file.
|
|
class ElfHeader {
|
|
final ElfIdentification elfIdent;
|
|
final int type;
|
|
final int machine;
|
|
final int entry;
|
|
final int flags;
|
|
final int headerSize;
|
|
final int programHeaderOffset;
|
|
final int programHeaderCount;
|
|
final int programHeaderEntrySize;
|
|
final int sectionHeaderOffset;
|
|
final int sectionHeaderCount;
|
|
final int sectionHeaderEntrySize;
|
|
final int sectionHeaderStringsIndex;
|
|
|
|
ElfHeader._(
|
|
this.elfIdent,
|
|
this.type,
|
|
this.machine,
|
|
this.entry,
|
|
this.flags,
|
|
this.headerSize,
|
|
this.programHeaderOffset,
|
|
this.sectionHeaderOffset,
|
|
this.programHeaderCount,
|
|
this.sectionHeaderCount,
|
|
this.programHeaderEntrySize,
|
|
this.sectionHeaderEntrySize,
|
|
this.sectionHeaderStringsIndex);
|
|
|
|
static ElfHeader? fromReader(Reader reader) {
|
|
final start = reader.offset;
|
|
final fileSize = reader.remaining;
|
|
|
|
final elfIdent = ElfIdentification.fromReader(reader);
|
|
if (elfIdent == null) return null;
|
|
|
|
// Make sure the word size and endianness of the reader are set according
|
|
// to the values parsed from the ELF identification block.
|
|
assert(reader.offset == start + ElfIdentification._EI_NIDENT);
|
|
reader.wordSize = elfIdent.wordSize;
|
|
reader.endian = elfIdent.endian;
|
|
|
|
final calculatedHeaderSize = 0x18 + 3 * elfIdent.wordSize + 0x10;
|
|
|
|
if (fileSize < calculatedHeaderSize) {
|
|
throw FormatException('ELF file too small for header: '
|
|
'file size $fileSize < '
|
|
'calculated header size $calculatedHeaderSize');
|
|
}
|
|
|
|
final type = _readElfHalf(reader);
|
|
final machine = _readElfHalf(reader);
|
|
|
|
// This word should also be set to EV_CURRENT.
|
|
if (_readElfWord(reader) != ElfIdentification._EV_CURRENT) {
|
|
throw FormatException('Unexpected e_version value');
|
|
}
|
|
|
|
final entry = _readElfAddress(reader);
|
|
final programHeaderOffset = _readElfOffset(reader);
|
|
final sectionHeaderOffset = _readElfOffset(reader);
|
|
final flags = _readElfWord(reader);
|
|
final headerSize = _readElfHalf(reader);
|
|
|
|
final programHeaderEntrySize = _readElfHalf(reader);
|
|
final programHeaderCount = _readElfHalf(reader);
|
|
final programHeaderSize = programHeaderEntrySize * programHeaderCount;
|
|
|
|
final sectionHeaderEntrySize = _readElfHalf(reader);
|
|
final sectionHeaderCount = _readElfHalf(reader);
|
|
final sectionHeaderSize = sectionHeaderEntrySize * sectionHeaderCount;
|
|
|
|
final sectionHeaderStringsIndex = _readElfHalf(reader);
|
|
|
|
if (reader.offset != headerSize) {
|
|
throw FormatException('Only read ${reader.offset} bytes, not the '
|
|
'full header size $headerSize');
|
|
}
|
|
|
|
if (headerSize != calculatedHeaderSize) {
|
|
throw FormatException('Stored ELF header size $headerSize != '
|
|
'calculated ELF header size $calculatedHeaderSize');
|
|
}
|
|
if (fileSize < programHeaderOffset) {
|
|
throw FormatException('File is truncated before program header');
|
|
}
|
|
if (fileSize < programHeaderOffset + programHeaderSize) {
|
|
throw FormatException('File is truncated within the program header');
|
|
}
|
|
if (fileSize < sectionHeaderOffset) {
|
|
throw FormatException('File is truncated before section header');
|
|
}
|
|
if (fileSize < sectionHeaderOffset + sectionHeaderSize) {
|
|
throw FormatException('File is truncated within the section header');
|
|
}
|
|
|
|
return ElfHeader._(
|
|
elfIdent,
|
|
type,
|
|
machine,
|
|
entry,
|
|
flags,
|
|
headerSize,
|
|
programHeaderOffset,
|
|
sectionHeaderOffset,
|
|
programHeaderCount,
|
|
sectionHeaderCount,
|
|
programHeaderEntrySize,
|
|
sectionHeaderEntrySize,
|
|
sectionHeaderStringsIndex);
|
|
}
|
|
|
|
// The architectures currently output by the Dart built-in ELF writer.
|
|
static const _EM_386 = 3;
|
|
static const _EM_ARM = 40;
|
|
static const _EM_X86_64 = 62;
|
|
static const _EM_AARCH64 = 183;
|
|
static const _EM_RISCV = 243;
|
|
|
|
String? get architecture {
|
|
switch (machine) {
|
|
case _EM_ARM:
|
|
assert(wordSize == 4);
|
|
return "arm";
|
|
case _EM_AARCH64:
|
|
assert(wordSize == 8);
|
|
return "arm64";
|
|
case _EM_386:
|
|
assert(wordSize == 4);
|
|
return "ia32";
|
|
case _EM_X86_64:
|
|
assert(wordSize == 8);
|
|
return "x64";
|
|
case _EM_RISCV:
|
|
return wordSize == 8 ? "riscv64" : "riscv32";
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
int get wordSize => elfIdent.wordSize;
|
|
Endian get endian => elfIdent.endian;
|
|
int get programHeaderSize => programHeaderCount * programHeaderEntrySize;
|
|
int get sectionHeaderSize => sectionHeaderCount * sectionHeaderEntrySize;
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Format is ')
|
|
..write(wordSize * 8)
|
|
..write(' bits');
|
|
switch (endian) {
|
|
case Endian.little:
|
|
buffer.writeln(' and little-endian');
|
|
break;
|
|
case Endian.big:
|
|
buffer.writeln(' and big-endian');
|
|
break;
|
|
}
|
|
buffer
|
|
..write('Type: 0x')
|
|
..writeln(paddedHex(type, 2))
|
|
..write('Machine: 0x')
|
|
..writeln(paddedHex(type, 2))
|
|
..write('Entry point: 0x')
|
|
..writeln(paddedHex(entry, wordSize))
|
|
..write('Flags: 0x')
|
|
..writeln(paddedHex(flags, 4))
|
|
..write('Program header offset: 0x')
|
|
..writeln(paddedHex(programHeaderOffset, wordSize))
|
|
..write('Program header entry size: ')
|
|
..writeln(programHeaderEntrySize)
|
|
..write('Program header entry count: ')
|
|
..writeln(programHeaderCount)
|
|
..write('Section header offset: 0x')
|
|
..writeln(paddedHex(sectionHeaderOffset, wordSize))
|
|
..write('Section header entry size: ')
|
|
..writeln(sectionHeaderEntrySize)
|
|
..write('Section header entry count: ')
|
|
..writeln(sectionHeaderCount)
|
|
..write('Section header strings index: ')
|
|
..write(sectionHeaderStringsIndex);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
var buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// An entry in the [ProgramHeader] describing a memory segment loaded into
|
|
/// memory and used during runtime.
|
|
class ProgramHeaderEntry {
|
|
final int type;
|
|
final int flags;
|
|
final int offset;
|
|
final int vaddr;
|
|
final int paddr;
|
|
final int filesz;
|
|
final int memsz;
|
|
final int align;
|
|
final int wordSize;
|
|
|
|
// p_type constants from ELF specification.
|
|
static const _PT_NULL = 0;
|
|
static const _PT_LOAD = 1;
|
|
static const _PT_DYNAMIC = 2;
|
|
static const _PT_NOTE = 4;
|
|
static const _PT_PHDR = 6;
|
|
static const _PT_GNU_EH_FRAME = 0x6474e550;
|
|
static const _PT_GNU_STACK = 0x6474e551;
|
|
static const _PT_GNU_RELRO = 0x6474e552;
|
|
|
|
ProgramHeaderEntry._(this.type, this.flags, this.offset, this.vaddr,
|
|
this.paddr, this.filesz, this.memsz, this.align, this.wordSize);
|
|
|
|
static ProgramHeaderEntry fromReader(Reader reader) {
|
|
var wordSize = reader.wordSize;
|
|
assert(wordSize == 4 || wordSize == 8);
|
|
final type = _readElfWord(reader);
|
|
late int flags;
|
|
if (wordSize == 8) {
|
|
flags = _readElfWord(reader);
|
|
}
|
|
final offset = _readElfOffset(reader);
|
|
final vaddr = _readElfAddress(reader);
|
|
final paddr = _readElfAddress(reader);
|
|
final filesz = _readElfNative(reader);
|
|
final memsz = _readElfNative(reader);
|
|
if (wordSize == 4) {
|
|
flags = _readElfWord(reader);
|
|
}
|
|
final align = _readElfNative(reader);
|
|
return ProgramHeaderEntry._(
|
|
type, flags, offset, vaddr, paddr, filesz, memsz, align, wordSize);
|
|
}
|
|
|
|
static const _typeStrings = <int, String>{
|
|
_PT_NULL: 'PT_NULL',
|
|
_PT_LOAD: 'PT_LOAD',
|
|
_PT_DYNAMIC: 'PT_DYNAMIC',
|
|
_PT_NOTE: 'PT_NOTE',
|
|
_PT_PHDR: 'PT_PHDR',
|
|
_PT_GNU_EH_FRAME: 'PT_GNU_EH_FRAME',
|
|
_PT_GNU_STACK: 'PT_GNU_STACK',
|
|
_PT_GNU_RELRO: 'PT_GNU_RELRO',
|
|
};
|
|
|
|
static String _typeToString(int type) =>
|
|
_typeStrings[type] ?? 'unknown (${paddedHex(type, 4)})';
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Type: ')
|
|
..writeln(_typeToString(type))
|
|
..write('Flags: 0x')
|
|
..writeln(paddedHex(flags, 4))
|
|
..write('Offset: 0x')
|
|
..writeln(paddedHex(offset, wordSize))
|
|
..write('Virtual address: 0x')
|
|
..writeln(paddedHex(vaddr, wordSize))
|
|
..write('Physical address: 0x')
|
|
..writeln(paddedHex(paddr, wordSize))
|
|
..write('Size in file: ')
|
|
..writeln(filesz)
|
|
..write('Size in memory: ')
|
|
..writeln(memsz)
|
|
..write('Alignment: 0x')
|
|
..write(paddedHex(align, wordSize));
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// A list of [ProgramHeaderEntry]s describing the memory segments loaded at
|
|
/// runtime when this file is used.
|
|
class ProgramHeader {
|
|
final List<ProgramHeaderEntry> _entries;
|
|
|
|
ProgramHeader._(this._entries);
|
|
|
|
int get length => _entries.length;
|
|
ProgramHeaderEntry operator [](int index) => _entries[index];
|
|
|
|
ProgramHeaderEntry? loadSegmentFor(int address) {
|
|
for (final entry in _entries) {
|
|
if (entry.vaddr <= address && address <= entry.vaddr + entry.memsz) {
|
|
return entry;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static ProgramHeader fromReader(Reader reader, ElfHeader header) {
|
|
final programReader =
|
|
reader.shrink(header.programHeaderOffset, header.programHeaderSize);
|
|
final entries =
|
|
programReader.readRepeated(ProgramHeaderEntry.fromReader).toList();
|
|
return ProgramHeader._(entries);
|
|
}
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
for (var i = 0; i < length; i++) {
|
|
if (i != 0) {
|
|
buffer
|
|
..writeln()
|
|
..writeln();
|
|
}
|
|
buffer
|
|
..write('Entry ')
|
|
..write(i)
|
|
..writeln(':');
|
|
_entries[i].writeToStringBuffer(buffer);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// An entry in the [SectionHeader] that describes a single [Section].
|
|
class SectionHeaderEntry {
|
|
final int nameIndex;
|
|
final int type;
|
|
final int flags;
|
|
final int addr;
|
|
final int offset;
|
|
final int size;
|
|
final int link;
|
|
final int info;
|
|
final int addrAlign;
|
|
final int entrySize;
|
|
final int wordSize;
|
|
late String name;
|
|
|
|
SectionHeaderEntry._(
|
|
this.nameIndex,
|
|
this.type,
|
|
this.flags,
|
|
this.addr,
|
|
this.offset,
|
|
this.size,
|
|
this.link,
|
|
this.info,
|
|
this.addrAlign,
|
|
this.entrySize,
|
|
this.wordSize);
|
|
|
|
static SectionHeaderEntry fromReader(Reader reader) {
|
|
final nameIndex = _readElfWord(reader);
|
|
final type = _readElfWord(reader);
|
|
final flags = _readElfNative(reader);
|
|
final addr = _readElfAddress(reader);
|
|
final offset = _readElfOffset(reader);
|
|
final size = _readElfNative(reader);
|
|
final link = _readElfWord(reader);
|
|
final info = _readElfWord(reader);
|
|
final addrAlign = _readElfNative(reader);
|
|
final entrySize = _readElfNative(reader);
|
|
return SectionHeaderEntry._(nameIndex, type, flags, addr, offset, size,
|
|
link, info, addrAlign, entrySize, reader.wordSize);
|
|
}
|
|
|
|
// sh_type constants from ELF specification.
|
|
static const _SHT_NULL = 0;
|
|
static const _SHT_PROGBITS = 1;
|
|
static const _SHT_SYMTAB = 2;
|
|
static const _SHT_STRTAB = 3;
|
|
static const _SHT_HASH = 5;
|
|
static const _SHT_DYNAMIC = 6;
|
|
static const _SHT_NOTE = 7;
|
|
static const _SHT_NOBITS = 8;
|
|
static const _SHT_DYNSYM = 11;
|
|
|
|
// sh_flags constants from ELF specification.
|
|
static const _SHF_WRITE = 0x1;
|
|
static const _SHF_ALLOC = 0x2;
|
|
static const _SHF_EXECINSTR = 0x4;
|
|
|
|
bool get isWritable => flags & _SHF_WRITE != 0;
|
|
bool get isAllocated => flags & _SHF_ALLOC != 0;
|
|
bool get isExecutable => flags & _SHF_EXECINSTR != 0;
|
|
|
|
bool get hasBits => type != _SHT_NOBITS;
|
|
|
|
void setName(StringTable nameTable) {
|
|
name = nameTable[nameIndex]!;
|
|
}
|
|
|
|
static const _typeStrings = <int, String>{
|
|
_SHT_NULL: 'SHT_NULL',
|
|
_SHT_PROGBITS: 'SHT_PROGBITS',
|
|
_SHT_SYMTAB: 'SHT_SYMTAB',
|
|
_SHT_STRTAB: 'SHT_STRTAB',
|
|
_SHT_HASH: 'SHT_HASH',
|
|
_SHT_DYNAMIC: 'SHT_DYNAMIC',
|
|
_SHT_NOTE: 'SHT_NOTE',
|
|
_SHT_NOBITS: 'SHT_NOBITS',
|
|
_SHT_DYNSYM: 'SHT_DYNSYM',
|
|
};
|
|
|
|
static String _typeToString(int type) =>
|
|
_typeStrings[type] ?? 'unknown (${paddedHex(type, 4)})';
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer.write('Name: ');
|
|
buffer
|
|
..write('"')
|
|
..write(name)
|
|
..write('" (@ ')
|
|
..write(nameIndex)
|
|
..writeln(')');
|
|
buffer
|
|
..write('Type: ')
|
|
..writeln(_typeToString(type))
|
|
..write('Flags: 0x')
|
|
..writeln(paddedHex(flags, wordSize))
|
|
..write('Address: 0x')
|
|
..writeln(paddedHex(addr, wordSize))
|
|
..write('Offset: 0x')
|
|
..writeln(paddedHex(offset, wordSize))
|
|
..write('Size: ')
|
|
..writeln(size)
|
|
..write('Link: ')
|
|
..writeln(link)
|
|
..write('Info: 0x')
|
|
..writeln(paddedHex(info, 4))
|
|
..write('Address alignment: 0x')
|
|
..writeln(paddedHex(addrAlign, wordSize))
|
|
..write('Entry size: ')
|
|
..write(entrySize);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// A list of [SectionHeaderEntry]s describing the [Section]s in the ELF file.
|
|
class SectionHeader {
|
|
final List<SectionHeaderEntry> entries;
|
|
|
|
SectionHeader._(this.entries);
|
|
|
|
static SectionHeader fromReader(Reader reader, ElfHeader header) {
|
|
final headerReader =
|
|
reader.shrink(header.sectionHeaderOffset, header.sectionHeaderSize);
|
|
final entries =
|
|
headerReader.readRepeated(SectionHeaderEntry.fromReader).toList();
|
|
final nameTableEntry = entries[header.sectionHeaderStringsIndex];
|
|
assert(nameTableEntry.type == SectionHeaderEntry._SHT_STRTAB);
|
|
return SectionHeader._(entries);
|
|
}
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
for (var i = 0; i < entries.length; i++) {
|
|
if (i != 0) {
|
|
buffer
|
|
..writeln()
|
|
..writeln();
|
|
}
|
|
buffer
|
|
..write('Entry ')
|
|
..write(i)
|
|
..writeln(':');
|
|
entries[i].writeToStringBuffer(buffer);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// A section in an ELF file.
|
|
///
|
|
/// Some sections correspond to segments from the [ProgramHeader] and contain
|
|
/// the information that will be loaded into memory for that segment, whereas
|
|
/// others include information, like debugging sections, that are not loaded
|
|
/// at runtime.
|
|
///
|
|
/// Only some sections are currently parsed by the ELF reader; most are left
|
|
/// unparsed as they are not needed for DWARF address translation.
|
|
class Section {
|
|
final SectionHeaderEntry headerEntry;
|
|
|
|
Section._(this.headerEntry);
|
|
|
|
static Section fromReader(Reader reader, SectionHeaderEntry entry) {
|
|
switch (entry.type) {
|
|
case SectionHeaderEntry._SHT_STRTAB:
|
|
return StringTable.fromReader(reader, entry);
|
|
case SectionHeaderEntry._SHT_SYMTAB:
|
|
return SymbolTable.fromReader(reader, entry);
|
|
case SectionHeaderEntry._SHT_DYNSYM:
|
|
return SymbolTable.fromReader(reader, entry);
|
|
case SectionHeaderEntry._SHT_NOTE:
|
|
return Note.fromReader(reader, entry);
|
|
case SectionHeaderEntry._SHT_DYNAMIC:
|
|
return DynamicTable.fromReader(reader, entry);
|
|
default:
|
|
return Section._(entry);
|
|
}
|
|
}
|
|
|
|
int get offset => headerEntry.offset;
|
|
int get virtualAddress => headerEntry.addr;
|
|
int get length => headerEntry.size;
|
|
|
|
// Convenience function for preparing a reader to read a particular section.
|
|
// Requires a reader for the entire ELF data where the reader's start is
|
|
// the start of the ELF data.
|
|
Reader shrink(Reader reader) => reader.shrink(offset, length);
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Section "')
|
|
..write(headerEntry.name)
|
|
..write('" is unparsed and ')
|
|
..write(length)
|
|
..writeln(' bytes long.');
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// A section that contains a single note.
|
|
class Note extends Section {
|
|
final int type;
|
|
final String name;
|
|
final Uint8List description;
|
|
|
|
Note._(super.headerEntry, this.type, this.name, this.description) : super._();
|
|
|
|
static Note fromReader(Reader originalReader, SectionHeaderEntry entry) {
|
|
final reader = originalReader.shrink(entry.offset, entry.size);
|
|
final nameLength = reader.readBytes(4);
|
|
final descriptionLength = reader.readBytes(4);
|
|
final type = reader.readBytes(4);
|
|
final nameEnd = reader.offset + nameLength;
|
|
final name = reader.readNullTerminatedString();
|
|
assert(reader.offset == nameEnd);
|
|
assert(reader.length - reader.offset == descriptionLength);
|
|
final descriptionStart = reader.offset;
|
|
final descriptionEnd = descriptionStart + descriptionLength;
|
|
final description =
|
|
Uint8List.sublistView(reader.bdata, descriptionStart, descriptionEnd);
|
|
return Note._(entry, type, name, description);
|
|
}
|
|
|
|
@override
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Section "')
|
|
..write(headerEntry.name)
|
|
..writeln('" is a note:');
|
|
buffer
|
|
..write(' Type: ')
|
|
..writeln(type);
|
|
buffer
|
|
..write(' Name: "')
|
|
..write(name)
|
|
..writeln('"');
|
|
buffer
|
|
..write(' Description: ')
|
|
..writeln(description);
|
|
}
|
|
}
|
|
|
|
/// A map from table offsets to strings, used to store names of ELF objects.
|
|
class StringTable extends Section implements DwarfContainerStringTable {
|
|
final Map<int, String> _entries;
|
|
|
|
StringTable._(super.headerEntry, this._entries) : super._();
|
|
|
|
static StringTable fromReader(Reader reader, SectionHeaderEntry entry) {
|
|
final sectionReader = reader.shrink(entry.offset, entry.size);
|
|
final entries = Map.fromEntries(sectionReader
|
|
.readRepeatedWithOffsets((r) => r.readNullTerminatedString()));
|
|
return StringTable._(entry, entries);
|
|
}
|
|
|
|
@override
|
|
String? operator [](int index) {
|
|
// Fast case: Index is for the start of a null terminated string.
|
|
if (_entries.containsKey(index)) {
|
|
return _entries[index];
|
|
}
|
|
// We can index into null terminated string entries for suffixes of
|
|
// that string, so do a linear search to find the appropriate entry.
|
|
for (final kv in _entries.entries) {
|
|
final start = index - kv.key;
|
|
if (start >= 0 && start <= kv.value.length) {
|
|
return kv.value.substring(start);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Section "')
|
|
..write(headerEntry.name)
|
|
..writeln('" is a string table:');
|
|
for (var key in _entries.keys) {
|
|
buffer
|
|
..write(' ')
|
|
..write(key)
|
|
..write(' => ')
|
|
..writeln(_entries[key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An enumeration of recognized symbol binding values used by the ELF format.
|
|
enum SymbolBinding {
|
|
// We only list the standard types here, not OS-specific ones.
|
|
STB_LOCAL(0, 'local'),
|
|
STB_GLOBAL(1, 'global'),
|
|
STB_WEAK(2, 'weak');
|
|
|
|
final int code;
|
|
final String description;
|
|
|
|
const SymbolBinding(this.code, this.description);
|
|
|
|
static SymbolBinding? fromCode(int code) {
|
|
for (final value in values) {
|
|
if (value.code == code) {
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// An enumeration of recognized symbol types used by the ELF format.
|
|
enum SymbolType {
|
|
// We only list the standard types here, not OS-specific ones.
|
|
STT_NOTYPE(0, 'notype'),
|
|
STT_OBJECT(1, 'object'),
|
|
STT_FUNC(2, 'function'),
|
|
STT_SECTION(3, 'section'),
|
|
STT_FILE(4, 'file'),
|
|
STT_COMMON(5, 'common'),
|
|
STT_TLS(6, 'thread-local');
|
|
|
|
final int code;
|
|
final String description;
|
|
|
|
const SymbolType(this.code, this.description);
|
|
|
|
static SymbolType? fromCode(int code) {
|
|
for (final value in values) {
|
|
if (value.code == code) {
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
enum SymbolVisibility {
|
|
// We only list the standard values here.
|
|
STV_DEFAULT(0, 'public'),
|
|
STV_INTERNAL(1, 'internal'),
|
|
STV_HIDDEN(2, 'hidden'),
|
|
STV_PROTECTED(3, 'protected');
|
|
|
|
final int code;
|
|
final String description;
|
|
|
|
const SymbolVisibility(this.code, this.description);
|
|
|
|
static SymbolVisibility? fromCode(int code) {
|
|
for (final value in values) {
|
|
if (value.code == code) {
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// A symbol in an ELF file, which names a portion of the virtual address space.
|
|
class Symbol implements DwarfContainerSymbol {
|
|
final int nameIndex;
|
|
final int info;
|
|
final int other;
|
|
final int sectionIndex;
|
|
@override
|
|
final int value;
|
|
final int size;
|
|
final int _wordSize;
|
|
@override
|
|
late final String name;
|
|
|
|
Symbol._(this.nameIndex, this.info, this.other, this.sectionIndex, this.value,
|
|
this.size, this._wordSize);
|
|
|
|
static Symbol fromReader(Reader reader) {
|
|
final wordSize = reader.wordSize;
|
|
final nameIndex = _readElfWord(reader);
|
|
late int info;
|
|
late int other;
|
|
late int sectionIndex;
|
|
if (wordSize == 8) {
|
|
info = reader.readByte();
|
|
other = reader.readByte();
|
|
sectionIndex = _readElfSection(reader);
|
|
}
|
|
final value = _readElfAddress(reader);
|
|
final size = _readElfNative(reader);
|
|
if (wordSize == 4) {
|
|
info = reader.readByte();
|
|
other = reader.readByte();
|
|
sectionIndex = _readElfSection(reader);
|
|
}
|
|
return Symbol._(
|
|
nameIndex, info, other, sectionIndex, value, size, wordSize);
|
|
}
|
|
|
|
SymbolBinding? get bind => SymbolBinding.fromCode(info >> 4);
|
|
SymbolType? get type => SymbolType.fromCode(info & 0x0f);
|
|
SymbolVisibility? get visibility => SymbolVisibility.fromCode(other & 0x03);
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('"')
|
|
..write(name)
|
|
..write('" => a ')
|
|
..write(bind?.description ?? '<binding unrecognized>')
|
|
..write(' ')
|
|
..write(type?.description ?? '<type unrecognized>')
|
|
..write(' ')
|
|
..write(visibility?.description ?? '<visibility unrecognized>')
|
|
..write(' symbol that points to ')
|
|
..write(size)
|
|
..write(' bytes at location 0x')
|
|
..write(paddedHex(value, _wordSize))
|
|
..write(' in section ')
|
|
..write(sectionIndex);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// A table of (static or dynamic) [Symbol]s.
|
|
class SymbolTable extends Section {
|
|
final List<Symbol> _entries;
|
|
final Map<String, Symbol> _nameCache;
|
|
|
|
SymbolTable._(super.headerEntry, this._entries)
|
|
: _nameCache = {},
|
|
super._();
|
|
|
|
static SymbolTable fromReader(Reader reader, SectionHeaderEntry entry) {
|
|
final sectionReader = reader.shrink(entry.offset, entry.size);
|
|
final entries = sectionReader.readRepeated(Symbol.fromReader).toList();
|
|
return SymbolTable._(entry, entries);
|
|
}
|
|
|
|
void _cacheNames(StringTable stringTable) {
|
|
_nameCache.clear();
|
|
for (final symbol in _entries) {
|
|
final index = symbol.nameIndex;
|
|
final name = stringTable[index];
|
|
if (name == null) {
|
|
throw FormatException('Index $index not found in string table');
|
|
}
|
|
symbol.name = name;
|
|
_nameCache[name] = symbol;
|
|
}
|
|
}
|
|
|
|
Iterable<String> get keys => _nameCache.keys;
|
|
Iterable<Symbol> get values => _entries;
|
|
Symbol? operator [](String name) => _nameCache[name];
|
|
bool containsKey(String name) => _nameCache.containsKey(name);
|
|
|
|
@override
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Section "')
|
|
..write(headerEntry.name)
|
|
..writeln('" is a symbol table:');
|
|
for (var symbol in _entries) {
|
|
buffer.write(' ');
|
|
symbol.writeToStringBuffer(buffer);
|
|
buffer.writeln();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents d_tag constants from ELF specification.
|
|
enum DynamicTableTag {
|
|
DT_NULL,
|
|
DT_NEEDED,
|
|
DT_PLTRELSZ,
|
|
DT_PLTGOT,
|
|
DT_HASH,
|
|
DT_STRTAB,
|
|
DT_SYMTAB,
|
|
DT_RELA,
|
|
DT_RELASZ,
|
|
DT_RELAENT,
|
|
DT_STRSZ,
|
|
DT_SYMENT,
|
|
// Later d_tag values are not currently used in Dart ELF files.
|
|
}
|
|
|
|
/// The dynamic table, which contains entries pointing to various relocated
|
|
/// addresses.
|
|
class DynamicTable extends Section {
|
|
// We don't use DynamicTableTag for the key so that we can handle ELF files
|
|
// that may use unknown (to us) tags.
|
|
final Map<int, int> _entries;
|
|
final int _wordSize;
|
|
|
|
DynamicTable._(super.headerEntry, this._entries, this._wordSize) : super._();
|
|
|
|
static DynamicTable fromReader(Reader reader, SectionHeaderEntry entry) {
|
|
final sectionReader = reader.shrink(entry.offset, entry.size);
|
|
final entries = <int, int>{};
|
|
while (true) {
|
|
// Each entry is a tag and a value, both native word sized.
|
|
final tag = _readElfNative(sectionReader);
|
|
final value = _readElfNative(sectionReader);
|
|
// A DT_NULL entry signifies the end of entries.
|
|
if (tag == DynamicTableTag.DT_NULL.index) break;
|
|
entries[tag] = value;
|
|
}
|
|
return DynamicTable._(entry, entries, sectionReader.wordSize);
|
|
}
|
|
|
|
int? operator [](DynamicTableTag tag) => _entries[tag.index];
|
|
bool containsKey(DynamicTableTag tag) => _entries.containsKey(tag.index);
|
|
|
|
// To avoid depending on EnumName.name from 2.15.
|
|
static const _tagStrings = {
|
|
DynamicTableTag.DT_NULL: 'DT_NULL',
|
|
DynamicTableTag.DT_NEEDED: 'DT_NEEDED',
|
|
DynamicTableTag.DT_PLTRELSZ: 'DT_PLTRELSZ',
|
|
DynamicTableTag.DT_PLTGOT: 'DT_PLTGOT',
|
|
DynamicTableTag.DT_HASH: 'DT_HASH',
|
|
DynamicTableTag.DT_STRTAB: 'DT_STRTAB',
|
|
DynamicTableTag.DT_SYMTAB: 'DT_SYMTAB',
|
|
DynamicTableTag.DT_RELA: 'DT_RELA',
|
|
DynamicTableTag.DT_RELASZ: 'DT_RELASZ',
|
|
DynamicTableTag.DT_STRSZ: 'DT_STRSZ',
|
|
DynamicTableTag.DT_SYMENT: 'DT_SYMENT',
|
|
};
|
|
static final _maxTagStringLength = (_tagStrings.values.toList()
|
|
..sort((s1, s2) => s2.length - s1.length))
|
|
.first
|
|
.length;
|
|
|
|
@override
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Section "')
|
|
..write(headerEntry.name)
|
|
..writeln('" is a dynamic table:');
|
|
for (var kv in _entries.entries) {
|
|
buffer.write(' ');
|
|
if (kv.key < DynamicTableTag.values.length) {
|
|
final tag = DynamicTableTag.values[kv.key];
|
|
buffer
|
|
..write(_tagStrings[tag]?.padRight(_maxTagStringLength))
|
|
..write(' => ');
|
|
switch (tag) {
|
|
// These are relocated addresses.
|
|
case DynamicTableTag.DT_HASH:
|
|
case DynamicTableTag.DT_PLTGOT:
|
|
case DynamicTableTag.DT_SYMTAB:
|
|
case DynamicTableTag.DT_STRTAB:
|
|
case DynamicTableTag.DT_RELA:
|
|
buffer
|
|
..write('0x')
|
|
..writeln(paddedHex(kv.value, _wordSize));
|
|
break;
|
|
// Other entries are just values or offsets.
|
|
default:
|
|
buffer.writeln(kv.value);
|
|
}
|
|
} else {
|
|
buffer
|
|
..write('Unknown tag ')
|
|
..write(kv.key)
|
|
..write(' => ')
|
|
..writeln(kv.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Information parsed from an Executable and Linking Format (ELF) file.
|
|
class Elf extends DwarfContainer {
|
|
final ElfHeader _header;
|
|
final ProgramHeader _programHeader;
|
|
final SectionHeader _sectionHeader;
|
|
final Map<SectionHeaderEntry, Section> _sections;
|
|
final Map<String, Set<Section>> _sectionsByName;
|
|
final StringTable? _debugStringTable;
|
|
final StringTable? _debugLineStringTable;
|
|
|
|
Elf._(this._header, this._programHeader, this._sectionHeader, this._sections,
|
|
this._sectionsByName, this._debugStringTable, this._debugLineStringTable);
|
|
|
|
/// Creates an [Elf] from [bytes].
|
|
///
|
|
/// Returns null if the file does not start with the ELF magic number.
|
|
static Elf? fromBuffer(Uint8List bytes) =>
|
|
Elf.fromReader(Reader.fromTypedData(bytes));
|
|
|
|
/// Creates an [Elf] from the file at [path].
|
|
///
|
|
/// Returns null if the file does not start with the ELF magic number.
|
|
static Elf? fromFile(String path) => Elf.fromReader(Reader.fromFile(path));
|
|
|
|
Iterable<Section> namedSections(String name) =>
|
|
_sectionsByName[name] ?? <Section>[];
|
|
|
|
/// Checks that the contents of a given section have valid addresses when the
|
|
/// file contents for the corresponding segment is loaded into memory.
|
|
///
|
|
/// Returns false for sections that are not allocated or where the address
|
|
/// does not correspond to file contents (i.e., NOBITS sections).
|
|
bool sectionHasValidSegmentAddresses(Section section) {
|
|
final headerEntry = section.headerEntry;
|
|
if (!headerEntry.isAllocated || !headerEntry.hasBits) return false;
|
|
final segment = _programHeader.loadSegmentFor(headerEntry.addr);
|
|
if (segment == null) return false;
|
|
return (headerEntry.addr < (segment.vaddr + segment.filesz)) &&
|
|
(headerEntry.addr + headerEntry.size) <=
|
|
(segment.vaddr + segment.filesz);
|
|
}
|
|
|
|
/// Lookup of a dynamic symbol by name.
|
|
///
|
|
/// Returns -1 if there is no dynamic symbol that matches [name].
|
|
Symbol? dynamicSymbolFor(String name) {
|
|
for (final section in namedSections('.dynsym')) {
|
|
final dynsym = section as SymbolTable;
|
|
if (dynsym.containsKey(name)) return dynsym[name];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Returns an iterable of the symbols in the dynamic symbol table(s).
|
|
/// The ordering of the symbols is not guaranteed.
|
|
Iterable<Symbol> get dynamicSymbols sync* {
|
|
for (final section in namedSections('.dynsym')) {
|
|
final dynsym = section as SymbolTable;
|
|
for (final symbol in dynsym.values) {
|
|
yield symbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reverse lookup of the static symbol that contains the given virtual
|
|
/// address. Returns null if no static symbol matching the address is found.
|
|
@override
|
|
Symbol? staticSymbolAt(int address) {
|
|
Symbol? bestSym;
|
|
for (final section in namedSections('.symtab')) {
|
|
final table = section as SymbolTable;
|
|
for (final symbol in table.values) {
|
|
final start = symbol.value;
|
|
if (start > address) continue;
|
|
// If given a non-zero extent of a symbol, make sure the address is
|
|
// within the extent.
|
|
if (symbol.size > 0 && (start + symbol.size <= address)) continue;
|
|
// Pick the symbol with a start closest to the given address.
|
|
if (bestSym == null || (bestSym.value < start)) {
|
|
bestSym = symbol;
|
|
}
|
|
}
|
|
}
|
|
return bestSym;
|
|
}
|
|
|
|
/// Returns an iterable of the symbols in the static symbol table(s).
|
|
/// The ordering of the symbols is not guaranteed.
|
|
@override
|
|
Iterable<Symbol> get staticSymbols sync* {
|
|
for (final section in namedSections('.symtab')) {
|
|
final symtab = section as SymbolTable;
|
|
for (final symbol in symtab.values) {
|
|
yield symbol;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates an [Elf] from the data pointed to by [reader].
|
|
///
|
|
/// After succesful completion, the [endian] and [wordSize] fields of the
|
|
/// reader are set to match the values read from the ELF header. The position
|
|
/// of the reader will be unchanged.
|
|
///
|
|
/// Returns null if the file does not start with the ELF magic number.
|
|
static Elf? fromReader(Reader elfReader) {
|
|
// ELF files contain absolute offsets from the start of the file, so
|
|
// make sure we have a reader that a) makes no assumptions about the
|
|
// endianness or word size, since we'll read those in the header and b)
|
|
// has an internal offset of 0 so absolute offsets can be used directly.
|
|
final reader = Reader.fromTypedData(
|
|
ByteData.sublistView(elfReader.bdata, elfReader.offset));
|
|
final header = ElfHeader.fromReader(reader);
|
|
// Only happens if the file didn't start with the expected magic number.
|
|
if (header == null) return null;
|
|
// At this point, the endianness and wordSize should have been set
|
|
// during ElfHeader.fromReader.
|
|
final programHeader = ProgramHeader.fromReader(reader, header);
|
|
final sectionHeader = SectionHeader.fromReader(reader, header);
|
|
final sections = <SectionHeaderEntry, Section>{};
|
|
for (var i = 0; i < sectionHeader.entries.length; i++) {
|
|
final entry = sectionHeader.entries[i];
|
|
sections[entry] = Section.fromReader(reader, entry);
|
|
}
|
|
// Now set up the by-name section table and cache the names in the section
|
|
// header entries.
|
|
if (header.sectionHeaderStringsIndex < 0 ||
|
|
header.sectionHeaderStringsIndex >= sectionHeader.entries.length) {
|
|
throw FormatException('Section header string table index invalid');
|
|
}
|
|
final sectionHeaderStringTableEntry =
|
|
sectionHeader.entries[header.sectionHeaderStringsIndex];
|
|
final sectionHeaderStringTable =
|
|
sections[sectionHeaderStringTableEntry] as StringTable?;
|
|
if (sectionHeaderStringTable == null) {
|
|
throw FormatException(
|
|
'No section for entry $sectionHeaderStringTableEntry');
|
|
}
|
|
final sectionsByName = <String, Set<Section>>{};
|
|
for (final entry in sectionHeader.entries) {
|
|
final section = sections[entry];
|
|
if (section == null) {
|
|
throw FormatException('No section found for entry $entry');
|
|
}
|
|
entry.setName(sectionHeaderStringTable);
|
|
sectionsByName.putIfAbsent(entry.name, () => {}).add(section);
|
|
}
|
|
void cacheSymbolNames(String stringTableTag, String symbolTableTag) {
|
|
final stringTables = sectionsByName[stringTableTag]?.cast<StringTable>();
|
|
if (stringTables == null) {
|
|
return;
|
|
}
|
|
final stringTableMap =
|
|
Map.fromEntries(stringTables.map((s) => MapEntry(s.headerEntry, s)));
|
|
final symbolTables = sectionsByName[symbolTableTag]?.cast<SymbolTable>();
|
|
if (symbolTables == null) {
|
|
return;
|
|
}
|
|
for (final symbolTable in symbolTables) {
|
|
final link = symbolTable.headerEntry.link;
|
|
final entry = sectionHeader.entries[link];
|
|
final stringTable = stringTableMap[entry];
|
|
if (stringTable == null) {
|
|
throw FormatException(
|
|
'String table not found at section header entry $link');
|
|
}
|
|
symbolTable._cacheNames(stringTable);
|
|
}
|
|
}
|
|
|
|
cacheSymbolNames('.strtab', '.symtab');
|
|
cacheSymbolNames('.dynstr', '.dynsym');
|
|
|
|
StringTable? debugStringTable;
|
|
if (sectionsByName.containsKey('.debug_str')) {
|
|
// Stored as PROGBITS, so need to explicitly parse as a string table.
|
|
debugStringTable = StringTable.fromReader(
|
|
reader, sectionsByName['.debug_str']!.single.headerEntry);
|
|
}
|
|
|
|
StringTable? debugLineStringTable;
|
|
if (sectionsByName.containsKey('.debug_line_str')) {
|
|
// Stored as PROGBITS, so need to explicitly parse as a string table.
|
|
debugLineStringTable = StringTable.fromReader(
|
|
reader, sectionsByName['.debug_line_str']!.single.headerEntry);
|
|
}
|
|
|
|
// Set the wordSize and endian of the original reader before returning.
|
|
elfReader.wordSize = reader.wordSize;
|
|
elfReader.endian = reader.endian;
|
|
return Elf._(header, programHeader, sectionHeader, sections, sectionsByName,
|
|
debugStringTable, debugLineStringTable);
|
|
}
|
|
|
|
@override
|
|
String? get architecture => _header.architecture;
|
|
|
|
@override
|
|
Reader? abbreviationsTableReader(Reader containerReader) {
|
|
final sections = namedSections('.debug_abbrev');
|
|
if (sections.length != 1) return null;
|
|
return sections.single.shrink(containerReader);
|
|
}
|
|
|
|
@override
|
|
Reader? lineNumberInfoReader(Reader containerReader) {
|
|
final sections = namedSections('.debug_line');
|
|
if (sections.length != 1) return null;
|
|
return sections.single.shrink(containerReader);
|
|
}
|
|
|
|
@override
|
|
Reader? debugInfoReader(Reader containerReader) {
|
|
final sections = namedSections('.debug_info');
|
|
if (sections.length != 1) return null;
|
|
return sections.single.shrink(containerReader);
|
|
}
|
|
|
|
@override
|
|
int? get vmStartAddress => dynamicSymbolFor(constants.vmSymbolName)?.value;
|
|
|
|
@override
|
|
int? get isolateStartAddress =>
|
|
dynamicSymbolFor(constants.isolateSymbolName)?.value;
|
|
|
|
@override
|
|
String? get buildId {
|
|
final sections = namedSections(constants.buildIdSectionName);
|
|
if (sections.isEmpty) return null;
|
|
final note = sections.single as Note;
|
|
if (note.type != constants.buildIdNoteType) return null;
|
|
if (note.name != constants.buildIdNoteName) return null;
|
|
return note.description
|
|
.map((i) => i.toRadixString(16).padLeft(2, '0'))
|
|
.join();
|
|
}
|
|
|
|
@override
|
|
DwarfContainerStringTable? get debugStringTable => _debugStringTable;
|
|
|
|
@override
|
|
DwarfContainerStringTable? get debugLineStringTable => _debugLineStringTable;
|
|
|
|
@override
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln(' ELF header information')
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln();
|
|
_header.writeToStringBuffer(buffer);
|
|
buffer
|
|
..writeln()
|
|
..writeln()
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln(' Program header information')
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln();
|
|
_programHeader.writeToStringBuffer(buffer);
|
|
buffer
|
|
..writeln()
|
|
..writeln()
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln(' Section header information')
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln();
|
|
_sectionHeader.writeToStringBuffer(buffer);
|
|
buffer
|
|
..writeln()
|
|
..writeln()
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln(' Section information')
|
|
..writeln('-----------------------------------------------------')
|
|
..writeln();
|
|
for (final entry in _sectionHeader.entries) {
|
|
_sections[entry]!.writeToStringBuffer(buffer);
|
|
buffer.writeln();
|
|
}
|
|
}
|
|
}
|