blob: 75353eedba471c7f0e0986d076c3f487ff674fc2 [file] [log] [blame]
// Copyright (c) 2017, 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.
// @dart = 2.9
/// VMOptions=--dwarf-stack-traces
import 'dart:convert';
import 'dart:io';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
import 'package:expect/expect.dart';
bar() {
// Keep the 'throw' and its argument on separate lines.
throw // force linebreak with dartfmt
"Hello, Dwarf!";
foo() {
Future<void> main() async {
String rawStack = "";
try {
} catch (e, st) {
rawStack = st.toString();
if (path.basenameWithoutExtension(Platform.executable) !=
"dart_precompiled_runtime") {
return; // Not running from an AOT compiled snapshot.
if (Platform.isAndroid) {
return; // Generated not available on the test device.
final dwarf = Dwarf.fromFile("");
await checkStackTrace(rawStack, dwarf, expectedCallsInfo);
Future<void> checkStackTrace(String rawStack, Dwarf dwarf,
List<List<DartCallInfo>> expectedCallsInfo) async {
print("Raw stack trace:");
final rawLines =
await Stream.value(rawStack).transform(const LineSplitter()).toList();
final pcOffsets = collectPCOffsets(rawLines).toList();
// We should have at least enough PC addresses to cover the frames we'll be
// checking.
Expect.isTrue(pcOffsets.length >= expectedCallsInfo.length);
final virtualAddresses = => dwarf.virtualAddressOf(o)).toList();
// Some double-checks using other information in the non-symbolic stack trace.
final dsoBase = dsoBaseAddresses(rawLines).single;
final absolutes = absoluteAddresses(rawLines);
final relocatedAddresses = => a - dsoBase);
final explicits = explicitVirtualAddresses(rawLines);
// Explicits will be empty if not generating ELF snapshots directly, which
// means we can't depend on virtual addresses in the snapshot lining up with
// those in the separate debugging information.
if (explicits.isNotEmpty) {
// Direct-to-ELF snapshots should have a build ID.
Expect.deepEquals(relocatedAddresses, virtualAddresses);
Expect.deepEquals(explicits, virtualAddresses);
final gotCallsInfo = <List<DartCallInfo>>[];
for (final addr in virtualAddresses) {
final externalCallInfo = dwarf.callInfoFor(addr);
final allCallInfo = dwarf.callInfoFor(addr, includeInternalFrames: true);
for (final call in externalCallInfo) {
Expect.isTrue(call is DartCallInfo, "got non-Dart call info ${call}");
"External call info ${call} is not among all calls");
for (final call in allCallInfo) {
if (!call.isInternal) {
"External call info ${call} is not among external calls");
print("Call information for PC addresses:");
for (var i = 0; i < virtualAddresses.length; i++) {
print("For PC 0x${virtualAddresses[i].toRadixString(16)}:");
print(" Calls corresponding to user or library code:");
gotCallsInfo[i].forEach((frame) => print(" ${frame}"));
// Remove empty entries which correspond to skipped internal frames.
gotCallsInfo.removeWhere((calls) => calls.isEmpty);
checkFrames(gotCallsInfo, expectedCallsInfo);
final gotSymbolizedLines = await Stream.fromIterable(rawLines)
.transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: false))
final gotSymbolizedCalls =
gotSymbolizedLines.where((s) => s.startsWith('#')).toList();
print("Symbolized stack trace:");
print("Extracted calls:");
final expectedStrings = extractCallStrings(expectedCallsInfo);
// There are two strings in the list for each line in the output.
final expectedCallCount = expectedStrings.length ~/ 2;
Expect.isTrue(gotSymbolizedCalls.length >= expectedCallCount);
// Strip off any unexpected lines, so we can also make sure we didn't get
// unexpected calls prior to those calls we expect.
final gotCallsTrace =
gotSymbolizedCalls.sublist(0, expectedCallCount).join('\n');
Expect.stringContainsInOrder(gotCallsTrace, expectedStrings);
final expectedCallsInfo = <List<DartCallInfo>>[
// The first frame should correspond to the throw in bar, which was inlined
// into foo (so we'll get information for two calls for that PC address).
function: "bar",
filename: "dwarf_stack_trace_test.dart",
line: 19,
column: 3,
inlined: true),
function: "foo",
filename: "dwarf_stack_trace_test.dart",
line: 25,
column: 3,
inlined: false)
// The second frame corresponds to call to foo in main.
function: "main",
filename: "dwarf_stack_trace_test.dart",
line: 31,
column: 5,
inlined: false)
// Don't assume anything about any of the frames below the call to foo
// in main, as this makes the test too brittle.
void checkFrames(
List<List<DartCallInfo>> gotInfo, List<List<DartCallInfo>> expectedInfo) {
// There may be frames below those we check.
Expect.isTrue(gotInfo.length >= expectedInfo.length);
// We can't just use deep equality, since we only have the filenames in the
// expected version, not the whole path, and we don't really care if
// non-positive line numbers match, as long as they're both non-positive.
for (var i = 0; i < expectedInfo.length; i++) {
for (var j = 0; j < expectedInfo[i].length; j++) {
final got = gotInfo[i][j];
final expected = expectedInfo[i][j];
Expect.equals(expected.function, got.function);
Expect.equals(expected.inlined, got.inlined);
Expect.equals(expected.filename, path.basename(got.filename));
if (expected.isInternal) {
} else {
Expect.equals(expected.line, got.line);
List<String> extractCallStrings(List<List<CallInfo>> expectedCalls) {
var ret = <String>[];
for (final frame in expectedCalls) {
for (final call in frame) {
if (call is DartCallInfo) {
if (call.isInternal) {
} else {
return ret;
Iterable<int> parseUsingAddressRegExp(RegExp re, Iterable<String> lines) sync* {
for (final line in lines) {
final match = re.firstMatch(line);
if (match == null) continue;
final s =;
if (s == null) continue;
yield int.parse(s, radix: 16);
final _absRE = RegExp(r'abs ([a-f\d]+)');
Iterable<int> absoluteAddresses(Iterable<String> lines) =>
parseUsingAddressRegExp(_absRE, lines);
final _virtRE = RegExp(r'virt ([a-f\d]+)');
Iterable<int> explicitVirtualAddresses(Iterable<String> lines) =>
parseUsingAddressRegExp(_virtRE, lines);
final _dsoBaseRE = RegExp(r'isolate_dso_base: ([a-f\d]+)');
Iterable<int> dsoBaseAddresses(Iterable<String> lines) =>
parseUsingAddressRegExp(_dsoBaseRE, lines);