blob: e2ee925f922ccf8c2045d89f32156a074f1211a2 [file] [log] [blame] [edit]
// 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();
}
}