Reland "[pkg/native_stack_traces] Support Mach-O dSYM debugging information."

This is a reland of commit 08c13f173c

Fixes test failures on non-x64 architectures, both in the test
harness and due to DWARF5 line number program headers having a
non-backwards compatible format. (We generate DWARF2 in the
ELF snapshot writer, but the assembler used for assembly snapshots
may generate DWARF5.)

TEST=vm/dart{,_2}/use_dwarf_stack_traces_flag

Original change's description:
> [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>

Bug: https://github.com/dart-lang/sdk/issues/43612
Change-Id: I8a9cb70e78bc8594bcae004809c5a1be778d691d
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,vm-kernel-precomp-linux-debug-x64c-try,vm-kernel-nnbd-linux-release-simarm64-try,vm-kernel-precomp-linux-release-simarm_x64-try,vm-kernel-precomp-nnbd-mac-release-simarm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251464
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Tess Strickland 2022-07-22 07:05:26 +00:00 committed by Commit Bot
parent 66c4559ff6
commit 27f6c6d660
28 changed files with 1874 additions and 502 deletions

View file

@ -1,3 +1,11 @@
## 0.5.0
- Require Dart >= 2.17 (enhanced enum support)
- Add support for parsing DWARF in Mach-O files and dSYM directories.
- Add some support for DWARF5.
- 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.

View file

@ -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!);
}
}

View file

@ -9,4 +9,10 @@ export 'src/convert.dart'
DwarfStackTraceDecoder,
StackTraceHeader;
export 'src/dwarf.dart'
show CallInfo, DartCallInfo, StubCallInfo, Dwarf, PCOffset;
show
CallInfo,
DartCallInfo,
Dwarf,
InstructionsSection,
PCOffset,
StubCallInfo;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
// 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 debugStringTable;
DwarfContainerStringTable? get debugLineStringTable;
void writeToStringBuffer(StringBuffer buffer);
}

View file

@ -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) =>
@ -636,17 +648,10 @@ class Note extends Section {
..write(' Description: ')
..writeln(description);
}
@override
String toString() {
final buffer = StringBuffer();
writeToStringBuffer(buffer);
return buffer.toString();
}
}
/// 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 +663,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 +699,7 @@ class StringTable extends Section {
enum SymbolBinding {
STB_LOCAL,
STB_GLOBAL,
STB_WEAK,
}
enum SymbolType {
@ -696,15 +716,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 +753,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 +769,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 +821,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,15 +954,17 @@ 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;
final Map<SectionHeaderEntry, Section> _sections;
final Map<String, Set<Section>> _sectionsByName;
final StringTable? _debugStringTable;
final StringTable? _debugLineStringTable;
Elf._(this._header, this._programHeader, this._sectionHeader, this._sections,
this._sectionsByName);
this._sectionsByName, this._debugStringTable, this._debugLineStringTable);
/// Creates an [Elf] from [bytes].
///
@ -984,16 +1008,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].
@ -1070,13 +1102,79 @@ class Elf {
cacheSymbolNames('.strtab', '.symtab');
cacheSymbolNames('.dynstr', '.dynsym');
StringTable? debugStringTable;
if (sectionsByName.containsKey('.debug_str')) {
// Stored as PROGBITS, so need to explicitly parse as a string table.
debugStringTable = StringTable.fromReader(
reader, sectionsByName['.debug_str']!.single.headerEntry);
}
StringTable? debugLineStringTable;
if (sectionsByName.containsKey('.debug_line_str')) {
// Stored as PROGBITS, so need to explicitly parse as a string table.
debugLineStringTable = StringTable.fromReader(
reader, sectionsByName['.debug_line_str']!.single.headerEntry);
}
// Set the wordSize and endian of the original reader before returning.
elfReader.wordSize = reader.wordSize;
elfReader.endian = reader.endian;
return Elf._(
header, programHeader, sectionHeader, sections, sectionsByName);
return Elf._(header, programHeader, sectionHeader, sections, sectionsByName,
debugStringTable, debugLineStringTable);
}
@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();
}
@override
DwarfContainerStringTable? get debugStringTable => _debugStringTable;
@override
DwarfContainerStringTable? get debugLineStringTable => _debugLineStringTable;
@override
void writeToStringBuffer(StringBuffer buffer) {
buffer
..writeln('-----------------------------------------------------')

View file

@ -0,0 +1,560 @@
// 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? _debugStringTable;
final StringTable? _debugLineStringTable;
MachO._(this._header, this._commands, this._symbolTable, this._dwarfSegment,
this._debugStringTable, this._debugLineStringTable);
static MachO? fromReader(Reader machOReader) {
// MachO files contain absolute offsets from the start of the file, so
// make sure we have a reader that a) makes no assumptions about the
// endianness or word size, since we'll read those in the header and b)
// has an internal offset of 0 so absolute offsets can be used directly.
final reader = 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 debugStringTableSection = dwarfSegment.sections['__debug_str'];
StringTable? debugStringTable;
if (debugStringTableSection != null) {
debugStringTable =
StringTable.fromReader(debugStringTableSection.refocus(reader));
}
final debugLineStringTableSection =
dwarfSegment.sections['__debug_line_str'];
StringTable? debugLineStringTable;
if (debugLineStringTableSection != null) {
debugLineStringTable =
StringTable.fromReader(debugLineStringTableSection.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,
debugStringTable, debugLineStringTable);
}
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 debugStringTable => _debugStringTable;
@override
DwarfContainerStringTable? get debugLineStringTable => _debugLineStringTable;
@override
Symbol? staticSymbolAt(int address) {
Symbol? bestSym;
for (final symbol in _symbolTable.values) {
if (symbol.value > address) continue;
// Pick the symbol with a value closest to the given address.
if (bestSym == null || (bestSym.value < symbol.value)) {
bestSym = symbol;
}
}
return bestSym;
}
@override
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();
}
}

View file

@ -28,8 +28,7 @@ class Reader {
Reader.fromTypedData(TypedData data, {int? wordSize, Endian? endian})
: _wordSize = wordSize,
_endian = endian,
bdata =
ByteData.view(data.buffer, data.offsetInBytes, data.lengthInBytes);
bdata = ByteData.sublistView(data);
Reader.fromFile(String path, {int? wordSize, Endian? endian})
: _wordSize = wordSize,
@ -37,9 +36,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);
}
@ -49,9 +54,11 @@ class Reader {
int get length => bdata.lengthInBytes;
bool get done => _offset >= length;
Uint8List get bytes => Uint8List.sublistView(bdata);
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;
}
@ -83,18 +90,38 @@ class Reader {
}
}
ByteData readRawBytes(int size) {
if (offset + size > length) {
throw ArgumentError('attempt to read $size bytes with only '
'${length - _offset} bytes remaining in the reader');
}
final start = _offset;
_offset += size;
return ByteData.sublistView(bdata, start, start + size);
}
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}) {

View file

@ -1,10 +1,10 @@
name: native_stack_traces
version: 0.4.6
version: 0.5.0
description: Utilities for working with non-symbolic stack traces.
repository: https://github.com/dart-lang/sdk/tree/main/pkg/native_stack_traces
environment:
sdk: '>=2.14.0 <3.0.0'
sdk: '>=2.17.0 <3.0.0'
executables:
decode:

View file

@ -318,6 +318,22 @@ DEFINE_NATIVE_ENTRY(Internal_deoptimizeFunctionsOnStack, 0, 0) {
return Object::null();
}
DEFINE_NATIVE_ENTRY(Internal_randomInstructionsOffsetInsideAllocateObjectStub,
0,
0) {
auto& stub = Code::Handle(
zone, isolate->group()->object_store()->allocate_object_stub());
const uword entry = stub.EntryPoint();
const uword random_offset = isolate->random()->NextUInt32() % stub.Size();
// We return the offset into the isolate instructions instead of the full
// address because that fits into small Smis on 32-bit architectures or
// compressed pointer builds.
const uword instructions_start =
reinterpret_cast<uword>(isolate->source()->snapshot_instructions);
ASSERT(entry >= instructions_start);
return Smi::New((entry - instructions_start) + random_offset);
}
static bool ExtractInterfaceTypeArgs(Zone* zone,
const Class& instance_cls,
const TypeArguments& instance_type_args,

View file

@ -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,7 @@ bar(c) => c * 4;
foo(c) => bar(c);
main() {
print(
VMInternalsForTesting.randomInstructionsOffsetInsideAllocateObjectStub());
var a = foo(new C());
}

View file

@ -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,249 @@ 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)!;
// Currently there are no appropriate buildtools on the SIMARM and SIMARM64
// trybots as normally they compile to ELF and don't need them for compiling
// assembly snapshots.
if ((Platform.isLinux || Platform.isMacOS) &&
!buildDir.endsWith('SIMARM') &&
!buildDir.endsWith('SIMARM64')) {
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 allocateObjectInstructionsOffset;
DwarfTestOutput(this.trace, this.allocateObjectInstructionsOffset);
}
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!);
final translatedDwarfTrace1 =
await Stream.fromIterable(output1.trace).transform(decoder).toList();
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace1);
final allocateObjectPCOffset1 = PCOffset(
output1.allocateObjectInstructionsOffset, InstructionsSection.isolate);
final allocateObjectPCOffset2 = PCOffset(
output2.allocateObjectInstructionsOffset, InstructionsSection.isolate);
print('Stack frames from translated non-symbolic stack trace:');
translatedStackFrames.forEach(print);
print('');
print('Offset of first stub address is $allocateObjectPCOffset1');
print('Offset of second stub address is $allocateObjectPCOffset2');
print('Stack frames from original symbolic stack trace:');
originalStackFrames.forEach(print);
print('');
final allocateObjectRelocatedAddress1 =
dwarf.virtualAddressOf(allocateObjectPCOffset1);
final allocateObjectRelocatedAddress2 =
dwarf.virtualAddressOf(allocateObjectPCOffset2);
Expect.isTrue(translatedStackFrames.length > 0);
Expect.isTrue(originalStackFrames.length > 0);
final allocateObjectCallInfo1 = dwarf.callInfoFor(
allocateObjectRelocatedAddress1,
includeInternalFrames: true);
final allocateObjectCallInfo2 = dwarf.callInfoFor(
allocateObjectRelocatedAddress2,
includeInternalFrames: true);
// 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);
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);
print('Stack frames from translated non-symbolic stack trace, no columns:');
columnStrippedTranslated.forEach(print);
print('');
print("Successfully matched AllocateObject stub addresses");
print("");
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace);
// 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 translated non-symbolic stack trace:');
translatedStackFrames.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;
print('Stack frames from original symbolic stack trace:');
originalStackFrames.forEach(print);
print('');
final absTrace1 = absoluteAddresses(dwarfTrace1);
final absTrace2 = absoluteAddresses(dwarfTrace2);
Expect.isTrue(translatedStackFrames.length > 0);
Expect.isTrue(originalStackFrames.length > 0);
final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1);
final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2);
// 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);
Expect.deepEquals(relocatedFromDso1, relocatedFromDso2);
print('Stack frames from translated non-symbolic stack trace, no columns:');
columnStrippedTranslated.forEach(print);
print('');
// 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);
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
Expect.deepEquals(virtTrace1, virtTrace2);
// 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, tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)));
Expect.deepEquals(
virtTrace2, tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)));
// 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, relocatedFromDso1);
Expect.deepEquals(virtTrace2, relocatedFromDso2);
});
final absTrace1 = absoluteAddresses(output1.trace);
final absTrace2 = absoluteAddresses(output2.trace);
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]+)'");

View file

@ -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";
}
@ -48,9 +49,7 @@ Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
String cc = 'gcc';
String shared = '-shared';
if (Platform.isMacOS) {
cc = 'clang';
} else if (buildDir.endsWith('SIMARM') || buildDir.endsWith('SIMARM64')) {
if (buildDir.endsWith('SIMARM') || buildDir.endsWith('SIMARM64')) {
final clangBuildTools = clangBuildToolsDir;
if (clangBuildTools != null) {
cc = path.join(clangBuildTools, 'clang');
@ -58,21 +57,31 @@ Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
throw 'Cannot assemble for ${path.basename(buildDir)} '
'without //buildtools on ${Platform.operatingSystem}';
}
} else if (Platform.isMacOS) {
cc = 'clang';
}
if (Platform.isMacOS) {
shared = '-dynamiclib';
// Tell Mac linker to give up generating eh_frame from dwarf.
ldFlags.add('-Wl,-no_compact_unwind');
} else if (buildDir.endsWith('SIMARM')) {
if (buildDir.endsWith('SIMARM')) {
ccFlags.add('--target=armv7-linux-gnueabihf');
} else if (buildDir.endsWith('SIMARM64')) {
ccFlags.add('--target=aarch64-linux-gnu');
} else 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');
}
if (buildDir.endsWith('X64') || buildDir.endsWith('SIMARM64')) {
ccFlags.add('-m64');
}
if (debug) {
ccFlags.add('-g');
}
await run(cc, <String>[
...ccFlags,
@ -93,8 +102,8 @@ Future<void> stripSnapshot(String snapshotPath, String strippedPath,
var strip = 'strip';
if ((Platform.isLinux &&
(buildDir.endsWith('SIMARM') || buildDir.endsWith('SIMARM64'))) ||
if (buildDir.endsWith('SIMARM') ||
buildDir.endsWith('SIMARM64') ||
(Platform.isMacOS && forceElf)) {
final clangBuildTools = clangBuildToolsDir;
if (clangBuildTools != null) {
@ -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';

View file

@ -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.

View file

@ -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());
}

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -6,6 +6,8 @@
// @dart = 2.9
import 'dart:_internal' show VMInternalsForTesting;
import "package:expect/expect.dart";
class C {
@ -17,5 +19,7 @@ bar(c) => c * 4;
foo(c) => bar(c);
main() {
print(
VMInternalsForTesting.randomInstructionsOffsetInsideAllocateObjectStub());
var a = foo(new C());
}

View file

@ -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,117 @@ 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);
// Currently there are no appropriate buildtools on the SIMARM and SIMARM64
// trybots as normally they compile to ELF and don't need them for compiling
// assembly snapshots.
if ((Platform.isLinux || Platform.isMacOS) &&
!buildDir.endsWith('SIMARM') &&
!buildDir.endsWith('SIMARM64')) {
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 allocateObjectInstructionsOffset;
DwarfTestOutput(this.trace, this.allocateObjectInstructionsOffset);
}
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 +198,131 @@ 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);
final translatedDwarfTrace1 =
await Stream.fromIterable(output1.trace).transform(decoder).toList();
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace1);
final allocateObjectPCOffset1 = PCOffset(
output1.allocateObjectInstructionsOffset, InstructionsSection.isolate);
final allocateObjectPCOffset2 = PCOffset(
output2.allocateObjectInstructionsOffset, InstructionsSection.isolate);
print('Stack frames from translated non-symbolic stack trace:');
translatedStackFrames.forEach(print);
print('');
print('Offset of first stub address is $allocateObjectPCOffset1');
print('Offset of second stub address is $allocateObjectPCOffset2');
print('Stack frames from original symbolic stack trace:');
originalStackFrames.forEach(print);
print('');
final allocateObjectRelocatedAddress1 =
dwarf.virtualAddressOf(allocateObjectPCOffset1);
final allocateObjectRelocatedAddress2 =
dwarf.virtualAddressOf(allocateObjectPCOffset2);
Expect.isTrue(translatedStackFrames.length > 0);
Expect.isTrue(originalStackFrames.length > 0);
final allocateObjectCallInfo1 = dwarf.callInfoFor(
allocateObjectRelocatedAddress1,
includeInternalFrames: true);
final allocateObjectCallInfo2 = dwarf.callInfoFor(
allocateObjectRelocatedAddress2,
includeInternalFrames: true);
// 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);
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);
print('Stack frames from translated non-symbolic stack trace, no columns:');
columnStrippedTranslated.forEach(print);
print('');
print("Successfully matched AllocateObject stub addresses");
print("");
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace);
// 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 translated non-symbolic stack trace:');
translatedStackFrames.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;
print('Stack frames from original symbolic stack trace:');
originalStackFrames.forEach(print);
print('');
final absTrace1 = absoluteAddresses(dwarfTrace1);
final absTrace2 = absoluteAddresses(dwarfTrace2);
Expect.isTrue(translatedStackFrames.length > 0);
Expect.isTrue(originalStackFrames.length > 0);
final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1);
final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2);
// 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);
Expect.deepEquals(relocatedFromDso1, relocatedFromDso2);
print('Stack frames from translated non-symbolic stack trace, no columns:');
columnStrippedTranslated.forEach(print);
print('');
// 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);
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
Expect.deepEquals(virtTrace1, virtTrace2);
// 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, tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)));
Expect.deepEquals(
virtTrace2, tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)));
// 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, relocatedFromDso1);
Expect.deepEquals(virtTrace2, relocatedFromDso2);
});
final absTrace1 = absoluteAddresses(output1.trace);
final absTrace2 = absoluteAddresses(output2.trace);
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]+)'");

View file

@ -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";
}
@ -50,30 +51,39 @@ Future<void> assembleSnapshot(String assemblyPath, String snapshotPath) async {
String cc = 'gcc';
String shared = '-shared';
if (Platform.isMacOS) {
cc = 'clang';
} else if (buildDir.endsWith('SIMARM') || buildDir.endsWith('SIMARM64')) {
if (clangBuildToolsDir != null) {
cc = path.join(clangBuildToolsDir, 'clang');
if (buildDir.endsWith('SIMARM') || buildDir.endsWith('SIMARM64')) {
final clangBuildTools = clangBuildToolsDir;
if (clangBuildTools != null) {
cc = path.join(clangBuildTools, 'clang');
} else {
throw 'Cannot assemble for ${path.basename(buildDir)} '
'without //buildtools on ${Platform.operatingSystem}';
}
} else if (Platform.isMacOS) {
cc = 'clang';
}
if (Platform.isMacOS) {
shared = '-dynamiclib';
// Tell Mac linker to give up generating eh_frame from dwarf.
ldFlags.add('-Wl,-no_compact_unwind');
} else if (buildDir.endsWith('SIMARM')) {
if (buildDir.endsWith('SIMARM')) {
ccFlags.add('--target=armv7-linux-gnueabihf');
} else if (buildDir.endsWith('SIMARM64')) {
ccFlags.add('--target=aarch64-linux-gnu');
} else 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');
}
if (buildDir.endsWith('X64') || buildDir.endsWith('SIMARM64')) {
ccFlags.add('-m64');
}
if (debug) {
ccFlags.add('-g');
}
await run(cc, <String>[
...ccFlags,
@ -94,8 +104,8 @@ Future<void> stripSnapshot(String snapshotPath, String strippedPath,
var strip = 'strip';
if ((Platform.isLinux &&
(buildDir.endsWith('SIMARM') || buildDir.endsWith('SIMARM64'))) ||
if (buildDir.endsWith('SIMARM') ||
buildDir.endsWith('SIMARM64') ||
(Platform.isMacOS && forceElf)) {
if (clangBuildToolsDir != null) {
strip = path.join(clangBuildToolsDir, 'llvm-strip');
@ -158,9 +168,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 +180,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';

View file

@ -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.

View file

@ -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());
}

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -329,6 +329,7 @@ namespace dart {
V(Internal_writeIntoOneByteString, 3) \
V(Internal_writeIntoTwoByteString, 3) \
V(Internal_deoptimizeFunctionsOnStack, 0) \
V(Internal_randomInstructionsOffsetInsideAllocateObjectStub, 0) \
V(InvocationMirror_unpackTypeArguments, 2) \
V(NoSuchMethodError_existingMethodSignature, 3) \
V(Uri_isWindowsPlatform, 0) \

View file

@ -179,6 +179,13 @@ 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
// by returning an offset into the isolate instructions that should correspond
// to a known stub.
@pragma("vm:external-name",
"Internal_randomInstructionsOffsetInsideAllocateObjectStub")
external static int randomInstructionsOffsetInsideAllocateObjectStub();
}
@patch