a003d5e69a
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>
276 lines
8.8 KiB
Dart
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);
|
|
}
|
|
}
|