mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 22:41:41 +00:00
[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:
parent
241d2c6cf5
commit
a003d5e69a
|
@ -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
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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
|
||||
]);
|
||||
|
|
|
@ -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
3
pkg/native_stack_traces/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.dart_tool/
|
||||
.packages
|
||||
pubspec.lock
|
6
pkg/native_stack_traces/AUTHORS
Normal file
6
pkg/native_stack_traces/AUTHORS
Normal 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
|
5
pkg/native_stack_traces/CHANGELOG.md
Normal file
5
pkg/native_stack_traces/CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Initial release
|
26
pkg/native_stack_traces/LICENSE
Normal file
26
pkg/native_stack_traces/LICENSE
Normal 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.
|
71
pkg/native_stack_traces/README.md
Normal file
71
pkg/native_stack_traces/README.md
Normal 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
|
1
pkg/native_stack_traces/analysis_options.yaml
Normal file
1
pkg/native_stack_traces/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
|||
include: package:pedantic/analysis_options.1.8.0.yaml
|
275
pkg/native_stack_traces/bin/decode.dart
Normal file
275
pkg/native_stack_traces/bin/decode.dart
Normal 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);
|
||||
}
|
||||
}
|
7
pkg/native_stack_traces/lib/native_stack_traces.dart
Normal file
7
pkg/native_stack_traces/lib/native_stack_traces.dart
Normal 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;
|
223
pkg/native_stack_traces/lib/src/convert.dart
Normal file
223
pkg/native_stack_traces/lib/src/convert.dart
Normal 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++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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]}";
|
|
@ -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);
|
||||
|
18
pkg/native_stack_traces/pubspec.yaml
Normal file
18
pkg/native_stack_traces/pubspec.yaml
Normal 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
|
|
@ -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();
|
||||
}
|
|
@ -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++));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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, '');
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue