blob: 12c61ef7211d167878e8f18633657adf1e6cf6b8 [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:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
ArgParser _createBaseDebugParser(ArgParser parser) => parser
abbr: 'd',
help: 'Filename containing debugging information (REQUIRED)',
valueHelp: 'FILE')
abbr: 'v',
negatable: false,
help: 'Translate all frames, not just user or library code frames')
negatable: false,
help: 'Dump all the parsed information from the debugging file');
final ArgParser _translateParser =
_createBaseDebugParser(ArgParser(allowTrailingOptions: true))
abbr: 'i', help: 'Filename for processed input', valueHelp: 'FILE')
abbr: 'o', help: 'Filename for generated output', valueHelp: 'FILE');
final ArgParser _findParser =
_createBaseDebugParser(ArgParser(allowTrailingOptions: true))
abbr: 'l', help: 'PC address to find', valueHelp: 'PC')
abbr: 'x',
negatable: false,
help: 'Always parse integers as hexadecimal')
help: 'Absolute address for start of VM instructions',
valueHelp: 'PC')
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)
abbr: 'h',
negatable: false,
help: 'Print usage information for this or a particular subcommand');
final String _mainUsage = '''
Usage: decode <command> [options] ...
Options shared by all commands:
final String _helpUsage = '''
Usage: decode help [<command>]
Returns usage for the decode utility or a particular command.
final String _translateUsage = '''
Usage: decode 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:
Options specific to the translate command:
final String _findUsage = '''
Usage: decode find [options] <PC> ...
The find command looks up program counter (PC) addresses, either given as
arguments on the command line or via the -l/--location option. For each
successful PC lookup, it outputs the call information in one of two formats:
- If the location corresponds to a call site in Dart source code, the call
information includes the file, function, and line number information.
- If it corresponds to a Dart VM stub, the call information includes the dynamic
symbol name for the instructions payload and an offset into that payload.
The -l option may be provided multiple times, or a single use of the -l option
may be given multiple arguments separated by commas.
PC addresses can be provided in one of two formats:
- An integer, e.g. 0x2a3f or 15049
- A static symbol in the VM snapshot plus an integer offset, e.g.,
_kDartIsolateSnapshotInstructions+1523 or _kDartVMSnapshotInstructions+0x403f
Integers without an "0x" prefix that do not includes hexadecimal digits are
assumed to be decimal unless the -x/--force_hexadecimal flag is used.
By default, integer PC addresses are assumed to be virtual addresses valid for
the given debugging information. Otherwise, use both the --vm_start and
--isolate_start arguments to provide the appropriate starting addresses of the
VM and isolate instructions sections.
Options shared by all commands:
Options specific to the find command:
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');
io.exitCode = _badUsageExitCode;
void help(ArgResults options) {
void usageError(String message) => errorWithUsage(message, command: 'help');
switch ( {
case 0:
return print(_usages['help']);
case 1:
final usage = _usages[];
if (usage != null) return print(usage);
return usageError('invalid command ${}');
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) {
final bool verbose = options['verbose'];
final bool forceHexadecimal = options['force_hexadecimal'];
void usageError(String message) => errorWithUsage(message, command: 'find');
int? tryParseIntAddress(String s) {
if (!forceHexadecimal && !s.startsWith('0x')) {
final decimal = int.tryParse(s);
if (decimal != null) return decimal;
return int.tryParse(s.startsWith('0x') ? s.substring(2) : s, radix: 16);
PCOffset? convertAddress(StackTraceHeader header, String s) {
final parsedOffset = tryParseSymbolOffset(s, forceHexadecimal);
if (parsedOffset != null) return parsedOffset;
final address = tryParseIntAddress(s);
if (address != null) return header.offsetOf(address);
return null;
final dwarf = _loadFromFile(options['debug'], usageError);
if (dwarf == null) return;
if (options['dump_debug_file_contents']) {
if ((options['vm_start'] == null) != (options['isolate_start'] == null)) {
return usageError('need both VM start and isolate start');
var vmStart = dwarf.vmStartAddress;
if (options['vm_start'] != null) {
final address = tryParseIntAddress(options['vm_start']);
if (address == null) {
return usageError('could not parse VM start address '
vmStart = address;
var isolateStart = dwarf.isolateStartAddress;
if (options['isolate_start'] != null) {
final address = tryParseIntAddress(options['isolate_start']);
if (address == null) {
return usageError('could not parse isolate start address '
isolateStart = address;
final header = StackTraceHeader(isolateStart, vmStart);
final locations = <PCOffset>[];
for (final String s in [
...(options['location'] as List<String>),,
]) {
final location = convertAddress(header, s);
if (location == null) return usageError('could not parse PC address $s');
if (locations.isEmpty) return usageError('no PC addresses to find');
for (final offset in locations) {
final addr = dwarf.virtualAddressOf(offset);
final frames = dwarf
.callInfoFor(addr, includeInternalFrames: verbose)
?.map((CallInfo c) => ' $c');
final addrString =
addr > 0 ? '0x${addr.toRadixString(16)}' : 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 {
Future<void> translate(ArgResults options) async {
void usageError(String message) =>
errorWithUsage(message, command: 'translate');
final dwarf = _loadFromFile(options['debug'], usageError);
if (dwarf == null) {
if (options['dump_debug_file_contents']) {
final verbose = options['verbose'];
final output = options['output'] != null
? io.File(path.canonicalize(path.normalize(options['output'])))
: io.stdout;
final input = options['input'] != null
? io.File(path.canonicalize(path.normalize(options['input']))).openRead()
: io.stdin;
final convertedStream = input
.transform(const LineSplitter())
.transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: verbose))
.map((s) => '$s\n')
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!);