ecf1968122
Also changes pkg/native_stack_traces to return a parsed MachO file if the MachO file doesn't contain DWARF information, so we can examine its static symbol information for the new test. Issue: https://github.com/flutter/flutter/issues/124715 TEST=vm/dart/unobfuscated_static_symbols Change-Id: I07d3ced56eeba852ebe4178dfd2b66ebb899eb76 Cq-Include-Trybots: luci.dart.try:vm-aot-dwarf-linux-product-x64-try,vm-aot-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-mac-product-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/305720 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
841 lines
25 KiB
Dart
841 lines
25 KiB
Dart
// 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:io';
|
|
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);
|
|
assert(reader.remaining >= cmdsize - (reader.offset - start));
|
|
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 shrink(Reader reader) => reader.shrink(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.shrink(_stroff, _strsize));
|
|
return SymbolTable.fromReader(reader.shrink(_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 const _MH_DSYM = 0xa;
|
|
|
|
static int? _wordSize(int magic) => (magic == _MH_MAGIC || magic == _MH_CIGAM)
|
|
? 4
|
|
: (magic == _MH_MAGIC_64 || magic == _MH_CIGAM_64)
|
|
? 8
|
|
: null;
|
|
|
|
static Endian? _endian(int magic) =>
|
|
(magic == _MH_MAGIC || magic == _MH_MAGIC_64)
|
|
? Endian.host
|
|
: (magic == _MH_CIGAM || magic == _MH_CIGAM_64)
|
|
? (Endian.host == Endian.big ? Endian.little : Endian.big)
|
|
: null;
|
|
|
|
static MachOHeader? fromReader(Reader reader) {
|
|
final start = reader.offset;
|
|
// Initially assume host endianness.
|
|
reader.endian = Endian.host;
|
|
final magic = _readMachOUint32(reader);
|
|
final wordSize = _wordSize(magic);
|
|
final endian = _endian(magic);
|
|
// Not an expected magic value, so not a supported Mach-O file.
|
|
if (wordSize == null || endian == null) return null;
|
|
reader.wordSize = wordSize;
|
|
reader.endian = endian;
|
|
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);
|
|
}
|
|
|
|
int get wordSize => _wordSize(magic)!;
|
|
Endian get endian => _endian(magic)!;
|
|
bool get isDSYM => filetype == _MH_DSYM;
|
|
|
|
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 extends DwarfContainer {
|
|
final MachOHeader _header;
|
|
final List<LoadCommand> _commands;
|
|
final SymbolTable _symbolTable;
|
|
final SegmentCommand? _dwarfSegment;
|
|
final StringTable? _debugStringTable;
|
|
final StringTable? _debugLineStringTable;
|
|
|
|
MachO._(this._header, this._commands, this._symbolTable, this._dwarfSegment,
|
|
this._debugStringTable, this._debugLineStringTable);
|
|
|
|
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 = machOReader.shrink(machOReader.offset);
|
|
final header = MachOHeader.fromReader(reader);
|
|
if (header == null) return null;
|
|
|
|
final commandReader = reader.shrink(reader.offset, header.sizeofcmds);
|
|
final commands =
|
|
List.of(commandReader.readRepeated(LoadCommand.fromReader));
|
|
assert(commands.length == header.ncmds);
|
|
|
|
// This MachO file can't contain any debugging information.
|
|
if (commands.isEmpty) return null;
|
|
|
|
final symbolTable =
|
|
commands.whereType<SymbolTableCommand>().single.load(reader);
|
|
|
|
final dwarfSegment = commands
|
|
.whereType<SegmentCommand?>()
|
|
.firstWhere((sc) => sc!.segname == '__DWARF', orElse: () => null);
|
|
StringTable? debugStringTable;
|
|
StringTable? debugLineStringTable;
|
|
if (dwarfSegment != null) {
|
|
final debugStringTableSection = dwarfSegment.sections['__debug_str'];
|
|
if (debugStringTableSection != null) {
|
|
debugStringTable =
|
|
StringTable.fromReader(debugStringTableSection.shrink(reader));
|
|
}
|
|
|
|
final debugLineStringTableSection =
|
|
dwarfSegment.sections['__debug_line_str'];
|
|
if (debugLineStringTableSection != null) {
|
|
debugLineStringTable =
|
|
StringTable.fromReader(debugLineStringTableSection.shrink(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,
|
|
debugStringTable, debugLineStringTable);
|
|
}
|
|
|
|
static String handleDSYM(String fileName) {
|
|
if (!fileName.endsWith('.dSYM')) {
|
|
return fileName;
|
|
}
|
|
final dwarfDir =
|
|
Directory(path.join(fileName, 'Contents', 'Resources', 'DWARF'));
|
|
// The DWARF directory inside the .dSYM should contain a single MachO file.
|
|
final machoFile = dwarfDir.listSync().single as File;
|
|
return machoFile.path;
|
|
}
|
|
|
|
static MachO? fromFile(String fileName) =>
|
|
MachO.fromReader(Reader.fromFile(MachO.handleDSYM(fileName)));
|
|
|
|
bool get isDSYM => _header.isDSYM;
|
|
bool get hasDwarf => _dwarfSegment != null;
|
|
|
|
Reader applyWordSizeAndEndian(Reader reader) =>
|
|
Reader.fromTypedData(reader.bdata,
|
|
wordSize: _header.wordSize, endian: _header.endian);
|
|
|
|
@override
|
|
String? get architecture => CpuType.fromCode(_header.cputype)?.dartName;
|
|
|
|
@override
|
|
Reader abbreviationsTableReader(Reader containerReader) =>
|
|
_dwarfSegment!.sections['__debug_abbrev']!.shrink(containerReader);
|
|
@override
|
|
Reader lineNumberInfoReader(Reader containerReader) =>
|
|
_dwarfSegment!.sections['__debug_line']!.shrink(containerReader);
|
|
@override
|
|
Reader debugInfoReader(Reader containerReader) =>
|
|
_dwarfSegment!.sections['__debug_info']!.shrink(containerReader);
|
|
|
|
@override
|
|
int? get vmStartAddress => _symbolTable[constants.vmSymbolName]?.value;
|
|
|
|
@override
|
|
int? get isolateStartAddress =>
|
|
_symbolTable[constants.isolateSymbolName]?.value;
|
|
|
|
@override
|
|
String? get buildId => null;
|
|
|
|
@override
|
|
DwarfContainerStringTable? get debugStringTable => _debugStringTable;
|
|
|
|
@override
|
|
DwarfContainerStringTable? get debugLineStringTable => _debugLineStringTable;
|
|
|
|
@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
|
|
Iterable<Symbol> get staticSymbols => _symbolTable.values;
|
|
|
|
@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('');
|
|
}
|
|
}
|
|
}
|
|
|
|
class UniversalBinaryArch {
|
|
final int cputype;
|
|
final int cpusubtype;
|
|
final int offset;
|
|
final int size;
|
|
final int align;
|
|
|
|
UniversalBinaryArch._(
|
|
this.cputype, this.cpusubtype, this.offset, this.size, this.align);
|
|
|
|
static UniversalBinaryArch fromReader(Reader reader) {
|
|
final cputype = _readMachOUint32(reader);
|
|
final cpusubtype = _readMachOUint32(reader);
|
|
final offset = _readMachOUint32(reader);
|
|
final size = _readMachOUint32(reader);
|
|
final align = _readMachOUint32(reader);
|
|
|
|
return UniversalBinaryArch._(cputype, cpusubtype, offset, size, align);
|
|
}
|
|
|
|
// Given a reader for the entire universal binary, returns a reader
|
|
// for only this architecture's contents.
|
|
Reader shrink(Reader reader) => reader.shrink(offset, size);
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write(' Cpu Type: 0x')
|
|
..writeln(paddedHex(cputype, 4));
|
|
buffer
|
|
..write(' Cpu Subtype: 0x')
|
|
..writeln(paddedHex(cpusubtype, 4));
|
|
buffer
|
|
..write(' Offset: 0x')
|
|
..writeln(paddedHex(offset, 4));
|
|
buffer
|
|
..write(' Size: ')
|
|
..writeln(size);
|
|
buffer
|
|
..write(' Alignment: ')
|
|
..writeln(align);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
class UniversalBinaryHeader {
|
|
final int magic;
|
|
final List<UniversalBinaryArch> _arches;
|
|
|
|
UniversalBinaryHeader._(this.magic, this._arches);
|
|
|
|
static const _FAT_MAGIC = 0xcafebabe;
|
|
static const _FAT_CIGAM = 0xbebafeca;
|
|
|
|
static UniversalBinaryHeader? fromReader(Reader originalReader) {
|
|
assert(originalReader.offset == 0);
|
|
// Make sure we have a reader that makes no assumptions about the
|
|
// endianness, since we'll read that in the header.
|
|
final reader =
|
|
Reader.fromTypedData(ByteData.sublistView(originalReader.bdata));
|
|
reader.wordSize = 4;
|
|
reader.endian = Endian.host;
|
|
final magic = _readMachOUint32(reader);
|
|
if (magic == _FAT_CIGAM) {
|
|
reader.endian = Endian.host == Endian.big ? Endian.little : Endian.big;
|
|
} else if (magic != _FAT_MAGIC) {
|
|
// Not a universal binary.
|
|
return null;
|
|
}
|
|
final archCount = _readMachOUint32(reader);
|
|
final arches = <UniversalBinaryArch>[];
|
|
for (int i = 0; i < archCount; i++) {
|
|
arches.add(UniversalBinaryArch.fromReader(reader));
|
|
}
|
|
return UniversalBinaryHeader._(magic, arches);
|
|
}
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..write('Magic: 0x')
|
|
..writeln(paddedHex(magic, 4));
|
|
buffer
|
|
..write('Number of architectures: ')
|
|
..writeln(_arches.length);
|
|
for (int i = 0; i < _arches.length; i++) {
|
|
buffer
|
|
..write('Arch ')
|
|
..write(i)
|
|
..writeln(':');
|
|
_arches[i].writeToStringBuffer(buffer);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// Represents Dart architectures that have valid CPU type values in MachO.
|
|
enum CpuType {
|
|
arm(_CPU_ARCH_ARM, "arm"),
|
|
arm64(_CPU_ARCH_ARM | _CPU_ARCH_ABI64, "arm64"),
|
|
i386(_CPU_ARCH_X86, "ia32"),
|
|
x64(_CPU_ARCH_X86 | _CPU_ARCH_ABI64, "x64");
|
|
|
|
static const _CPU_ARCH_ABI64 = 0x01000000;
|
|
static const _CPU_ARCH_X86 = 7;
|
|
static const _CPU_ARCH_ARM = 12;
|
|
|
|
static const _prefix = 'CPU_ARCH';
|
|
|
|
/// The 32-bit MachO encoding for this architecture.
|
|
final int code;
|
|
|
|
/// The name of this architecture as reported by Dart, e.g., in
|
|
/// non-symbolic stack traces.
|
|
final String dartName;
|
|
|
|
const CpuType(this.code, this.dartName);
|
|
|
|
static CpuType? fromCode(int code) {
|
|
for (final value in values) {
|
|
if (value.code == code) return value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static CpuType? fromDartName(String arch) {
|
|
for (final value in values) {
|
|
if (value.dartName == arch) return value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Whether this CpuType represents a 64-bit architecture.
|
|
bool get is64Bit => code & _CPU_ARCH_ABI64 != 0;
|
|
|
|
@override
|
|
String toString() => '${_prefix}_${name.toUpperCase()}';
|
|
}
|
|
|
|
class UniversalBinary {
|
|
final UniversalBinaryHeader _header;
|
|
final Map<CpuType, UniversalBinaryArch> _arches;
|
|
final Map<UniversalBinaryArch, MachO> _contents;
|
|
|
|
UniversalBinary._(this._header, this._arches, this._contents);
|
|
|
|
static UniversalBinary? fromReader(Reader originalReader) {
|
|
// Universal binary files contain absolute offsets from the start of the
|
|
// file, so make sure we have a reader that has an internal offset of 0 so
|
|
// absolute offsets can be used directly.
|
|
final reader = originalReader.shrink(originalReader.offset);
|
|
final header = UniversalBinaryHeader.fromReader(reader);
|
|
if (header == null) {
|
|
return null;
|
|
}
|
|
final arches = <CpuType, UniversalBinaryArch>{};
|
|
final contents = <UniversalBinaryArch, MachO>{};
|
|
for (final arch in header._arches) {
|
|
final cpuType = CpuType.fromCode(arch.cputype);
|
|
if (cpuType == null) continue;
|
|
final archReader = arch.shrink(reader);
|
|
final macho = MachO.fromReader(archReader);
|
|
// The MachO parser either failed (likely due to a lack of debugging
|
|
// information) or this contains debugging information for something other
|
|
// than a Dart snapshot (since it contains neither a VM or isolate
|
|
// instruction section symbol).
|
|
if ((macho == null) ||
|
|
((macho.vmStartAddress == null) &&
|
|
(macho.isolateStartAddress == null))) {
|
|
continue;
|
|
}
|
|
if (!arches.containsKey(cpuType)) {
|
|
arches[cpuType] = arch;
|
|
} else if (macho.isDSYM) {
|
|
// Always take a dSYM section above a non-dSYM section. If there are
|
|
// multiple dSYM sections for some reason, the last one read is fine.
|
|
arches[cpuType] = arch;
|
|
} else if (!contents[arches[cpuType]!]!.hasDwarf) {
|
|
// If the old section didn't have DWARF information but the new one
|
|
// does, take it instead.
|
|
arches[cpuType] = arch;
|
|
}
|
|
contents[arch] = macho;
|
|
}
|
|
return UniversalBinary._(header, arches, contents);
|
|
}
|
|
|
|
static UniversalBinary? fromFile(String fileName) =>
|
|
UniversalBinary.fromReader(Reader.fromFile(MachO.handleDSYM(fileName)));
|
|
|
|
Iterable<CpuType> get architectures => _arches.keys;
|
|
|
|
Reader? readerForCpuType(Reader originalReader, CpuType cpuType) {
|
|
final arch = _arches[cpuType];
|
|
if (arch == null) return null;
|
|
final macho = _contents[arch]!;
|
|
// Universal binary files contain absolute offsets from the start of the
|
|
// file, so make sure to feed arch.shrink a reader that has an internal
|
|
// offset of 0.
|
|
return macho.applyWordSizeAndEndian(
|
|
arch.shrink(originalReader.shrink(originalReader.offset)));
|
|
}
|
|
|
|
DwarfContainer? containerForCpuType(CpuType cpuType) {
|
|
final arch = _arches[cpuType];
|
|
if (arch == null) return null;
|
|
return _contents[arch];
|
|
}
|
|
|
|
void writeToStringBuffer(StringBuffer buffer) {
|
|
buffer
|
|
..writeln('----------------------------------------')
|
|
..writeln(' Universal Binary Header')
|
|
..writeln('----------------------------------------')
|
|
..writeln('');
|
|
_header.writeToStringBuffer(buffer);
|
|
for (final cpuType in _arches.keys) {
|
|
buffer
|
|
..writeln('')
|
|
..writeln('')
|
|
..writeln('----------------------------------------------------------')
|
|
..writeln(' Selected Mach-O Contents for $cpuType')
|
|
..writeln('----------------------------------------------------------')
|
|
..writeln('');
|
|
_contents[_arches[cpuType]!]!.writeToStringBuffer(buffer);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final buffer = StringBuffer();
|
|
writeToStringBuffer(buffer);
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
const _magicByteOffset = 0;
|
|
const _cpuTypeByteOffset = 4;
|
|
const _fileTypeByteOffset = 12;
|
|
|
|
/// Used by certain Dart tests to create a MachO file that only contains a
|
|
/// header for the given architecture.
|
|
Uint8List? emptyMachOForArchitecture(String dartName) {
|
|
final cpuType = CpuType.fromDartName(dartName);
|
|
if (cpuType == null) return null;
|
|
// 4 bytes * 7 fields + 4 padding bytes on 64-bit architectures.
|
|
final contents = Uint8List(32);
|
|
// We'll leave most of the fields at 0.
|
|
final byteData = ByteData.sublistView(contents);
|
|
// Use the host endian magic number marker corresponding to the bit size.
|
|
byteData.setUint32(
|
|
_magicByteOffset,
|
|
cpuType.is64Bit ? MachOHeader._MH_MAGIC_64 : MachOHeader._MH_MAGIC,
|
|
Endian.host);
|
|
byteData.setUint32(_cpuTypeByteOffset, cpuType.code, Endian.host);
|
|
// Just to set it to a valid file type, even though there are no contents.
|
|
byteData.setUint32(_fileTypeByteOffset, MachOHeader._MH_DSYM, Endian.host);
|
|
return contents;
|
|
}
|