| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // ignore_for_file: constant_identifier_names | |
| import 'dart:async'; | |
| import 'dart:collection'; | |
| import 'dart:math'; | |
| import 'dart:typed_data'; | |
| import 'dwarf_container.dart'; | |
| import 'elf.dart'; | |
| import 'macho.dart'; | |
| import 'reader.dart'; | |
| const String _debugStringTableKey = 'debugStringTable'; | |
| const String _debugLineStringTableKey = 'debugLineStringTable'; | |
| int _initialLengthValue(Reader reader) { | |
| final length = reader.readBytes(4); | |
| if (length == 0xffffffff) { | |
| throw FormatException('64-bit DWARF format detected'); | |
| } else if (length > 0xfffffff0) { | |
| throw FormatException('Unrecognized reserved initial length value'); | |
| } | |
| return length; | |
| } | |
| enum _Tag { | |
| // Snake case used to match DWARF specification. Skipped values are reserved | |
| // entries that have been deprecated in more recent DWARF versions and | |
| // should not be used. | |
| array_type(0x01), | |
| class_type(0x02), | |
| entry_point(0x03), | |
| enumeration_type(0x04), | |
| formal_parameter(0x05), | |
| imported_declaration(0x08), | |
| label(0x0a), | |
| lexical_block(0x0b), | |
| member(0x0d), | |
| pointer_type(0x0f), | |
| reference_type(0x10), | |
| compile_unit(0x11), | |
| string_type(0x12), | |
| structure_type(0x13), | |
| subroutine_type(0x15), | |
| typedefTag(0x16, name: 'typedef'), | |
| union_type(0x17), | |
| unspecified_parameters(0x18), | |
| variant(0x19), | |
| common_block(0x1a), | |
| common_inclusion(0x1b), | |
| inheritance(0x1c), | |
| inlined_subroutine(0x1d), | |
| module(0x1e), | |
| ptr_to_member_type(0x1f), | |
| set_type(0x20), | |
| subrange_type(0x21), | |
| with_stmt(0x22), | |
| access_declaration(0x23), | |
| base_type(0x24), | |
| catch_block(0x25), | |
| const_type(0x26), | |
| constant(0x27), | |
| enumerator(0x28), | |
| file_type(0x29), | |
| friend(0x2a), | |
| namelist(0x2b), | |
| namelist_item(0x2c), | |
| packed_type(0x2d), | |
| subprogram(0x2e), | |
| template_type_parameter(0x2f), | |
| template_value_parameter(0x30), | |
| thrown_type(0x31), | |
| try_block(0x32), | |
| variant_part(0x33), | |
| variable(0x34), | |
| volatile_type(0x35), | |
| dwarf_procedure(0x36), | |
| restrict_type(0x37), | |
| interface_type(0x38), | |
| namespace(0x39), | |
| imported_module(0x3a), | |
| unspecified_type(0x3b), | |
| partial_unit(0x3c), | |
| imported_unit(0x3d), | |
| condition(0x3f), | |
| shared_type(0x40), | |
| type_unit(0x41), | |
| rvalue_reference_type(0x42), | |
| template_alias(0x43), | |
| coarray_type(0x44), | |
| generic_subrange(0x45), | |
| dynamic_type(0x46), | |
| atomic_type(0x47), | |
| call_site(0x48), | |
| call_site_parameter(0x49), | |
| skeleton_unit(0x4a), | |
| immutable_type(0x4b), | |
| lo_user(0x4080), | |
| hi_user(0xffff), | |
| unrecognized(-1); | |
| final int code; | |
| final String? _name; // For renames due to keywords/existing members. | |
| const _Tag(this.code, {String? name}) : _name = name; | |
| static const _prefix = 'DW_TAG'; | |
| static _Tag fromReader(Reader reader) { | |
| final code = reader.readLEB128EncodedInteger(); | |
| for (final name in values) { | |
| if (name.code == code) { | |
| return name; | |
| } | |
| } | |
| return unrecognized; | |
| } | |
| @override | |
| String toString() { | |
| if (this == unrecognized) { | |
| return 'unrecognized $_prefix code'; | |
| } | |
| return '${_prefix}_${_name ?? name}'; | |
| } | |
| } | |
| enum _FormClass { | |
| // Location in the address space of the program. | |
| address, | |
| // Location in the DWARF section. | |
| addrptr, | |
| // An arbitrary number of uninterpreted bytes of data, with the length | |
| // either implicit by context or preceded by a length marker. | |
| block, | |
| // An (S)LEB128 encoded or specific-sized uninterpreted data value. | |
| constant, | |
| // A DWARF expression for a value or location in the program's address space | |
| // preceded by a length marker. | |
| exprloc, | |
| // A small constant indicating the presence or absence of an attribute. | |
| flag, | |
| // A location in the line number information DWARF section. | |
| lineptr, | |
| // A location in the location lists DWARF section. | |
| loclist, | |
| // A location in the location lists DWARF section. | |
| loclistsptr, | |
| // A location in the macro definition DWARF section. | |
| macptr, | |
| // A reference to one of the debugging information entries. | |
| reference, | |
| // A location in the non-contiguous address ranges DWARF section. | |
| rnglist, | |
| // A location in the non-contiguous address ranges DWARF section. | |
| rnglistsptr, | |
| // Either a null-terminated sequence of zero or more bytes or the offset of | |
| // one in a separate string table. | |
| string, | |
| // An offset into a DWARF section that itself holds offsets into a DWARF | |
| // string section. | |
| stroffsetsptr; | |
| } | |
| enum _AttributeName { | |
| // Snake case used to match DWARF specification. Skipped code values are | |
| // reserved entries that have been deprecated in more recent DWARF versions | |
| // and should not be used. | |
| sibling(0x01, {_FormClass.reference}), | |
| location(0x02, {_FormClass.exprloc, _FormClass.loclist}), | |
| nameValue(0x03, {_FormClass.string}, name: 'name'), | |
| ordering(0x09, {_FormClass.constant}), | |
| byte_size( | |
| 0x0b, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| // Reserved but allowed for backwards compatibility. | |
| bit_offset( | |
| 0x0c, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| bit_size( | |
| 0x0d, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| stmt_list(0x10, {_FormClass.lineptr}), | |
| low_pc(0x11, {_FormClass.address}), | |
| high_pc(0x12, {_FormClass.address, _FormClass.constant}), | |
| language(0x13, {_FormClass.constant}), | |
| discr(0x15, {_FormClass.reference}), | |
| discr_value(0x16, {_FormClass.constant}), | |
| visibility(0x17, {_FormClass.constant}), | |
| import(0x18, {_FormClass.reference}), | |
| string_length( | |
| 0x19, {_FormClass.exprloc, _FormClass.loclist, _FormClass.reference}), | |
| common_reference(0x1a, {_FormClass.reference}), | |
| comp_dir(0x1b, {_FormClass.string}), | |
| const_value(0x1c, {_FormClass.block, _FormClass.constant, _FormClass.string}), | |
| containing_type(0x1d, {_FormClass.reference}), | |
| default_value( | |
| 0x1e, {_FormClass.constant, _FormClass.reference, _FormClass.flag}), | |
| inline(0x20, {_FormClass.constant}), | |
| is_optional(0x21, {_FormClass.flag}), | |
| lower_bound( | |
| 0x22, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| producer(0x25, {_FormClass.string}), | |
| prototyped(0x27, {_FormClass.flag}), | |
| return_addr(0x2a, {_FormClass.exprloc, _FormClass.loclist}), | |
| start_scope(0x2c, {_FormClass.constant, _FormClass.rnglist}), | |
| bit_stride( | |
| 0x2e, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| upper_bound( | |
| 0x2f, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| abstract_origin(0x31, {_FormClass.reference}), | |
| accessibility(0x32, {_FormClass.constant}), | |
| address_class(0x33, {_FormClass.constant}), | |
| artificial(0x34, {_FormClass.flag}), | |
| base_types(0x35, {_FormClass.reference}), | |
| calling_convention(0x36, {_FormClass.constant}), | |
| count(0x37, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| data_member_location( | |
| 0x38, {_FormClass.constant, _FormClass.exprloc, _FormClass.loclist}), | |
| decl_column(0x39, {_FormClass.constant}), | |
| decl_file(0x3a, {_FormClass.constant}), | |
| decl_line(0x3b, {_FormClass.constant}), | |
| declaration(0x3c, {_FormClass.flag}), | |
| discr_list(0x3d, {_FormClass.block}), | |
| encoding(0x3e, {_FormClass.constant}), | |
| externalFlag(0x3f, {_FormClass.flag}, name: 'external'), | |
| frame_base(0x40, {_FormClass.exprloc, _FormClass.loclist}), | |
| friend(0x41, {_FormClass.reference}), | |
| identifier_case(0x42, {_FormClass.constant}), | |
| // Reserved but allowed for backwards compatibility. | |
| macro_info(0x43, {_FormClass.macptr}), | |
| namelist_item(0x44, {_FormClass.reference}), | |
| priority(0x45, {_FormClass.reference}), | |
| segment(0x46, {_FormClass.exprloc, _FormClass.loclist}), | |
| specification(0x47, {_FormClass.reference}), | |
| static_link(0x48, {_FormClass.exprloc, _FormClass.loclist}), | |
| type(0x49, {_FormClass.reference}), | |
| use_location(0x4a, {_FormClass.exprloc, _FormClass.loclist}), | |
| variable_parameter(0x4b, {_FormClass.flag}), | |
| virtuality(0x4c, {_FormClass.constant}), | |
| vtable_elem_location(0x4d, {_FormClass.exprloc, _FormClass.loclist}), | |
| allocated( | |
| 0x4e, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| associated( | |
| 0x4f, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| data_location(0x50, {_FormClass.exprloc}), | |
| byte_stride( | |
| 0x51, {_FormClass.constant, _FormClass.exprloc, _FormClass.reference}), | |
| entry_pc(0x52, {_FormClass.address, _FormClass.constant}), | |
| use_UTF8(0x53, {_FormClass.flag}), | |
| extensionValue(0x54, {_FormClass.reference}, name: 'extension'), | |
| ranges(0x55, {_FormClass.rnglist}), | |
| trampoline(0x56, { | |
| _FormClass.address, | |
| _FormClass.flag, | |
| _FormClass.reference, | |
| _FormClass.string | |
| }), | |
| call_column(0x57, {_FormClass.constant}), | |
| call_file(0x58, {_FormClass.constant}), | |
| call_line(0x59, {_FormClass.constant}), | |
| description(0x5a, {_FormClass.string}), | |
| binary_scale(0x5b, {_FormClass.constant}), | |
| decimal_scale(0x5c, {_FormClass.constant}), | |
| small(0x5d, {_FormClass.reference}), | |
| decimal_sign(0x5e, {_FormClass.constant}), | |
| digit_count(0x5f, {_FormClass.constant}), | |
| picture_string(0x60, {_FormClass.string}), | |
| mutable(0x61, {_FormClass.flag}), | |
| threads_scaled(0x62, {_FormClass.flag}), | |
| explicit(0x63, {_FormClass.flag}), | |
| object_pointer(0x64, {_FormClass.reference}), | |
| endianity(0x65, {_FormClass.constant}), | |
| elemental(0x66, {_FormClass.flag}), | |
| pure(0x67, {_FormClass.flag}), | |
| recursive(0x68, {_FormClass.flag}), | |
| signature(0x69, {_FormClass.reference}), | |
| main_subprogram(0x6a, {_FormClass.flag}), | |
| data_bit_offset(0x6b, {_FormClass.constant}), | |
| const_expr(0x6c, {_FormClass.flag}), | |
| enum_class(0x6d, {_FormClass.flag}), | |
| linkage_name(0x6e, {_FormClass.string}), | |
| string_length_bit_size(0x6f, {_FormClass.constant}), | |
| string_length_byte_size(0x70, {_FormClass.constant}), | |
| rank(0x71, {_FormClass.constant, _FormClass.exprloc}), | |
| str_offsets_base(0x72, {_FormClass.stroffsetsptr}), | |
| addr_base(0x73, {_FormClass.addrptr}), | |
| rnglists_base(0x74, {_FormClass.rnglistsptr}), | |
| dwo_name(0x76, {_FormClass.string}), | |
| reference(0x77, {_FormClass.flag}), | |
| rvalue_reference(0x78, {_FormClass.flag}), | |
| macros(0x79, {_FormClass.macptr}), | |
| call_all_calls(0x7a, {_FormClass.flag}), | |
| call_all_source_calls(0x7b, {_FormClass.flag}), | |
| call_all_tail_calls(0x7c, {_FormClass.flag}), | |
| call_return_pc(0x7d, {_FormClass.address}), | |
| call_value(0x7e, {_FormClass.exprloc}), | |
| call_origin(0x7f, {_FormClass.exprloc}), | |
| call_parameter(0x80, {_FormClass.reference}), | |
| call_pc(0x81, {_FormClass.address}), | |
| call_tail_call(0x82, {_FormClass.flag}), | |
| call_target(0x83, {_FormClass.exprloc}), | |
| call_target_clobbered(0x84, {_FormClass.exprloc}), | |
| call_data_location(0x85, {_FormClass.exprloc}), | |
| call_data_value(0x86, {_FormClass.exprloc}), | |
| noreturn(0x87, {_FormClass.flag}), | |
| alignment(0x88, {_FormClass.constant}), | |
| export_symbols(0x89, {_FormClass.flag}), | |
| deleted(0x8a, {_FormClass.flag}), | |
| defaulted(0x8b, {_FormClass.constant}), | |
| loclists_base(0x8c, {_FormClass.loclistsptr}), | |
| lo_user(0x2000, {}), | |
| hi_user(0x3fff, {}), | |
| unrecognized(-1, {}); | |
| final int code; | |
| // Used for error checking when forming Attributes. | |
| final Set<_FormClass> classes; | |
| final String? _name; // For renames due to keywords/existing members. | |
| const _AttributeName(this.code, this.classes, {String? name}) : _name = name; | |
| static const _prefix = 'DW_AT'; | |
| static _AttributeName? fromReader(Reader reader) { | |
| final code = reader.readLEB128EncodedInteger(); | |
| if (code == 0x00) return null; // Used as end marker in some cases. | |
| for (final name in values) { | |
| if (name.code == code) { | |
| return name; | |
| } | |
| } | |
| return unrecognized; | |
| } | |
| @override | |
| String toString() { | |
| if (this == unrecognized) { | |
| return 'unrecognized $_prefix code'; | |
| } | |
| return '${_prefix}_${_name ?? name}'; | |
| } | |
| } | |
| class _IndirectFormValue { | |
| final _AttributeForm form; | |
| final Object value; | |
| _IndirectFormValue._(this.form, this.value); | |
| static _IndirectFormValue fromReader(Reader reader, {int? addressSize}) { | |
| final form = _AttributeForm.fromReader(reader)!; | |
| final value = form.read(reader, addressSize: addressSize); | |
| return _IndirectFormValue._(form, value); | |
| } | |
| @override | |
| String toString() => '$form:${form.valueToString(value)}'; | |
| } | |
| enum _AttributeForm { | |
| // Snake case used to match DWARF specification. Skipped values are reserved | |
| // entries that have been deprecated in more recent DWARF versions and | |
| // should not be used. | |
| addr(0x01, {_FormClass.address}), | |
| block2(0x03, {_FormClass.block}, dataSize: 2), | |
| block4(0x04, {_FormClass.block}, dataSize: 4), | |
| data2(0x05, {_FormClass.constant}, dataSize: 2), | |
| data4(0x06, {_FormClass.constant}, dataSize: 4), | |
| data8(0x07, {_FormClass.constant}, dataSize: 8), | |
| string(0x08, {_FormClass.string}), | |
| block(0x09, {_FormClass.block}), | |
| block1(0x0a, {_FormClass.block}, dataSize: 1), | |
| data1(0x0b, {_FormClass.constant}, dataSize: 1), | |
| flag(0x0c, {_FormClass.flag}), | |
| sdata(0x0d, {_FormClass.constant}), | |
| strp(0x0e, {_FormClass.string}), | |
| udata(0x0f, {_FormClass.constant}), | |
| ref_addr(0x10, {_FormClass.reference}), | |
| ref1(0x11, {_FormClass.reference}, dataSize: 1), | |
| ref2(0x12, {_FormClass.reference}, dataSize: 2), | |
| ref4(0x13, {_FormClass.reference}, dataSize: 4), | |
| ref8(0x14, {_FormClass.reference}, dataSize: 8), | |
| ref_udata(0x15, {_FormClass.reference}), | |
| indirect(0x16, {}), | |
| sec_offset(0x17, { | |
| _FormClass.addrptr, | |
| _FormClass.lineptr, | |
| _FormClass.loclist, | |
| _FormClass.loclistsptr, | |
| _FormClass.macptr, | |
| _FormClass.rnglist, | |
| _FormClass.rnglistsptr, | |
| _FormClass.stroffsetsptr, | |
| }), | |
| exprloc(0x18, {_FormClass.exprloc}), | |
| flag_present(0x19, {_FormClass.flag}), | |
| strx(0x1a, {_FormClass.string}), | |
| addrx(0x1b, {_FormClass.address}), | |
| ref_sup4(0x1c, {_FormClass.reference}), | |
| strp_sup(0x1d, {_FormClass.string}), | |
| data16(0x1e, {_FormClass.constant}), | |
| line_strp(0x1f, {_FormClass.string}), | |
| ref_sig8(0x20, {_FormClass.reference}), | |
| implicit_const(0x21, {_FormClass.constant}), | |
| loclistx(0x22, {_FormClass.loclist}), | |
| rnglistx(0x23, {_FormClass.rnglist}), | |
| ref_sup8(0x24, {_FormClass.reference}, dataSize: 8), | |
| strx1(0x25, {_FormClass.string}, dataSize: 1), | |
| strx2(0x26, {_FormClass.string}, dataSize: 2), | |
| strx3(0x27, {_FormClass.string}, dataSize: 3), | |
| strx4(0x28, {_FormClass.string}, dataSize: 4), | |
| addrx1(0x29, {_FormClass.address}, dataSize: 1), | |
| addrx2(0x2a, {_FormClass.address}, dataSize: 2), | |
| addrx3(0x2b, {_FormClass.address}, dataSize: 3), | |
| addrx4(0x2c, {_FormClass.address}, dataSize: 4), | |
| unrecognized(-1, {}); | |
| final int code; | |
| // The classes that this format represents. Generally holds a single value | |
| // except for certain format codes. | |
| final Set<_FormClass> classes; | |
| // For constant-sized data, the size of the data (or, in the case of blocks, | |
| // the size of the length of the block). | |
| final int? dataSize; | |
| const _AttributeForm(this.code, this.classes, {this.dataSize}); | |
| static const _prefix = 'DW_FORM'; | |
| static _AttributeForm? fromReader(Reader reader) { | |
| final code = reader.readLEB128EncodedInteger(); | |
| if (code == 0x00) return null; // Used as end marker in some cases. | |
| for (final name in values) { | |
| if (name.code == code) { | |
| return name; | |
| } | |
| } | |
| return unrecognized; | |
| } | |
| Object read(Reader reader, {int? addressSize}) { | |
| switch (this) { | |
| case _AttributeForm.string: | |
| return reader.readNullTerminatedString(); | |
| case _AttributeForm.strp: | |
| final offset = reader.readBytes(4); // Assumed 32-bit DWARF | |
| final debugStringTable = | |
| Zone.current[_debugStringTableKey] as DwarfContainerStringTable?; | |
| if (debugStringTable == null) { | |
| throw FormatException('No .debug_str available'); | |
| } | |
| return debugStringTable[offset]!; | |
| case _AttributeForm.line_strp: | |
| final offset = reader.readBytes(4); // Assumed 32-bit DWARF | |
| final debugLineStringTable = Zone.current[_debugLineStringTableKey] | |
| as DwarfContainerStringTable?; | |
| if (debugLineStringTable == null) { | |
| throw FormatException('No .debug_line_str available'); | |
| } | |
| return debugLineStringTable[offset]!; | |
| case _AttributeForm.flag: | |
| case _AttributeForm.flag_present: | |
| return reader.readByte() != 0; | |
| case _AttributeForm.addr: | |
| if (addressSize == null) { | |
| throw FormatException('No address size available'); | |
| } | |
| return reader.readBytes(addressSize); | |
| case _AttributeForm.block: | |
| final length = reader.readLEB128EncodedInteger(); | |
| return reader.readRawBytes(length); | |
| case _AttributeForm.block1: | |
| final length = reader.readByte(); | |
| return reader.readRawBytes(length); | |
| case _AttributeForm.block2: | |
| final length = reader.readBytes(2); | |
| return reader.readRawBytes(length); | |
| case _AttributeForm.block4: | |
| final length = reader.readBytes(4); | |
| return reader.readRawBytes(length); | |
| case _AttributeForm.data1: | |
| case _AttributeForm.ref1: | |
| return reader.readByte(); | |
| case _AttributeForm.data2: | |
| case _AttributeForm.ref2: | |
| return reader.readBytes(2); | |
| case _AttributeForm.data4: | |
| case _AttributeForm.ref4: | |
| case _AttributeForm.ref_addr: // Assumed 32-bit DWARF | |
| case _AttributeForm.ref_sup4: | |
| case _AttributeForm.sec_offset: // Assumed 32-bit DWARF | |
| return reader.readBytes(4); | |
| case _AttributeForm.data8: | |
| case _AttributeForm.ref8: | |
| case _AttributeForm.ref_sup8: | |
| return reader.readBytes(8); | |
| case _AttributeForm.sdata: | |
| return reader.readLEB128EncodedInteger(signed: true); | |
| case _AttributeForm.ref_udata: | |
| case _AttributeForm.udata: | |
| return reader.readLEB128EncodedInteger(); | |
| case _AttributeForm.data16: | |
| return reader.readRawBytes(16); | |
| case _AttributeForm.indirect: | |
| return _IndirectFormValue.fromReader(reader, addressSize: addressSize); | |
| default: | |
| throw FormatException('Reading $this values not currently handled'); | |
| } | |
| } | |
| String valueToString(Object value, | |
| {CompilationUnit? unit, int? addressSize}) { | |
| switch (this) { | |
| case _AttributeForm.string: | |
| case _AttributeForm.strp: | |
| case _AttributeForm.line_strp: | |
| return value as String; | |
| case _AttributeForm.flag: | |
| case _AttributeForm.flag_present: | |
| return value.toString(); | |
| case _AttributeForm.addr: | |
| return '0x${paddedHex(value as int, addressSize)}'; | |
| case _AttributeForm.sec_offset: | |
| return paddedHex(value as int, 4); // Assumed 32-bit DWARF | |
| case _AttributeForm.data1: | |
| case _AttributeForm.data2: | |
| case _AttributeForm.data4: | |
| case _AttributeForm.data8: | |
| case _AttributeForm.sdata: | |
| case _AttributeForm.udata: | |
| return value.toString(); | |
| case _AttributeForm.ref1: | |
| case _AttributeForm.ref2: | |
| case _AttributeForm.ref4: | |
| case _AttributeForm.ref8: | |
| case _AttributeForm.ref_udata: | |
| final intValue = value as int; | |
| final unresolvedValue = paddedHex(intValue, dataSize); | |
| final name = unit?.nameOfOrigin(intValue) ?? '<unresolved>'; | |
| return '0x$unresolvedValue (origin: $name)'; | |
| case _AttributeForm.data16: | |
| final bdata = value as ByteData; | |
| final buffer = StringBuffer(); | |
| for (int i = 0; i < 16; i++) { | |
| buffer.write(bdata.getUint8(i).toRadixString(16)); | |
| } | |
| return buffer.toString(); | |
| case _AttributeForm.indirect: | |
| return value.toString(); | |
| default: | |
| throw FormatException('Converting $this values not currently handled'); | |
| } | |
| } | |
| @override | |
| String toString() { | |
| if (this == unrecognized) { | |
| return 'unrecognized $_prefix code'; | |
| } | |
| return '${_prefix}_$name'; | |
| } | |
| } | |
| class _Attribute { | |
| final _AttributeName name; | |
| final _AttributeForm form; | |
| final Object? implicit; | |
| _Attribute._(this.name, this.form, this.implicit); | |
| static _Attribute? fromReader(Reader reader) { | |
| final name = _AttributeName.fromReader(reader); | |
| final form = _AttributeForm.fromReader(reader); | |
| if (name == null || form == null) { | |
| // If one is null, the other should be null. | |
| assert(name == null && form == null); | |
| return null; | |
| } | |
| // Name or form values with empty classes cannot be checked here. | |
| if (name.classes.isNotEmpty && form.classes.isNotEmpty) { | |
| if (name.classes.intersection(form.classes).isEmpty) { | |
| throw FormatException('$form is not a valid format for $name'); | |
| } | |
| } | |
| // Handle implicit_const, which contains a third piece of data which is | |
| // the implicit (signed LEB128) value. | |
| Object? implicit; | |
| if (form == _AttributeForm.implicit_const) { | |
| implicit = reader.readLEB128EncodedInteger(signed: true); | |
| } | |
| return _Attribute._(name, form, implicit); | |
| } | |
| Object read(Reader reader, {int? addressSize}) => | |
| implicit ?? form.read(reader, addressSize: addressSize); | |
| String valueToString(Object value, | |
| {CompilationUnit? unit, int? addressSize}) => | |
| // Implicit values are signed LEB128 data, so equivalent to sdata. | |
| implicit != null | |
| ? _AttributeForm.sdata.valueToString(value) | |
| : form.valueToString(value, unit: unit, addressSize: addressSize); | |
| } | |
| enum _AbbreviationChildren { | |
| no(0x00), | |
| yes(0x01); | |
| final int code; | |
| const _AbbreviationChildren(this.code); | |
| static const _prefix = 'DW_CHILDREN'; | |
| static _AbbreviationChildren fromReader(Reader reader) { | |
| final code = reader.readByte(); | |
| for (final value in values) { | |
| if (value.code == code) { | |
| return value; | |
| } | |
| } | |
| throw FormatException('Unexpected $_prefix code ' | |
| '0x${code.toRadixString(16)}'); | |
| } | |
| bool get value => this == _AbbreviationChildren.yes; | |
| @override | |
| String toString() => '${_prefix}_$name'; | |
| } | |
| class _Abbreviation { | |
| final int code; | |
| final _Tag tag; | |
| final _AbbreviationChildren children; | |
| final List<_Attribute> attributes; | |
| _Abbreviation._(this.code, this.tag, this.children, this.attributes); | |
| static _Abbreviation? fromReader(Reader reader) { | |
| final code = reader.readLEB128EncodedInteger(); | |
| if (code == 0) return null; | |
| final tag = _Tag.fromReader(reader); | |
| final children = _AbbreviationChildren.fromReader(reader); | |
| final attributes = reader.readRepeated(_Attribute.fromReader).toList(); | |
| return _Abbreviation._(code, tag, children, attributes); | |
| } | |
| bool get hasChildren => children.value; | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| buffer | |
| ..write(' Tag: ') | |
| ..writeln(tag) | |
| ..write(' Children: ') | |
| ..writeln(children) | |
| ..writeln(' Attributes:'); | |
| for (final attribute in attributes) { | |
| buffer | |
| ..write(' ') | |
| ..write(attribute.name) | |
| ..write(': ') | |
| ..writeln(attribute.form); | |
| } | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| class _AbbreviationsTable { | |
| final Map<int, _Abbreviation> _abbreviations; | |
| _AbbreviationsTable._(this._abbreviations); | |
| bool containsKey(int code) => _abbreviations.containsKey(code); | |
| _Abbreviation? operator [](int code) => _abbreviations[code]; | |
| static _AbbreviationsTable? fromReader(Reader reader) { | |
| final abbreviations = Map.fromEntries(reader | |
| .readRepeated(_Abbreviation.fromReader) | |
| .map((abbr) => MapEntry(abbr.code, abbr))); | |
| return _AbbreviationsTable._(abbreviations); | |
| } | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| buffer | |
| ..writeln('Abbreviations table:') | |
| ..writeln(); | |
| _abbreviations.forEach((key, abbreviation) { | |
| buffer | |
| ..write(' ') | |
| ..write(key) | |
| ..writeln(':'); | |
| abbreviation.writeToStringBuffer(buffer); | |
| buffer.writeln(); | |
| }); | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// A DWARF Debug Information Entry (DIE). | |
| class DebugInformationEntry { | |
| // The index of the entry in the abbreviation table for this DIE. | |
| final int code; | |
| // ignore: library_private_types_in_public_api | |
| final Map<_Attribute, Object> attributes; | |
| final Map<int, DebugInformationEntry> children; | |
| DebugInformationEntry._(this.code, this.attributes, this.children); | |
| static DebugInformationEntry? fromReader( | |
| Reader reader, CompilationUnitHeader header) { | |
| final code = reader.readLEB128EncodedInteger(); | |
| // DIEs with an abbreviation table index of 0 are list end markers. | |
| if (code == 0) return null; | |
| if (!header.abbreviations.containsKey(code)) { | |
| throw FormatException('Unknown abbreviation code 0x${paddedHex(code)}'); | |
| } | |
| final abbreviation = header.abbreviations[code]!; | |
| final attributes = <_Attribute, Object>{}; | |
| for (final attribute in abbreviation.attributes) { | |
| attributes[attribute] = | |
| attribute.read(reader, addressSize: header.addressSize); | |
| } | |
| final children = <int, DebugInformationEntry>{}; | |
| if (abbreviation.hasChildren) { | |
| children.addEntries(reader.readRepeatedWithOffsets( | |
| (r) => DebugInformationEntry.fromReader(r, header), | |
| absolute: true)); | |
| } | |
| return DebugInformationEntry._(code, attributes, children); | |
| } | |
| _Attribute? _namedAttribute(_AttributeName name) { | |
| for (final attribute in attributes.keys) { | |
| if (attribute.name == name) { | |
| return attribute; | |
| } | |
| } | |
| return null; | |
| } | |
| // ignore: library_private_types_in_public_api | |
| bool containsKey(_AttributeName name) => _namedAttribute(name) != null; | |
| // ignore: library_private_types_in_public_api | |
| Object? operator [](_AttributeName name) => attributes[_namedAttribute(name)]; | |
| int? get sectionOffset => this[_AttributeName.stmt_list] as int?; | |
| int? get abstractOrigin => this[_AttributeName.abstract_origin] as int?; | |
| int? get lowPC => this[_AttributeName.low_pc] as int?; | |
| int? get highPC => this[_AttributeName.high_pc] as int?; | |
| bool get isArtificial => (this[_AttributeName.artificial] ?? false) as bool; | |
| bool containsPC(int virtualAddress) => | |
| (lowPC ?? 0) <= virtualAddress && virtualAddress < (highPC ?? -1); | |
| String? get name => this[_AttributeName.nameValue] as String?; | |
| int? get callFileIndex => this[_AttributeName.call_file] as int?; | |
| int? get callLine => this[_AttributeName.call_line] as int?; | |
| int? get callColumn => this[_AttributeName.call_column] as int?; | |
| List<CallInfo>? callInfo( | |
| CompilationUnit unit, LineNumberProgram lineNumberProgram, int address) { | |
| String callFilename(int index) => | |
| lineNumberProgram.header.filesInfo[index]?.name ?? '<unknown file>'; | |
| if (!containsPC(address)) return null; | |
| final tag = unit.header.abbreviations[code]!.tag; | |
| final inlined = tag == _Tag.inlined_subroutine; | |
| for (final child in children.values) { | |
| final callInfo = child.callInfo(unit, lineNumberProgram, address); | |
| if (callInfo == null) continue; | |
| if (tag == _Tag.compile_unit) return callInfo; | |
| return callInfo | |
| ..add(DartCallInfo( | |
| function: unit.nameOfOrigin(abstractOrigin ?? -1), | |
| inlined: inlined, | |
| internal: isArtificial, | |
| filename: callFilename(child.callFileIndex ?? -1), | |
| line: child.callLine ?? 0, | |
| column: child.callColumn ?? 0)); | |
| } | |
| if (tag == _Tag.compile_unit) return null; | |
| final filename = lineNumberProgram.filename(address)!; | |
| final line = lineNumberProgram.lineNumber(address)!; | |
| final column = lineNumberProgram.column(address)!; | |
| return [ | |
| DartCallInfo( | |
| function: unit.nameOfOrigin(abstractOrigin ?? -1), | |
| inlined: inlined, | |
| // Don't hide artificial (invisible) methods which appear as | |
| // Future listeners. This is because tear-offs of static methods | |
| // are marked as invisible (even if the method itself is visible) | |
| // and we want these to appear in stack traces. | |
| internal: isArtificial && address != lowPC, | |
| filename: filename, | |
| line: line, | |
| column: column) | |
| ]; | |
| } | |
| void writeToStringBuffer(StringBuffer buffer, | |
| {CompilationUnit? unit, String indent = ''}) { | |
| buffer | |
| ..write(indent) | |
| ..write('Abbreviation code: ') | |
| ..write(code) | |
| ..writeln('):'); | |
| attributes.forEach((attribute, value) { | |
| buffer | |
| ..write(indent) | |
| ..write(' ') | |
| ..write(attribute.name) | |
| ..write(' => ') | |
| ..writeln(attribute.valueToString(value, unit: unit)); | |
| }); | |
| if (children.isNotEmpty) { | |
| buffer | |
| ..write(indent) | |
| ..write('Children (') | |
| ..write(children.length) | |
| ..writeln('):'); | |
| final sortedChildren = children.entries.toList() | |
| ..sort((kv1, kv2) => Comparable.compare(kv1.key, kv2.key)); | |
| for (var i = 0; i < sortedChildren.length; i++) { | |
| final offset = sortedChildren[i].key; | |
| final child = sortedChildren[i].value; | |
| buffer | |
| ..write(indent) | |
| ..write('Child ') | |
| ..write(i) | |
| ..write(' (at offset 0x') | |
| ..write(paddedHex(offset)) | |
| ..writeln('):'); | |
| child.writeToStringBuffer(buffer, unit: unit, indent: '$indent '); | |
| } | |
| } | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| class CompilationUnitHeader { | |
| final int size; | |
| final int version; | |
| final int abbreviationsOffset; | |
| final int addressSize; | |
| // ignore: library_private_types_in_public_api | |
| final _AbbreviationsTable abbreviations; | |
| CompilationUnitHeader._(this.size, this.version, this.abbreviationsOffset, | |
| this.addressSize, this.abbreviations); | |
| static CompilationUnitHeader? fromReader( | |
| Reader reader, | |
| // ignore: library_private_types_in_public_api | |
| Map<int, _AbbreviationsTable> abbreviationsTables) { | |
| final size = _initialLengthValue(reader); | |
| // An empty unit is an ending marker. | |
| if (size == 0) return null; | |
| final version = reader.readBytes(2); | |
| if (version != 2) { | |
| throw FormatException('Expected DWARF version 2, got $version'); | |
| } | |
| final abbreviationsOffset = reader.readBytes(4); | |
| final abbreviationsTable = abbreviationsTables[abbreviationsOffset]; | |
| if (abbreviationsTable == null) { | |
| throw FormatException('No abbreviation table found for offset ' | |
| '0x${paddedHex(abbreviationsOffset, 4)}'); | |
| } | |
| final addressSize = reader.readByte(); | |
| return CompilationUnitHeader._( | |
| size, version, abbreviationsOffset, addressSize, abbreviationsTable); | |
| } | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| buffer | |
| ..writeln('Compilation unit:') | |
| ..write(' Size: ') | |
| ..writeln(size) | |
| ..write(' Version: ') | |
| ..writeln(version) | |
| ..write(' Abbreviations offset: 0x') | |
| ..writeln(paddedHex(abbreviationsOffset, 4)) | |
| ..write(' Address size: ') | |
| ..writeln(addressSize) | |
| ..writeln(); | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// A class representing a DWARF compilation unit. | |
| class CompilationUnit { | |
| CompilationUnitHeader header; | |
| Map<int, DebugInformationEntry> referenceTable; | |
| CompilationUnit._(this.header, this.referenceTable); | |
| static CompilationUnit? fromReader( | |
| Reader reader, | |
| // ignore: library_private_types_in_public_api | |
| Map<int, _AbbreviationsTable> abbreviationsTables) { | |
| final header = | |
| CompilationUnitHeader.fromReader(reader, abbreviationsTables); | |
| if (header == null) return null; | |
| final referenceTable = Map.fromEntries(reader.readRepeatedWithOffsets( | |
| (r) => DebugInformationEntry.fromReader(r, header), | |
| absolute: true)); | |
| _addChildEntries(referenceTable); | |
| return CompilationUnit._(header, referenceTable); | |
| } | |
| static void _addChildEntries(Map<int, DebugInformationEntry> table) { | |
| final workList = Queue<MapEntry<int, DebugInformationEntry>>(); | |
| for (final die in table.values) { | |
| workList.addAll(die.children.entries); | |
| } | |
| while (workList.isNotEmpty) { | |
| final kv = workList.removeFirst(); | |
| final offset = kv.key; | |
| final child = kv.value; | |
| table[offset] = child; | |
| workList.addAll(child.children.entries); | |
| } | |
| } | |
| Iterable<CallInfo>? callInfo(LineNumberInfo lineNumberInfo, int address) { | |
| for (final die in referenceTable.values) { | |
| final lineNumberProgram = lineNumberInfo[die.sectionOffset ?? -1]; | |
| if (lineNumberProgram == null) continue; | |
| final callInfo = die.callInfo(this, lineNumberProgram, address); | |
| if (callInfo != null) return callInfo; | |
| } | |
| return null; | |
| } | |
| String nameOfOrigin(int offset) { | |
| final origin = referenceTable[offset]; | |
| if (origin == null) { | |
| throw ArgumentError( | |
| '${paddedHex(offset)} is not the offset of an abbreviated unit'); | |
| } | |
| return origin[_AttributeName.nameValue] as String; | |
| } | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| header.writeToStringBuffer(buffer); | |
| referenceTable.forEach((offset, die) { | |
| buffer | |
| ..write('Debug information entry at offset 0x') | |
| ..write(paddedHex(offset)) | |
| ..writeln(':'); | |
| die.writeToStringBuffer(buffer, unit: this); | |
| buffer.writeln(); | |
| }); | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// A class representing a DWARF `.debug_info` section. | |
| class DebugInfo { | |
| final List<CompilationUnit> units; | |
| DebugInfo._(this.units); | |
| static DebugInfo fromReader( | |
| Reader reader, | |
| // ignore: library_private_types_in_public_api | |
| Map<int, _AbbreviationsTable> abbreviationsTable) { | |
| final units = reader | |
| .readRepeated( | |
| (r) => CompilationUnit.fromReader(reader, abbreviationsTable)) | |
| .toList(); | |
| return DebugInfo._(units); | |
| } | |
| Iterable<CallInfo>? callInfo(LineNumberInfo lineNumberInfo, int address) { | |
| for (final unit in units) { | |
| final callInfo = unit.callInfo(lineNumberInfo, address); | |
| if (callInfo != null) return callInfo; | |
| } | |
| return null; | |
| } | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| for (final unit in units) { | |
| unit.writeToStringBuffer(buffer); | |
| buffer.writeln(); | |
| } | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| enum _LineNumberContentType { | |
| path(0x01), | |
| directory_index(0x02), | |
| timestamp(0x03), | |
| size(0x04), | |
| md5(0x05); | |
| final int code; | |
| const _LineNumberContentType(this.code); | |
| static const String _prefix = 'DW_LNCT'; | |
| static _LineNumberContentType fromReader(Reader reader) { | |
| final code = reader.readLEB128EncodedInteger(); | |
| for (final type in values) { | |
| if (type.code == code) { | |
| return type; | |
| } | |
| } | |
| throw FormatException('Unexpected $_prefix code ' | |
| '0x${code.toRadixString(16)}'); | |
| } | |
| void validate(_AttributeForm form) { | |
| switch (this) { | |
| case _LineNumberContentType.path: | |
| if (form == _AttributeForm.string || form == _AttributeForm.line_strp) { | |
| return; | |
| } | |
| break; | |
| case _LineNumberContentType.directory_index: | |
| if (form == _AttributeForm.data1 || | |
| form == _AttributeForm.data2 || | |
| form == _AttributeForm.udata) { | |
| return; | |
| } | |
| break; | |
| case _LineNumberContentType.timestamp: | |
| if (form == _AttributeForm.data4 || | |
| form == _AttributeForm.data8 || | |
| form == _AttributeForm.udata) { | |
| return; | |
| } | |
| break; | |
| case _LineNumberContentType.size: | |
| if (form == _AttributeForm.data1 || | |
| form == _AttributeForm.data2 || | |
| form == _AttributeForm.data4 || | |
| form == _AttributeForm.data8 || | |
| form == _AttributeForm.udata) { | |
| return; | |
| } | |
| break; | |
| case _LineNumberContentType.md5: | |
| if (form == _AttributeForm.data16) { | |
| return; | |
| } | |
| break; | |
| } | |
| throw FormatException('Unexpected form $form for $this'); | |
| } | |
| @override | |
| String toString() => '${_prefix}_${this == md5 ? 'MD5' : name}'; | |
| } | |
| class FileEntry { | |
| final String name; | |
| final int directoryIndex; | |
| final int lastModified; | |
| final int size; | |
| FileEntry._(this.name, this.directoryIndex, this.lastModified, this.size); | |
| static FileEntry? fromReader(Reader reader) { | |
| final name = reader.readNullTerminatedString(); | |
| // An empty null-terminated string marks the table end. | |
| if (name == '') return null; | |
| final directoryIndex = reader.readLEB128EncodedInteger(); | |
| final lastModified = reader.readLEB128EncodedInteger(); | |
| final size = reader.readLEB128EncodedInteger(); | |
| return FileEntry._(name, directoryIndex, lastModified, size); | |
| } | |
| @override | |
| String toString() => 'File name: $name\n' | |
| ' Directory index: $directoryIndex\n' | |
| ' Last modified: $lastModified\n' | |
| ' Size: $size\n'; | |
| } | |
| class FileInfo { | |
| final Map<int, FileEntry> _files; | |
| FileInfo._(this._files); | |
| static FileInfo fromReader(Reader reader) { | |
| final offsetFiles = reader.readRepeated(FileEntry.fromReader).toList(); | |
| final files = <int, FileEntry>{}; | |
| for (var i = 0; i < offsetFiles.length; i++) { | |
| // File entries are one-based, not zero-based. | |
| files[i + 1] = offsetFiles[i]; | |
| } | |
| return FileInfo._(files); | |
| } | |
| static FileInfo fromReaderDwarf5(Reader reader, {int? addressSize}) { | |
| final entryFormatCount = reader.readByte(); | |
| final entryFormatTypes = <_LineNumberContentType>[]; | |
| final entryFormatForms = <_AttributeForm>[]; | |
| int? sizeIndex; | |
| int? directoryIndexIndex; | |
| int? timestampIndex; | |
| int? nameIndex; | |
| for (int i = 0; i < entryFormatCount; i++) { | |
| final type = _LineNumberContentType.fromReader(reader); | |
| final form = _AttributeForm.fromReader(reader)!; | |
| type.validate(form); | |
| entryFormatTypes.add(type); | |
| entryFormatForms.add(form); | |
| switch (type) { | |
| case _LineNumberContentType.path: | |
| if (nameIndex != null) { | |
| throw FormatException('Multiple $type entries in format'); | |
| } | |
| nameIndex = i; | |
| break; | |
| case _LineNumberContentType.directory_index: | |
| if (directoryIndexIndex != null) { | |
| throw FormatException('Multiple $type entries in format'); | |
| } | |
| directoryIndexIndex = i; | |
| break; | |
| case _LineNumberContentType.timestamp: | |
| if (timestampIndex != null) { | |
| throw FormatException('Multiple $type entries in format'); | |
| } | |
| timestampIndex = i; | |
| break; | |
| case _LineNumberContentType.size: | |
| if (sizeIndex != null) { | |
| throw FormatException('Multiple $type entries in format'); | |
| } | |
| sizeIndex = i; | |
| break; | |
| case _LineNumberContentType.md5: | |
| break; | |
| } | |
| } | |
| if (nameIndex == null) { | |
| throw FormatException( | |
| 'Missing ${_LineNumberContentType.path} entry in format'); | |
| } | |
| final fileNamesCount = reader.readLEB128EncodedInteger(); | |
| if (entryFormatCount == 0 && fileNamesCount != 0) { | |
| throw FormatException('Missing entry format(s)'); | |
| } | |
| final files = <int, FileEntry>{}; | |
| for (int i = 0; i < fileNamesCount; i++) { | |
| final values = <Object>[]; | |
| for (int j = 0; j < entryFormatCount; j++) { | |
| final form = entryFormatForms[j]; | |
| final value = form.read(reader, addressSize: addressSize); | |
| values.add(value); | |
| } | |
| final name = values[nameIndex] as String; | |
| // For any missing values, just use 0. | |
| final size = sizeIndex == null ? 0 : values[sizeIndex] as int; | |
| final directoryIndex = | |
| directoryIndexIndex == null ? 0 : values[directoryIndexIndex] as int; | |
| final timestamp = | |
| timestampIndex == null ? 0 : values[timestampIndex] as int; | |
| // In DWARF5, file entries are zero-based, as the current compilation file | |
| // name is provided first instead of implicit. | |
| files[i] = FileEntry._(name, directoryIndex, timestamp, size); | |
| } | |
| return FileInfo._(files); | |
| } | |
| bool containsKey(int index) => _files.containsKey(index); | |
| FileEntry? operator [](int index) => _files[index]; | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| if (_files.isEmpty) { | |
| buffer.writeln('No file information.'); | |
| return; | |
| } | |
| final indexHeader = 'Entry'; | |
| final dirIndexHeader = 'Dir'; | |
| final modifiedHeader = 'Time'; | |
| final sizeHeader = 'Size'; | |
| final nameHeader = 'Name'; | |
| final indexStrings = _files | |
| .map((int i, FileEntry f) => MapEntry<int, String>(i, i.toString())); | |
| final dirIndexStrings = _files.map((int i, FileEntry f) => | |
| MapEntry<int, String>(i, f.directoryIndex.toString())); | |
| final modifiedStrings = _files.map((int i, FileEntry f) => | |
| MapEntry<int, String>(i, f.lastModified.toString())); | |
| final sizeStrings = _files.map( | |
| (int i, FileEntry f) => MapEntry<int, String>(i, f.size.toString())); | |
| final maxIndexLength = indexStrings.values | |
| .fold(indexHeader.length, (int acc, String s) => max(acc, s.length)); | |
| final maxDirIndexLength = dirIndexStrings.values | |
| .fold(dirIndexHeader.length, (int acc, String s) => max(acc, s.length)); | |
| final maxModifiedLength = modifiedStrings.values | |
| .fold(modifiedHeader.length, (int acc, String s) => max(acc, s.length)); | |
| final maxSizeLength = sizeStrings.values | |
| .fold(sizeHeader.length, (int acc, String s) => max(acc, s.length)); | |
| buffer.writeln('File information:'); | |
| buffer | |
| ..write(' ') | |
| ..write(indexHeader.padRight(maxIndexLength)); | |
| buffer | |
| ..write(' ') | |
| ..write(dirIndexHeader.padRight(maxDirIndexLength)); | |
| buffer | |
| ..write(' ') | |
| ..write(modifiedHeader.padRight(maxModifiedLength)); | |
| buffer | |
| ..write(' ') | |
| ..write(sizeHeader.padRight(maxSizeLength)); | |
| buffer | |
| ..write(' ') | |
| ..writeln(nameHeader); | |
| for (final index in _files.keys) { | |
| buffer | |
| ..write(' ') | |
| ..write(indexStrings[index]!.padRight(maxIndexLength)); | |
| buffer | |
| ..write(' ') | |
| ..write(dirIndexStrings[index]!.padRight(maxDirIndexLength)); | |
| buffer | |
| ..write(' ') | |
| ..write(modifiedStrings[index]!.padRight(maxModifiedLength)); | |
| buffer | |
| ..write(' ') | |
| ..write(sizeStrings[index]!.padRight(maxSizeLength)); | |
| buffer | |
| ..write(' ') | |
| ..writeln(_files[index]!.name); | |
| } | |
| } | |
| @override | |
| String toString() { | |
| var buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| class LineNumberState { | |
| final bool defaultIsStatement; | |
| late int address; | |
| late int opIndex; | |
| late int fileIndex; | |
| late int line; | |
| late int column; | |
| late bool isStatement; | |
| late bool basicBlock; | |
| late bool endSequence; | |
| late bool prologueEnd; | |
| late bool epilogueBegin; | |
| late int isa; | |
| late int discriminator; | |
| LineNumberState(this.defaultIsStatement) { | |
| reset(); | |
| } | |
| void reset() { | |
| address = 0; | |
| opIndex = 0; | |
| fileIndex = 1; | |
| line = 1; | |
| column = 0; | |
| isStatement = defaultIsStatement; | |
| basicBlock = false; | |
| endSequence = false; | |
| prologueEnd = false; | |
| epilogueBegin = false; | |
| isa = 0; | |
| discriminator = 0; | |
| } | |
| LineNumberState clone() { | |
| final clone = LineNumberState(defaultIsStatement); | |
| clone.address = address; | |
| clone.opIndex = opIndex; | |
| clone.fileIndex = fileIndex; | |
| clone.line = line; | |
| clone.column = column; | |
| clone.isStatement = isStatement; | |
| clone.basicBlock = basicBlock; | |
| clone.endSequence = endSequence; | |
| clone.prologueEnd = prologueEnd; | |
| clone.epilogueBegin = epilogueBegin; | |
| clone.isa = isa; | |
| clone.discriminator = discriminator; | |
| return clone; | |
| } | |
| @override | |
| String toString() => 'Current line number state machine registers:\n' | |
| ' Address: ${paddedHex(address)}\n' | |
| ' Op index: $opIndex\n' | |
| ' File index: $fileIndex\n' | |
| ' Line number: $line\n' | |
| ' Column number: $column\n' | |
| " Is ${isStatement ? "" : "not "}a statement.\n" | |
| " Is ${basicBlock ? "" : "not "}at the beginning of a basic block.\n" | |
| " Is ${endSequence ? "" : "not "}just after the end of a sequence.\n" | |
| " Is ${prologueEnd ? "" : "not "}at a function entry breakpoint.\n" | |
| " Is ${epilogueBegin ? "" : "not "}at a function exit breakpoint.\n" | |
| ' Applicable instruction set architecture: $isa\n' | |
| ' Block discrimator: $discriminator\n'; | |
| } | |
| class LineNumberProgramHeader { | |
| final int size; | |
| final int version; | |
| final int headerLength; | |
| final int minimumInstructionLength; | |
| final bool defaultIsStatement; | |
| final int lineBase; | |
| final int lineRange; | |
| final int opcodeBase; | |
| final Map<int, int> standardOpcodeLengths; | |
| final List<String> includeDirectories; | |
| final FileInfo filesInfo; | |
| final int _fullHeaderSize; | |
| LineNumberProgramHeader._( | |
| this.size, | |
| this.version, | |
| this.headerLength, | |
| this.minimumInstructionLength, | |
| this.defaultIsStatement, | |
| this.lineBase, | |
| this.lineRange, | |
| this.opcodeBase, | |
| this.standardOpcodeLengths, | |
| this.includeDirectories, | |
| this.filesInfo, | |
| this._fullHeaderSize); | |
| static LineNumberProgramHeader? fromReader(Reader reader) { | |
| final size = _initialLengthValue(reader); | |
| if (size == 0) return null; | |
| final headerStart = reader.offset; | |
| final version = reader.readBytes(2); | |
| // Only used for DWARF5. | |
| int? addressSize; | |
| if (version == 5) { | |
| // These fields are DWARF5 specific. | |
| addressSize = reader.readByte(); | |
| final segmentSelectorSize = reader.readByte(); | |
| // We don't support segmented memory addresses here; | |
| assert(segmentSelectorSize == 0); | |
| } | |
| final headerLength = reader.readBytes(4); | |
| // We'll need this later as a double-check that we've read the entire | |
| // header. | |
| final offsetAfterHeaderLength = reader.offset; | |
| final minimumInstructionLength = reader.readByte(); | |
| final isStmtByte = reader.readByte(); | |
| if (isStmtByte < 0 || isStmtByte > 1) { | |
| throw FormatException( | |
| 'Unexpected value for default_is_stmt: $isStmtByte'); | |
| } | |
| final defaultIsStatement = isStmtByte == 1; | |
| final lineBase = reader.readByte(signed: true); | |
| final lineRange = reader.readByte(); | |
| final opcodeBase = reader.readByte(); | |
| final standardOpcodeLengths = <int, int>{}; | |
| // Standard opcode numbering starts at 1. | |
| for (var i = 1; i < opcodeBase; i++) { | |
| standardOpcodeLengths[i] = reader.readLEB128EncodedInteger(); | |
| } | |
| final includeDirectories = <String>[]; | |
| if (version == 5) { | |
| final directoryEntryFormatCount = reader.readByte(); | |
| if (directoryEntryFormatCount > 1) { | |
| throw FormatException( | |
| 'Multiple directory formats not currently handled'); | |
| } | |
| final contentType = _LineNumberContentType.fromReader(reader); | |
| if (contentType != _LineNumberContentType.path) { | |
| throw FormatException('Unexpected content type $contentType'); | |
| } | |
| final form = _AttributeForm.fromReader(reader)!; | |
| contentType.validate(form); | |
| final directoryCount = reader.readLEB128EncodedInteger(); | |
| for (int i = 0; i < directoryCount; i++) { | |
| final value = form.read(reader, addressSize: addressSize); | |
| includeDirectories.add(value as String); | |
| } | |
| } else { | |
| while (!reader.done) { | |
| final directory = reader.readNullTerminatedString(); | |
| if (directory == '') break; | |
| includeDirectories.add(directory); | |
| } | |
| if (reader.done) { | |
| throw FormatException('Unterminated directory entry'); | |
| } | |
| } | |
| final filesInfo = version == 5 | |
| ? FileInfo.fromReaderDwarf5(reader, addressSize: addressSize) | |
| : FileInfo.fromReader(reader); | |
| // Header length doesn't include anything up to the header length field. | |
| if (reader.offset != offsetAfterHeaderLength + headerLength) { | |
| throw FormatException('At offset ${reader.offset} after header, ' | |
| 'expected to be at offset ${headerStart + headerLength}'); | |
| } | |
| // We also keep note of the full header size internally so we can adjust | |
| // readers as necessary later. | |
| final fullHeaderSize = reader.offset - headerStart; | |
| return LineNumberProgramHeader._( | |
| size, | |
| version, | |
| headerLength, | |
| minimumInstructionLength, | |
| defaultIsStatement, | |
| lineBase, | |
| lineRange, | |
| opcodeBase, | |
| standardOpcodeLengths, | |
| includeDirectories, | |
| filesInfo, | |
| fullHeaderSize); | |
| } | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| buffer | |
| ..write(' Size: ') | |
| ..writeln(size) | |
| ..write(' Version: ') | |
| ..writeln(version) | |
| ..write(' Header length: ') | |
| ..writeln(headerLength) | |
| ..write(' Min instruction length: ') | |
| ..writeln(minimumInstructionLength) | |
| ..write(' Default value of is_stmt: ') | |
| ..writeln(defaultIsStatement) | |
| ..write(' Line base: ') | |
| ..writeln(lineBase) | |
| ..write(' Line range: ') | |
| ..writeln(lineRange) | |
| ..write(' Opcode base: ') | |
| ..writeln(opcodeBase) | |
| ..writeln('Standard opcode lengths:'); | |
| for (var i = 1; i < opcodeBase; i++) { | |
| buffer | |
| ..write(' Opcode ') | |
| ..write(i) | |
| ..write(': ') | |
| ..writeln(standardOpcodeLengths[i]); | |
| } | |
| if (includeDirectories.isEmpty) { | |
| buffer.writeln('No include directories.'); | |
| } else { | |
| buffer.writeln('Include directories:'); | |
| for (final dir in includeDirectories) { | |
| buffer | |
| ..write(' ') | |
| ..writeln(dir); | |
| } | |
| } | |
| filesInfo.writeToStringBuffer(buffer); | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// A class representing a DWARF line number program. | |
| class LineNumberProgram { | |
| final LineNumberProgramHeader header; | |
| final List<LineNumberState> calculatedMatrix; | |
| final Map<int, LineNumberState> cachedLookups; | |
| LineNumberProgram._(this.header, this.calculatedMatrix) : cachedLookups = {}; | |
| static LineNumberProgram? fromReader(Reader reader) { | |
| final header = LineNumberProgramHeader.fromReader(reader); | |
| if (header == null) return null; | |
| final calculatedMatrix = _readOpcodes(reader, header).toList(); | |
| // Sometimes the assembler will generate an empty DWARF LNP, so don't check | |
| // for non-empty LNPs. | |
| return LineNumberProgram._(header, calculatedMatrix); | |
| } | |
| static Iterable<LineNumberState> _readOpcodes( | |
| Reader originalReader, LineNumberProgramHeader header) sync* { | |
| final state = LineNumberState(header.defaultIsStatement); | |
| final programSize = header.size - header._fullHeaderSize; | |
| final reader = originalReader.shrink(originalReader.offset, programSize); | |
| void applySpecialOpcode(int opcode) { | |
| final adjustedOpcode = opcode - header.opcodeBase; | |
| final addrDiff = (adjustedOpcode ~/ header.lineRange) * | |
| header.minimumInstructionLength; | |
| final lineDiff = header.lineBase + (adjustedOpcode % header.lineRange); | |
| state.address += addrDiff; | |
| state.line += lineDiff; | |
| } | |
| while (!reader.done) { | |
| final opcode = reader.readByte(); | |
| if (opcode >= header.opcodeBase) { | |
| applySpecialOpcode(opcode); | |
| yield state.clone(); | |
| state.basicBlock = false; | |
| state.prologueEnd = false; | |
| state.epilogueBegin = false; | |
| state.discriminator = 0; | |
| continue; | |
| } | |
| switch (opcode) { | |
| case 0: // Extended opcodes | |
| final extendedLength = reader.readByte(); | |
| final subOpcode = reader.readByte(); | |
| switch (subOpcode) { | |
| case 0: | |
| throw FormatException('Attempted to execute extended opcode 0'); | |
| case 1: // DW_LNE_end_sequence | |
| state.endSequence = true; | |
| yield state.clone(); | |
| state.reset(); | |
| continue; | |
| case 2: // DW_LNE_set_address | |
| // The length includes the subopcode. | |
| final valueLength = extendedLength - 1; | |
| assert(valueLength == 4 || valueLength == 8); | |
| final newAddress = reader.readBytes(valueLength); | |
| state.address = newAddress; | |
| break; | |
| case 3: // DW_LNE_define_file | |
| throw FormatException( | |
| 'DW_LNE_define_file instruction not handled'); | |
| default: | |
| throw FormatException( | |
| 'Extended opcode $subOpcode not in DWARF 2'); | |
| } | |
| break; | |
| case 1: // DW_LNS_copy | |
| yield state.clone(); | |
| state.basicBlock = false; | |
| state.prologueEnd = false; | |
| state.epilogueBegin = false; | |
| state.discriminator = 0; | |
| break; | |
| case 2: // DW_LNS_advance_pc | |
| final increment = reader.readLEB128EncodedInteger(); | |
| state.address += header.minimumInstructionLength * increment; | |
| break; | |
| case 3: // DW_LNS_advance_line | |
| state.line += reader.readLEB128EncodedInteger(signed: true); | |
| break; | |
| case 4: // DW_LNS_set_file | |
| state.fileIndex = reader.readLEB128EncodedInteger(); | |
| break; | |
| case 5: // DW_LNS_set_column | |
| state.column = reader.readLEB128EncodedInteger(); | |
| break; | |
| case 6: // DW_LNS_negate_stmt | |
| state.isStatement = !state.isStatement; | |
| break; | |
| case 7: // DW_LNS_set_basic_block | |
| state.basicBlock = true; | |
| break; | |
| case 8: // DW_LNS_const_add_pc | |
| state.address += ((255 - header.opcodeBase) ~/ header.lineRange) * | |
| header.minimumInstructionLength; | |
| break; | |
| case 9: // DW_LNS_fixed_advance_pc | |
| state.address += reader.readBytes(2); | |
| break; | |
| case 10: // DW_LNS_set_prologue_end (DWARF5) | |
| state.prologueEnd = true; | |
| break; | |
| case 11: // DW_LNS_set_epilogue_begin (DWARF5) | |
| state.epilogueBegin = true; | |
| break; | |
| case 12: // DW_LNS_set_isa (DWARF5) | |
| state.isa = reader.readLEB128EncodedInteger(); | |
| break; | |
| default: | |
| throw FormatException('Standard opcode $opcode not in DWARF 2'); | |
| } | |
| } | |
| // Adjust the original reader to be at the same offset. | |
| originalReader.seek(programSize); | |
| } | |
| bool containsKey(int address) { | |
| assert(calculatedMatrix.last.endSequence); | |
| return address >= calculatedMatrix.first.address && | |
| address < calculatedMatrix.last.address; | |
| } | |
| LineNumberState? operator [](int address) { | |
| if (cachedLookups.containsKey(address)) return cachedLookups[address]; | |
| if (!containsKey(address)) return null; | |
| // Since the addresses are generated in increasing order, we can do a | |
| // binary search to find the right state. | |
| assert(calculatedMatrix.isNotEmpty); | |
| var minIndex = 0; | |
| var maxIndex = calculatedMatrix.length - 1; | |
| while (true) { | |
| if (minIndex == maxIndex || minIndex + 1 == maxIndex) { | |
| final found = calculatedMatrix[minIndex]; | |
| cachedLookups[address] = found; | |
| return found; | |
| } | |
| final index = minIndex + ((maxIndex - minIndex) ~/ 2); | |
| final compared = calculatedMatrix[index].address.compareTo(address); | |
| if (compared == 0) { | |
| return calculatedMatrix[index]; | |
| } else if (compared < 0) { | |
| minIndex = index; | |
| } else if (compared > 0) { | |
| maxIndex = index; | |
| } | |
| } | |
| } | |
| String? filename(int address) => | |
| header.filesInfo[this[address]?.fileIndex ?? -1]?.name; | |
| int? lineNumber(int address) => this[address]?.line; | |
| int? column(int address) => this[address]?.column; | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| header.writeToStringBuffer(buffer); | |
| buffer.writeln('Results of line number program:'); | |
| for (final state in calculatedMatrix) { | |
| buffer.writeln(state); | |
| } | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// A class representing a DWARF .debug_line section. | |
| class LineNumberInfo { | |
| final Map<int, LineNumberProgram> programs; | |
| LineNumberInfo._(this.programs); | |
| static LineNumberInfo fromReader(Reader reader) { | |
| final programs = Map.fromEntries( | |
| reader.readRepeatedWithOffsets(LineNumberProgram.fromReader)); | |
| return LineNumberInfo._(programs); | |
| } | |
| bool containsKey(int address) => programs.containsKey(address); | |
| LineNumberProgram? operator [](int address) => programs[address]; | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| programs.forEach((offset, program) { | |
| buffer | |
| ..write('Line number program @ 0x') | |
| ..writeln(paddedHex(offset)); | |
| program.writeToStringBuffer(buffer); | |
| }); | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// Represents the information for a call site. | |
| abstract class CallInfo { | |
| /// Whether this call site is considered internal (i.e. not located in either | |
| /// user or library Dart source code). | |
| bool get isInternal => true; | |
| } | |
| /// Represents the information for a call site located in Dart source code. | |
| class DartCallInfo extends CallInfo { | |
| final bool inlined; | |
| final bool internal; | |
| final String function; | |
| final String filename; | |
| final int line; | |
| final int column; | |
| DartCallInfo( | |
| {this.inlined = false, | |
| this.internal = false, | |
| required this.function, | |
| required this.filename, | |
| required this.line, | |
| required this.column}); | |
| @override | |
| bool get isInternal => internal; | |
| @override | |
| int get hashCode => Object.hash( | |
| inlined, | |
| internal, | |
| function, | |
| filename, | |
| line, | |
| column, | |
| ); | |
| @override | |
| bool operator ==(Object other) => | |
| other is DartCallInfo && | |
| inlined == other.inlined && | |
| internal == other.internal && | |
| function == other.function && | |
| filename == other.filename && | |
| line == other.line && | |
| column == other.column; | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| buffer | |
| ..write(function) | |
| ..write(' (') | |
| ..write(filename); | |
| if (line > 0) { | |
| buffer | |
| ..write(':') | |
| ..write(line); | |
| if (column > 0) { | |
| buffer | |
| ..write(':') | |
| ..write(column); | |
| } | |
| } | |
| buffer.write(')'); | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// Represents the information for a call site located in a Dart stub. | |
| class StubCallInfo extends CallInfo { | |
| final String name; | |
| final int offset; | |
| StubCallInfo({required this.name, required this.offset}); | |
| @override | |
| int get hashCode => Object.hash(name, offset); | |
| @override | |
| bool operator ==(Object other) => | |
| other is StubCallInfo && name == other.name && offset == other.offset; | |
| @override | |
| String toString() => '$name+0x${offset.toRadixString(16)}'; | |
| } | |
| /// The instructions section in which a program counter address is located. | |
| enum InstructionsSection { none, vm, isolate } | |
| /// A program counter address viewed as an offset into the appropriate | |
| /// instructions section of a Dart snapshot. Includes other information | |
| /// parsed from the corresponding stack trace header when possible. | |
| class PCOffset { | |
| /// The offset into the corresponding instructions section. | |
| final int offset; | |
| /// The instructions section into which this is an offset. | |
| final InstructionsSection section; | |
| /// The operating system on which the stack trace was generated, when | |
| /// available. | |
| final String? os; | |
| /// The architecture on which the stack trace was generated, when | |
| /// available. | |
| final String? architecture; | |
| /// Whether compressed pointers were enabled, when available. | |
| final bool? compressedPointers; | |
| /// Whether the architecture was being simulated, when available. | |
| final bool? usingSimulator; | |
| /// The build ID of the corresponding instructions section, when available. | |
| final String? buildId; | |
| /// The loading unit ID of the corresponding instructions section, when | |
| /// available. | |
| final int? unitId; | |
| PCOffset(this.offset, this.section, | |
| {this.os, | |
| this.architecture, | |
| this.compressedPointers, | |
| this.usingSimulator, | |
| this.buildId, | |
| this.unitId}); | |
| /// The virtual address for this [PCOffset] in [dwarf]. | |
| int virtualAddressIn(Dwarf dwarf) => dwarf.virtualAddressOf(this); | |
| /// The call information found for this [PCOffset] in [dwarf]. | |
| /// | |
| /// Returns null if the PCOffset is invalid for the given DWARF information. | |
| /// | |
| /// If [includeInternalFrames] is false, then only information corresponding | |
| /// to user or library code is returned. | |
| Iterable<CallInfo>? callInfoFrom(Dwarf dwarf, | |
| {bool includeInternalFrames = false}) => | |
| dwarf.callInfoForPCOffset(this, | |
| includeInternalFrames: includeInternalFrames); | |
| @override | |
| int get hashCode => Object.hash(offset, section); | |
| @override | |
| bool operator ==(Object other) => | |
| other is PCOffset && | |
| offset == other.offset && | |
| section == other.section && | |
| os == other.os && | |
| architecture == other.architecture && | |
| compressedPointers == other.compressedPointers && | |
| usingSimulator == other.usingSimulator && | |
| buildId == other.buildId && | |
| unitId == other.unitId; | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| buffer | |
| ..write('PCOffset(') | |
| ..write(section.name) | |
| ..write(', 0x') | |
| ..write(offset.toRadixString(16)); | |
| if (os != null) { | |
| buffer | |
| ..write(', os: ') | |
| ..write(os!); | |
| } | |
| if (architecture != null) { | |
| buffer.write(', architecture: '); | |
| if (usingSimulator ?? false) { | |
| buffer.write('SIM'); | |
| } | |
| buffer.write(architecture!.toUpperCase()); | |
| if (compressedPointers ?? false) { | |
| buffer.write('C'); | |
| } | |
| } | |
| if (buildId != null) { | |
| buffer | |
| ..write(", buildId: '") | |
| ..write(buildId) | |
| ..write("'"); | |
| } | |
| if (unitId != null) { | |
| buffer | |
| ..write(', unitId: ') | |
| ..write(unitId!); | |
| } | |
| buffer.write(')'); | |
| return buffer.toString(); | |
| } | |
| } | |
| // The DWARF debugging information for a given file. | |
| abstract class Dwarf { | |
| /// Attempts to load the DWARF debugging information from the given bytes. | |
| /// | |
| /// Returns a [Dwarf] object if the load succeeds, otherwise returns null. | |
| static Dwarf? fromBytes(Uint8List bytes) => | |
| Dwarf.fromReader(Reader.fromTypedData(bytes)); | |
| /// Attempts to load the DWARF debugging information from the file at [path]. | |
| /// | |
| /// Returns a [Dwarf] object if the load succeeds, otherwise returns null. | |
| static Dwarf? fromFile(String path) => | |
| Dwarf.fromReader(Reader.fromFile(MachO.handleDSYM(path))); | |
| /// Attempts to load the DWARF debugging information from the reader. | |
| /// | |
| /// Returns a [Dwarf] object if the load succeeds, otherwise returns null. | |
| static Dwarf? fromReader(Reader reader) { | |
| final elf = Elf.fromReader(reader); | |
| if (elf != null) { | |
| return DwarfSnapshot.fromDwarfContainer(reader, elf); | |
| } | |
| final macho = MachO.fromReader(reader); | |
| if (macho != null) { | |
| return DwarfSnapshot.fromDwarfContainer(reader, macho); | |
| } | |
| final universalBinary = UniversalBinary.fromReader(reader); | |
| if (universalBinary != null) { | |
| return DwarfUniversalBinary.fromUniversalBinary(reader, universalBinary); | |
| } | |
| return null; | |
| } | |
| /// The build ID for the debugging information. | |
| /// | |
| /// If debugging information is recorded for multiple architectures, then | |
| /// [arch] must be provided to get the correct build ID for that architecture. | |
| /// | |
| /// Returns null if there is no build ID information recorded or if no | |
| /// architecture was provided when needed to disambiguate. | |
| String? buildId([String? arch]); | |
| /// The starting address for the VM instructions section. | |
| /// | |
| /// If debugging information is recorded for multiple architectures, then | |
| /// [arch] must be provided to get the correct build ID for that architecture. | |
| /// | |
| /// Returns null if a VM instructions section could not be located or if no | |
| /// architecture was provided when needed to disambiguate. | |
| int? vmStartAddress([String? arch]); | |
| /// The starting address for the isolate instructions section. | |
| /// | |
| /// If debugging information is recorded for multiple architectures, then | |
| /// [arch] must be provided to get the correct build ID for that architecture. | |
| /// | |
| /// Returns null if an isolate instructions section could not be located or if | |
| /// no architecture was provided when needed to disambiguate. | |
| int? isolateStartAddress([String? arch]); | |
| /// The call information for the given [PCOffset]. There may be multiple | |
| /// [CallInfo] objects returned for a single [PCOffset] when code has been | |
| /// inlined. | |
| /// | |
| /// Returns null if the given address is invalid for the DWARF information | |
| /// or if the PCOffset contains no architecture information and there is | |
| /// debugging information recorded for multiple architectures. | |
| /// | |
| /// If [includeInternalFrames] is false, then only information corresponding | |
| /// to user or library code is returned. | |
| Iterable<CallInfo>? callInfoForPCOffset(PCOffset pcOffset, | |
| {bool includeInternalFrames = false}); | |
| /// The virtual address in this DWARF information for the given [PCOffset]. | |
| int virtualAddressOf(PCOffset pcOffset); | |
| void writeToStringBuffer(StringBuffer buffer); | |
| String dumpFileInfo(); | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// The DWARF debugging information for a single Dart snapshot. | |
| class DwarfSnapshot extends Dwarf { | |
| final DwarfContainer _container; | |
| final Map<int, _AbbreviationsTable> _abbreviationsTables; | |
| final DebugInfo _debugInfo; | |
| final LineNumberInfo _lineNumberInfo; | |
| DwarfSnapshot._(this._container, this._abbreviationsTables, this._debugInfo, | |
| this._lineNumberInfo); | |
| static DwarfSnapshot? fromDwarfContainer( | |
| Reader reader, DwarfContainer container) => | |
| // We use Zone values to pass around the string tables that may be used | |
| // when parsing different sections. | |
| runZoned(() { | |
| final abbrevReader = container.abbreviationsTableReader(reader); | |
| if (abbrevReader == null) return null; | |
| final abbreviationsTables = Map.fromEntries(abbrevReader | |
| .readRepeatedWithOffsets(_AbbreviationsTable.fromReader)); | |
| final debugInfoReader = container.debugInfoReader(reader); | |
| if (debugInfoReader == null) return null; | |
| final debugInfo = | |
| DebugInfo.fromReader(debugInfoReader, abbreviationsTables); | |
| final lineNumberInfoReader = container.lineNumberInfoReader(reader); | |
| if (lineNumberInfoReader == null) return null; | |
| final lineNumberInfo = LineNumberInfo.fromReader(lineNumberInfoReader); | |
| return DwarfSnapshot._( | |
| container, abbreviationsTables, debugInfo, lineNumberInfo); | |
| }, zoneValues: { | |
| _debugStringTableKey: container.debugStringTable, | |
| _debugLineStringTableKey: container.debugLineStringTable, | |
| }); | |
| @override | |
| String? buildId([String? arch]) => _container.buildId; | |
| @override | |
| int? vmStartAddress([String? arch]) => _container.vmStartAddress; | |
| @override | |
| int? isolateStartAddress([String? arch]) => _container.isolateStartAddress; | |
| /// The call information for the given virtual address. There may be | |
| /// multiple [CallInfo] objects returned for a single virtual address when | |
| /// code has been inlined. | |
| /// | |
| /// Returns null if the given address is invalid for the DWARF information. | |
| /// | |
| /// If [includeInternalFrames] is false, then only information corresponding | |
| /// to user or library code is returned. | |
| Iterable<CallInfo>? _callInfoFor(int address, | |
| {bool includeInternalFrames = false}) { | |
| var calls = _debugInfo.callInfo(_lineNumberInfo, address); | |
| if (calls == null) { | |
| final symbol = _container.staticSymbolAt(address); | |
| if (symbol != null) { | |
| final offset = address - symbol.value; | |
| calls = <CallInfo>[StubCallInfo(name: symbol.name, offset: offset)]; | |
| } | |
| } | |
| if (!includeInternalFrames) { | |
| return calls?.where((CallInfo c) => !c.isInternal); | |
| } | |
| return calls; | |
| } | |
| @override | |
| Iterable<CallInfo>? callInfoForPCOffset(PCOffset pcOffset, | |
| {bool includeInternalFrames = false}) => | |
| _callInfoFor(virtualAddressOf(pcOffset), | |
| includeInternalFrames: includeInternalFrames); | |
| @override | |
| int virtualAddressOf(PCOffset pcOffset) { | |
| switch (pcOffset.section) { | |
| case InstructionsSection.none: | |
| // This address is already virtualized, so we don't need to change it. | |
| return pcOffset.offset; | |
| case InstructionsSection.vm: | |
| final vmStart = vmStartAddress(pcOffset.architecture); | |
| if (vmStart == null) { | |
| throw 'Cannot locate VM instructions section in snapshot'; | |
| } | |
| return pcOffset.offset + vmStart; | |
| case InstructionsSection.isolate: | |
| final isolateStart = isolateStartAddress(pcOffset.architecture); | |
| if (isolateStart == null) { | |
| throw 'Cannot locate isolate instructions section in snapshot'; | |
| } | |
| return pcOffset.offset + isolateStart; | |
| } | |
| } | |
| @override | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| buffer | |
| ..writeln('----------------------------------------') | |
| ..writeln(' Abbreviation tables') | |
| ..writeln('----------------------------------------') | |
| ..writeln(); | |
| _abbreviationsTables.forEach((offset, table) { | |
| buffer | |
| ..write('(Offset ') | |
| ..write(paddedHex(offset, 4)) | |
| ..write(') '); | |
| table.writeToStringBuffer(buffer); | |
| }); | |
| buffer | |
| ..writeln('----------------------------------------') | |
| ..writeln(' Debug information') | |
| ..writeln('----------------------------------------') | |
| ..writeln(); | |
| _debugInfo.writeToStringBuffer(buffer); | |
| buffer | |
| ..writeln('----------------------------------------') | |
| ..writeln(' Line number information') | |
| ..writeln('----------------------------------------') | |
| ..writeln(); | |
| _lineNumberInfo.writeToStringBuffer(buffer); | |
| } | |
| @override | |
| String dumpFileInfo() { | |
| final buffer = StringBuffer(); | |
| _container.writeToStringBuffer(buffer); | |
| buffer.writeln(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| @override | |
| String toString() { | |
| final buffer = StringBuffer(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } | |
| /// The DWARF information for a MacOS universal binary. | |
| class DwarfUniversalBinary extends Dwarf { | |
| final UniversalBinary _binary; | |
| final Map<CpuType, DwarfSnapshot> _dwarfs; | |
| DwarfUniversalBinary._(this._binary, this._dwarfs); | |
| static DwarfUniversalBinary? fromUniversalBinary( | |
| Reader originalReader, UniversalBinary binary) { | |
| final dwarfs = <CpuType, DwarfSnapshot>{}; | |
| for (final cpuType in binary.architectures) { | |
| final container = binary.containerForCpuType(cpuType)!; | |
| final reader = binary.readerForCpuType(originalReader, cpuType)!; | |
| final dwarf = DwarfSnapshot.fromDwarfContainer(reader, container); | |
| if (dwarf == null) continue; | |
| dwarfs[cpuType] = dwarf; | |
| } | |
| if (dwarfs.isEmpty) return null; | |
| return DwarfUniversalBinary._(binary, dwarfs); | |
| } | |
| DwarfSnapshot? _dwarfForArch(String? arch) { | |
| if (arch == null) { | |
| // If there's only one DWARF section, then use that information. (This | |
| // avoids having to specify an architecture in this case.) | |
| return _dwarfs.length == 1 ? _dwarfs.values.single : null; | |
| } | |
| return _dwarfs[CpuType.fromDartName(arch)]; | |
| } | |
| @override | |
| String? buildId([String? arch]) => _dwarfForArch(arch)?.buildId(arch); | |
| @override | |
| int? vmStartAddress([String? arch]) => | |
| _dwarfForArch(arch)?.vmStartAddress(arch); | |
| @override | |
| int? isolateStartAddress([String? arch]) => | |
| _dwarfForArch(arch)?.isolateStartAddress(arch); | |
| @override | |
| Iterable<CallInfo>? callInfoForPCOffset(PCOffset pcOffset, | |
| {bool includeInternalFrames = false}) => | |
| _dwarfForArch(pcOffset.architecture)?.callInfoForPCOffset(pcOffset, | |
| includeInternalFrames: includeInternalFrames); | |
| @override | |
| int virtualAddressOf(PCOffset pcOffset) { | |
| final dwarf = _dwarfForArch(pcOffset.architecture); | |
| if (dwarf == null) { | |
| throw 'Cannot determine which architecture to use'; | |
| } | |
| return dwarf.virtualAddressOf(pcOffset); | |
| } | |
| @override | |
| void writeToStringBuffer(StringBuffer buffer) { | |
| for (final entry in _dwarfs.entries) { | |
| buffer | |
| ..write('For architecture ') | |
| ..write(entry.key) | |
| ..writeln(':'); | |
| entry.value.writeToStringBuffer(buffer); | |
| buffer.writeln(''); | |
| } | |
| } | |
| @override | |
| String dumpFileInfo() { | |
| final buffer = StringBuffer(); | |
| _binary.writeToStringBuffer(buffer); | |
| buffer.writeln(); | |
| writeToStringBuffer(buffer); | |
| return buffer.toString(); | |
| } | |
| } |