blob: fe59643ba883595f9102e96bee1ce43381e40ef8 [file] [log] [blame]
// 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 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');
}
}
void find(ArgResults options) {
void usageError(String message) => errorWithUsage(message, command: 'find');
int convertAddress(String s) => int.tryParse(s, radix: 16);
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.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');
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 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);
}
}