[vm] Refactor debug info handling code into a new package.

Change-Id: Iaf944564ebbe4bdcc215166f784e949362583a69
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-mac-release-simarm64-try,vm-kernel-precomp-win-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/132281
Commit-Queue: Teagan Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Teagan Strickland 2020-01-23 10:14:17 +00:00 committed by commit-bot@chromium.org
parent 241d2c6cf5
commit a003d5e69a
25 changed files with 721 additions and 339 deletions

View file

@ -67,6 +67,7 @@ mime:third_party/pkg/mime/lib
mockito:third_party/pkg/mockito/lib
modular_test:pkg/modular_test/lib
mustache:third_party/pkg/mustache/lib
native_stack_traces:pkg/native_stack_traces/lib
nnbd_migration:pkg/nnbd_migration/lib
oauth2:third_party/pkg/oauth2/lib
observatory:runtime/observatory/lib

View file

@ -28,6 +28,7 @@ Future<void> generateNative(
Kind kind,
String sourceFile,
String outputFile,
String debugFile,
String packages,
List<String> defines,
bool enableAsserts,
@ -68,7 +69,7 @@ Future<void> generateNative(
print('Generating AOT snapshot.');
}
final snapshotResult = await generateAotSnapshot(
genSnapshot, kernelFile, snapshotFile, enableAsserts);
genSnapshot, kernelFile, snapshotFile, debugFile, enableAsserts);
if (snapshotResult.exitCode != 0) {
stderr.writeln(snapshotResult.stdout);
stderr.writeln(snapshotResult.stderr);
@ -138,6 +139,9 @@ E.g.: dart2native main.dart -o ../bin/my_app.exe
..addOption('packages', abbr: 'p', valueHelp: 'path', help: '''
Get package locations from the specified file instead of .packages. <path> can be relative or absolute.
E.g.: dart2native --packages=/tmp/pkgs main.dart
''')
..addOption('save-debugging-info', abbr: 'S', valueHelp: 'path', help: '''
Remove debugging information from the output and save it separately to the specified file. <path> can be relative or absolute.
''')
..addFlag('verbose',
abbr: 'v', negatable: false, help: 'Show verbose output.');
@ -176,6 +180,9 @@ E.g.: dart2native --packages=/tmp/pkgs main.dart
Kind.aot: '${sourceWithoutDart}.aot',
Kind.exe: '${sourceWithoutDart}.exe',
}[kind]));
final debugPath = parsedArgs['save-debugging-info'] != null
? path.canonicalize(path.normalize(parsedArgs['save-debugging-info']))
: null;
if (!FileSystemEntity.isFileSync(sourcePath)) {
stderr.writeln(
@ -189,6 +196,7 @@ E.g.: dart2native --packages=/tmp/pkgs main.dart
kind,
sourcePath,
outputPath,
debugPath,
parsedArgs['packages'],
parsedArgs['define'],
parsedArgs['enable-asserts'],

View file

@ -60,10 +60,13 @@ Future generateAotKernel(
}
Future generateAotSnapshot(String genSnapshot, String kernelFile,
String snapshotFile, bool enableAsserts) {
String snapshotFile, String debugFile, bool enableAsserts) {
return Process.run(genSnapshot, [
'--snapshot-kind=app-aot-elf',
'--elf=${snapshotFile}',
if (debugFile != null) '--save-debugging-info=$debugFile',
if (debugFile != null) '--dwarf-stack-traces',
if (debugFile != null) '--strip',
if (enableAsserts) '--enable-asserts',
kernelFile
]);

View file

@ -438,6 +438,7 @@ class SourceLibraryBuilder extends LibraryBuilderImpl {
'mockito',
'modular_test',
'mustache',
'native_stack_traces',
'nnbd_migration',
'oauth2',
'observatory',

3
pkg/native_stack_traces/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.dart_tool/
.packages
pubspec.lock

View file

@ -0,0 +1,6 @@
# Below is a list of people and organizations that have contributed
# to the Dart project. Names should be added to the list like so:
#
# Name/Organization <email address>
Google LLC

View file

@ -0,0 +1,5 @@
# Changelog
## 0.1.0
- Initial release

View file

@ -0,0 +1,26 @@
Copyright 2020, the Dart project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,71 @@
# native_stack_traces
This package provides libraries and a utility for decoding non-symbolic
stack traces generated by an AOT-compiled Dart application.
## Converting stack traces
In some modes of AOT compilation, information on mapping execution points to
source locations is no longer stored in the Dart image. Instead, this
information is translated to separately stored debugging information.
This debugging information can then be stripped from the application
before shipping.
However, there is a drawback. Stack traces generated by such an application no
longer includes file, function, and line number information (i.e., symbolic
stack traces). Instead, stack trace frames simply include program counter
information. Thus, to find the source information for these frames, we must use
the debugging information. This means either keeping the original unstripped
application, or saving the debugging information into a separate file.
Given this debugging information, the libraries in this package can turn
non-symbolic stack traces back into symbolic stack traces. In addition, this
package includes a command line tool `decode` whose output is the same as its
input except that non-symbolic stack traces are translated.
### Using `decode`
Take the following Dart code, which we put in `throws.dart`. The inlining
pragmas are here just to ensure that `bar` is inlined into `foo` and that `foo`
is _not_ inlined into `bar`, to illustrate how inlined code is handled in the
translated output.
```dart
@pragma('vm:prefer-inline')
bar() => throw null;
@pragma('vm:never-inline')
foo() => bar();
main() => foo();
```
Now we run the following commands:
```bash
# Make sure that we have the native_stack_traces package.
$ pub get native_stack_traces
$ pub global activate native_stack_traces
# We compile the example program, removing the source location information
# from the snapshot and saving the debugging information into throws.debug.
$ dart2native -k aot -S throws.debug -o throws.aotsnapshot throws.dart
# Run the program, saving the error output to throws.err.
$ dartaotruntime throws.aotsnapshot 2>throws.err
# Using the saved debugging information, we can translate the stack trace
# contained in throws.err to its symbolic form.
$ pub global run native_stack_traces:decode translate -d throws.debug -i throws.err
# We can also just pipe the output of running the program directly into
# the utility.
$ dartaotruntime throws.aotsnapshot |& \
pub global run native_stack_traces:decode translate -d throws.debug
```
## Features and bugs
Please file feature requests and bugs at the [issue tracker][tracker].
[tracker]: https://github.com/dart-lang/sdk/issues

View file

@ -0,0 +1 @@
include: package:pedantic/analysis_options.1.8.0.yaml

View file

@ -0,0 +1,275 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:async";
import "dart:convert";
import "dart:io" as io;
import 'package:args/args.dart' show ArgParser, ArgResults;
import 'package:path/path.dart' as path;
import 'package:native_stack_traces/native_stack_traces.dart';
final ArgParser _translateParser = ArgParser(allowTrailingOptions: true)
..addOption('debug',
abbr: 'd',
help: 'Filename containing debugging information (REQUIRED)',
valueHelp: 'FILE')
..addOption('input',
abbr: 'i', help: 'Filename for processed input', valueHelp: 'FILE')
..addOption('output',
abbr: 'o', help: 'Filename for generated output', valueHelp: 'FILE')
..addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Translate all frames, not just user or library code frames');
final ArgParser _findParser = ArgParser(allowTrailingOptions: true)
..addOption('debug',
abbr: 'd',
help: 'Filename containing debugging information (REQUIRED)',
valueHelp: 'FILE')
..addMultiOption('location',
abbr: 'l', help: 'PC address to find', valueHelp: 'PC')
..addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Translate all frames, not just user or library code frames')
..addOption('vm_start',
help: 'Absolute address for start of VM instructions', valueHelp: 'PC')
..addOption('isolate_start',
help: 'Absolute address for start of isolate instructions',
valueHelp: 'PC');
final ArgParser _helpParser = ArgParser(allowTrailingOptions: true);
final ArgParser _argParser = ArgParser(allowTrailingOptions: true)
..addCommand('help', _helpParser)
..addCommand('find', _findParser)
..addCommand('translate', _translateParser)
..addFlag('help',
abbr: 'h',
negatable: false,
help: 'Print usage information for this or a particular subcommand');
final String _mainUsage = '''
Usage: convert_stack_traces <command> [options] ...
Commands:
${_argParser.commands.keys.join("\n")}
Options shared by all commands:
${_argParser.usage}''';
final String _helpUsage = '''
Usage: convert_stack_traces help [<command>]
Returns usage for the convert_stack_traces utility or a particular command.
Commands:
${_argParser.commands.keys.join("\n")}''';
final String _translateUsage = '''
Usage: convert_stack_traces translate [options]
The translate command takes text that includes Dart stack traces generated by a
snapshot compiled with the --dwarf-stack-traces flag and outputs the same text,
but with the stack traces converted to regular Dart stack traces that contain
function names, file names, and line numbers.
Options shared by all commands:
${_argParser.usage}
Options specific to the translate command:
${_translateParser.usage}''';
final String _findUsage = '''
Usage: convert_stack_traces find [options] <PC> ...
The find command looks up PC addresses either given as arguments on the command
line or via the -l option and outputs the file, function, and line number
information for those PC addresses. By default, the PC addresses are
assumed to be virtual addresses valid for the DWARF in the debugging information
file.
PC addresses generally must be integers in a format Dart recognizes with one
exception: the leading "0x" may be omitted from hexidecimal integers to allow
for copying and pasting from Dart error traces.
The -l option may be provided multiple times, or a single use of the -l option
may be given multiple arguments separated by commas.
The --vm_start and --isolate_start arguments provide the absolute address in
memory of the start of the instructions section for the VM and isolate,
respectively. When one is provided, both must be provided, and the PC addresses
to find are then assumed to be absolute PC addresses, not virtual addresses.
Options shared by all commands:
${_argParser.usage}
Options specific to the find command:
${_findParser.usage}''';
final _usages = <String, String>{
null: _mainUsage,
'': _mainUsage,
'help': _helpUsage,
'translate': _translateUsage,
'find': _findUsage,
};
const int _badUsageExitCode = 1;
void errorWithUsage(String message, {String command}) {
print("Error: $message.\n");
print(_usages[command]);
io.exitCode = _badUsageExitCode;
}
void help(ArgResults options) {
void usageError(String message) => errorWithUsage(message, command: 'help');
switch (options.rest.length) {
case 0:
return print(_usages['help']);
case 1:
{
final usage = _usages[options.rest.first];
if (usage != null) return print(usage);
return usageError('invalid command ${options.rest.first}');
}
default:
return usageError('too many arguments');
}
}
void find(ArgResults options) {
void usageError(String message) => errorWithUsage(message, command: 'find');
int convertAddress(String s) {
int address = int.tryParse(s);
if (address != null) return address;
// Try adding an initial "0x", as DWARF stack traces don't normally include
// the hex marker on the PC addresses and this may have been copy/pasted.
return int.tryParse("0x" + s);
}
if (options['debug'] == null) {
return usageError('must provide -d/--debug');
}
final dwarf = Dwarf.fromFile(options['debug']);
final verbose = options['verbose'];
int vm_start;
if (options['vm_start'] != null) {
vm_start = convertAddress(options['vm_start']);
if (vm_start == null) {
return usageError('could not parse VM start address '
'${options['vm_start']}');
}
}
int isolate_start;
if (options['isolate_start'] != null) {
isolate_start = convertAddress(options['isolate_start']);
if (isolate_start == null) {
return usageError('could not parse isolate start address '
'${options['isolate_start']}');
}
}
if ((vm_start == null) != (isolate_start == null)) {
return usageError("need both VM start and isolate start");
}
final locations = <int>[];
for (final s in options['location'] + options.rest) {
final location = convertAddress(s);
if (location == null) {
return usageError('could not parse PC address ${s}');
}
locations.add(location);
}
if (locations.isEmpty) return usageError('no PC addresses to find');
// Used to approximate how many hex digits we should have in the final output.
final maxDigits = options['location']
.fold(0, ((acc, s) => s.startsWith('0x') ? s.length - 2 : s.length));
Iterable<int> addresses = locations;
if (vm_start != null) {
final header = StackTraceHeader(isolate_start, vm_start);
addresses = locations
.map((l) => header.convertAbsoluteAddress(l).virtualAddress(dwarf));
}
for (final addr in addresses) {
final frames = dwarf
.callInfo(addr, includeInternalFrames: verbose)
?.map((CallInfo c) => " " + c.toString());
final addrString = addr > 0
? "0x" + addr.toRadixString(16).padLeft(maxDigits, '0')
: addr.toString();
print("For virtual address ${addrString}:");
if (frames == null) {
print(" Invalid virtual address.");
} else if (frames.isEmpty) {
print(" Not a call from user or library code.");
} else {
frames.forEach(print);
}
}
}
Future<void> translate(ArgResults options) async {
void usageError(String message) =>
errorWithUsage(message, command: 'translate');
if (options['debug'] == null) {
return usageError('must provide -d/--debug');
}
final dwarf =
Dwarf.fromFile(path.canonicalize(path.normalize(options['debug'])));
final verbose = options['verbose'];
final output = options['output'] != null
? io.File(path.canonicalize(path.normalize(options['output'])))
.openWrite()
: io.stdout;
final input = options['input'] != null
? io.File(path.canonicalize(path.normalize(options['input']))).openRead()
: io.stdin;
final newline = (StringBuffer()..writeln()).toString();
final convertedStream = input
.transform(utf8.decoder)
.transform(const LineSplitter())
.transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: verbose))
.map((s) => s + newline)
.transform(utf8.encoder);
await output.addStream(convertedStream);
await output.flush();
await output.close();
}
Future<void> main(List<String> arguments) async {
ArgResults options;
try {
options = _argParser.parse(arguments);
} on FormatException catch (e) {
return errorWithUsage(e.message);
}
if (options['help']) return print(_usages[options.command?.name]);
if (options.command == null) return errorWithUsage('no command provided');
switch (options.command.name) {
case 'help':
return help(options.command);
case 'find':
return find(options.command);
case 'translate':
return await translate(options.command);
}
}

View file

@ -0,0 +1,7 @@
// Copyright (c) 2020, 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.
export 'src/convert.dart'
show collectPCOffsets, DwarfStackTraceDecoder, PCOffset, StackTraceHeader;
export 'src/dwarf.dart' show CallInfo, Dwarf;

View file

@ -0,0 +1,223 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:async";
import "dart:math";
import "dwarf.dart";
String _stackTracePiece(CallInfo call, int depth) => "#${depth}\t${call}";
/// An enum describing the section where a PC address is located.
enum InstructionSection { vm, isolate }
/// A class representing a program counter address as an offset into the
/// appropriate instructions section.
class PCOffset {
final int offset;
final InstructionSection section;
PCOffset(this.offset, this.section);
/// Returns the corresponding virtual address in [dwarf] or null if
/// [dwarf] is null.
int virtualAddress(Dwarf dwarf) {
if (dwarf != null) {
switch (section) {
case InstructionSection.vm:
return dwarf.convertToVMVirtualAddress(offset);
case InstructionSection.isolate:
return dwarf.convertToIsolateVirtualAddress(offset);
}
}
return null;
}
@override
int get hashCode => offset.hashCode + section.index;
@override
bool operator ==(Object other) {
return other is PCOffset &&
offset == other.offset &&
section == other.section;
}
}
/// A class representing header information for a Dart stack trace generated
/// when running a snapshot compiled with the `--dwarf-stack-traces` flag.
class StackTraceHeader {
final int _isolateStart;
final int _vmStart;
StackTraceHeader(this._isolateStart, this._vmStart);
// There are currently four lines in what we consider the stack trace header.
// The warning, the * line, the pid/tid/name line, and the instructions line.
static const lineCount = 4;
/// The start of the stack trace header when running with
/// --dwarf-stack-traces. Follows the exception information.
static const _headerStart = 'Warning: This VM has been configured to produce '
'stack traces that violate the Dart standard.';
/// The last line of the stack trace header when running with
/// --dwarf-stack-traces.
///
/// Currently, this happens to include the only pieces of information from the
/// stack trace header we need: the absolute addresses during program
/// execution of the start of the isolate and VM instructions.
static final _headerEndRE = RegExp(
r'isolate_instructions: ([0-9a-f]+) vm_instructions: ([0-9a-f]+)$');
static bool matchesStart(String line) => line.endsWith(_headerStart);
/// Tries to parse stack trace header information from the given lines of
/// input. Returns null if the line does not contain the expected header
/// information.
factory StackTraceHeader.fromLines(Iterable<String> lines) {
if (lines.length != lineCount) return null;
final match = _headerEndRE.firstMatch(lines.last);
if (match == null) return null;
final isolateAddr = int.tryParse("0x" + match[1]);
final vmAddr = int.tryParse("0x" + match[2]);
if (isolateAddr == null || vmAddr == null) return null;
return StackTraceHeader(isolateAddr, vmAddr);
}
/// Converts an absolute program counter address from a Dart stack trace
/// to a [PCOffset].
PCOffset convertAbsoluteAddress(int address) {
final isolateOffset = address - _isolateStart;
int vmOffset = address - _vmStart;
if (vmOffset > 0 && vmOffset == min(vmOffset, isolateOffset)) {
return PCOffset(vmOffset, InstructionSection.vm);
} else {
return PCOffset(isolateOffset, InstructionSection.isolate);
}
}
}
/// A Dart DWARF stack trace contains up to four pieces of information:
/// - The zero-based frame index from the top of the stack.
/// - The absolute address of the program counter.
/// - The virtual address of the program counter, if the snapshot was
/// loaded as a dynamic library, otherwise not present.
/// - The path to the snapshot, if it was loaded as a dynamic library,
/// otherwise the string "<unknown>".
final _traceLineRE =
RegExp(r' #(\d{2}) abs ([0-9a-f]+)(?: virt ([0-9a-f]+))? (.*)$');
PCOffset _retrievePCOffset(StackTraceHeader header, Match match) {
if (header == null || match == null) return null;
final address = int.tryParse("0x" + match[2]);
return header.convertAbsoluteAddress(address);
}
/// Returns the [PCOffset] for each frame's absolute PC address if [lines]
/// contains one or more Dart stack traces generated by a snapshot compiled
/// with `--dwarf-stack-traces`.
Iterable<PCOffset> collectPCOffsets(Iterable<String> lines) sync* {
final headerCache = <String>[];
StackTraceHeader header;
for (var line in lines) {
if (headerCache.isNotEmpty) {
headerCache.add(line);
if (headerCache.length == StackTraceHeader.lineCount) {
header = StackTraceHeader.fromLines(headerCache);
headerCache.clear();
}
continue;
}
if (StackTraceHeader.matchesStart(line)) {
header = null;
headerCache.add(line);
continue;
}
final match = _traceLineRE.firstMatch(line);
final offset = _retrievePCOffset(header, match);
if (offset != null) yield offset;
}
}
/// A [StreamTransformer] that scans a stream of lines for Dart stack traces
/// generated by a snapshot compiled with `--dwarf-stack-traces`. The
/// transformer assumes that there may be text preceeding the Dart output on
/// individual lines, like in log files, but that there is no trailing text.
/// For each stack frame found, the transformer attempts to locate a function
/// name, file name and line number using the provided DWARF information.
///
/// If no information is found, or the line is not a stack frame, the line is
/// output to the sink unchanged.
///
/// If the located information corresponds to Dart internals, the frame will be
/// dropped if and only if [includeInternalFrames] is false.
///
/// Otherwise, at least one altered stack frame is generated and replaces the
/// stack frame portion of the original line. If the PC address corresponds to
/// inlined code, then multiple stack frames may be generated. When multiple
/// stack frames are generated, only the first replaces the stack frame portion
/// of the original line, and the remaining frames are separately output.
class DwarfStackTraceDecoder extends StreamTransformerBase<String, String> {
final Dwarf _dwarf;
final bool _includeInternalFrames;
final _cachedHeaderLines = <String>[];
int _cachedDepth = 0;
StackTraceHeader _cachedHeader;
DwarfStackTraceDecoder(this._dwarf, {bool includeInternalFrames = false})
: _includeInternalFrames = includeInternalFrames;
Stream<String> bind(Stream<String> stream) async* {
await for (final line in stream) {
if (_cachedHeaderLines.isNotEmpty) {
_cachedHeaderLines.add(line);
if (_cachedHeaderLines.length == StackTraceHeader.lineCount) {
_cachedHeader = StackTraceHeader.fromLines(_cachedHeaderLines);
// If we failed to parse a header, output the cached lines unchanged.
if (_cachedHeader == null) {
for (final cachedLine in _cachedHeaderLines) {
yield cachedLine;
}
}
_cachedHeaderLines.clear();
}
continue;
}
// Reset any stack-related state when we see the start of a new
// stacktrace.
if (StackTraceHeader.matchesStart(line)) {
_cachedDepth = 0;
_cachedHeader = null;
_cachedHeaderLines.add(line);
continue;
}
// 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(_cachedHeader, lineMatch);
final location = offset?.virtualAddress(_dwarf);
if (location == null) {
yield line;
continue;
}
final callInfo = _dwarf
.callInfo(location, includeInternalFrames: _includeInternalFrames)
?.toList();
if (callInfo == null) {
yield line;
continue;
}
// No lines to output (as this corresponds to Dart internals).
if (callInfo.isEmpty) continue;
// Output the lines for the symbolic frame with the prefix found on the
// original non-symbolic frame line.
final prefix = line.substring(0, lineMatch.start);
yield prefix + _stackTracePiece(callInfo.first, _cachedDepth++);
for (int i = 1; i < callInfo.length; i++) {
yield prefix + _stackTracePiece(callInfo[i], _cachedDepth++);
}
}
}
}

View file

@ -127,7 +127,7 @@ class _Abbreviation {
bool children;
List<_Attribute> attributes;
_Abbreviation.fromReader(Reader this.reader) {
_Abbreviation.fromReader(this.reader) {
_read();
}
@ -173,6 +173,7 @@ class _Abbreviation {
}
}
@override
String toString() {
var ret = " Tag: ${_tagStrings[tag]}\n"
" Children: ${children ? "DW_CHILDREN_yes" : "DW_CHILDREN_no"}\n"
@ -212,6 +213,7 @@ class _AbbreviationsTable {
}
}
@override
String toString() =>
"Abbreviations table:\n\n" +
_abbreviations.keys
@ -219,6 +221,7 @@ class _AbbreviationsTable {
.join("\n");
}
/// A class representing a DWARF Debug Information Entry (DIE).
class DebugInformationEntry {
final Reader reader;
final CompilationUnit compilationUnit;
@ -246,6 +249,7 @@ class DebugInformationEntry {
case _AttributeForm.reference4:
return reader.readBytes(4);
}
return null;
}
String _nameOfOrigin(int offset) {
@ -272,6 +276,7 @@ class DebugInformationEntry {
return paddedHex(value as int, 4) +
" (origin: ${_nameOfOrigin(value as int)})";
}
return "<unknown>";
}
int get _unitOffset => reader.start - compilationUnit.reader.start;
@ -372,6 +377,7 @@ class DebugInformationEntry {
];
}
@override
String toString() {
var ret =
"Abbreviated unit (code $code, offset ${paddedHex(_unitOffset)}):\n";
@ -379,7 +385,7 @@ class DebugInformationEntry {
ret += " ${_attributeNameStrings[attribute.name]} => "
"${_attributeValueToString(attribute, attributes[attribute])}\n";
}
if (children == null || children.length == 0) {
if (children == null || children.isEmpty) {
ret += "Has no children.\n\n";
return ret;
}
@ -394,6 +400,7 @@ class DebugInformationEntry {
}
}
/// A class representing a DWARF compilation unit.
class CompilationUnit {
final Reader reader;
final Dwarf dwarf;
@ -455,6 +462,7 @@ class CompilationUnit {
_AbbreviationsTable get abbreviations =>
dwarf.abbreviationTables[abbreviationOffset];
@override
String toString() =>
"Compilation unit:\n"
" Version: $version\n"
@ -463,6 +471,7 @@ class CompilationUnit {
contents.map((DebugInformationEntry u) => u.toString()).join();
}
/// A class representing a DWARF `.debug_info` section.
class DebugInfo {
final Reader reader;
final Dwarf dwarf;
@ -525,6 +534,7 @@ class FileEntry {
size = reader.readLEB128EncodedInteger();
}
@override
String toString() => "File name: $name\n"
" Directory index: $directoryIndex\n"
" Last modified: $lastModified\n"
@ -559,6 +569,7 @@ class FileInfo {
bool containsKey(int index) => _files.containsKey(index);
FileEntry operator [](int index) => _files[index];
@override
String toString() {
if (_files.isEmpty) {
return "No file information.\n";
@ -619,7 +630,7 @@ class LineNumberState {
bool basicBlock;
bool endSequence;
LineNumberState(bool this.defaultIsStatement) {
LineNumberState(this.defaultIsStatement) {
reset();
}
@ -655,6 +666,7 @@ class LineNumberState {
" Is ${endSequence ? "" : "not "}just after the end of a sequence.\n";
}
/// A class representing a DWARF line number program.
class LineNumberProgram {
final Reader reader;
@ -720,7 +732,7 @@ class LineNumberProgram {
while (!reader.done) {
_applyNextOpcode(currentState);
}
if (calculatedMatrix.length == 0) {
if (calculatedMatrix.isEmpty) {
throw FormatException("No line number information generated by program");
}
// Set the offset to the declared size in case of padding. The declared
@ -857,6 +869,7 @@ class LineNumberProgram {
return state.line;
}
@override
String toString() {
var ret = " Size: $size\n"
" Version: $version\n"
@ -887,6 +900,7 @@ class LineNumberProgram {
}
}
/// A class representing a DWARF .debug_line section.
class LineNumberInfo {
final Reader reader;
@ -964,6 +978,7 @@ class CallInfo {
"${function} (${filename}:${line <= 0 ? "??" : line.toString()})";
}
/// A class representing DWARF debugging information.
class Dwarf {
final Elf elf;
Map<int, _AbbreviationsTable> abbreviationTables;
@ -972,12 +987,15 @@ class Dwarf {
int vmStartAddress;
int isolateStartAddress;
Dwarf.fromElf(Elf this.elf) {
Dwarf.fromElf(this.elf) {
_loadSections();
}
factory Dwarf.fromFile(String filename) {
final elf = Elf.fromFile(filename);
/// Attempts to load the DWARF debugging information from the file at [path].
/// Returns a [Dwarf] object if the load succeeds, otherwise returns null.
factory Dwarf.fromFile(String path) {
final elf = Elf.fromFile(path);
if (elf == null) return null;
return Dwarf.fromElf(elf);
}
@ -1010,6 +1028,9 @@ class Dwarf {
isolateStartAddress = textAddresses[1];
}
/// Returns an iterable of [CallInfo] for the given virtual address.
/// If [includeInternalFrames] is false, then only information corresponding
/// to user or library code is returned.
Iterable<CallInfo> callInfo(int address,
{bool includeInternalFrames = false}) {
final calls = debugInfo.callInfo(address);
@ -1019,14 +1040,19 @@ class Dwarf {
return calls;
}
/// Returns the virtual address for the given offset into the VM
/// instructions image.
int convertToVMVirtualAddress(int textOffset) {
return textOffset + vmStartAddress;
}
/// Returns the virtual address for the given offset into the isolate
/// instructions image.
int convertToIsolateVirtualAddress(int textOffset) {
return textOffset + isolateStartAddress;
}
@override
String toString() =>
"DWARF debugging information:\n\n" +
abbreviationTables

View file

@ -87,7 +87,7 @@ class ElfHeader {
static const _ELFDATA2LSB = 0x01;
static const _ELFDATA2MSB = 0x02;
ElfHeader.fromReader(Reader this.startingReader) {
ElfHeader.fromReader(this.startingReader) {
_read();
}
@ -229,7 +229,7 @@ class ProgramHeaderEntry {
static const _PT_DYNAMIC = 2;
static const _PT_PHDR = 6;
ProgramHeaderEntry.fromReader(Reader this.reader) {
ProgramHeaderEntry.fromReader(this.reader) {
assert(reader.wordSize == 4 || reader.wordSize == 8);
_read();
}
@ -282,8 +282,7 @@ class ProgramHeader {
List<ProgramHeaderEntry> _entries;
ProgramHeader.fromReader(Reader this.reader,
{int this.entrySize, int this.entryCount}) {
ProgramHeader.fromReader(this.reader, {this.entrySize, this.entryCount}) {
_read();
}
@ -393,7 +392,7 @@ class SectionHeader {
final int stringsIndex;
List<SectionHeaderEntry> _entries;
StringTable nameTable = null;
StringTable nameTable;
SectionHeader.fromReader(this.reader,
{this.entrySize, this.entryCount, this.stringsIndex}) {
@ -488,16 +487,23 @@ class Elf {
Elf.fromReader(this.startingReader) {
_read();
}
Elf.fromFile(String filename)
: this.fromReader(Reader.fromTypedData(File(filename).readAsBytesSync(),
// We provide null for the wordSize and endianness to ensure
// we don't accidentally call any methods that use them until
// we have gotten that information from the ELF header.
wordSize: null,
endian: null));
static bool startsWithMagicNumber(String filename) {
final file = File(filename).openSync();
/// Returns either an [Elf] object representing the ELF information in the
/// file at [path] or null if the file does not start with the ELF magic
/// number.
factory Elf.fromFile(String path) {
if (!startsWithMagicNumber(path)) return null;
return Elf.fromReader(Reader.fromTypedData(File(path).readAsBytesSync(),
// We provide null for the wordSize and endianness to ensure
// we don't accidentally call any methods that use them until
// we have gotten that information from the ELF header.
wordSize: null,
endian: null));
}
/// Checks that the file at [path] starts with the ELF magic number.
static bool startsWithMagicNumber(String path) {
final file = File(path).openSync();
var ret = true;
for (int code in ElfHeader._ELFMAG.codeUnits) {
if (file.readByteSync() != code) {
@ -509,6 +515,7 @@ class Elf {
return ret;
}
/// Returns an iterable of [Section]s whose name matches [name].
Iterable<Section> namedSection(String name) {
final ret = <Section>[];
for (var entry in sections.keys) {
@ -549,6 +556,7 @@ class Elf {
}
}
@override
String toString() {
String accumulateSection(String acc, SectionHeaderEntry entry) =>
acc + "\nSection ${entry.name} is ${sections[entry]}";

View file

@ -2,8 +2,6 @@
// 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.
library vm.elf.reader;
import 'dart:typed_data';
import 'dart:math';
@ -18,7 +16,7 @@ class Reader {
int _offset = 0;
Reader.fromTypedData(TypedData data, {int this.wordSize, Endian this.endian})
Reader.fromTypedData(TypedData data, {this.wordSize, this.endian})
: bdata =
ByteData.view(data.buffer, data.offsetInBytes, data.lengthInBytes);

View file

@ -0,0 +1,18 @@
name: native_stack_traces
description: Utilities for working with non-symbolic stack traces.
version: 0.1.0
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/native_stack_traces
environment:
sdk: '>=2.7.0 <3.0.0'
executables:
decode:
dependencies:
args: ^1.5.2
path: ^1.6.4
dev_dependencies:
pedantic: ^1.8.0

View file

@ -1,123 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:async";
import "dart:convert";
import "dart:io" as io;
import 'package:args/args.dart' show ArgParser, ArgResults;
import 'package:vm/dwarf/convert.dart';
import 'package:vm/dwarf/dwarf.dart';
final ArgParser _argParser = new ArgParser(allowTrailingOptions: true)
..addOption('elf',
abbr: 'e',
help: 'Path to ELF file with debugging information',
defaultsTo: null,
valueHelp: 'FILE')
..addOption('input',
abbr: 'i',
help: 'Path to input file',
defaultsTo: null,
valueHelp: 'FILE')
..addOption('location',
abbr: 'l',
help: 'PC address to convert to a file name and line number',
defaultsTo: null,
valueHelp: 'INT')
..addOption('output',
abbr: 'o',
help: 'Path to output file',
defaultsTo: null,
valueHelp: 'FILE')
..addFlag('verbose',
abbr: 'v',
help: 'Translate all frames, not just frames for user or library code',
defaultsTo: false);
final String _usage = '''
Usage: convert_stack_traces [options]
Takes text that includes DWARF-based stack traces with PC addresses and
outputs the same text, but with the DWARF stack traces converted to stack traces
that contain function names, file names, and line numbers.
Reads from the file named by the argument to -i/--input as input, or stdin if
no input flag is given.
Outputs the converted contents to the file named by the argument to
-o/--output, or stdout if no output flag is given.
The -e/-elf option must be provided, and DWARF debugging information is
read from the file named by its argument.
When the -v/--verbose option is given, the converter translates all frames, not
just those corresponding to user or library code.
If an -l/--location option is provided, then the file and line number
information for the given location is looked up and output instead.
Options:
${_argParser.usage}
''';
const int _badUsageExitCode = 1;
Future<void> main(List<String> arguments) async {
final ArgResults options = _argParser.parse(arguments);
if ((options.rest.length > 0) || (options['elf'] == null)) {
print(_usage);
io.exitCode = _badUsageExitCode;
return;
}
int location = null;
if (options['location'] != null) {
location = int.tryParse(options['location']);
if (location == null) {
// Try adding an initial "0x", as DWARF stack traces don't normally
// include the hex marker on the PC addresses.
location = int.tryParse("0x" + options['location']);
}
if (location == null) {
print("Location could not be parsed as an int: ${options['location']}\n");
print(_usage);
io.exitCode = _badUsageExitCode;
return;
}
}
final dwarf = Dwarf.fromFile(options['elf']);
final output = options['output'] != null
? io.File(options['output']).openWrite()
: io.stdout;
final verbose = options['verbose'];
var convertedStream;
if (location != null) {
final frames = dwarf
.callInfo(location, includeInternalFrames: verbose)
?.map((CallInfo c) => c.toString());
if (frames == null) {
throw "No call information found for PC 0x${location.toRadixString(16)}";
}
convertedStream = Stream.fromIterable(frames);
} else {
final input = options['input'] != null
? io.File(options['input']).openRead()
: io.stdin;
convertedStream = input
.transform(utf8.decoder)
.transform(const LineSplitter())
.transform(
DwarfStackTraceDecoder(dwarf, includeInternalFrames: verbose));
}
await convertedStream.forEach(output.writeln);
await output.flush();
await output.close();
}

View file

@ -1,179 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:async";
import "dart:math";
import "dwarf.dart";
String _stackTracePiece(CallInfo call, int depth) => "#${depth}\t${call}";
final _traceStart = 'Warning: This VM has been configured to produce '
'stack traces that violate the Dart standard.';
final _traceInstructionsStartRE = RegExp(r'isolate_instructions: ([0-9a-f]+) '
r'vm_instructions: ([0-9a-f]+)$');
final _traceLineRE =
RegExp(r' #(\d{2}) abs ([0-9a-f]+)(?: virt [0-9a-f]+)? (.*)$');
enum InstructionSection { vm, isolate }
class PCOffset {
final int offset;
final InstructionSection section;
PCOffset(this.offset, this.section);
int virtualAddress(Dwarf dwarf) {
switch (section) {
case InstructionSection.vm:
return dwarf.convertToVMVirtualAddress(offset);
case InstructionSection.isolate:
return dwarf.convertToIsolateVirtualAddress(offset);
}
}
int get hashCode => offset.hashCode;
bool operator ==(Object other) {
return other is PCOffset &&
offset == other.offset &&
section == other.section;
}
}
class StackTraceHeader {
final int _isolateStart;
final int _vmStart;
StackTraceHeader(this._isolateStart, this._vmStart);
factory StackTraceHeader.fromMatch(Match match) {
if (match == null) {
return null;
}
final isolateAddr = int.parse("0x" + match[1]);
final vmAddr = int.parse("0x" + match[2]);
return StackTraceHeader(isolateAddr, vmAddr);
}
PCOffset convertAbsoluteAddress(int address) {
int isolateOffset = address - _isolateStart;
int vmOffset = address - _vmStart;
if (vmOffset > 0 && vmOffset == min(vmOffset, isolateOffset)) {
return PCOffset(vmOffset, InstructionSection.vm);
} else {
return PCOffset(isolateOffset, InstructionSection.isolate);
}
}
}
PCOffset retrievePCOffset(StackTraceHeader header, Match match) {
assert(header != null && match != null);
final address = int.parse("0x" + match[2]);
return header.convertAbsoluteAddress(address);
}
// Returns the [PCOffset] for each frame's absolute PC address if [lines]
// contains one or more DWARF stack traces.
Iterable<PCOffset> collectPCOffsets(Iterable<String> lines) {
final ret = <PCOffset>[];
StackTraceHeader header = null;
for (var line in lines) {
if (line.endsWith(_traceStart)) {
header = null;
}
final startMatch = _traceInstructionsStartRE.firstMatch(line);
if (startMatch != null) {
header = StackTraceHeader.fromMatch(startMatch);
continue;
}
final lineMatch = _traceLineRE.firstMatch(line);
if (lineMatch != null) {
ret.add(retrievePCOffset(header, lineMatch));
}
}
return ret;
}
// Scans a stream of lines for Dart DWARF-based stack traces (i.e., Dart stack
// traces where the frame entries include PC addresses). For each stack frame
// found, the transformer attempts to locate a function name, file name and line
// number using the provided DWARF information.
//
// If no information is found, or the line is not a stack frame, the line is
// output to the sink unchanged.
//
// If the located information corresponds to Dart internals, the frame will be
// dropped.
//
// Otherwise, at least one altered stack frame is generated and replaces the
// stack frame portion of the original line. If the PC address corresponds to
// inlined code, then multiple stack frames may be generated. When multiple
// stack frames are generated, only the first replaces the stack frame portion
// of the original line, and the remaining frames are separately output.
class DwarfStackTraceDecoder extends StreamTransformerBase<String, String> {
final Dwarf _dwarf;
final bool includeInternalFrames;
DwarfStackTraceDecoder(this._dwarf, {this.includeInternalFrames = false});
Stream<String> bind(Stream<String> stream) => Stream<String>.eventTransformed(
stream,
(sink) => _DwarfStackTraceEventSink(sink, _dwarf,
includeInternalFrames: includeInternalFrames));
}
class _DwarfStackTraceEventSink implements EventSink<String> {
final EventSink<String> _sink;
final Dwarf _dwarf;
final bool includeInternalFrames;
int _cachedDepth = 0;
StackTraceHeader _cachedHeader = null;
_DwarfStackTraceEventSink(this._sink, this._dwarf,
{this.includeInternalFrames = false});
void close() => _sink.close();
void addError(Object e, [StackTrace st]) => _sink.addError(e, st);
Future addStream(Stream<String> stream) => stream.forEach(add);
void add(String line) {
// Reset any stack-related state when we see the start of a new
// stacktrace.
if (line.endsWith(_traceStart)) {
_cachedDepth = 0;
_cachedHeader = null;
}
final startMatch = _traceInstructionsStartRE.firstMatch(line);
if (startMatch != null) {
_cachedHeader = StackTraceHeader.fromMatch(startMatch);
_sink.add(line);
return;
}
final lineMatch = _traceLineRE.firstMatch(line);
if (lineMatch == null) {
_sink.add(line);
return;
}
final location =
retrievePCOffset(_cachedHeader, lineMatch).virtualAddress(_dwarf);
final callInfo = _dwarf
.callInfo(location, includeInternalFrames: includeInternalFrames)
?.toList();
if (callInfo == null) {
// If we can't get appropriate information for the stack trace line,
// then just return the line unchanged.
_sink.add(line);
return;
} else if (callInfo.isEmpty) {
// No lines to output (as this corresponds to Dart internals).
return;
}
_sink.add(line.substring(0, lineMatch.start) +
_stackTracePiece(callInfo.first, _cachedDepth++));
for (int i = 1; i < callInfo.length; i++) {
_sink.add(_stackTracePiece(callInfo[i], _cachedDepth++));
}
}
}

View file

@ -36,8 +36,8 @@ Future<void> main(List<String> args) async {
}
{
final result =
await generateAotSnapshot(genSnapshot, dillPath, aotPath, false);
final result = await generateAotSnapshot(
genSnapshot, dillPath, aotPath, null, false);
Expect.equals(result.stderr, '');
Expect.equals(result.exitCode, 0);
Expect.equals(result.stdout, '');

View file

@ -12,8 +12,8 @@ import "dart:async";
import "dart:io";
import 'package:expect/expect.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
import 'package:vm/dwarf/convert.dart';
import 'use_flag_test_helper.dart';

View file

@ -13,9 +13,8 @@ import "dart:math";
import "dart:typed_data";
import 'package:expect/expect.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
import 'package:vm/dwarf/convert.dart';
import 'package:vm/dwarf/dwarf.dart';
import 'use_flag_test_helper.dart';

View file

@ -6,8 +6,8 @@
import 'dart:io';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
import 'package:vm/dwarf/dwarf.dart';
import 'dwarf_stack_trace_test.dart' as base;

View file

@ -7,10 +7,9 @@
import 'dart:convert';
import 'dart:io';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
import 'package:unittest/unittest.dart';
import 'package:vm/dwarf/convert.dart';
import 'package:vm/dwarf/dwarf.dart';
@pragma("vm:prefer-inline")
bar() {
@ -144,12 +143,12 @@ final expectedCallsInfo = <List<CallInfo>>[
CallInfo(
function: "bar",
filename: "dwarf_stack_trace_test.dart",
line: 18,
line: 17,
inlined: true),
CallInfo(
function: "foo",
filename: "dwarf_stack_trace_test.dart",
line: 24,
line: 23,
inlined: false)
],
// The second frame corresponds to call to foo in main.
@ -157,7 +156,7 @@ final expectedCallsInfo = <List<CallInfo>>[
CallInfo(
function: "main",
filename: "dwarf_stack_trace_test.dart",
line: 30,
line: 29,
inlined: false)
],
// Don't assume anything about any of the frames below the call to foo

View file

@ -319,6 +319,7 @@
"pkg/js/",
"pkg/kernel/",
"pkg/meta/",
"pkg/native_stack_traces/",
"pkg/pkg.status",
"pkg/smith/",
"pkg/status_file/",
@ -2457,6 +2458,11 @@
"script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
"arguments": ["--fatal-warnings", "pkg/meta"]
},
{
"name": "analyze pkg/native_stack_traces",
"script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
"arguments": ["--fatal-warnings", "pkg/native_stack_traces"]
},
{
"name": "analyze pkg/smith",
"script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",