Files
sdk/pkg/native_stack_traces/bin/decode.dart
T
Teagan Strickland ba8baa46b4 [pkg/native_stack_traces] Return stub call information when appropriate.
Now when -v/--verbose is used, we will also print call information
when those calls appear in stubs that do not correspond to Dart code.
The returned information contains the name of the stub (the name of
the static symbol in the ELF file generated for the stub) as well as
the offset of the frame PC within the stub payload.

This change also adds a flag --dump-debug-file-contents that
outputs the parsed information from the file passed via -d/--debug.

Change-Id: Ic52dd6825f2f3564efa3c2c12b46a8bef23d56bc
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-release-simarm_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/+/139280
Commit-Queue: Teagan Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2020-03-20 11:26:02 +00:00

288 lines
8.9 KiB
Dart

// 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';
ArgParser _createBaseDebugParser(ArgParser parser) => parser
..addOption('debug',
abbr: 'd',
help: 'Filename containing debugging information (REQUIRED)',
valueHelp: 'FILE')
..addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Translate all frames, not just user or library code frames')
..addFlag('dump_debug_file_contents',
negatable: false,
help: 'Dump all the parsed information from the debugging file');
final ArgParser _translateParser =
_createBaseDebugParser(ArgParser(allowTrailingOptions: true))
..addOption('input',
abbr: 'i', help: 'Filename for processed input', valueHelp: 'FILE')
..addOption('output',
abbr: 'o', help: 'Filename for generated output', valueHelp: 'FILE');
final ArgParser _findParser =
_createBaseDebugParser(ArgParser(allowTrailingOptions: true))
..addMultiOption('location',
abbr: 'l', help: 'PC address to find', valueHelp: 'PC')
..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 non-symbolic stack traces
generated by the VM when executing a snapshot compiled with the
--dwarf-stack-traces flag. It outputs almost the same text, but with any
non-symbolic stack traces converted to symbolic 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 program counter (PC) addresses, either given as
arguments on the command line or via the -l option. For each PC address,
it outputs the file, function, and line number information if found.
PC addresses are expected to be hexadecimal numbers with or without an initial
"0x" marker.
The -l option may be provided multiple times, or a single use of the -l option
may be given multiple arguments separated by commas.
By default, PC addresses are assumed to be virtual addresses valid for the
given debugging information. To find absolute PC addresses, use both the
--vm_start and --isolate_start arguments tp provide the absolute addresses of
the VM and isolate instructions sections.
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');
}
}
Dwarf _loadFromFile(String original, Function(String) usageError) {
if (original == null) {
usageError('must provide -d/--debug');
return null;
}
final filename = path.canonicalize(path.normalize(original));
if (!io.File(filename).existsSync()) {
usageError('debug file "$original" does not exist');
return null;
}
final dwarf = Dwarf.fromFile(filename);
if (dwarf == null) {
usageError('file "$original" does not contain debugging information');
}
return dwarf;
}
void find(ArgResults options) {
void usageError(String message) => errorWithUsage(message, command: 'find');
int convertAddress(String s) => int.tryParse(s, radix: 16);
final dwarf = _loadFromFile(options['debug'], usageError);
if (dwarf == null) {
return;
}
if (options['dump_debug_file_contents']) {
print(dwarf.dumpFileInfo());
}
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.offsetOf(l).virtualAddressIn(dwarf));
}
for (final addr in addresses) {
final frames = dwarf
.callInfoFor(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');
final dwarf = _loadFromFile(options['debug'], usageError);
if (dwarf == null) {
return;
}
if (options['dump_debug_file_contents']) {
print(dwarf.dumpFileInfo());
}
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 convertedStream = input
.transform(utf8.decoder)
.transform(const LineSplitter())
.transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: verbose))
.map((s) => s + "\n")
.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);
}
}