| // 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_REQ_DYLD = 0x80000000; |
| |
| static const LC_SEGMENT = 0x1; |
| static const LC_SYMTAB = 0x2; |
| static const LC_ID_DYLIB = 0xd; |
| static const LC_SEGMENT_64 = 0x19; |
| static const LC_UUID = 0x1b; |
| static const LC_RPATH = 0x1c | LC_REQ_DYLD; |
| static const LC_CODE_SIGNATURE = 0x1d; // Only used in vm/dart tests. |
| static const LC_BUILD_VERSION = 0x32; |
| |
| 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; |
| case LC_UUID: |
| command = UuidCommand.fromReader(reader, cmd, cmdsize); |
| break; |
| case LC_BUILD_VERSION: |
| command = BuildVersionCommand.fromReader(reader, cmd, cmdsize); |
| break; |
| case LC_ID_DYLIB: |
| command = DylibCommand.fromReader(reader, cmd, cmdsize); |
| case LC_RPATH: |
| command = RunPathCommand.fromReader(reader, cmd, cmdsize); |
| 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._( |
| super.cmd, |
| super.cmdsize, |
| this.segname, |
| this.vmaddr, |
| this.vmsize, |
| this.fileoff, |
| this.filesize, |
| this.maxprot, |
| this.initprot, |
| this.nsects, |
| this.flags, |
| this.sections) |
| : super._(); |
| |
| 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._(super.cmd, super.cmdsize, this._symoff, this._nsyms, |
| this._stroff, this._strsize) |
| : super._(); |
| |
| 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 UuidCommand extends LoadCommand { |
| Uint8List uuid; |
| |
| static const kUuidSize = 16; |
| |
| UuidCommand._(super.cmd, super.cmdsize, this.uuid) : super._(); |
| |
| static UuidCommand fromReader(Reader reader, int cmd, int cmdsize) { |
| final uuid = Uint8List.sublistView( |
| reader.bytes, reader.offset, reader.offset + kUuidSize); |
| return UuidCommand._(cmd, cmdsize, uuid); |
| } |
| |
| String get uuidString => uuid.map((i) => paddedHex(i, 1)).join(); |
| |
| @override |
| void writeToStringBuffer(StringBuffer buffer) { |
| buffer |
| ..write('UUID: ') |
| ..write(uuidString); |
| } |
| } |
| |
| class DylibInfo { |
| final String name; |
| final int timestamp; |
| final int currentVersion; |
| final int compatibilityVersion; |
| |
| const DylibInfo._(this.name, this.timestamp, this.currentVersion, |
| this.compatibilityVersion); |
| |
| static DylibInfo fromReader(Reader reader, int cmdsize) { |
| final start = reader.offset - 8; // cmd + cmdsize |
| final offset = _readMachOUint32(reader); |
| final timestamp = _readMachOUint32(reader); |
| final currentVersion = _readMachOUint32(reader); |
| final compatibilityVersion = _readMachOUint32(reader); |
| reader.seek(start + offset, absolute: true); |
| final name = reader.readNullTerminatedString(maxSize: cmdsize - offset); |
| return DylibInfo._(name, timestamp, currentVersion, compatibilityVersion); |
| } |
| |
| void writeToStringBuffer(StringBuffer buffer) { |
| buffer |
| ..write(' Name: ') |
| ..writeln(name) |
| ..write(' Timestamp: ') |
| ..writeln(timestamp) |
| ..write(' Current version: ') |
| ..writeln(currentVersion) |
| ..write(' Compatibility version: ') |
| ..writeln(compatibilityVersion); |
| } |
| |
| @override |
| String toString() { |
| final buffer = StringBuffer(); |
| writeToStringBuffer(buffer); |
| return buffer.toString(); |
| } |
| } |
| |
| class DylibCommand extends LoadCommand { |
| final DylibInfo info; |
| |
| DylibCommand._(super.cmd, super.cmdsize, this.info) : super._(); |
| |
| static DylibCommand fromReader(Reader reader, int cmd, int cmdsize) => |
| DylibCommand._(cmd, cmdsize, DylibInfo.fromReader(reader, cmdsize)); |
| |
| @override |
| void writeToStringBuffer(StringBuffer buffer) { |
| if (cmd == LoadCommand.LC_ID_DYLIB) { |
| buffer.writeln('LC_ID_DYLIB:'); |
| info.writeToStringBuffer(buffer); |
| } else { |
| throw StateError("Unexpected command code $cmd"); |
| } |
| } |
| } |
| |
| class RunPathCommand extends LoadCommand { |
| String path; |
| |
| RunPathCommand._(super.cmd, super.cmdsize, this.path) : super._(); |
| |
| static RunPathCommand fromReader(Reader reader, int cmd, int cmdsize) { |
| final start = reader.offset - 8; // cmd + cmdsize |
| final offset = _readMachOUint32(reader); |
| reader.seek(start + offset, absolute: true); |
| final path = reader.readNullTerminatedString(maxSize: cmdsize - offset); |
| return RunPathCommand._(cmd, cmdsize, path); |
| } |
| |
| @override |
| void writeToStringBuffer(StringBuffer buffer) { |
| buffer |
| ..write('Run path: ') |
| ..write(path); |
| } |
| } |
| |
| class Version { |
| final int x; |
| final int y; |
| final int z; |
| |
| const Version(this.x, this.y, this.z); |
| |
| static Version fromInt(int v) { |
| final x = (v & 0xffffffff) >> 16; |
| final y = (v & 0xffff) >> 8; |
| final z = v & 0xff; |
| return Version(x, y, z); |
| } |
| |
| @override |
| int get hashCode => Object.hash(x, y, z); |
| |
| @override |
| bool operator ==(Object other) { |
| if (identical(this, other)) return true; |
| return other is Version && x == other.x && y == other.y && z == other.z; |
| } |
| |
| @override |
| String toString() => '$x.$y.$z'; |
| } |
| |
| enum Platform { |
| PLATFORM_UNKNOWN(0, 'Unknown'), |
| PLATFORM_ANY(0xFFFFFFFF, 'any'), |
| PLATFORM_MACOS(0x1, 'MacOS'), |
| PLATFORM_IOS(0x2, 'iOS'), |
| PLATFORM_TVOS(0x3, 'TVOS'), |
| PLATFORM_WATCHOS(0x4, 'WatchOS'), |
| PLATFORM_IOSSIMULATOR(0x7, 'iOS Simulator'), |
| PLATFORM_TVOSSIMULATOR(0x8, 'TVOS Simulator'), |
| PLATFORM_WATCHOSSIMULATOR(0x9, 'WatchOS Simulator'); |
| |
| final int id; |
| final String description; |
| |
| const Platform(this.id, this.description); |
| |
| static Platform? fromId(int id) { |
| for (final platform in values) { |
| if (platform.id == id) { |
| return platform; |
| } |
| } |
| return null; |
| } |
| |
| @override |
| String toString() => description; |
| } |
| |
| enum BuildTool { |
| TOOL_CLANG(1, 'clang'), |
| TOOL_SWIFT(2, 'Swift'), |
| TOOL_LD(3, 'ld'), |
| TOOL_LLD(4, 'lld'); |
| |
| final int id; |
| final String description; |
| |
| const BuildTool(this.id, this.description); |
| |
| static BuildTool? fromId(int id) { |
| for (final tool in values) { |
| if (tool.id == id) { |
| return tool; |
| } |
| } |
| return null; |
| } |
| |
| @override |
| String toString() => description; |
| } |
| |
| class BuildToolVersion { |
| final int _toolId; |
| final BuildTool? tool; |
| final Version version; |
| |
| const BuildToolVersion._(this._toolId, this.tool, this.version); |
| |
| factory BuildToolVersion.fromReader(Reader reader) { |
| final id = _readMachOUint32(reader); |
| final tool = BuildTool.fromId(id); |
| final version = Version.fromInt(_readMachOUint32(reader)); |
| return BuildToolVersion._(id, tool, version); |
| } |
| |
| void writeToStringBuffer(StringBuffer buffer) { |
| buffer |
| ..write(' - ') |
| ..write(tool ?? 'Unknown (0x${paddedHex(_toolId, 4)})') |
| ..write(' (') |
| ..write(version) |
| ..writeln(')'); |
| } |
| |
| @override |
| String toString() { |
| final buffer = StringBuffer(); |
| writeToStringBuffer(buffer); |
| return buffer.toString(); |
| } |
| } |
| |
| class BuildVersionCommand extends LoadCommand { |
| final int _platformId; |
| final Platform? platform; |
| final Version minOS; |
| final Version sdk; |
| final List<BuildToolVersion> toolVersions; |
| |
| BuildVersionCommand._(super.cmd, super.cmdsize, this._platformId, |
| this.platform, this.minOS, this.sdk, this.toolVersions) |
| : super._(); |
| |
| static BuildVersionCommand fromReader(Reader reader, int cmd, int cmdsize) { |
| final platformId = _readMachOUint32(reader); |
| final platform = Platform.fromId(platformId); |
| final minOS = Version.fromInt(_readMachOUint32(reader)); |
| final sdk = Version.fromInt(_readMachOUint32(reader)); |
| final toolCount = _readMachOUint32(reader); |
| final toolVersions = <BuildToolVersion>[]; |
| for (int i = 0; i < toolCount; i++) { |
| toolVersions.add(BuildToolVersion.fromReader(reader)); |
| } |
| return BuildVersionCommand._( |
| cmd, cmdsize, platformId, platform, minOS, sdk, toolVersions); |
| } |
| |
| @override |
| void writeToStringBuffer(StringBuffer buffer) { |
| buffer |
| ..writeln('Build version:') |
| ..write(' Platform: ') |
| ..writeln(platform ?? 'Unknown (0x${paddedHex(_platformId, 4)})') |
| ..write(' Minimum OS: ') |
| ..writeln(minOS) |
| ..write(' Minimum SDK: ') |
| ..writeln(sdk); |
| if (toolVersions.isNotEmpty) { |
| buffer.writeln(' Tools:'); |
| for (final v in toolVersions) { |
| v.writeToStringBuffer(buffer); |
| } |
| } |
| } |
| } |
| |
| 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); |
| |
| Iterable<LoadCommand> get commands => _commands; |
| Iterable<T> commandsWhereType<T extends LoadCommand>() => |
| _commands.whereType<T>(); |
| |
| @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 => |
| _commands.whereType<UuidCommand>().firstOrNull?.uuidString; |
| |
| @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; |
| } |