[pkg/native_stack_traces] Support Mach-O dSYM debugging information.
TEST=vm/dart{,_2}/use_dwarf_stack_traces_flag
Bug: https://github.com/dart-lang/sdk/issues/43612
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-mac-product-x64-try,pkg-mac-release-arm64-try,vm-kernel-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try
Change-Id: Icda21bb14dcc0cf4784cea118e6ba7dd4edd35aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/250381
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
Commit Bot
parent
593eb468f6
commit
08c13f173c
@@ -1,3 +1,9 @@
|
||||
## 0.5.0
|
||||
|
||||
- Add support for parsing DWARF in Mach-O files and dSYM directories.
|
||||
- Add `dump` command to replace the old `--dump_debug_file_contents`
|
||||
flag to `find` and `translate`.
|
||||
|
||||
## 0.4.6
|
||||
|
||||
- Upgrade to `package:lints` 2.0.
|
||||
|
||||
@@ -18,10 +18,11 @@ ArgParser _createBaseDebugParser(ArgParser parser) => parser
|
||||
..addFlag('verbose',
|
||||
abbr: 'v',
|
||||
negatable: false,
|
||||
help: 'Translate all frames, not just user or library code frames')
|
||||
..addFlag('dump_debug_file_contents',
|
||||
negatable: false,
|
||||
help: 'Dump all the parsed information from the debugging file');
|
||||
help: 'Translate all frames, not just user or library code frames');
|
||||
|
||||
final ArgParser _dumpParser = ArgParser(allowTrailingOptions: true)
|
||||
..addOption('output',
|
||||
abbr: 'o', help: 'Filename for generated output', valueHelp: 'FILE');
|
||||
|
||||
final ArgParser _translateParser =
|
||||
_createBaseDebugParser(ArgParser(allowTrailingOptions: true))
|
||||
@@ -48,6 +49,7 @@ final ArgParser _findParser =
|
||||
final ArgParser _helpParser = ArgParser(allowTrailingOptions: true);
|
||||
|
||||
final ArgParser _argParser = ArgParser(allowTrailingOptions: true)
|
||||
..addCommand('dump', _dumpParser)
|
||||
..addCommand('help', _helpParser)
|
||||
..addCommand('find', _findParser)
|
||||
..addCommand('translate', _translateParser)
|
||||
@@ -123,12 +125,22 @@ ${_argParser.usage}
|
||||
Options specific to the find command:
|
||||
${_findParser.usage}''';
|
||||
|
||||
final String _dumpUsage = '''
|
||||
Usage: decode dump [options] <snapshot>
|
||||
|
||||
The dump command dumps the DWARF information in the given snapshot to either
|
||||
standard output or a given output file.
|
||||
|
||||
Options specific to the dump command:
|
||||
${_dumpParser.usage}''';
|
||||
|
||||
final _usages = <String?, String>{
|
||||
null: _mainUsage,
|
||||
'': _mainUsage,
|
||||
'help': _helpUsage,
|
||||
'translate': _translateUsage,
|
||||
'find': _findUsage,
|
||||
'dump': _dumpUsage,
|
||||
};
|
||||
|
||||
const int _badUsageExitCode = 1;
|
||||
@@ -162,15 +174,16 @@ Dwarf? _loadFromFile(String? original, Function(String) usageError) {
|
||||
return null;
|
||||
}
|
||||
final filename = path.canonicalize(path.normalize(original));
|
||||
if (!io.File(filename).existsSync()) {
|
||||
try {
|
||||
final dwarf = Dwarf.fromFile(filename);
|
||||
if (dwarf == null) {
|
||||
usageError('file "$original" does not contain debugging information');
|
||||
}
|
||||
return dwarf;
|
||||
} on io.FileSystemException {
|
||||
usageError('debug file "$original" does not exist');
|
||||
return null;
|
||||
}
|
||||
final dwarf = Dwarf.fromFile(filename);
|
||||
if (dwarf == null) {
|
||||
usageError('file "$original" does not contain debugging information');
|
||||
}
|
||||
return dwarf;
|
||||
}
|
||||
|
||||
void find(ArgResults options) {
|
||||
@@ -199,10 +212,6 @@ void find(ArgResults options) {
|
||||
final dwarf = _loadFromFile(options['debug'], usageError);
|
||||
if (dwarf == null) return;
|
||||
|
||||
if (options['dump_debug_file_contents']) {
|
||||
print(dwarf.dumpFileInfo());
|
||||
}
|
||||
|
||||
if ((options['vm_start'] == null) != (options['isolate_start'] == null)) {
|
||||
return usageError('need both VM start and isolate start');
|
||||
}
|
||||
@@ -266,9 +275,6 @@ Future<void> translate(ArgResults options) async {
|
||||
if (dwarf == null) {
|
||||
return;
|
||||
}
|
||||
if (options['dump_debug_file_contents']) {
|
||||
print(dwarf.dumpFileInfo());
|
||||
}
|
||||
|
||||
final verbose = options['verbose'];
|
||||
final output = options['output'] != null
|
||||
@@ -291,6 +297,27 @@ Future<void> translate(ArgResults options) async {
|
||||
await output.close();
|
||||
}
|
||||
|
||||
Future<void> dump(ArgResults options) async {
|
||||
void usageError(String message) => errorWithUsage(message, command: 'dump');
|
||||
|
||||
if (options.rest.isEmpty) {
|
||||
return usageError('must provide a path to an ELF file or dSYM directory '
|
||||
'that contains DWARF information');
|
||||
}
|
||||
final dwarf = _loadFromFile(options.rest.first, usageError);
|
||||
if (dwarf == null) {
|
||||
return usageError("'${options.rest.first}' contains no DWARF information");
|
||||
}
|
||||
|
||||
final output = options['output'] != null
|
||||
? io.File(path.canonicalize(path.normalize(options['output'])))
|
||||
.openWrite()
|
||||
: io.stdout;
|
||||
output.write(dwarf.dumpFileInfo());
|
||||
await output.flush();
|
||||
await output.close();
|
||||
}
|
||||
|
||||
Future<void> main(List<String> arguments) async {
|
||||
ArgResults options;
|
||||
|
||||
@@ -310,5 +337,7 @@ Future<void> main(List<String> arguments) async {
|
||||
return find(options.command!);
|
||||
case 'translate':
|
||||
return await translate(options.command!);
|
||||
case 'dump':
|
||||
return await dump(options.command!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,18 +178,21 @@ Iterable<PCOffset> collectPCOffsets(Iterable<String> lines) sync* {
|
||||
class DwarfStackTraceDecoder extends StreamTransformerBase<String, String> {
|
||||
final Dwarf _dwarf;
|
||||
final bool _includeInternalFrames;
|
||||
StackTraceHeader? _header;
|
||||
|
||||
DwarfStackTraceDecoder(this._dwarf, {bool includeInternalFrames = false})
|
||||
: _includeInternalFrames = includeInternalFrames;
|
||||
|
||||
PCOffset? offsetOf(int address) => _header?.offsetOf(address);
|
||||
|
||||
@override
|
||||
Stream<String> bind(Stream<String> stream) async* {
|
||||
var depth = 0;
|
||||
StackTraceHeader? header;
|
||||
_header = null;
|
||||
await for (final line in stream) {
|
||||
final parsedHeader = _parseInstructionsLine(line);
|
||||
if (parsedHeader != null) {
|
||||
header = parsedHeader;
|
||||
_header = parsedHeader;
|
||||
depth = 0;
|
||||
yield line;
|
||||
continue;
|
||||
@@ -197,7 +200,7 @@ class DwarfStackTraceDecoder extends StreamTransformerBase<String, String> {
|
||||
// If at any point we can't get appropriate information for the current
|
||||
// line as a stack trace line, then just pass the line through unchanged.
|
||||
final lineMatch = _traceLineRE.firstMatch(line);
|
||||
final offset = _retrievePCOffset(header, lineMatch);
|
||||
final offset = _retrievePCOffset(_header, lineMatch);
|
||||
final callInfo = offset?.callInfoFrom(_dwarf,
|
||||
includeInternalFrames: _includeInternalFrames);
|
||||
if (callInfo == null) {
|
||||
|
||||
@@ -6,8 +6,9 @@ import 'dart:collection';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'constants.dart' as constants;
|
||||
import 'dwarf_container.dart';
|
||||
import 'elf.dart';
|
||||
import 'macho.dart';
|
||||
import 'reader.dart';
|
||||
|
||||
int _initialLengthValue(Reader reader) {
|
||||
@@ -102,12 +103,14 @@ enum _AttributeForm {
|
||||
reference4,
|
||||
sectionOffset,
|
||||
string,
|
||||
strp,
|
||||
}
|
||||
|
||||
const _attributeForms = <int, _AttributeForm>{
|
||||
0x01: _AttributeForm.address,
|
||||
0x08: _AttributeForm.string,
|
||||
0x0c: _AttributeForm.flag,
|
||||
0x0e: _AttributeForm.strp,
|
||||
0x0f: _AttributeForm.constant,
|
||||
0x13: _AttributeForm.reference4,
|
||||
0x17: _AttributeForm.sectionOffset,
|
||||
@@ -116,6 +119,7 @@ const _attributeForms = <int, _AttributeForm>{
|
||||
const _attributeFormStrings = <_AttributeForm, String>{
|
||||
_AttributeForm.address: 'DW_FORM_addr',
|
||||
_AttributeForm.string: 'DW_FORM_string',
|
||||
_AttributeForm.strp: 'DW_FORM_strp',
|
||||
_AttributeForm.flag: 'DW_FORM_flag',
|
||||
_AttributeForm.constant: 'DW_FORM_udata',
|
||||
_AttributeForm.reference4: 'DW_FORM_ref4',
|
||||
@@ -145,6 +149,9 @@ class _Attribute {
|
||||
switch (form) {
|
||||
case _AttributeForm.string:
|
||||
return reader.readNullTerminatedString();
|
||||
case _AttributeForm.strp:
|
||||
final offset = reader.readBytes(4);
|
||||
return header.stringTable[offset]!;
|
||||
case _AttributeForm.flag:
|
||||
return reader.readByte() != 0;
|
||||
case _AttributeForm.address:
|
||||
@@ -161,6 +168,7 @@ class _Attribute {
|
||||
String valueToString(Object value, [CompilationUnit? unit]) {
|
||||
switch (form) {
|
||||
case _AttributeForm.string:
|
||||
case _AttributeForm.strp:
|
||||
return value as String;
|
||||
case _AttributeForm.flag:
|
||||
return value.toString();
|
||||
@@ -431,14 +439,16 @@ class CompilationUnitHeader {
|
||||
final int addressSize;
|
||||
// ignore: library_private_types_in_public_api
|
||||
final _AbbreviationsTable abbreviations;
|
||||
final DwarfContainerStringTable stringTable;
|
||||
|
||||
CompilationUnitHeader._(this.size, this.version, this.abbreviationsOffset,
|
||||
this.addressSize, this.abbreviations);
|
||||
this.addressSize, this.abbreviations, this.stringTable);
|
||||
|
||||
static CompilationUnitHeader? fromReader(
|
||||
Reader reader,
|
||||
// ignore: library_private_types_in_public_api
|
||||
Map<int, _AbbreviationsTable> abbreviationsTables) {
|
||||
Map<int, _AbbreviationsTable> abbreviationsTables,
|
||||
DwarfContainerStringTable stringTable) {
|
||||
final size = _initialLengthValue(reader);
|
||||
// An empty unit is an ending marker.
|
||||
if (size == 0) return null;
|
||||
@@ -453,8 +463,8 @@ class CompilationUnitHeader {
|
||||
'0x${paddedHex(abbreviationsOffset, 4)}');
|
||||
}
|
||||
final addressSize = reader.readByte();
|
||||
return CompilationUnitHeader._(
|
||||
size, version, abbreviationsOffset, addressSize, abbreviationsTable);
|
||||
return CompilationUnitHeader._(size, version, abbreviationsOffset,
|
||||
addressSize, abbreviationsTable, stringTable);
|
||||
}
|
||||
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
@@ -489,9 +499,10 @@ class CompilationUnit {
|
||||
static CompilationUnit? fromReader(
|
||||
Reader reader,
|
||||
// ignore: library_private_types_in_public_api
|
||||
Map<int, _AbbreviationsTable> abbreviationsTables) {
|
||||
final header =
|
||||
CompilationUnitHeader.fromReader(reader, abbreviationsTables);
|
||||
Map<int, _AbbreviationsTable> abbreviationsTables,
|
||||
DwarfContainerStringTable stringTable) {
|
||||
final header = CompilationUnitHeader.fromReader(
|
||||
reader, abbreviationsTables, stringTable);
|
||||
if (header == null) return null;
|
||||
|
||||
final referenceTable = Map.fromEntries(reader.readRepeatedWithOffsets(
|
||||
@@ -563,10 +574,11 @@ class DebugInfo {
|
||||
static DebugInfo fromReader(
|
||||
Reader reader,
|
||||
// ignore: library_private_types_in_public_api
|
||||
Map<int, _AbbreviationsTable> abbreviationsTable) {
|
||||
Map<int, _AbbreviationsTable> abbreviationsTable,
|
||||
DwarfContainerStringTable stringTable) {
|
||||
final units = reader
|
||||
.readRepeated(
|
||||
(r) => CompilationUnit.fromReader(reader, abbreviationsTable))
|
||||
.readRepeated((r) =>
|
||||
CompilationUnit.fromReader(reader, abbreviationsTable, stringTable))
|
||||
.toList();
|
||||
return DebugInfo._(units);
|
||||
}
|
||||
@@ -914,14 +926,18 @@ class LineNumberProgram {
|
||||
|
||||
void applySpecialOpcode(int opcode) {
|
||||
final adjustedOpcode = opcode - header.opcodeBase;
|
||||
state.address = adjustedOpcode ~/ header.lineRange;
|
||||
state.line += header.lineBase + (adjustedOpcode % header.lineRange);
|
||||
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();
|
||||
continue;
|
||||
}
|
||||
switch (opcode) {
|
||||
@@ -975,7 +991,8 @@ class LineNumberProgram {
|
||||
state.basicBlock = true;
|
||||
break;
|
||||
case 8: // DW_LNS_const_add_pc
|
||||
applySpecialOpcode(255);
|
||||
state.address += ((255 - header.opcodeBase) ~/ header.lineRange) *
|
||||
header.minimumInstructionLength;
|
||||
break;
|
||||
case 9: // DW_LNS_fixed_advance_pc
|
||||
state.address += reader.readBytes(2);
|
||||
@@ -1200,12 +1217,19 @@ class PCOffset {
|
||||
other is PCOffset && offset == other.offset && section == other.section;
|
||||
|
||||
@override
|
||||
String toString() => 'PCOffset($section, $offset)';
|
||||
String toString() => 'PCOffset($section, 0x${offset.toRadixString(16)})';
|
||||
}
|
||||
|
||||
class UnimplementedStringTable extends DwarfContainerStringTable {
|
||||
@override
|
||||
String? operator [](int index) {
|
||||
throw 'No string table found in DWARF information';
|
||||
}
|
||||
}
|
||||
|
||||
/// The DWARF debugging information for a Dart snapshot.
|
||||
class Dwarf {
|
||||
final Elf _elf;
|
||||
final DwarfContainer _container;
|
||||
final Map<int, _AbbreviationsTable> _abbreviationsTables;
|
||||
final DebugInfo _debugInfo;
|
||||
final LineNumberInfo _lineNumberInfo;
|
||||
@@ -1218,17 +1242,38 @@ class Dwarf {
|
||||
/// DWARF information.
|
||||
final int isolateStartAddress;
|
||||
|
||||
Dwarf._(this._elf, this._abbreviationsTables, this._debugInfo,
|
||||
Dwarf._(this._container, this._abbreviationsTables, this._debugInfo,
|
||||
this._lineNumberInfo, this.vmStartAddress, this.isolateStartAddress);
|
||||
|
||||
static Dwarf fromDwarfContainer(Reader reader, DwarfContainer container) {
|
||||
final abbrevReader = container.abbreviationsTableReader(reader);
|
||||
final abbreviationsTables = Map.fromEntries(
|
||||
abbrevReader.readRepeatedWithOffsets(_AbbreviationsTable.fromReader));
|
||||
|
||||
final lineNumberInfo =
|
||||
LineNumberInfo.fromReader(container.lineNumberInfoReader(reader));
|
||||
|
||||
final stringTable = container.stringTable ?? UnimplementedStringTable();
|
||||
final debugInfo = DebugInfo.fromReader(
|
||||
container.debugInfoReader(reader), abbreviationsTables, stringTable);
|
||||
|
||||
return Dwarf._(container, abbreviationsTables, debugInfo, lineNumberInfo,
|
||||
container.vmStartAddress, container.isolateStartAddress);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
// Currently, the only DWARF-containing format we recognize is ELF.
|
||||
final elf = Elf.fromReader(reader);
|
||||
if (elf == null) return null;
|
||||
return Dwarf._loadSectionsFromElf(reader, elf);
|
||||
if (elf != null) {
|
||||
return Dwarf.fromDwarfContainer(reader, elf);
|
||||
}
|
||||
final macho = MachO.fromReader(reader);
|
||||
if (macho != null) {
|
||||
return Dwarf.fromDwarfContainer(reader, macho);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Attempts to load the DWARF debugging information from the given bytes.
|
||||
@@ -1241,54 +1286,12 @@ class Dwarf {
|
||||
///
|
||||
/// Returns a [Dwarf] object if the load succeeds, otherwise returns null.
|
||||
static Dwarf? fromFile(String path) =>
|
||||
Dwarf.fromReader(Reader.fromFile(path));
|
||||
|
||||
static Dwarf _loadSectionsFromElf(Reader reader, Elf elf) {
|
||||
final abbrevSection = elf.namedSections('.debug_abbrev').single;
|
||||
final abbrevReader = abbrevSection.refocusedCopy(reader);
|
||||
final abbreviationsTables = Map.fromEntries(
|
||||
abbrevReader.readRepeatedWithOffsets(_AbbreviationsTable.fromReader));
|
||||
|
||||
final lineNumberSection = elf.namedSections('.debug_line').single;
|
||||
final lineNumberInfo =
|
||||
LineNumberInfo.fromReader(lineNumberSection.refocusedCopy(reader));
|
||||
|
||||
final infoSection = elf.namedSections('.debug_info').single;
|
||||
final debugInfo = DebugInfo.fromReader(
|
||||
infoSection.refocusedCopy(reader), abbreviationsTables);
|
||||
|
||||
final vmStartSymbol = elf.dynamicSymbolFor(constants.vmSymbolName);
|
||||
if (vmStartSymbol == null) {
|
||||
throw FormatException(
|
||||
'Expected a dynamic symbol with name ${constants.vmSymbolName}');
|
||||
}
|
||||
final vmStartAddress = vmStartSymbol.value;
|
||||
|
||||
final isolateStartSymbol =
|
||||
elf.dynamicSymbolFor(constants.isolateSymbolName);
|
||||
if (isolateStartSymbol == null) {
|
||||
throw FormatException(
|
||||
'Expected a dynamic symbol with name ${constants.isolateSymbolName}');
|
||||
}
|
||||
final isolateStartAddress = isolateStartSymbol.value;
|
||||
|
||||
return Dwarf._(elf, abbreviationsTables, debugInfo, lineNumberInfo,
|
||||
vmStartAddress, isolateStartAddress);
|
||||
}
|
||||
Dwarf.fromReader(Reader.fromFile(MachO.handleDSYM(path)));
|
||||
|
||||
/// The build ID for the debugging information.
|
||||
///
|
||||
/// Returns null if there is no build ID information recorded.
|
||||
String? get buildId {
|
||||
final sections = _elf.namedSections(constants.buildIdSectionName);
|
||||
if (sections.isEmpty) return null;
|
||||
final note = sections.single as Note;
|
||||
if (note.type != constants.buildIdNoteType) return null;
|
||||
if (note.name != constants.buildIdNoteName) return null;
|
||||
return note.description
|
||||
.map((i) => i.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
}
|
||||
String? get buildId => _container.buildId;
|
||||
|
||||
/// The call information for the given virtual address. There may be
|
||||
/// multiple [CallInfo] objects returned for a single virtual address when
|
||||
@@ -1302,7 +1305,7 @@ class Dwarf {
|
||||
{bool includeInternalFrames = false}) {
|
||||
var calls = _debugInfo.callInfo(_lineNumberInfo, address);
|
||||
if (calls == null) {
|
||||
final symbol = _elf.staticSymbolAt(address);
|
||||
final symbol = _container.staticSymbolAt(address);
|
||||
if (symbol != null) {
|
||||
final offset = address - symbol.value;
|
||||
calls = <CallInfo>[StubCallInfo(name: symbol.name, offset: offset)];
|
||||
@@ -1358,7 +1361,7 @@ class Dwarf {
|
||||
|
||||
String dumpFileInfo() {
|
||||
final buffer = StringBuffer();
|
||||
_elf.writeToStringBuffer(buffer);
|
||||
_container.writeToStringBuffer(buffer);
|
||||
buffer.writeln();
|
||||
writeToStringBuffer(buffer);
|
||||
return buffer.toString();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'reader.dart';
|
||||
|
||||
abstract class DwarfContainerStringTable {
|
||||
String? operator [](int index);
|
||||
}
|
||||
|
||||
abstract class DwarfContainerSymbol {
|
||||
int get value;
|
||||
String get name;
|
||||
}
|
||||
|
||||
abstract class DwarfContainer {
|
||||
Reader debugInfoReader(Reader containerReader);
|
||||
Reader lineNumberInfoReader(Reader containerReader);
|
||||
Reader abbreviationsTableReader(Reader containerReader);
|
||||
DwarfContainerSymbol? staticSymbolAt(int address);
|
||||
|
||||
int get vmStartAddress;
|
||||
int get isolateStartAddress;
|
||||
|
||||
String? get buildId;
|
||||
|
||||
DwarfContainerStringTable? get stringTable;
|
||||
|
||||
void writeToStringBuffer(StringBuffer buffer);
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'constants.dart' as constants;
|
||||
import 'dwarf_container.dart';
|
||||
import 'reader.dart';
|
||||
|
||||
int _readElfBytes(Reader reader, int bytes, int alignment) {
|
||||
@@ -90,10 +92,12 @@ class ElfHeader {
|
||||
this.sectionHeaderStringsIndex);
|
||||
|
||||
static ElfHeader? fromReader(Reader reader) {
|
||||
final start = reader.offset;
|
||||
final fileSize = reader.length;
|
||||
|
||||
for (final sigByte in _ELFMAG.codeUnits) {
|
||||
if (reader.readByte() != sigByte) {
|
||||
reader.seek(start, absolute: true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -266,7 +270,11 @@ class ProgramHeaderEntry {
|
||||
static const _PT_NULL = 0;
|
||||
static const _PT_LOAD = 1;
|
||||
static const _PT_DYNAMIC = 2;
|
||||
static const _PT_NOTE = 4;
|
||||
static const _PT_PHDR = 6;
|
||||
static const _PT_GNU_EH_FRAME = 0x6474e550;
|
||||
static const _PT_GNU_STACK = 0x6474e551;
|
||||
static const _PT_GNU_RELRO = 0x6474e552;
|
||||
|
||||
ProgramHeaderEntry._(this.type, this.flags, this.offset, this.vaddr,
|
||||
this.paddr, this.filesz, this.memsz, this.align, this.wordSize);
|
||||
@@ -296,7 +304,11 @@ class ProgramHeaderEntry {
|
||||
_PT_NULL: 'PT_NULL',
|
||||
_PT_LOAD: 'PT_LOAD',
|
||||
_PT_DYNAMIC: 'PT_DYNAMIC',
|
||||
_PT_NOTE: 'PT_NOTE',
|
||||
_PT_PHDR: 'PT_PHDR',
|
||||
_PT_GNU_EH_FRAME: 'PT_GNU_EH_FRAME',
|
||||
_PT_GNU_STACK: 'PT_GNU_STACK',
|
||||
_PT_GNU_RELRO: 'PT_GNU_RELRO',
|
||||
};
|
||||
|
||||
static String _typeToString(int type) =>
|
||||
@@ -646,7 +658,7 @@ class Note extends Section {
|
||||
}
|
||||
|
||||
/// A map from table offsets to strings, used to store names of ELF objects.
|
||||
class StringTable extends Section {
|
||||
class StringTable extends Section implements DwarfContainerStringTable {
|
||||
final Map<int, String> _entries;
|
||||
|
||||
StringTable._(entry, this._entries) : super._(entry);
|
||||
@@ -658,8 +670,22 @@ class StringTable extends Section {
|
||||
return StringTable._(entry, entries);
|
||||
}
|
||||
|
||||
String? operator [](int index) => _entries[index];
|
||||
bool containsKey(int index) => _entries.containsKey(index);
|
||||
@override
|
||||
String? operator [](int index) {
|
||||
// Fast case: Index is for the start of a null terminated string.
|
||||
if (_entries.containsKey(index)) {
|
||||
return _entries[index];
|
||||
}
|
||||
// We can index into null terminated string entries for suffixes of
|
||||
// that string, so do a linear search to find the appropriate entry.
|
||||
for (final kv in _entries.entries) {
|
||||
final start = index - kv.key;
|
||||
if (start >= 0 && start <= kv.value.length) {
|
||||
return kv.value.substring(start);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
@@ -680,6 +706,7 @@ class StringTable extends Section {
|
||||
enum SymbolBinding {
|
||||
STB_LOCAL,
|
||||
STB_GLOBAL,
|
||||
STB_WEAK,
|
||||
}
|
||||
|
||||
enum SymbolType {
|
||||
@@ -696,15 +723,17 @@ enum SymbolVisibility {
|
||||
}
|
||||
|
||||
/// A symbol in an ELF file, which names a portion of the virtual address space.
|
||||
class Symbol {
|
||||
class Symbol implements DwarfContainerSymbol {
|
||||
final int nameIndex;
|
||||
final int info;
|
||||
final int other;
|
||||
final int sectionIndex;
|
||||
@override
|
||||
final int value;
|
||||
final int size;
|
||||
final int _wordSize;
|
||||
late String name;
|
||||
@override
|
||||
late final String name;
|
||||
|
||||
Symbol._(this.nameIndex, this.info, this.other, this.sectionIndex, this.value,
|
||||
this.size, this._wordSize);
|
||||
@@ -731,14 +760,6 @@ class Symbol {
|
||||
nameIndex, info, other, sectionIndex, value, size, wordSize);
|
||||
}
|
||||
|
||||
void _cacheNameFromStringTable(StringTable table) {
|
||||
final nameFromTable = table[nameIndex];
|
||||
if (nameFromTable == null) {
|
||||
throw FormatException('Index $nameIndex not found in string table');
|
||||
}
|
||||
name = nameFromTable;
|
||||
}
|
||||
|
||||
SymbolBinding get bind => SymbolBinding.values[info >> 4];
|
||||
SymbolType get type => SymbolType.values[info & 0x0f];
|
||||
SymbolVisibility get visibility => SymbolVisibility.values[other & 0x03];
|
||||
@@ -755,6 +776,9 @@ class Symbol {
|
||||
case SymbolBinding.STB_LOCAL:
|
||||
buffer.write(' a local');
|
||||
break;
|
||||
case SymbolBinding.STB_WEAK:
|
||||
buffer.write(' a weak');
|
||||
break;
|
||||
}
|
||||
switch (visibility) {
|
||||
case SymbolVisibility.STV_DEFAULT:
|
||||
@@ -804,8 +828,13 @@ class SymbolTable extends Section {
|
||||
void _cacheNames(StringTable stringTable) {
|
||||
_nameCache.clear();
|
||||
for (final symbol in _entries) {
|
||||
symbol._cacheNameFromStringTable(stringTable);
|
||||
_nameCache[symbol.name] = symbol;
|
||||
final index = symbol.nameIndex;
|
||||
final name = stringTable[index];
|
||||
if (name == null) {
|
||||
throw FormatException('Index $index not found in string table');
|
||||
}
|
||||
symbol.name = name;
|
||||
_nameCache[name] = symbol;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -932,7 +961,7 @@ class DynamicTable extends Section {
|
||||
}
|
||||
|
||||
/// Information parsed from an Executable and Linking Format (ELF) file.
|
||||
class Elf {
|
||||
class Elf implements DwarfContainer {
|
||||
final ElfHeader _header;
|
||||
final ProgramHeader _programHeader;
|
||||
final SectionHeader _sectionHeader;
|
||||
@@ -984,16 +1013,24 @@ class Elf {
|
||||
|
||||
/// Reverse lookup of the static symbol that contains the given virtual
|
||||
/// address. Returns null if no static symbol matching the address is found.
|
||||
@override
|
||||
Symbol? staticSymbolAt(int address) {
|
||||
Symbol? bestSym;
|
||||
for (final section in namedSections('.symtab')) {
|
||||
final table = section as SymbolTable;
|
||||
for (final symbol in table.values) {
|
||||
final start = symbol.value;
|
||||
final end = start + symbol.size;
|
||||
if (start <= address && address < end) return symbol;
|
||||
if (start > address) continue;
|
||||
// If given a non-zero extent of a symbol, make sure the address is
|
||||
// within the extent.
|
||||
if (symbol.size > 0 && (start + symbol.size <= address)) continue;
|
||||
// Pick the symbol with a start closest to the given address.
|
||||
if (bestSym == null || (bestSym.value < start)) {
|
||||
bestSym = symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return bestSym;
|
||||
}
|
||||
|
||||
/// Creates an [Elf] from the data pointed to by [reader].
|
||||
@@ -1077,6 +1114,55 @@ class Elf {
|
||||
header, programHeader, sectionHeader, sections, sectionsByName);
|
||||
}
|
||||
|
||||
@override
|
||||
Reader abbreviationsTableReader(Reader containerReader) =>
|
||||
namedSections('.debug_abbrev').single.refocusedCopy(containerReader);
|
||||
|
||||
@override
|
||||
Reader lineNumberInfoReader(Reader containerReader) =>
|
||||
namedSections('.debug_line').single.refocusedCopy(containerReader);
|
||||
|
||||
@override
|
||||
Reader debugInfoReader(Reader containerReader) =>
|
||||
namedSections('.debug_info').single.refocusedCopy(containerReader);
|
||||
|
||||
@override
|
||||
int get vmStartAddress {
|
||||
final vmStartSymbol = dynamicSymbolFor(constants.vmSymbolName);
|
||||
if (vmStartSymbol == null) {
|
||||
throw FormatException(
|
||||
'Expected a dynamic symbol with name ${constants.vmSymbolName}');
|
||||
}
|
||||
return vmStartSymbol.value;
|
||||
}
|
||||
|
||||
@override
|
||||
int get isolateStartAddress {
|
||||
final isolateStartSymbol = dynamicSymbolFor(constants.isolateSymbolName);
|
||||
if (isolateStartSymbol == null) {
|
||||
throw FormatException(
|
||||
'Expected a dynamic symbol with name ${constants.isolateSymbolName}');
|
||||
}
|
||||
return isolateStartSymbol.value;
|
||||
}
|
||||
|
||||
@override
|
||||
String? get buildId {
|
||||
final sections = namedSections(constants.buildIdSectionName);
|
||||
if (sections.isEmpty) return null;
|
||||
final note = sections.single as Note;
|
||||
if (note.type != constants.buildIdNoteType) return null;
|
||||
if (note.name != constants.buildIdNoteName) return null;
|
||||
return note.description
|
||||
.map((i) => i.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
}
|
||||
|
||||
// Currently we don't handle DWARF uses of string tables in ELF files.
|
||||
@override
|
||||
DwarfContainerStringTable? get stringTable => null;
|
||||
|
||||
@override
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..writeln('-----------------------------------------------------')
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'constants.dart' as constants;
|
||||
import 'dwarf_container.dart';
|
||||
import 'reader.dart';
|
||||
|
||||
int _readMachOUint8(Reader reader) => reader.readByte(signed: false);
|
||||
|
||||
int _readMachOUint16(Reader reader) => reader.readBytes(2, signed: false);
|
||||
|
||||
int _readMachOUint32(Reader reader) => reader.readBytes(4, signed: false);
|
||||
|
||||
int _readMachOUword(Reader reader) =>
|
||||
reader.readBytes(reader.wordSize, signed: false);
|
||||
|
||||
class StringTable implements DwarfContainerStringTable {
|
||||
final Map<int, String> _stringsByOffset;
|
||||
|
||||
StringTable._(this._stringsByOffset);
|
||||
|
||||
static StringTable fromReader(Reader reader) => StringTable._(Map.fromEntries(
|
||||
reader.readRepeatedWithOffsets((r) => r.readNullTerminatedString())));
|
||||
|
||||
@override
|
||||
String? operator [](int index) {
|
||||
// Fast case: Index is for the start of a null terminated string.
|
||||
if (_stringsByOffset.containsKey(index)) {
|
||||
return _stringsByOffset[index];
|
||||
}
|
||||
// We can index into null terminated string entries for suffixes of
|
||||
// that string, so do a linear search to find the appropriate entry.
|
||||
for (final kv in _stringsByOffset.entries) {
|
||||
final start = index - kv.key;
|
||||
if (start >= 0 && start <= kv.value.length) {
|
||||
return kv.value.substring(start);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
for (final k in _stringsByOffset.keys) {
|
||||
buffer
|
||||
..write(k.toString().padLeft(8, ' '))
|
||||
..write(' => ')
|
||||
..writeln(_stringsByOffset[k]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final buffer = StringBuffer();
|
||||
writeToStringBuffer(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class Symbol implements DwarfContainerSymbol {
|
||||
final int index;
|
||||
final int type;
|
||||
final int sect;
|
||||
final int desc;
|
||||
@override
|
||||
final int value;
|
||||
@override
|
||||
late final String name;
|
||||
|
||||
Symbol._(this.index, this.type, this.sect, this.desc, this.value);
|
||||
|
||||
static Symbol fromReader(Reader reader) {
|
||||
final index = _readMachOUint32(reader);
|
||||
final type = _readMachOUint8(reader);
|
||||
final sect = _readMachOUint8(reader);
|
||||
final desc = _readMachOUint16(reader);
|
||||
final value = _readMachOUword(reader);
|
||||
return Symbol._(index, type, sect, desc, value);
|
||||
}
|
||||
}
|
||||
|
||||
class SymbolTable {
|
||||
final Map<String, Symbol> _symbols;
|
||||
|
||||
SymbolTable._(this._symbols);
|
||||
|
||||
static SymbolTable fromReader(
|
||||
Reader reader, int nsyms, StringTable stringTable) {
|
||||
final symbols = <String, Symbol>{};
|
||||
for (int i = 0; i < nsyms; i++) {
|
||||
final symbol = Symbol.fromReader(reader);
|
||||
final index = symbol.index;
|
||||
final name = stringTable[index];
|
||||
if (name == null) {
|
||||
throw FormatException('Index $index not found in string table');
|
||||
}
|
||||
symbol.name = name;
|
||||
symbols[name] = symbol;
|
||||
}
|
||||
return SymbolTable._(symbols);
|
||||
}
|
||||
|
||||
Iterable<String> get keys => _symbols.keys;
|
||||
Iterable<Symbol> get values => _symbols.values;
|
||||
|
||||
Symbol? operator [](String name) => _symbols[name];
|
||||
|
||||
bool containsKey(String name) => _symbols.containsKey(name);
|
||||
}
|
||||
|
||||
class LoadCommand {
|
||||
final int cmd;
|
||||
final int cmdsize;
|
||||
|
||||
LoadCommand._(this.cmd, this.cmdsize);
|
||||
|
||||
static const LC_SEGMENT = 0x1;
|
||||
static const LC_SYMTAB = 0x2;
|
||||
static const LC_SEGMENT_64 = 0x19;
|
||||
|
||||
static LoadCommand fromReader(Reader reader) {
|
||||
final start = reader.offset; // cmdsize includes size of cmd and cmdsize.
|
||||
final cmd = _readMachOUint32(reader);
|
||||
final cmdsize = _readMachOUint32(reader);
|
||||
LoadCommand command = LoadCommand._(cmd, cmdsize);
|
||||
switch (cmd) {
|
||||
case LC_SEGMENT:
|
||||
case LC_SEGMENT_64:
|
||||
command = SegmentCommand.fromReader(reader, cmd, cmdsize);
|
||||
break;
|
||||
case LC_SYMTAB:
|
||||
command = SymbolTableCommand.fromReader(reader, cmd, cmdsize);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
reader.seek(start + cmdsize, absolute: true);
|
||||
return command;
|
||||
}
|
||||
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..write('Uninterpreted command 0x')
|
||||
..write(cmd.toRadixString(16))
|
||||
..write(' of size ')
|
||||
..writeln(cmdsize);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
StringBuffer buffer = StringBuffer();
|
||||
writeToStringBuffer(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class SegmentCommand extends LoadCommand {
|
||||
final String segname;
|
||||
final int vmaddr;
|
||||
final int vmsize;
|
||||
final int fileoff;
|
||||
final int filesize;
|
||||
final int maxprot;
|
||||
final int initprot;
|
||||
final int nsects;
|
||||
final int flags;
|
||||
final Map<String, Section> sections;
|
||||
|
||||
SegmentCommand._(
|
||||
int cmd,
|
||||
int cmdsize,
|
||||
this.segname,
|
||||
this.vmaddr,
|
||||
this.vmsize,
|
||||
this.fileoff,
|
||||
this.filesize,
|
||||
this.maxprot,
|
||||
this.initprot,
|
||||
this.nsects,
|
||||
this.flags,
|
||||
this.sections)
|
||||
: super._(cmd, cmdsize);
|
||||
|
||||
static SegmentCommand fromReader(Reader reader, int cmd, int cmdsize) {
|
||||
final segname = reader.readFixedLengthNullTerminatedString(16);
|
||||
final vmaddr = _readMachOUword(reader);
|
||||
final vmsize = _readMachOUword(reader);
|
||||
final fileoff = _readMachOUword(reader);
|
||||
final filesize = _readMachOUword(reader);
|
||||
final maxprot = _readMachOUint32(reader);
|
||||
final initprot = _readMachOUint32(reader);
|
||||
final nsects = _readMachOUint32(reader);
|
||||
final flags = _readMachOUint32(reader);
|
||||
final sections = <String, Section>{};
|
||||
for (int i = 0; i < nsects; i++) {
|
||||
final section = Section.fromReader(reader);
|
||||
sections[section.sectname] = section;
|
||||
}
|
||||
return SegmentCommand._(cmd, cmdsize, segname, vmaddr, vmsize, fileoff,
|
||||
filesize, maxprot, initprot, nsects, flags, sections);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..write('Segment "')
|
||||
..write(segname)
|
||||
..write('" of size ')
|
||||
..write(filesize)
|
||||
..write(' at offset 0x')
|
||||
..writeln(fileoff.toRadixString(16));
|
||||
buffer.writeln('Sections:');
|
||||
for (final section in sections.values) {
|
||||
section.writeToStringBuffer(buffer);
|
||||
buffer.writeln();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Section {
|
||||
String sectname;
|
||||
String segname;
|
||||
int addr;
|
||||
int size;
|
||||
int offset;
|
||||
int align;
|
||||
int reloff;
|
||||
int nreloc;
|
||||
int flags;
|
||||
int reserved1;
|
||||
int reserved2;
|
||||
int? reserved3;
|
||||
|
||||
Section._(
|
||||
this.sectname,
|
||||
this.segname,
|
||||
this.addr,
|
||||
this.size,
|
||||
this.offset,
|
||||
this.align,
|
||||
this.reloff,
|
||||
this.nreloc,
|
||||
this.flags,
|
||||
this.reserved1,
|
||||
this.reserved2,
|
||||
this.reserved3);
|
||||
|
||||
static Section fromReader(Reader reader) {
|
||||
final sectname = reader.readFixedLengthNullTerminatedString(16);
|
||||
final segname = reader.readFixedLengthNullTerminatedString(16);
|
||||
final addr = _readMachOUword(reader);
|
||||
final size = _readMachOUword(reader);
|
||||
final offset = _readMachOUint32(reader);
|
||||
final align = _readMachOUint32(reader);
|
||||
final reloff = _readMachOUint32(reader);
|
||||
final nreloc = _readMachOUint32(reader);
|
||||
final flags = _readMachOUint32(reader);
|
||||
final reserved1 = _readMachOUint32(reader);
|
||||
final reserved2 = _readMachOUint32(reader);
|
||||
final reserved3 = (reader.wordSize == 8) ? _readMachOUint32(reader) : null;
|
||||
return Section._(sectname, segname, addr, size, offset, align, reloff,
|
||||
nreloc, flags, reserved1, reserved2, reserved3);
|
||||
}
|
||||
|
||||
Reader refocus(Reader reader) => reader.refocusedCopy(offset, size);
|
||||
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..write('Section "')
|
||||
..write(sectname)
|
||||
..write('" of size ')
|
||||
..write(size)
|
||||
..write(' at offset 0x')
|
||||
..write(paddedHex(offset, 4));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
StringBuffer buffer = StringBuffer();
|
||||
writeToStringBuffer(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class SymbolTableCommand extends LoadCommand {
|
||||
final int _symoff;
|
||||
final int _nsyms;
|
||||
final int _stroff;
|
||||
final int _strsize;
|
||||
|
||||
SymbolTableCommand._(int cmd, int cmdsize, this._symoff, this._nsyms,
|
||||
this._stroff, this._strsize)
|
||||
: super._(cmd, cmdsize);
|
||||
|
||||
static SymbolTableCommand fromReader(Reader reader, int cmd, int cmdsize) {
|
||||
final symoff = _readMachOUint32(reader);
|
||||
final nsyms = _readMachOUint32(reader);
|
||||
final stroff = _readMachOUint32(reader);
|
||||
final strsize = _readMachOUint32(reader);
|
||||
return SymbolTableCommand._(cmd, cmdsize, symoff, nsyms, stroff, strsize);
|
||||
}
|
||||
|
||||
SymbolTable load(Reader reader) {
|
||||
final stringTable =
|
||||
StringTable.fromReader(reader.refocusedCopy(_stroff, _strsize));
|
||||
return SymbolTable.fromReader(
|
||||
reader.refocusedCopy(_symoff), _nsyms, stringTable);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..write('Symbol table with ')
|
||||
..write(_nsyms)
|
||||
..write(' symbols of size ')
|
||||
..writeln(cmdsize);
|
||||
}
|
||||
}
|
||||
|
||||
class MachOHeader {
|
||||
final int magic;
|
||||
final int cputype;
|
||||
final int cpusubtype;
|
||||
final int filetype;
|
||||
final int ncmds;
|
||||
final int sizeofcmds;
|
||||
final int flags;
|
||||
final int? reserved;
|
||||
final int size;
|
||||
|
||||
MachOHeader._(this.magic, this.cputype, this.cpusubtype, this.filetype,
|
||||
this.ncmds, this.sizeofcmds, this.flags, this.reserved, this.size);
|
||||
|
||||
static const _MH_MAGIC = 0xfeedface;
|
||||
static const _MH_CIGAM = 0xcefaedfe;
|
||||
static const _MH_MAGIC_64 = 0xfeedfacf;
|
||||
static const _MH_CIGAM_64 = 0xcffaedfe;
|
||||
|
||||
static MachOHeader? fromReader(Reader reader) {
|
||||
final start = reader.offset;
|
||||
// Initially assume host endianness.
|
||||
reader.endian = Endian.host;
|
||||
final magic = _readMachOUint32(reader);
|
||||
if (magic == _MH_MAGIC || magic == _MH_CIGAM) {
|
||||
reader.wordSize = 4;
|
||||
} else if (magic == _MH_MAGIC_64 || magic == _MH_CIGAM_64) {
|
||||
reader.wordSize = 8;
|
||||
} else {
|
||||
// Not an expected magic value, so not a supported Mach-O file.
|
||||
return null;
|
||||
}
|
||||
if (magic == _MH_CIGAM || magic == _MH_CIGAM_64) {
|
||||
reader.endian = Endian.host == Endian.big ? Endian.little : Endian.big;
|
||||
}
|
||||
final cputype = _readMachOUint32(reader);
|
||||
final cpusubtype = _readMachOUint32(reader);
|
||||
final filetype = _readMachOUint32(reader);
|
||||
final ncmds = _readMachOUint32(reader);
|
||||
final sizeofcmds = _readMachOUint32(reader);
|
||||
final flags = _readMachOUint32(reader);
|
||||
final reserved = reader.wordSize == 8 ? _readMachOUint32(reader) : null;
|
||||
final size = reader.offset - start;
|
||||
return MachOHeader._(magic, cputype, cpusubtype, filetype, ncmds,
|
||||
sizeofcmds, flags, reserved, size);
|
||||
}
|
||||
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..write('Magic: 0x')
|
||||
..writeln(paddedHex(magic, 4));
|
||||
buffer
|
||||
..write('Cpu Type: 0x')
|
||||
..writeln(paddedHex(cputype, 4));
|
||||
buffer
|
||||
..write('Cpu Subtype: 0x')
|
||||
..writeln(paddedHex(cpusubtype, 4));
|
||||
buffer
|
||||
..write('Filetype: 0x')
|
||||
..writeln(paddedHex(filetype, 4));
|
||||
buffer
|
||||
..write('Number of commands: ')
|
||||
..writeln(ncmds);
|
||||
buffer
|
||||
..write('Size of commands: ')
|
||||
..writeln(sizeofcmds);
|
||||
buffer
|
||||
..write('Flags: 0x')
|
||||
..writeln(paddedHex(flags, 4));
|
||||
if (reserved != null) {
|
||||
buffer
|
||||
..write('Reserved: 0x')
|
||||
..writeln(paddedHex(reserved!, 4));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final buffer = StringBuffer();
|
||||
writeToStringBuffer(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class MachO implements DwarfContainer {
|
||||
final MachOHeader _header;
|
||||
final List<LoadCommand> _commands;
|
||||
final SymbolTable _symbolTable;
|
||||
final SegmentCommand _dwarfSegment;
|
||||
final StringTable? _dwarfStringTable;
|
||||
|
||||
MachO._(this._header, this._commands, this._symbolTable, this._dwarfSegment,
|
||||
this._dwarfStringTable);
|
||||
|
||||
static MachO? fromReader(Reader machOReader) {
|
||||
// MachO files contain absolute offsets from the start of the file, so
|
||||
// make sure we have a reader that a) makes no assumptions about the
|
||||
// endianness or word size, since we'll read those in the header and b)
|
||||
// has an internal offset of 0 so absolute offsets can be used directly.
|
||||
final reader = Reader.fromTypedData(ByteData.sublistView(machOReader.bdata,
|
||||
machOReader.bdata.offsetInBytes + machOReader.offset));
|
||||
final header = MachOHeader.fromReader(reader);
|
||||
if (header == null) return null;
|
||||
|
||||
final commandReader =
|
||||
reader.refocusedCopy(reader.offset, header.sizeofcmds);
|
||||
final commands =
|
||||
List.of(commandReader.readRepeated(LoadCommand.fromReader));
|
||||
assert(commands.length == header.ncmds);
|
||||
|
||||
final symbolTable =
|
||||
commands.whereType<SymbolTableCommand>().single.load(reader);
|
||||
|
||||
final dwarfSegment = commands
|
||||
.whereType<SegmentCommand?>()
|
||||
.firstWhere((sc) => sc!.segname == '__DWARF', orElse: () => null);
|
||||
if (dwarfSegment == null) {
|
||||
print("No DWARF information in Mach-O file");
|
||||
return null;
|
||||
}
|
||||
|
||||
final dwarfStringTableSection = dwarfSegment.sections['__debug_str'];
|
||||
StringTable? dwarfStringTable;
|
||||
if (dwarfStringTableSection != null) {
|
||||
dwarfStringTable =
|
||||
StringTable.fromReader(dwarfStringTableSection.refocus(reader));
|
||||
}
|
||||
|
||||
// Set the wordSize and endian of the original reader before returning.
|
||||
machOReader.wordSize = reader.wordSize;
|
||||
machOReader.endian = reader.endian;
|
||||
|
||||
return MachO._(
|
||||
header, commands, symbolTable, dwarfSegment, dwarfStringTable);
|
||||
}
|
||||
|
||||
static String handleDSYM(String fileName) {
|
||||
if (!fileName.endsWith('.dSYM')) {
|
||||
return fileName;
|
||||
}
|
||||
var baseName = path.basename(fileName);
|
||||
baseName = baseName.substring(0, baseName.length - '.dSYM'.length);
|
||||
return path.join(fileName, 'Contents', 'Resources', 'DWARF', baseName);
|
||||
}
|
||||
|
||||
static MachO? fromFile(String fileName) =>
|
||||
MachO.fromReader(Reader.fromFile(MachO.handleDSYM(fileName)));
|
||||
|
||||
@override
|
||||
Reader abbreviationsTableReader(Reader reader) =>
|
||||
_dwarfSegment.sections['__debug_abbrev']!.refocus(reader);
|
||||
@override
|
||||
Reader lineNumberInfoReader(Reader reader) =>
|
||||
_dwarfSegment.sections['__debug_line']!.refocus(reader);
|
||||
@override
|
||||
Reader debugInfoReader(Reader reader) =>
|
||||
_dwarfSegment.sections['__debug_info']!.refocus(reader);
|
||||
|
||||
@override
|
||||
int get vmStartAddress {
|
||||
if (!_symbolTable.containsKey(constants.vmSymbolName)) {
|
||||
throw FormatException(
|
||||
'Expected a dynamic symbol with name ${constants.vmSymbolName}');
|
||||
}
|
||||
return _symbolTable[constants.vmSymbolName]!.value;
|
||||
}
|
||||
|
||||
@override
|
||||
int get isolateStartAddress {
|
||||
if (!_symbolTable.containsKey(constants.isolateSymbolName)) {
|
||||
throw FormatException(
|
||||
'Expected a dynamic symbol with name ${constants.isolateSymbolName}');
|
||||
}
|
||||
return _symbolTable[constants.isolateSymbolName]!.value;
|
||||
}
|
||||
|
||||
@override
|
||||
String? get buildId => null;
|
||||
|
||||
@override
|
||||
DwarfContainerStringTable? get stringTable => _dwarfStringTable;
|
||||
|
||||
@override
|
||||
Symbol? staticSymbolAt(int address) {
|
||||
Symbol? bestSym;
|
||||
for (final symbol in _symbolTable.values) {
|
||||
if (symbol.value > address) continue;
|
||||
// Pick the symbol with a value closest to the given address.
|
||||
if (bestSym == null || (bestSym.value < symbol.value)) {
|
||||
bestSym = symbol;
|
||||
}
|
||||
}
|
||||
return bestSym;
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..writeln('----------------------------------------')
|
||||
..writeln(' Header')
|
||||
..writeln('----------------------------------------')
|
||||
..writeln('');
|
||||
_header.writeToStringBuffer(buffer);
|
||||
buffer
|
||||
..writeln('')
|
||||
..writeln('')
|
||||
..writeln('----------------------------------------')
|
||||
..writeln(' Load commands')
|
||||
..writeln('----------------------------------------')
|
||||
..writeln('');
|
||||
for (final command in _commands) {
|
||||
command.writeToStringBuffer(buffer);
|
||||
buffer.writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final buffer = StringBuffer();
|
||||
writeToStringBuffer(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,15 @@ class Reader {
|
||||
bdata = ByteData.sublistView(File(path).readAsBytesSync());
|
||||
|
||||
/// Returns a reader focused on a different portion of the underlying buffer.
|
||||
Reader refocusedCopy(int pos, int size) {
|
||||
/// If size is not provided, then the new reader extends to the end of the
|
||||
/// buffer.
|
||||
Reader refocusedCopy(int pos, [int? size]) {
|
||||
assert(pos >= 0 && pos < bdata.buffer.lengthInBytes);
|
||||
assert(size >= 0 && (pos + size) <= bdata.buffer.lengthInBytes);
|
||||
if (size != null) {
|
||||
assert(size >= 0 && (pos + size) <= bdata.buffer.lengthInBytes);
|
||||
} else {
|
||||
size = bdata.buffer.lengthInBytes - pos;
|
||||
}
|
||||
return Reader.fromTypedData(ByteData.view(bdata.buffer, pos, size),
|
||||
wordSize: _wordSize, endian: _endian);
|
||||
}
|
||||
@@ -51,7 +57,7 @@ class Reader {
|
||||
|
||||
void seek(int offset, {bool absolute = false}) {
|
||||
final newOffset = (absolute ? 0 : _offset) + offset;
|
||||
assert(newOffset >= 0 && newOffset < bdata.lengthInBytes);
|
||||
assert(newOffset >= 0 && newOffset <= bdata.lengthInBytes);
|
||||
_offset = newOffset;
|
||||
}
|
||||
|
||||
@@ -85,16 +91,26 @@ class Reader {
|
||||
|
||||
int readByte({bool signed = false}) => readBytes(1, signed: signed);
|
||||
int readWord() => readBytes(wordSize);
|
||||
String readNullTerminatedString() {
|
||||
final start = bdata.offsetInBytes + _offset;
|
||||
for (var i = 0; _offset + i < bdata.lengthInBytes; i++) {
|
||||
if (bdata.getUint8(_offset + i) == 0) {
|
||||
_offset += i + 1;
|
||||
return String.fromCharCodes(bdata.buffer.asUint8List(start, i));
|
||||
String readNullTerminatedString({int? maxSize}) {
|
||||
final start = _offset;
|
||||
int end = maxSize != null ? _offset + maxSize : bdata.lengthInBytes;
|
||||
for (; _offset < end; _offset++) {
|
||||
if (bdata.getUint8(_offset) == 0) {
|
||||
end = _offset;
|
||||
_offset++; // Move reader past null terminator.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return String.fromCharCodes(
|
||||
bdata.buffer.asUint8List(start, bdata.lengthInBytes - _offset));
|
||||
bdata.buffer.asUint8List(bdata.offsetInBytes + start, end - start));
|
||||
}
|
||||
|
||||
String readFixedLengthNullTerminatedString(int maxSize) {
|
||||
final start = _offset;
|
||||
final str = readNullTerminatedString(maxSize: maxSize);
|
||||
// Ensure reader points past fixed space, not at end of string within it.
|
||||
_offset = start + maxSize;
|
||||
return str;
|
||||
}
|
||||
|
||||
int readLEB128EncodedInteger({bool signed = false}) {
|
||||
|
||||
@@ -318,6 +318,13 @@ DEFINE_NATIVE_ENTRY(Internal_deoptimizeFunctionsOnStack, 0, 0) {
|
||||
return Object::null();
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Internal_randomAddressInsideAllocateObjectStub, 0, 0) {
|
||||
auto& stub = Code::Handle(
|
||||
zone, isolate->group()->object_store()->allocate_object_stub());
|
||||
const uword random_offset = isolate->random()->NextUInt32() % stub.Size();
|
||||
return Smi::New(stub.EntryPoint() + random_offset);
|
||||
}
|
||||
|
||||
static bool ExtractInterfaceTypeArgs(Zone* zone,
|
||||
const Class& instance_cls,
|
||||
const TypeArguments& instance_type_args,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// Test that the full stacktrace in an error object matches the stacktrace
|
||||
// handed to the catch clause.
|
||||
|
||||
import 'dart:_internal' show VMInternalsForTesting;
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
class C {
|
||||
@@ -15,5 +17,6 @@ bar(c) => c * 4;
|
||||
foo(c) => bar(c);
|
||||
|
||||
main() {
|
||||
print(VMInternalsForTesting.randomAddressInsideAllocateObjectStub());
|
||||
var a = foo(new C());
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
// compile-time will be used at runtime (irrespective if other values were
|
||||
// passed to the runtime).
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
|
||||
import "dart:async";
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
@@ -38,8 +37,11 @@ main(List<String> args) async {
|
||||
}
|
||||
|
||||
await withTempDir('dwarf-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
// We have to use the program in its original location so it can use
|
||||
// the dart:_internal library (as opposed to adding it as an OtherResources
|
||||
// option to the test).
|
||||
final script = path.join(sdkDir, 'runtime', 'tests', 'vm', 'dart',
|
||||
'use_dwarf_stack_traces_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
@@ -76,123 +78,252 @@ main(List<String> args) async {
|
||||
]);
|
||||
|
||||
// Run the resulting Dwarf-AOT compiled script.
|
||||
final dwarfTrace1 = await runError(aotRuntime, <String>[
|
||||
'--dwarf-stack-traces-mode',
|
||||
scriptDwarfSnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
final dwarfTrace2 = await runError(aotRuntime, <String>[
|
||||
|
||||
final output1 = await runTestProgram(aotRuntime,
|
||||
<String>['--dwarf-stack-traces-mode', scriptDwarfSnapshot, scriptDill]);
|
||||
final output2 = await runTestProgram(aotRuntime, <String>[
|
||||
'--no-dwarf-stack-traces-mode',
|
||||
scriptDwarfSnapshot,
|
||||
scriptDill,
|
||||
scriptDill
|
||||
]);
|
||||
|
||||
// Run the resulting non-Dwarf-AOT compiled script.
|
||||
final nonDwarfTrace1 = await runError(aotRuntime, <String>[
|
||||
final nonDwarfTrace1 = (await runTestProgram(aotRuntime, <String>[
|
||||
'--dwarf-stack-traces-mode',
|
||||
scriptNonDwarfSnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
final nonDwarfTrace2 = await runError(aotRuntime, <String>[
|
||||
]))
|
||||
.trace;
|
||||
final nonDwarfTrace2 = (await runTestProgram(aotRuntime, <String>[
|
||||
'--no-dwarf-stack-traces-mode',
|
||||
scriptNonDwarfSnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
]))
|
||||
.trace;
|
||||
|
||||
// Ensure the result is based off the flag passed to gen_snapshot, not
|
||||
// the one passed to the runtime.
|
||||
Expect.deepEquals(nonDwarfTrace1, nonDwarfTrace2);
|
||||
|
||||
// For DWARF stack traces, we can't guarantee that the stack traces are
|
||||
// textually equal on all platforms, but if we retrieve the PC offsets
|
||||
// out of the stack trace, those should be equal.
|
||||
final tracePCOffsets1 = collectPCOffsets(dwarfTrace1);
|
||||
final tracePCOffsets2 = collectPCOffsets(dwarfTrace2);
|
||||
Expect.deepEquals(tracePCOffsets1, tracePCOffsets2);
|
||||
// Check with DWARF from separate debugging information.
|
||||
await compareTraces(nonDwarfTrace1, output1, output2, scriptDwarfDebugInfo);
|
||||
// Check with DWARF in generated snapshot.
|
||||
await compareTraces(nonDwarfTrace1, output1, output2, scriptDwarfSnapshot);
|
||||
|
||||
// Check that translating the DWARF stack trace (without internal frames)
|
||||
// matches the symbolic stack trace.
|
||||
final dwarf = Dwarf.fromFile(scriptDwarfDebugInfo)!;
|
||||
if (Platform.isLinux || Platform.isMacOS) {
|
||||
final scriptAssembly = path.join(tempDir, 'dwarf_assembly.S');
|
||||
final scriptDwarfAssemblyDebugInfo =
|
||||
path.join(tempDir, 'dwarf_assembly_info.so');
|
||||
final scriptDwarfAssemblySnapshot =
|
||||
path.join(tempDir, 'dwarf_assembly.so');
|
||||
// We get a separate .dSYM bundle on MacOS.
|
||||
final scriptDwarfAssemblyDebugSnapshot =
|
||||
scriptDwarfAssemblySnapshot + (Platform.isMacOS ? '.dSYM' : '');
|
||||
|
||||
// Check that build IDs match for traces.
|
||||
Expect.isNotNull(dwarf.buildId);
|
||||
await run(genSnapshot, <String>[
|
||||
// We test --dwarf-stack-traces-mode, not --dwarf-stack-traces, because
|
||||
// the latter is a handler that sets the former and also may change
|
||||
// other flags. This way, we limit the difference between the two
|
||||
// snapshots and also directly test the flag saved as a VM global flag.
|
||||
'--dwarf-stack-traces-mode',
|
||||
'--save-debugging-info=$scriptDwarfAssemblyDebugInfo',
|
||||
'--snapshot-kind=app-aot-assembly',
|
||||
'--assembly=$scriptAssembly',
|
||||
scriptDill,
|
||||
]);
|
||||
|
||||
await assembleSnapshot(scriptAssembly, scriptDwarfAssemblySnapshot,
|
||||
debug: true);
|
||||
|
||||
// Run the resulting Dwarf-AOT compiled script.
|
||||
final assemblyOutput1 = await runTestProgram(aotRuntime, <String>[
|
||||
'--dwarf-stack-traces-mode',
|
||||
scriptDwarfAssemblySnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
final assemblyOutput2 = await runTestProgram(aotRuntime, <String>[
|
||||
'--no-dwarf-stack-traces-mode',
|
||||
scriptDwarfAssemblySnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
|
||||
// Check with DWARF in assembled snapshot.
|
||||
await compareTraces(nonDwarfTrace1, assemblyOutput1, assemblyOutput2,
|
||||
scriptDwarfAssemblyDebugSnapshot,
|
||||
fromAssembly: true);
|
||||
// Check with DWARF from separate debugging information.
|
||||
await compareTraces(nonDwarfTrace1, assemblyOutput1, assemblyOutput2,
|
||||
scriptDwarfAssemblyDebugInfo,
|
||||
fromAssembly: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class DwarfTestOutput {
|
||||
final List<String> trace;
|
||||
final int allocateObjectAddress;
|
||||
|
||||
DwarfTestOutput(this.trace, this.allocateObjectAddress);
|
||||
}
|
||||
|
||||
Future<void> compareTraces(List<String> nonDwarfTrace, DwarfTestOutput output1,
|
||||
DwarfTestOutput output2, String dwarfPath,
|
||||
{bool fromAssembly = false}) async {
|
||||
// For DWARF stack traces, we can't guarantee that the stack traces are
|
||||
// textually equal on all platforms, but if we retrieve the PC offsets
|
||||
// out of the stack trace, those should be equal.
|
||||
final tracePCOffsets1 = collectPCOffsets(output1.trace);
|
||||
final tracePCOffsets2 = collectPCOffsets(output2.trace);
|
||||
Expect.deepEquals(tracePCOffsets1, tracePCOffsets2);
|
||||
|
||||
// Check that translating the DWARF stack trace (without internal frames)
|
||||
// matches the symbolic stack trace.
|
||||
print("Reading DWARF info from ${dwarfPath}");
|
||||
final dwarf = Dwarf.fromFile(dwarfPath);
|
||||
Expect.isNotNull(dwarf);
|
||||
|
||||
// Check that build IDs match for traces from running ELF snapshots.
|
||||
if (!fromAssembly) {
|
||||
Expect.isNotNull(dwarf!.buildId);
|
||||
print('Dwarf build ID: "${dwarf.buildId!}"');
|
||||
// We should never generate an all-zero build ID.
|
||||
Expect.notEquals(dwarf.buildId, "00000000000000000000000000000000");
|
||||
// This is a common failure case as well, when HashBitsContainer ends up
|
||||
// hashing over seemingly empty sections.
|
||||
Expect.notEquals(dwarf.buildId, "01000000010000000100000001000000");
|
||||
final buildId1 = buildId(dwarfTrace1);
|
||||
final buildId1 = buildId(output1.trace);
|
||||
Expect.isFalse(buildId1.isEmpty);
|
||||
print('Trace 1 build ID: "${buildId1}"');
|
||||
Expect.equals(dwarf.buildId, buildId1);
|
||||
final buildId2 = buildId(dwarfTrace2);
|
||||
final buildId2 = buildId(output2.trace);
|
||||
Expect.isFalse(buildId2.isEmpty);
|
||||
print('Trace 2 build ID: "${buildId2}"');
|
||||
Expect.equals(dwarf.buildId, buildId2);
|
||||
}
|
||||
|
||||
final translatedDwarfTrace1 = await Stream.fromIterable(dwarfTrace1)
|
||||
.transform(DwarfStackTraceDecoder(dwarf))
|
||||
.toList();
|
||||
final decoder = DwarfStackTraceDecoder(dwarf!);
|
||||
// Run on the second trace just for the side effect of getting the header
|
||||
// information so we can get the PC offset of the output address.
|
||||
await Stream.fromIterable(output2.trace).transform(decoder).toList();
|
||||
final allocateObjectPCOffset2 =
|
||||
decoder.offsetOf(output2.allocateObjectAddress);
|
||||
final translatedDwarfTrace1 =
|
||||
await Stream.fromIterable(output1.trace).transform(decoder).toList();
|
||||
final allocateObjectPCOffset1 =
|
||||
decoder.offsetOf(output1.allocateObjectAddress);
|
||||
|
||||
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
|
||||
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace1);
|
||||
print('Offset of first stub address '
|
||||
'0x${output1.allocateObjectAddress.toRadixString(16)}'
|
||||
' is $allocateObjectPCOffset1');
|
||||
print('Offset of second stub address '
|
||||
'0x${output2.allocateObjectAddress.toRadixString(16)}'
|
||||
' is $allocateObjectPCOffset2');
|
||||
|
||||
print('Stack frames from translated non-symbolic stack trace:');
|
||||
translatedStackFrames.forEach(print);
|
||||
print('');
|
||||
Expect.isNotNull(allocateObjectPCOffset1);
|
||||
Expect.isNotNull(allocateObjectPCOffset2);
|
||||
final allocateObjectRelocatedAddress1 =
|
||||
dwarf.virtualAddressOf(allocateObjectPCOffset1!);
|
||||
final allocateObjectRelocatedAddress2 =
|
||||
dwarf.virtualAddressOf(allocateObjectPCOffset2!);
|
||||
|
||||
print('Stack frames from original symbolic stack trace:');
|
||||
originalStackFrames.forEach(print);
|
||||
print('');
|
||||
final allocateObjectCallInfo1 = dwarf.callInfoFor(
|
||||
allocateObjectRelocatedAddress1,
|
||||
includeInternalFrames: true);
|
||||
final allocateObjectCallInfo2 = dwarf.callInfoFor(
|
||||
allocateObjectRelocatedAddress2,
|
||||
includeInternalFrames: true);
|
||||
|
||||
Expect.isTrue(translatedStackFrames.length > 0);
|
||||
Expect.isTrue(originalStackFrames.length > 0);
|
||||
Expect.isNotNull(allocateObjectCallInfo1);
|
||||
Expect.isNotNull(allocateObjectCallInfo2);
|
||||
Expect.equals(allocateObjectCallInfo1!.length, 1);
|
||||
Expect.equals(allocateObjectCallInfo2!.length, 1);
|
||||
Expect.isTrue(
|
||||
allocateObjectCallInfo1.first is StubCallInfo, 'is not a StubCall');
|
||||
Expect.isTrue(
|
||||
allocateObjectCallInfo2.first is StubCallInfo, 'is not a StubCall');
|
||||
final stubCall1 = allocateObjectCallInfo1.first as StubCallInfo;
|
||||
final stubCall2 = allocateObjectCallInfo2.first as StubCallInfo;
|
||||
Expect.equals(stubCall1.name, stubCall2.name);
|
||||
Expect.contains('AllocateObject', stubCall1.name);
|
||||
Expect.contains('AllocateObject', stubCall2.name);
|
||||
|
||||
// In symbolic mode, we don't store column information to avoid an increase
|
||||
// in size of CodeStackMaps. Thus, we need to strip any columns from the
|
||||
// translated non-symbolic stack to compare them via equality.
|
||||
final columnStrippedTranslated = removeColumns(translatedStackFrames);
|
||||
print("Successfully matched AllocateObject stub addresses");
|
||||
print("");
|
||||
|
||||
print('Stack frames from translated non-symbolic stack trace, no columns:');
|
||||
columnStrippedTranslated.forEach(print);
|
||||
print('');
|
||||
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
|
||||
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace);
|
||||
|
||||
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
|
||||
print('Stack frames from translated non-symbolic stack trace:');
|
||||
translatedStackFrames.forEach(print);
|
||||
print('');
|
||||
|
||||
// Since we compiled directly to ELF, there should be a DSO base address
|
||||
// in the stack trace header and 'virt' markers in the stack frames.
|
||||
print('Stack frames from original symbolic stack trace:');
|
||||
originalStackFrames.forEach(print);
|
||||
print('');
|
||||
|
||||
// The offsets of absolute addresses from their respective DSO base
|
||||
// should be the same for both traces.
|
||||
final dsoBase1 = dsoBaseAddresses(dwarfTrace1).single;
|
||||
final dsoBase2 = dsoBaseAddresses(dwarfTrace2).single;
|
||||
Expect.isTrue(translatedStackFrames.length > 0);
|
||||
Expect.isTrue(originalStackFrames.length > 0);
|
||||
|
||||
final absTrace1 = absoluteAddresses(dwarfTrace1);
|
||||
final absTrace2 = absoluteAddresses(dwarfTrace2);
|
||||
// In symbolic mode, we don't store column information to avoid an increase
|
||||
// in size of CodeStackMaps. Thus, we need to strip any columns from the
|
||||
// translated non-symbolic stack to compare them via equality.
|
||||
final columnStrippedTranslated = removeColumns(translatedStackFrames);
|
||||
|
||||
final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1);
|
||||
final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2);
|
||||
print('Stack frames from translated non-symbolic stack trace, no columns:');
|
||||
columnStrippedTranslated.forEach(print);
|
||||
print('');
|
||||
|
||||
Expect.deepEquals(relocatedFromDso1, relocatedFromDso2);
|
||||
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
|
||||
|
||||
// The relocated addresses marked with 'virt' should match between the
|
||||
// different runs, and they should also match the relocated address
|
||||
// calculated from the PCOffset for each frame as well as the relocated
|
||||
// address for each frame calculated using the respective DSO base.
|
||||
final virtTrace1 = explicitVirtualAddresses(dwarfTrace1);
|
||||
final virtTrace2 = explicitVirtualAddresses(dwarfTrace2);
|
||||
// Since we compiled directly to ELF, there should be a DSO base address
|
||||
// in the stack trace header and 'virt' markers in the stack frames.
|
||||
|
||||
Expect.deepEquals(virtTrace1, virtTrace2);
|
||||
// The offsets of absolute addresses from their respective DSO base
|
||||
// should be the same for both traces.
|
||||
final dsoBase1 = dsoBaseAddresses(output1.trace).single;
|
||||
final dsoBase2 = dsoBaseAddresses(output2.trace).single;
|
||||
|
||||
Expect.deepEquals(
|
||||
virtTrace1, tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)));
|
||||
Expect.deepEquals(
|
||||
virtTrace2, tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)));
|
||||
final absTrace1 = absoluteAddresses(output1.trace);
|
||||
final absTrace2 = absoluteAddresses(output2.trace);
|
||||
|
||||
Expect.deepEquals(virtTrace1, relocatedFromDso1);
|
||||
Expect.deepEquals(virtTrace2, relocatedFromDso2);
|
||||
});
|
||||
final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1);
|
||||
final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2);
|
||||
|
||||
Expect.deepEquals(relocatedFromDso1, relocatedFromDso2);
|
||||
|
||||
// We don't print 'virt' relocated addresses when running assembled snapshots.
|
||||
if (fromAssembly) return;
|
||||
|
||||
// The relocated addresses marked with 'virt' should match between the
|
||||
// different runs, and they should also match the relocated address
|
||||
// calculated from the PCOffset for each frame as well as the relocated
|
||||
// address for each frame calculated using the respective DSO base.
|
||||
final virtTrace1 = explicitVirtualAddresses(output1.trace);
|
||||
final virtTrace2 = explicitVirtualAddresses(output2.trace);
|
||||
|
||||
Expect.deepEquals(virtTrace1, virtTrace2);
|
||||
|
||||
Expect.deepEquals(
|
||||
virtTrace1, tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)));
|
||||
Expect.deepEquals(
|
||||
virtTrace2, tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)));
|
||||
|
||||
Expect.deepEquals(virtTrace1, relocatedFromDso1);
|
||||
Expect.deepEquals(virtTrace2, relocatedFromDso2);
|
||||
}
|
||||
|
||||
Future<DwarfTestOutput> runTestProgram(
|
||||
String executable, List<String> args) async {
|
||||
final result = await runHelper(executable, args);
|
||||
|
||||
if (result.exitCode == 0) {
|
||||
throw 'Command did not fail with non-zero exit code';
|
||||
}
|
||||
Expect.isTrue(result.stdout.isNotEmpty);
|
||||
Expect.isTrue(result.stderr.isNotEmpty);
|
||||
|
||||
return DwarfTestOutput(
|
||||
LineSplitter.split(result.stderr).toList(), int.parse(result.stdout));
|
||||
}
|
||||
|
||||
final _buildIdRE = RegExp(r"build_id: '([a-f\d]+)'");
|
||||
|
||||
@@ -38,7 +38,8 @@ String? get clangBuildToolsDir {
|
||||
return Directory(clangDir).existsSync() ? clangDir : null;
|
||||
}
|
||||
|
||||
Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
|
||||
Future<void> assembleSnapshot(String assemblyPath, String snapshotPath,
|
||||
{bool debug = false}) async {
|
||||
if (!Platform.isLinux && !Platform.isMacOS) {
|
||||
throw "Unsupported platform ${Platform.operatingSystem} for assembling";
|
||||
}
|
||||
@@ -62,6 +63,11 @@ Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
shared = '-dynamiclib';
|
||||
if (buildDir.endsWith('ARM64')) {
|
||||
// ld: dynamic main executables must link with libSystem.dylib for
|
||||
// architecture arm64
|
||||
ldFlags.add('-lSystem');
|
||||
}
|
||||
// Tell Mac linker to give up generating eh_frame from dwarf.
|
||||
ldFlags.add('-Wl,-no_compact_unwind');
|
||||
} else if (buildDir.endsWith('SIMARM')) {
|
||||
@@ -73,6 +79,9 @@ Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
|
||||
if (buildDir.endsWith('X64') || buildDir.endsWith('SIMARM64')) {
|
||||
ccFlags.add('-m64');
|
||||
}
|
||||
if (debug) {
|
||||
ccFlags.add('-g');
|
||||
}
|
||||
|
||||
await run(cc, <String>[
|
||||
...ccFlags,
|
||||
@@ -158,9 +167,7 @@ Future<List<String>> runOutput(String executable, List<String> args) async {
|
||||
Expect.isTrue(result.stdout.isNotEmpty);
|
||||
Expect.isTrue(result.stderr.isEmpty);
|
||||
|
||||
return await Stream.value(result.stdout as String)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
return LineSplitter.split(result.stdout).toList(growable: false);
|
||||
}
|
||||
|
||||
Future<List<String>> runError(String executable, List<String> args) async {
|
||||
@@ -172,9 +179,7 @@ Future<List<String>> runError(String executable, List<String> args) async {
|
||||
Expect.isTrue(result.stdout.isEmpty);
|
||||
Expect.isTrue(result.stderr.isNotEmpty);
|
||||
|
||||
return await Stream.value(result.stderr as String)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
return LineSplitter.split(result.stderr).toList(growable: false);
|
||||
}
|
||||
|
||||
const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// This test checks that --resolve-dwarf-paths outputs absolute and relative
|
||||
// paths in DWARF information.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
@@ -38,7 +38,8 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('dwarf-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
// Test that the full stacktrace in an error object matches the stacktrace
|
||||
// handed to the catch clause.
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
class C {
|
||||
// operator*(o) is missing to trigger a noSuchMethodError when a C object
|
||||
// is used in the multiplication below.
|
||||
}
|
||||
|
||||
bar(c) => c * 4;
|
||||
foo(c) => bar(c);
|
||||
|
||||
main() {
|
||||
var a = foo(new C());
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
// for stripped ELF output, and that using the debugging information to look
|
||||
// up stripped stack trace information matches the non-stripped version.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
@@ -41,8 +41,8 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('save-debug-info-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
// We can just reuse the program for the use_dwarf_stack_traces test.
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// ELF and assembly output. This test is currently very weak, in that it just
|
||||
// checks that the stripped version is strictly smaller than the unstripped one.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:io";
|
||||
|
||||
@@ -34,8 +34,9 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('strip-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
// We can just reuse the program for the use_dwarf_stack_traces test.
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
// We can just reuse the program for the use_save_debugging_info_flag test.
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// This test ensures that --trace-precompiler runs without issue and prints
|
||||
// valid JSON for reasons to retain objects.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
@@ -37,8 +37,9 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('trace-precompiler-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
// We can just reuse the program for the use_dwarf_stack_traces test.
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
// We can just reuse the program for the use_save_debugging_info_flag test.
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:_internal' show VMInternalsForTesting;
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
class C {
|
||||
@@ -17,5 +19,6 @@ bar(c) => c * 4;
|
||||
foo(c) => bar(c);
|
||||
|
||||
main() {
|
||||
print(VMInternalsForTesting.randomAddressInsideAllocateObjectStub());
|
||||
var a = foo(new C());
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
// compile-time will be used at runtime (irrespective if other values were
|
||||
// passed to the runtime).
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
|
||||
import "dart:async";
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
@@ -40,8 +39,11 @@ main(List<String> args) async {
|
||||
}
|
||||
|
||||
await withTempDir('dwarf-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
// We have to use the program in its original location so it can use
|
||||
// the dart:_internal library (as opposed to adding it as an OtherResources
|
||||
// option to the test).
|
||||
final script = path.join(sdkDir, 'runtime', 'tests', 'vm', 'dart',
|
||||
'use_dwarf_stack_traces_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
@@ -78,46 +80,112 @@ main(List<String> args) async {
|
||||
]);
|
||||
|
||||
// Run the resulting Dwarf-AOT compiled script.
|
||||
final dwarfTrace1 = await runError(aotRuntime, <String>[
|
||||
'--dwarf-stack-traces-mode',
|
||||
scriptDwarfSnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
final dwarfTrace2 = await runError(aotRuntime, <String>[
|
||||
|
||||
final output1 = await runTestProgram(aotRuntime,
|
||||
<String>['--dwarf-stack-traces-mode', scriptDwarfSnapshot, scriptDill]);
|
||||
final output2 = await runTestProgram(aotRuntime, <String>[
|
||||
'--no-dwarf-stack-traces-mode',
|
||||
scriptDwarfSnapshot,
|
||||
scriptDill,
|
||||
scriptDill
|
||||
]);
|
||||
|
||||
// Run the resulting non-Dwarf-AOT compiled script.
|
||||
final nonDwarfTrace1 = await runError(aotRuntime, <String>[
|
||||
final nonDwarfTrace1 = (await runTestProgram(aotRuntime, <String>[
|
||||
'--dwarf-stack-traces-mode',
|
||||
scriptNonDwarfSnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
final nonDwarfTrace2 = await runError(aotRuntime, <String>[
|
||||
]))
|
||||
.trace;
|
||||
final nonDwarfTrace2 = (await runTestProgram(aotRuntime, <String>[
|
||||
'--no-dwarf-stack-traces-mode',
|
||||
scriptNonDwarfSnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
]))
|
||||
.trace;
|
||||
|
||||
// Ensure the result is based off the flag passed to gen_snapshot, not
|
||||
// the one passed to the runtime.
|
||||
Expect.deepEquals(nonDwarfTrace1, nonDwarfTrace2);
|
||||
|
||||
// For DWARF stack traces, we can't guarantee that the stack traces are
|
||||
// textually equal on all platforms, but if we retrieve the PC offsets
|
||||
// out of the stack trace, those should be equal.
|
||||
final tracePCOffsets1 = collectPCOffsets(dwarfTrace1);
|
||||
final tracePCOffsets2 = collectPCOffsets(dwarfTrace2);
|
||||
Expect.deepEquals(tracePCOffsets1, tracePCOffsets2);
|
||||
// Check with DWARF from separate debugging information.
|
||||
await compareTraces(nonDwarfTrace1, output1, output2, scriptDwarfDebugInfo);
|
||||
// Check with DWARF in generated snapshot.
|
||||
await compareTraces(nonDwarfTrace1, output1, output2, scriptDwarfSnapshot);
|
||||
|
||||
// Check that translating the DWARF stack trace (without internal frames)
|
||||
// matches the symbolic stack trace.
|
||||
final dwarf = Dwarf.fromFile(scriptDwarfDebugInfo);
|
||||
Expect.isNotNull(dwarf);
|
||||
if (Platform.isLinux || Platform.isMacOS) {
|
||||
final scriptAssembly = path.join(tempDir, 'dwarf_assembly.S');
|
||||
final scriptDwarfAssemblyDebugInfo =
|
||||
path.join(tempDir, 'dwarf_assembly_info.so');
|
||||
final scriptDwarfAssemblySnapshot =
|
||||
path.join(tempDir, 'dwarf_assembly.so');
|
||||
// We get a separate .dSYM bundle on MacOS.
|
||||
final scriptDwarfAssemblyDebugSnapshot =
|
||||
scriptDwarfAssemblySnapshot + (Platform.isMacOS ? '.dSYM' : '');
|
||||
|
||||
// Check that build IDs match for traces.
|
||||
await run(genSnapshot, <String>[
|
||||
// We test --dwarf-stack-traces-mode, not --dwarf-stack-traces, because
|
||||
// the latter is a handler that sets the former and also may change
|
||||
// other flags. This way, we limit the difference between the two
|
||||
// snapshots and also directly test the flag saved as a VM global flag.
|
||||
'--dwarf-stack-traces-mode',
|
||||
'--save-debugging-info=$scriptDwarfAssemblyDebugInfo',
|
||||
'--snapshot-kind=app-aot-assembly',
|
||||
'--assembly=$scriptAssembly',
|
||||
scriptDill,
|
||||
]);
|
||||
|
||||
await assembleSnapshot(scriptAssembly, scriptDwarfAssemblySnapshot,
|
||||
debug: true);
|
||||
|
||||
// Run the resulting Dwarf-AOT compiled script.
|
||||
final assemblyOutput1 = await runTestProgram(aotRuntime, <String>[
|
||||
'--dwarf-stack-traces-mode',
|
||||
scriptDwarfAssemblySnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
final assemblyOutput2 = await runTestProgram(aotRuntime, <String>[
|
||||
'--no-dwarf-stack-traces-mode',
|
||||
scriptDwarfAssemblySnapshot,
|
||||
scriptDill,
|
||||
]);
|
||||
|
||||
// Check with DWARF in assembled snapshot.
|
||||
await compareTraces(nonDwarfTrace1, assemblyOutput1, assemblyOutput2,
|
||||
scriptDwarfAssemblyDebugSnapshot,
|
||||
fromAssembly: true);
|
||||
// Check with DWARF from separate debugging information.
|
||||
await compareTraces(nonDwarfTrace1, assemblyOutput1, assemblyOutput2,
|
||||
scriptDwarfAssemblyDebugInfo,
|
||||
fromAssembly: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class DwarfTestOutput {
|
||||
final List<String> trace;
|
||||
final int allocateObjectAddress;
|
||||
|
||||
DwarfTestOutput(this.trace, this.allocateObjectAddress);
|
||||
}
|
||||
|
||||
Future<void> compareTraces(List<String> nonDwarfTrace, DwarfTestOutput output1,
|
||||
DwarfTestOutput output2, String dwarfPath,
|
||||
{bool fromAssembly = false}) async {
|
||||
// For DWARF stack traces, we can't guarantee that the stack traces are
|
||||
// textually equal on all platforms, but if we retrieve the PC offsets
|
||||
// out of the stack trace, those should be equal.
|
||||
final tracePCOffsets1 = collectPCOffsets(output1.trace);
|
||||
final tracePCOffsets2 = collectPCOffsets(output2.trace);
|
||||
Expect.deepEquals(tracePCOffsets1, tracePCOffsets2);
|
||||
|
||||
// Check that translating the DWARF stack trace (without internal frames)
|
||||
// matches the symbolic stack trace.
|
||||
print("Reading DWARF info from ${dwarfPath}");
|
||||
final dwarf = Dwarf.fromFile(dwarfPath);
|
||||
Expect.isNotNull(dwarf);
|
||||
|
||||
// Check that build IDs match for traces from running ELF snapshots.
|
||||
if (!fromAssembly) {
|
||||
Expect.isNotNull(dwarf.buildId);
|
||||
print('Dwarf build ID: "${dwarf.buildId}"');
|
||||
// We should never generate an all-zero build ID.
|
||||
@@ -125,77 +193,139 @@ main(List<String> args) async {
|
||||
// This is a common failure case as well, when HashBitsContainer ends up
|
||||
// hashing over seemingly empty sections.
|
||||
Expect.notEquals(dwarf.buildId, "01000000010000000100000001000000");
|
||||
final buildId1 = buildId(dwarfTrace1);
|
||||
final buildId1 = buildId(output1.trace);
|
||||
Expect.isFalse(buildId1.isEmpty);
|
||||
print('Trace 1 build ID: "${buildId1}"');
|
||||
Expect.equals(dwarf.buildId, buildId1);
|
||||
final buildId2 = buildId(dwarfTrace2);
|
||||
final buildId2 = buildId(output2.trace);
|
||||
Expect.isFalse(buildId2.isEmpty);
|
||||
print('Trace 2 build ID: "${buildId2}"');
|
||||
Expect.equals(dwarf.buildId, buildId2);
|
||||
}
|
||||
|
||||
final translatedDwarfTrace1 = await Stream.fromIterable(dwarfTrace1)
|
||||
.transform(DwarfStackTraceDecoder(dwarf))
|
||||
.toList();
|
||||
final decoder = DwarfStackTraceDecoder(dwarf);
|
||||
// Run on the second trace just for the side effect of getting the header
|
||||
// information so we can get the PC offset of the output address.
|
||||
await Stream.fromIterable(output2.trace).transform(decoder).toList();
|
||||
final allocateObjectPCOffset2 =
|
||||
decoder.offsetOf(output2.allocateObjectAddress);
|
||||
final translatedDwarfTrace1 =
|
||||
await Stream.fromIterable(output1.trace).transform(decoder).toList();
|
||||
final allocateObjectPCOffset1 =
|
||||
decoder.offsetOf(output1.allocateObjectAddress);
|
||||
|
||||
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
|
||||
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace1);
|
||||
print('Offset of first stub address '
|
||||
'0x${output1.allocateObjectAddress.toRadixString(16)}'
|
||||
' is $allocateObjectPCOffset1');
|
||||
print('Offset of second stub address '
|
||||
'0x${output2.allocateObjectAddress.toRadixString(16)}'
|
||||
' is $allocateObjectPCOffset2');
|
||||
|
||||
print('Stack frames from translated non-symbolic stack trace:');
|
||||
translatedStackFrames.forEach(print);
|
||||
print('');
|
||||
Expect.isNotNull(allocateObjectPCOffset1);
|
||||
Expect.isNotNull(allocateObjectPCOffset2);
|
||||
final allocateObjectRelocatedAddress1 =
|
||||
dwarf.virtualAddressOf(allocateObjectPCOffset1);
|
||||
final allocateObjectRelocatedAddress2 =
|
||||
dwarf.virtualAddressOf(allocateObjectPCOffset2);
|
||||
|
||||
print('Stack frames from original symbolic stack trace:');
|
||||
originalStackFrames.forEach(print);
|
||||
print('');
|
||||
final allocateObjectCallInfo1 = dwarf.callInfoFor(
|
||||
allocateObjectRelocatedAddress1,
|
||||
includeInternalFrames: true);
|
||||
final allocateObjectCallInfo2 = dwarf.callInfoFor(
|
||||
allocateObjectRelocatedAddress2,
|
||||
includeInternalFrames: true);
|
||||
|
||||
Expect.isTrue(translatedStackFrames.length > 0);
|
||||
Expect.isTrue(originalStackFrames.length > 0);
|
||||
Expect.isNotNull(allocateObjectCallInfo1);
|
||||
Expect.isNotNull(allocateObjectCallInfo2);
|
||||
Expect.equals(allocateObjectCallInfo1.length, 1);
|
||||
Expect.equals(allocateObjectCallInfo2.length, 1);
|
||||
Expect.isTrue(
|
||||
allocateObjectCallInfo1.first is StubCallInfo, 'is not a StubCall');
|
||||
Expect.isTrue(
|
||||
allocateObjectCallInfo2.first is StubCallInfo, 'is not a StubCall');
|
||||
final stubCall1 = allocateObjectCallInfo1.first as StubCallInfo;
|
||||
final stubCall2 = allocateObjectCallInfo2.first as StubCallInfo;
|
||||
Expect.equals(stubCall1.name, stubCall2.name);
|
||||
Expect.contains('AllocateObject', stubCall1.name);
|
||||
Expect.contains('AllocateObject', stubCall2.name);
|
||||
|
||||
// In symbolic mode, we don't store column information to avoid an increase
|
||||
// in size of CodeStackMaps. Thus, we need to strip any columns from the
|
||||
// translated non-symbolic stack to compare them via equality.
|
||||
final columnStrippedTranslated = removeColumns(translatedStackFrames);
|
||||
print("Successfully matched AllocateObject stub addresses");
|
||||
print("");
|
||||
|
||||
print('Stack frames from translated non-symbolic stack trace, no columns:');
|
||||
columnStrippedTranslated.forEach(print);
|
||||
print('');
|
||||
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
|
||||
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace);
|
||||
|
||||
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
|
||||
print('Stack frames from translated non-symbolic stack trace:');
|
||||
translatedStackFrames.forEach(print);
|
||||
print('');
|
||||
|
||||
// Since we compiled directly to ELF, there should be a DSO base address
|
||||
// in the stack trace header and 'virt' markers in the stack frames.
|
||||
print('Stack frames from original symbolic stack trace:');
|
||||
originalStackFrames.forEach(print);
|
||||
print('');
|
||||
|
||||
// The offsets of absolute addresses from their respective DSO base
|
||||
// should be the same for both traces.
|
||||
final dsoBase1 = dsoBaseAddresses(dwarfTrace1).single;
|
||||
final dsoBase2 = dsoBaseAddresses(dwarfTrace2).single;
|
||||
Expect.isTrue(translatedStackFrames.length > 0);
|
||||
Expect.isTrue(originalStackFrames.length > 0);
|
||||
|
||||
final absTrace1 = absoluteAddresses(dwarfTrace1);
|
||||
final absTrace2 = absoluteAddresses(dwarfTrace2);
|
||||
// In symbolic mode, we don't store column information to avoid an increase
|
||||
// in size of CodeStackMaps. Thus, we need to strip any columns from the
|
||||
// translated non-symbolic stack to compare them via equality.
|
||||
final columnStrippedTranslated = removeColumns(translatedStackFrames);
|
||||
|
||||
final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1);
|
||||
final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2);
|
||||
print('Stack frames from translated non-symbolic stack trace, no columns:');
|
||||
columnStrippedTranslated.forEach(print);
|
||||
print('');
|
||||
|
||||
Expect.deepEquals(relocatedFromDso1, relocatedFromDso2);
|
||||
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
|
||||
|
||||
// The relocated addresses marked with 'virt' should match between the
|
||||
// different runs, and they should also match the relocated address
|
||||
// calculated from the PCOffset for each frame as well as the relocated
|
||||
// address for each frame calculated using the respective DSO base.
|
||||
final virtTrace1 = explicitVirtualAddresses(dwarfTrace1);
|
||||
final virtTrace2 = explicitVirtualAddresses(dwarfTrace2);
|
||||
// Since we compiled directly to ELF, there should be a DSO base address
|
||||
// in the stack trace header and 'virt' markers in the stack frames.
|
||||
|
||||
Expect.deepEquals(virtTrace1, virtTrace2);
|
||||
// The offsets of absolute addresses from their respective DSO base
|
||||
// should be the same for both traces.
|
||||
final dsoBase1 = dsoBaseAddresses(output1.trace).single;
|
||||
final dsoBase2 = dsoBaseAddresses(output2.trace).single;
|
||||
|
||||
Expect.deepEquals(
|
||||
virtTrace1, tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)));
|
||||
Expect.deepEquals(
|
||||
virtTrace2, tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)));
|
||||
final absTrace1 = absoluteAddresses(output1.trace);
|
||||
final absTrace2 = absoluteAddresses(output2.trace);
|
||||
|
||||
Expect.deepEquals(virtTrace1, relocatedFromDso1);
|
||||
Expect.deepEquals(virtTrace2, relocatedFromDso2);
|
||||
});
|
||||
final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1);
|
||||
final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2);
|
||||
|
||||
Expect.deepEquals(relocatedFromDso1, relocatedFromDso2);
|
||||
|
||||
// We don't print 'virt' relocated addresses when running assembled snapshots.
|
||||
if (fromAssembly) return;
|
||||
|
||||
// The relocated addresses marked with 'virt' should match between the
|
||||
// different runs, and they should also match the relocated address
|
||||
// calculated from the PCOffset for each frame as well as the relocated
|
||||
// address for each frame calculated using the respective DSO base.
|
||||
final virtTrace1 = explicitVirtualAddresses(output1.trace);
|
||||
final virtTrace2 = explicitVirtualAddresses(output2.trace);
|
||||
|
||||
Expect.deepEquals(virtTrace1, virtTrace2);
|
||||
|
||||
Expect.deepEquals(
|
||||
virtTrace1, tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)));
|
||||
Expect.deepEquals(
|
||||
virtTrace2, tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)));
|
||||
|
||||
Expect.deepEquals(virtTrace1, relocatedFromDso1);
|
||||
Expect.deepEquals(virtTrace2, relocatedFromDso2);
|
||||
}
|
||||
|
||||
Future<DwarfTestOutput> runTestProgram(
|
||||
String executable, List<String> args) async {
|
||||
final result = await runHelper(executable, args);
|
||||
|
||||
if (result.exitCode == 0) {
|
||||
throw 'Command did not fail with non-zero exit code';
|
||||
}
|
||||
Expect.isTrue(result.stdout.isNotEmpty);
|
||||
Expect.isTrue(result.stderr.isNotEmpty);
|
||||
|
||||
return DwarfTestOutput(
|
||||
LineSplitter.split(result.stderr).toList(), int.parse(result.stdout));
|
||||
}
|
||||
|
||||
final _buildIdRE = RegExp(r"build_id: '([a-f\d]+)'");
|
||||
|
||||
@@ -40,7 +40,8 @@ String get clangBuildToolsDir {
|
||||
return Directory(clangDir).existsSync() ? clangDir : null;
|
||||
}
|
||||
|
||||
Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
|
||||
Future<void> assembleSnapshot(String assemblyPath, String snapshotPath,
|
||||
{bool debug = false}) async {
|
||||
if (!Platform.isLinux && !Platform.isMacOS) {
|
||||
throw "Unsupported platform ${Platform.operatingSystem} for assembling";
|
||||
}
|
||||
@@ -63,6 +64,11 @@ Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
shared = '-dynamiclib';
|
||||
if (buildDir.endsWith('ARM64')) {
|
||||
// ld: dynamic main executables must link with libSystem.dylib for
|
||||
// architecture arm64
|
||||
ldFlags.add('-lSystem');
|
||||
}
|
||||
// Tell Mac linker to give up generating eh_frame from dwarf.
|
||||
ldFlags.add('-Wl,-no_compact_unwind');
|
||||
} else if (buildDir.endsWith('SIMARM')) {
|
||||
@@ -74,6 +80,9 @@ Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
|
||||
if (buildDir.endsWith('X64') || buildDir.endsWith('SIMARM64')) {
|
||||
ccFlags.add('-m64');
|
||||
}
|
||||
if (debug) {
|
||||
ccFlags.add('-g');
|
||||
}
|
||||
|
||||
await run(cc, <String>[
|
||||
...ccFlags,
|
||||
@@ -158,9 +167,7 @@ Future<List<String>> runOutput(String executable, List<String> args) async {
|
||||
Expect.isTrue(result.stdout.isNotEmpty);
|
||||
Expect.isTrue(result.stderr.isEmpty);
|
||||
|
||||
return await Stream.value(result.stdout as String)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
return LineSplitter.split(result.stdout).toList(growable: false);
|
||||
}
|
||||
|
||||
Future<List<String>> runError(String executable, List<String> args) async {
|
||||
@@ -172,9 +179,7 @@ Future<List<String>> runError(String executable, List<String> args) async {
|
||||
Expect.isTrue(result.stdout.isEmpty);
|
||||
Expect.isTrue(result.stderr.isNotEmpty);
|
||||
|
||||
return await Stream.value(result.stderr as String)
|
||||
.transform(const LineSplitter())
|
||||
.toList();
|
||||
return LineSplitter.split(result.stderr).toList(growable: false);
|
||||
}
|
||||
|
||||
const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// This test checks that --resolve-dwarf-paths outputs absolute and relative
|
||||
// paths in DWARF information.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
@@ -40,7 +40,8 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('dwarf-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
// Test that the full stacktrace in an error object matches the stacktrace
|
||||
// handed to the catch clause.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
class C {
|
||||
// operator*(o) is missing to trigger a noSuchMethodError when a C object
|
||||
// is used in the multiplication below.
|
||||
}
|
||||
|
||||
bar(c) => c * 4;
|
||||
foo(c) => bar(c);
|
||||
|
||||
main() {
|
||||
var a = foo(new C());
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
// for stripped ELF output, and that using the debugging information to look
|
||||
// up stripped stack trace information matches the non-stripped version.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
@@ -43,8 +43,8 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('save-debug-info-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
// We can just reuse the program for the use_dwarf_stack_traces test.
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// ELF and assembly output. This test is currently very weak, in that it just
|
||||
// checks that the stripped version is strictly smaller than the unstripped one.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:io";
|
||||
|
||||
@@ -36,8 +36,9 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('strip-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
// We can just reuse the program for the use_dwarf_stack_traces test.
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
// We can just reuse the program for the use_save_debugging_info_flag test.
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// This test ensures that --trace-precompiler runs without issue and prints
|
||||
// valid JSON for reasons to retain objects.
|
||||
|
||||
// OtherResources=use_dwarf_stack_traces_flag_program.dart
|
||||
// OtherResources=use_save_debugging_info_flag_program.dart
|
||||
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
@@ -39,8 +39,9 @@ main(List<String> args) async {
|
||||
|
||||
await withTempDir('trace-precompiler-flag-test', (String tempDir) async {
|
||||
final cwDir = path.dirname(Platform.script.toFilePath());
|
||||
// We can just reuse the program for the use_dwarf_stack_traces test.
|
||||
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
|
||||
// We can just reuse the program for the use_save_debugging_info_flag test.
|
||||
final script =
|
||||
path.join(cwDir, 'use_save_debugging_info_flag_program.dart');
|
||||
final scriptDill = path.join(tempDir, 'flag_program.dill');
|
||||
|
||||
// Compile script to Kernel IR.
|
||||
|
||||
@@ -330,6 +330,7 @@ namespace dart {
|
||||
V(Internal_writeIntoOneByteString, 3) \
|
||||
V(Internal_writeIntoTwoByteString, 3) \
|
||||
V(Internal_deoptimizeFunctionsOnStack, 0) \
|
||||
V(Internal_randomAddressInsideAllocateObjectStub, 0) \
|
||||
V(InvocationMirror_unpackTypeArguments, 2) \
|
||||
V(NoSuchMethodError_existingMethodSignature, 3) \
|
||||
V(Uri_isWindowsPlatform, 0) \
|
||||
|
||||
@@ -179,6 +179,10 @@ abstract class VMInternalsForTesting {
|
||||
|
||||
@pragma("vm:external-name", "Internal_deoptimizeFunctionsOnStack")
|
||||
external static void deoptimizeFunctionsOnStack();
|
||||
|
||||
// Used to verify that PC addresses in stubs can be named using DWARF info.
|
||||
@pragma("vm:external-name", "Internal_randomAddressInsideAllocateObjectStub")
|
||||
external static int randomAddressInsideAllocateObjectStub();
|
||||
}
|
||||
|
||||
@patch
|
||||
|
||||
Reference in New Issue
Block a user