Files
sdk/pkg/native_stack_traces/bin/decode.dart
T
Teagan Strickland a003d5e69a [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>
2020-01-23 10:14:17 +00:00

276 lines
8.8 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';
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);
}
}