blob: 3d86e66964dec8ff2d88287183ecbfce72705f8d [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../convert.dart';
import '../runner/flutter_command.dart';
/// Support for symbolizing a Dart stack trace.
/// This command accepts either paths to an input file containing the
/// stack trace and an output file for the symbolizing trace to be
/// written, or it accepts a stack trace over stdin and outputs it
/// over stdout.
class SymbolizeCommand extends FlutterCommand {
@required Stdio stdio,
@required FileSystem fileSystem,
DwarfSymbolizationService dwarfSymbolizationService = const DwarfSymbolizationService(),
}) : _stdio = stdio,
_fileSystem = fileSystem,
_dwarfSymbolizationService = dwarfSymbolizationService {
abbr: 'd',
valueHelp: '/out/android/app.arm64.symbols',
help: 'A path to the symbols file generated with "--split-debug-info".'
abbr: 'i',
valueHelp: '/crashes/stack_trace.err',
help: 'A file path containing a Dart stack trace.'
abbr: 'o',
help: 'A file path for a symbolized stack trace to be written to.'
final Stdio _stdio;
final FileSystem _fileSystem;
final DwarfSymbolizationService _dwarfSymbolizationService;
String get description => 'Symbolize a stack trace from an AOT-compiled Flutter app.';
String get name => 'symbolize';
bool get shouldUpdateCache => false;
Future<void> validateCommand() {
if (!argResults.wasParsed('debug-info')) {
throwToolExit('"--debug-info" is required to symbolize stack traces.');
if (!_fileSystem.isFileSync(stringArg('debug-info'))) {
throwToolExit('${stringArg('debug-info')} does not exist.');
if (argResults.wasParsed('input') && !_fileSystem.isFileSync(stringArg('input'))) {
throwToolExit('${stringArg('input')} does not exist.');
return super.validateCommand();
Future<FlutterCommandResult> runCommand() async {
Stream<List<int>> input;
IOSink output;
// Configure output to either specified file or stdout.
if (argResults.wasParsed('output')) {
final File outputFile = _fileSystem.file(stringArg('output'));
if (!outputFile.parent.existsSync()) {
outputFile.parent.createSync(recursive: true);
output = outputFile.openWrite();
} else {
final StreamController<List<int>> outputController = StreamController<List<int>>();
output = IOSink(outputController);
// Configure input from either specified file or stdin.
if (argResults.wasParsed('input')) {
input = _fileSystem.file(stringArg('input')).openRead();
} else {
input = _stdio.stdin;
final Uint8List symbols = _fileSystem.file(stringArg('debug-info')).readAsBytesSync();
await _dwarfSymbolizationService.decode(
input: input,
output: output,
symbols: symbols,
return FlutterCommandResult.success();
typedef SymbolsTransformer = StreamTransformer<String, String> Function(Uint8List);
StreamTransformer<String, String> _defaultTransformer(Uint8List symbols) {
final Dwarf dwarf = Dwarf.fromBytes(symbols);
if (dwarf == null) {
throwToolExit('Failed to decode symbols file');
return DwarfStackTraceDecoder(dwarf, includeInternalFrames: true);
// A no-op transformer for `DwarfSymbolizationService.test`
StreamTransformer<String, String> _testTransformer(Uint8List buffer) {
return StreamTransformer<String, String>.fromHandlers(
handleData: (String data, EventSink<String> sink) {
handleDone: (EventSink<String> sink) {
handleError: (dynamic error, StackTrace stackTrace, EventSink<String> sink) {
sink.addError(error, stackTrace);
/// A service which decodes stack traces from Dart applications.
class DwarfSymbolizationService {
const DwarfSymbolizationService({
SymbolsTransformer symbolsTransformer = _defaultTransformer,
}) : _transformer = symbolsTransformer;
/// Create a DwarfSymbolizationService with a no-op transformer for testing.
factory DwarfSymbolizationService.test() {
return const DwarfSymbolizationService(
symbolsTransformer: _testTransformer
final SymbolsTransformer _transformer;
/// Decode a stack trace from [input] and place the results in [output].
/// Requires [symbols] to be a buffer created from the `--split-debug-info`
/// command line flag.
/// Throws a [ToolExit] if the symbols cannot be parsed or the stack trace
/// cannot be decoded.
Future<void> decode({
@required Stream<List<int>> input,
@required IOSink output,
@required Uint8List symbols,
}) async {
final Completer<void> onDone = Completer<void>();
StreamSubscription<void> subscription;
subscription = input
.transform(const Utf8Decoder())
.transform(const LineSplitter())
.listen((String line) {
try {
} on Exception catch(e, s) {
subscription.cancel().whenComplete(() {
if (!onDone.isCompleted) {
onDone.completeError(e, s);
}, onDone: onDone.complete, onError: onDone.completeError);
try {
await onDone.future;
await output.close();
} on Exception catch (err) {
throwToolExit('Failed to symbolize stack trace:\n $err');