Files
sdk/pkg/native_stack_traces/lib/src/elf.dart
T
Tess Strickland 6f83a5ff9b [vm/aot] Add a GNU build ID to direct-to-ELF snapshots.
For proper crashpad integration, we need to generate a build ID, as the
build ID generated by crashpad if there is not one will be a simple XOR
of the first text page, which rarely changes for Dart snapshots.
Assembly snapshots already have a build ID included by the assembler, so
we currently only do this for ELF snapshots.

Currently the build ID is a 128-bit hash value that is four separate
32-bit hash values concatenated together. Those hash values come from
the contents of the VM and isolate .text and .rodata sections.

This change also contains work to separate out the concepts of sections
and segments in the ELF builder. Now, consecutive allocated sections
with the same write and execute flags are combined into a single PT_LOAD
segment when possible, which reduces the padding needed to ensure that
segments start on page boundaries in ELF snapshots.

Bug: https://github.com/dart-lang/sdk/issues/42020
Change-Id: I42a837dae665a3902d881b8d151b49ede87d6c67
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-release-simarm_x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150625
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2020-06-16 12:46:24 +00:00

963 lines
29 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.
import 'dart:typed_data';
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) {
return _readElfBytes(reader, reader.wordSize, reader.wordSize);
}
// Reads an Elf{32,64}_Off.
int _readElfOffset(Reader reader) {
return _readElfBytes(reader, reader.wordSize, reader.wordSize);
}
// Reads an Elf{32,64}_Half.
int _readElfHalf(Reader reader) {
return _readElfBytes(reader, 2, 2);
}
// Reads an Elf{32,64}_Word.
int _readElfWord(Reader reader) {
return _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) {
return _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 header of the ELF file, which includes information necessary to parse
/// the rest of the file.
class ElfHeader {
final int wordSize;
final Endian endian;
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.wordSize,
this.endian,
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 fileSize = reader.length;
for (final sigByte in _ELFMAG.codeUnits) {
if (reader.readByte() != sigByte) {
return null;
}
}
int wordSize;
switch (reader.readByte()) {
case _ELFCLASS32:
wordSize = 4;
break;
case _ELFCLASS64:
wordSize = 8;
break;
default:
throw FormatException("Unexpected e_ident[EI_CLASS] value");
}
final calculatedHeaderSize = 0x18 + 3 * wordSize + 0x10;
if (fileSize < calculatedHeaderSize) {
throw FormatException("ELF file too small for header: "
"file size ${fileSize} < "
"calculated header size $calculatedHeaderSize");
}
Endian endian;
switch (reader.readByte()) {
case _ELFDATA2LSB:
endian = Endian.little;
break;
case _ELFDATA2MSB:
endian = Endian.big;
break;
default:
throw FormatException("Unexpected e_indent[EI_DATA] value");
}
if (reader.readByte() != 0x01) {
throw FormatException("Unexpected e_ident[EI_VERSION] value");
}
// After this point, we need the reader to be correctly set up re: word
// size and endianness, since we start reading more than single bytes.
reader.endian = endian;
reader.wordSize = wordSize;
// Skip rest of e_ident/e_type/e_machine, i.e. move to e_version.
reader.seek(0x14, absolute: true);
if (_readElfWord(reader) != 0x01) {
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._(
wordSize,
endian,
entry,
flags,
headerSize,
programHeaderOffset,
sectionHeaderOffset,
programHeaderCount,
sectionHeaderCount,
programHeaderEntrySize,
sectionHeaderEntrySize,
sectionHeaderStringsIndex);
}
int get programHeaderSize => programHeaderCount * programHeaderEntrySize;
int get sectionHeaderSize => sectionHeaderCount * sectionHeaderEntrySize;
// Constants used within the ELF specification.
static const _ELFMAG = "\x7fELF";
static const _ELFCLASS32 = 0x01;
static const _ELFCLASS64 = 0x02;
static const _ELFDATA2LSB = 0x01;
static const _ELFDATA2MSB = 0x02;
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('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_PHDR = 6;
ProgramHeaderEntry._(this.type, this.flags, this.offset, this.vaddr,
this.paddr, this.filesz, this.memsz, this.align, this.wordSize);
static ProgramHeaderEntry fromReader(Reader reader) {
assert(reader.wordSize == 4 || reader.wordSize == 8);
final type = _readElfWord(reader);
int flags;
if (reader.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 (reader.wordSize == 4) {
flags = _readElfWord(reader);
}
final align = _readElfNative(reader);
return ProgramHeaderEntry._(type, flags, offset, vaddr, paddr, filesz,
memsz, align, reader.wordSize);
}
static const _typeStrings = <int, String>{
_PT_NULL: "PT_NULL",
_PT_LOAD: "PT_LOAD",
_PT_DYNAMIC: "PT_DYNAMIC",
_PT_PHDR: "PT_PHDR",
};
static String _typeToString(int type) {
if (_typeStrings.containsKey(type)) {
return _typeStrings[type];
}
return "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));
}
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];
static ProgramHeader fromReader(Reader reader, ElfHeader header) {
final programReader = reader.refocusedCopy(
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);
}
}
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;
String _cachedName;
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;
void setName(StringTable nameTable) {
_cachedName = nameTable[nameIndex];
}
String get name => _cachedName != null ? _cachedName : '<${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) {
if (_typeStrings.containsKey(type)) {
return _typeStrings[type];
}
return "unknown (${paddedHex(type, 4)})";
}
void writeToStringBuffer(StringBuffer buffer) {
buffer.write('Name: ');
if (_cachedName != null) {
buffer
..write('"')
..write(name)
..write('" (@ ')
..write(nameIndex)
..writeln(')');
} else {
buffer.writeln(name);
}
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);
}
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.refocusedCopy(
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);
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.
Reader refocusedCopy(Reader reader) => reader.refocusedCopy(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._(entry, this.type, this.name, this.description) : super._(entry);
static Note fromReader(Reader originalReader, SectionHeaderEntry entry) {
final reader = originalReader.refocusedCopy(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);
}
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);
}
@override
String toString() {
final buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}
/// A map from table offsets to strings, used to store names of ELF objects.
class StringTable extends Section {
final _entries;
StringTable._(entry, this._entries) : super._(entry);
static StringTable fromReader(Reader reader, SectionHeaderEntry entry) {
final sectionReader = reader.refocusedCopy(entry.offset, entry.size);
final entries = Map.fromEntries(sectionReader
.readRepeatedWithOffsets((r) => r.readNullTerminatedString()));
return StringTable._(entry, entries);
}
String operator [](int index) => _entries[index];
bool containsKey(int index) => _entries.containsKey(index);
@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]);
}
}
}
enum SymbolBinding {
STB_LOCAL,
STB_GLOBAL,
}
enum SymbolType {
STT_NOTYPE,
STT_OBJECT,
STT_FUNC,
}
enum SymbolVisibility {
STV_DEFAULT,
STV_INTERNAL,
STV_HIDDEN,
STV_PROTECTED,
}
/// A symbol in an ELF file, which names a portion of the virtual address space.
class Symbol {
final int nameIndex;
final int info;
final int other;
final int sectionIndex;
final int value;
final int size;
final int _wordSize;
String name;
Symbol._(this.nameIndex, this.info, this.other, this.sectionIndex, this.value,
this.size, this._wordSize);
static Symbol fromReader(Reader reader) {
final nameIndex = _readElfWord(reader);
int info;
int other;
int sectionIndex;
if (reader.wordSize == 8) {
info = reader.readByte();
other = reader.readByte();
sectionIndex = _readElfSection(reader);
}
final value = _readElfAddress(reader);
final size = _readElfNative(reader);
if (reader.wordSize == 4) {
info = reader.readByte();
other = reader.readByte();
sectionIndex = _readElfSection(reader);
}
return Symbol._(
nameIndex, info, other, sectionIndex, value, size, reader.wordSize);
}
void _cacheNameFromStringTable(StringTable table) {
if (!table.containsKey(nameIndex)) {
throw FormatException("Index $nameIndex not found in string table");
}
name = table[nameIndex];
}
SymbolBinding get bind => SymbolBinding.values[info >> 4];
SymbolType get type => SymbolType.values[info & 0x0f];
SymbolVisibility get visibility => SymbolVisibility.values[other & 0x03];
void writeToStringBuffer(StringBuffer buffer) {
if (name != null) {
buffer..write('"')..write(name)..write('" =>');
} else {
buffer..write('<')..write(nameIndex)..write('> =>');
}
switch (bind) {
case SymbolBinding.STB_GLOBAL:
buffer..write(' a global');
break;
case SymbolBinding.STB_LOCAL:
buffer..write(' a local');
break;
}
switch (visibility) {
case SymbolVisibility.STV_DEFAULT:
break;
case SymbolVisibility.STV_HIDDEN:
buffer..write(' hidden');
break;
case SymbolVisibility.STV_INTERNAL:
buffer..write(' internal');
break;
case SymbolVisibility.STV_PROTECTED:
buffer..write(' protected');
break;
}
buffer
..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._(SectionHeaderEntry entry, this._entries)
: _nameCache = {},
super._(entry);
static SymbolTable fromReader(Reader reader, SectionHeaderEntry entry) {
final sectionReader = reader.refocusedCopy(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) {
symbol._cacheNameFromStringTable(stringTable);
_nameCache[symbol.name] = symbol;
}
}
Iterable<String> get keys => _nameCache.keys;
Iterable<Symbol> get values => _nameCache.values;
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();
}
}
}
/// Information parsed from an Executable and Linking Format (ELF) file.
class Elf {
final ElfHeader _header;
final ProgramHeader _programHeader;
final SectionHeader _sectionHeader;
final Map<SectionHeaderEntry, Section> _sections;
final Map<String, Set<Section>> _sectionsByName;
Elf._(this._header, this._programHeader, this._sectionHeader, this._sections,
this._sectionsByName);
/// 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];
/// Lookup of a dynamic symbol by name.
///
/// Returns -1 if there is no dynamic symbol that matches [name].
Symbol dynamicSymbolFor(String name) {
for (final SymbolTable dynsym in namedSections(".dynsym")) {
if (dynsym.containsKey(name)) return dynsym[name];
}
return null;
}
/// Reverse lookup of the static symbol that contains the given virtual
/// address. Returns null if no static symbol matching the address is found.
Symbol staticSymbolAt(int address) {
for (final SymbolTable table in namedSections('.symtab')) {
for (final symbol in table.values) {
final start = symbol.value;
final end = start + symbol.size;
if (start <= address && address < end) return symbol;
}
}
return null;
}
/// 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.bdata.offsetInBytes + 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.
assert(reader.endian != null && reader.wordSize != null);
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.
final StringTable sectionHeaderStringTable =
sections[sectionHeader.entries[header.sectionHeaderStringsIndex]];
final sectionsByName = <String, Set<Section>>{};
for (final entry in sectionHeader.entries) {
entry.setName(sectionHeaderStringTable);
sectionsByName.putIfAbsent(entry.name, () => {}).add(sections[entry]);
}
void _cacheSymbolNames(String stringTableTag, String symbolTableTag) {
final stringTables = Map.fromEntries(sectionsByName[stringTableTag]
.map((s) => MapEntry(s.headerEntry, s)));
for (final SymbolTable symbolTable in sectionsByName[symbolTableTag]) {
final link = symbolTable.headerEntry.link;
final entry = sectionHeader.entries[link];
if (!stringTables.containsKey(entry)) {
throw FormatException(
"String table not found at section header entry ${link}");
}
symbolTable._cacheNames(stringTables[entry]);
}
}
_cacheSymbolNames('.strtab', '.symtab');
_cacheSymbolNames('.dynstr', '.dynsym');
// 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);
}
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();
}
}
@override
String toString() {
StringBuffer buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}