blob: d46a5076dc7eebf09eec51b0d26ad9b832ef427a [file] [log] [blame]
// Copyright (c) 2022, 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 'package:path/path.dart' as path;
import 'constants.dart' as constants;
import 'dwarf_container.dart';
import 'reader.dart';
int _readMachOUint8(Reader reader) => reader.readByte(signed: false);
int _readMachOUint16(Reader reader) => reader.readBytes(2, signed: false);
int _readMachOUint32(Reader reader) => reader.readBytes(4, signed: false);
int _readMachOUword(Reader reader) =>
reader.readBytes(reader.wordSize, signed: false);
class StringTable implements DwarfContainerStringTable {
final Map<int, String> _stringsByOffset;
StringTable._(this._stringsByOffset);
static StringTable fromReader(Reader reader) => StringTable._(Map.fromEntries(
reader.readRepeatedWithOffsets((r) => r.readNullTerminatedString())));
@override
String? operator [](int index) {
// Fast case: Index is for the start of a null terminated string.
if (_stringsByOffset.containsKey(index)) {
return _stringsByOffset[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 _stringsByOffset.entries) {
final start = index - kv.key;
if (start >= 0 && start <= kv.value.length) {
return kv.value.substring(start);
}
}
return null;
}
void writeToStringBuffer(StringBuffer buffer) {
for (final k in _stringsByOffset.keys) {
buffer
..write(k.toString().padLeft(8, ' '))
..write(' => ')
..writeln(_stringsByOffset[k]);
}
}
@override
String toString() {
final buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}
class Symbol implements DwarfContainerSymbol {
final int index;
final int type;
final int sect;
final int desc;
@override
final int value;
@override
late final String name;
Symbol._(this.index, this.type, this.sect, this.desc, this.value);
static Symbol fromReader(Reader reader) {
final index = _readMachOUint32(reader);
final type = _readMachOUint8(reader);
final sect = _readMachOUint8(reader);
final desc = _readMachOUint16(reader);
final value = _readMachOUword(reader);
return Symbol._(index, type, sect, desc, value);
}
}
class SymbolTable {
final Map<String, Symbol> _symbols;
SymbolTable._(this._symbols);
static SymbolTable fromReader(
Reader reader, int nsyms, StringTable stringTable) {
final symbols = <String, Symbol>{};
for (int i = 0; i < nsyms; i++) {
final symbol = Symbol.fromReader(reader);
final index = symbol.index;
final name = stringTable[index];
if (name == null) {
throw FormatException('Index $index not found in string table');
}
symbol.name = name;
symbols[name] = symbol;
}
return SymbolTable._(symbols);
}
Iterable<String> get keys => _symbols.keys;
Iterable<Symbol> get values => _symbols.values;
Symbol? operator [](String name) => _symbols[name];
bool containsKey(String name) => _symbols.containsKey(name);
}
class LoadCommand {
final int cmd;
final int cmdsize;
LoadCommand._(this.cmd, this.cmdsize);
static const LC_SEGMENT = 0x1;
static const LC_SYMTAB = 0x2;
static const LC_SEGMENT_64 = 0x19;
static LoadCommand fromReader(Reader reader) {
final start = reader.offset; // cmdsize includes size of cmd and cmdsize.
final cmd = _readMachOUint32(reader);
final cmdsize = _readMachOUint32(reader);
LoadCommand command = LoadCommand._(cmd, cmdsize);
switch (cmd) {
case LC_SEGMENT:
case LC_SEGMENT_64:
command = SegmentCommand.fromReader(reader, cmd, cmdsize);
break;
case LC_SYMTAB:
command = SymbolTableCommand.fromReader(reader, cmd, cmdsize);
break;
default:
break;
}
reader.seek(start + cmdsize, absolute: true);
return command;
}
void writeToStringBuffer(StringBuffer buffer) {
buffer
..write('Uninterpreted command 0x')
..write(cmd.toRadixString(16))
..write(' of size ')
..writeln(cmdsize);
}
@override
String toString() {
StringBuffer buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}
class SegmentCommand extends LoadCommand {
final String segname;
final int vmaddr;
final int vmsize;
final int fileoff;
final int filesize;
final int maxprot;
final int initprot;
final int nsects;
final int flags;
final Map<String, Section> sections;
SegmentCommand._(
int cmd,
int cmdsize,
this.segname,
this.vmaddr,
this.vmsize,
this.fileoff,
this.filesize,
this.maxprot,
this.initprot,
this.nsects,
this.flags,
this.sections)
: super._(cmd, cmdsize);
static SegmentCommand fromReader(Reader reader, int cmd, int cmdsize) {
final segname = reader.readFixedLengthNullTerminatedString(16);
final vmaddr = _readMachOUword(reader);
final vmsize = _readMachOUword(reader);
final fileoff = _readMachOUword(reader);
final filesize = _readMachOUword(reader);
final maxprot = _readMachOUint32(reader);
final initprot = _readMachOUint32(reader);
final nsects = _readMachOUint32(reader);
final flags = _readMachOUint32(reader);
final sections = <String, Section>{};
for (int i = 0; i < nsects; i++) {
final section = Section.fromReader(reader);
sections[section.sectname] = section;
}
return SegmentCommand._(cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
filesize, maxprot, initprot, nsects, flags, sections);
}
@override
void writeToStringBuffer(StringBuffer buffer) {
buffer
..write('Segment "')
..write(segname)
..write('" of size ')
..write(filesize)
..write(' at offset 0x')
..writeln(fileoff.toRadixString(16));
buffer.writeln('Sections:');
for (final section in sections.values) {
section.writeToStringBuffer(buffer);
buffer.writeln();
}
}
}
class Section {
String sectname;
String segname;
int addr;
int size;
int offset;
int align;
int reloff;
int nreloc;
int flags;
int reserved1;
int reserved2;
int? reserved3;
Section._(
this.sectname,
this.segname,
this.addr,
this.size,
this.offset,
this.align,
this.reloff,
this.nreloc,
this.flags,
this.reserved1,
this.reserved2,
this.reserved3);
static Section fromReader(Reader reader) {
final sectname = reader.readFixedLengthNullTerminatedString(16);
final segname = reader.readFixedLengthNullTerminatedString(16);
final addr = _readMachOUword(reader);
final size = _readMachOUword(reader);
final offset = _readMachOUint32(reader);
final align = _readMachOUint32(reader);
final reloff = _readMachOUint32(reader);
final nreloc = _readMachOUint32(reader);
final flags = _readMachOUint32(reader);
final reserved1 = _readMachOUint32(reader);
final reserved2 = _readMachOUint32(reader);
final reserved3 = (reader.wordSize == 8) ? _readMachOUint32(reader) : null;
return Section._(sectname, segname, addr, size, offset, align, reloff,
nreloc, flags, reserved1, reserved2, reserved3);
}
Reader refocus(Reader reader) => reader.refocusedCopy(offset, size);
void writeToStringBuffer(StringBuffer buffer) {
buffer
..write('Section "')
..write(sectname)
..write('" of size ')
..write(size)
..write(' at offset 0x')
..write(paddedHex(offset, 4));
}
@override
String toString() {
StringBuffer buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}
class SymbolTableCommand extends LoadCommand {
final int _symoff;
final int _nsyms;
final int _stroff;
final int _strsize;
SymbolTableCommand._(int cmd, int cmdsize, this._symoff, this._nsyms,
this._stroff, this._strsize)
: super._(cmd, cmdsize);
static SymbolTableCommand fromReader(Reader reader, int cmd, int cmdsize) {
final symoff = _readMachOUint32(reader);
final nsyms = _readMachOUint32(reader);
final stroff = _readMachOUint32(reader);
final strsize = _readMachOUint32(reader);
return SymbolTableCommand._(cmd, cmdsize, symoff, nsyms, stroff, strsize);
}
SymbolTable load(Reader reader) {
final stringTable =
StringTable.fromReader(reader.refocusedCopy(_stroff, _strsize));
return SymbolTable.fromReader(
reader.refocusedCopy(_symoff), _nsyms, stringTable);
}
@override
void writeToStringBuffer(StringBuffer buffer) {
buffer
..write('Symbol table with ')
..write(_nsyms)
..write(' symbols of size ')
..writeln(cmdsize);
}
}
class MachOHeader {
final int magic;
final int cputype;
final int cpusubtype;
final int filetype;
final int ncmds;
final int sizeofcmds;
final int flags;
final int? reserved;
final int size;
MachOHeader._(this.magic, this.cputype, this.cpusubtype, this.filetype,
this.ncmds, this.sizeofcmds, this.flags, this.reserved, this.size);
static const _MH_MAGIC = 0xfeedface;
static const _MH_CIGAM = 0xcefaedfe;
static const _MH_MAGIC_64 = 0xfeedfacf;
static const _MH_CIGAM_64 = 0xcffaedfe;
static MachOHeader? fromReader(Reader reader) {
final start = reader.offset;
// Initially assume host endianness.
reader.endian = Endian.host;
final magic = _readMachOUint32(reader);
if (magic == _MH_MAGIC || magic == _MH_CIGAM) {
reader.wordSize = 4;
} else if (magic == _MH_MAGIC_64 || magic == _MH_CIGAM_64) {
reader.wordSize = 8;
} else {
// Not an expected magic value, so not a supported Mach-O file.
return null;
}
if (magic == _MH_CIGAM || magic == _MH_CIGAM_64) {
reader.endian = Endian.host == Endian.big ? Endian.little : Endian.big;
}
final cputype = _readMachOUint32(reader);
final cpusubtype = _readMachOUint32(reader);
final filetype = _readMachOUint32(reader);
final ncmds = _readMachOUint32(reader);
final sizeofcmds = _readMachOUint32(reader);
final flags = _readMachOUint32(reader);
final reserved = reader.wordSize == 8 ? _readMachOUint32(reader) : null;
final size = reader.offset - start;
return MachOHeader._(magic, cputype, cpusubtype, filetype, ncmds,
sizeofcmds, flags, reserved, size);
}
void writeToStringBuffer(StringBuffer buffer) {
buffer
..write('Magic: 0x')
..writeln(paddedHex(magic, 4));
buffer
..write('Cpu Type: 0x')
..writeln(paddedHex(cputype, 4));
buffer
..write('Cpu Subtype: 0x')
..writeln(paddedHex(cpusubtype, 4));
buffer
..write('Filetype: 0x')
..writeln(paddedHex(filetype, 4));
buffer
..write('Number of commands: ')
..writeln(ncmds);
buffer
..write('Size of commands: ')
..writeln(sizeofcmds);
buffer
..write('Flags: 0x')
..writeln(paddedHex(flags, 4));
if (reserved != null) {
buffer
..write('Reserved: 0x')
..writeln(paddedHex(reserved!, 4));
}
}
@override
String toString() {
final buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}
class MachO implements DwarfContainer {
final MachOHeader _header;
final List<LoadCommand> _commands;
final SymbolTable _symbolTable;
final SegmentCommand _dwarfSegment;
final StringTable? _dwarfStringTable;
MachO._(this._header, this._commands, this._symbolTable, this._dwarfSegment,
this._dwarfStringTable);
static MachO? fromReader(Reader machOReader) {
// MachO 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(machOReader.bdata,
machOReader.bdata.offsetInBytes + machOReader.offset));
final header = MachOHeader.fromReader(reader);
if (header == null) return null;
final commandReader =
reader.refocusedCopy(reader.offset, header.sizeofcmds);
final commands =
List.of(commandReader.readRepeated(LoadCommand.fromReader));
assert(commands.length == header.ncmds);
final symbolTable =
commands.whereType<SymbolTableCommand>().single.load(reader);
final dwarfSegment = commands
.whereType<SegmentCommand?>()
.firstWhere((sc) => sc!.segname == '__DWARF', orElse: () => null);
if (dwarfSegment == null) {
print("No DWARF information in Mach-O file");
return null;
}
final dwarfStringTableSection = dwarfSegment.sections['__debug_str'];
StringTable? dwarfStringTable;
if (dwarfStringTableSection != null) {
dwarfStringTable =
StringTable.fromReader(dwarfStringTableSection.refocus(reader));
}
// Set the wordSize and endian of the original reader before returning.
machOReader.wordSize = reader.wordSize;
machOReader.endian = reader.endian;
return MachO._(
header, commands, symbolTable, dwarfSegment, dwarfStringTable);
}
static String handleDSYM(String fileName) {
if (!fileName.endsWith('.dSYM')) {
return fileName;
}
var baseName = path.basename(fileName);
baseName = baseName.substring(0, baseName.length - '.dSYM'.length);
return path.join(fileName, 'Contents', 'Resources', 'DWARF', baseName);
}
static MachO? fromFile(String fileName) =>
MachO.fromReader(Reader.fromFile(MachO.handleDSYM(fileName)));
@override
Reader abbreviationsTableReader(Reader reader) =>
_dwarfSegment.sections['__debug_abbrev']!.refocus(reader);
@override
Reader lineNumberInfoReader(Reader reader) =>
_dwarfSegment.sections['__debug_line']!.refocus(reader);
@override
Reader debugInfoReader(Reader reader) =>
_dwarfSegment.sections['__debug_info']!.refocus(reader);
@override
int get vmStartAddress {
if (!_symbolTable.containsKey(constants.vmSymbolName)) {
throw FormatException(
'Expected a dynamic symbol with name ${constants.vmSymbolName}');
}
return _symbolTable[constants.vmSymbolName]!.value;
}
@override
int get isolateStartAddress {
if (!_symbolTable.containsKey(constants.isolateSymbolName)) {
throw FormatException(
'Expected a dynamic symbol with name ${constants.isolateSymbolName}');
}
return _symbolTable[constants.isolateSymbolName]!.value;
}
@override
String? get buildId => null;
@override
DwarfContainerStringTable? get stringTable => _dwarfStringTable;
@override
Symbol? staticSymbolAt(int address) {
Symbol? bestSym;
for (final symbol in _symbolTable.values) {
if (symbol.value > address) continue;
// Pick the symbol with a value closest to the given address.
if (bestSym == null || (bestSym.value < symbol.value)) {
bestSym = symbol;
}
}
return bestSym;
}
@override
void writeToStringBuffer(StringBuffer buffer) {
buffer
..writeln('----------------------------------------')
..writeln(' Header')
..writeln('----------------------------------------')
..writeln('');
_header.writeToStringBuffer(buffer);
buffer
..writeln('')
..writeln('')
..writeln('----------------------------------------')
..writeln(' Load commands')
..writeln('----------------------------------------')
..writeln('');
for (final command in _commands) {
command.writeToStringBuffer(buffer);
buffer.writeln('');
}
}
@override
String toString() {
final buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}