blob: d8cb33f0cd8a2228c0730408d8d76017f9bdfd52 [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.
// This test ensures that the flag for --dwarf-stack-traces given at AOT
// compile-time will be used at runtime (irrespective if other values were
// passed to the runtime).
// OtherResources=use_dwarf_stack_traces_flag_program.dart
import "dart:async";
import "dart:io";
import 'package:expect/expect.dart';
import 'package:native_stack_traces/native_stack_traces.dart';
import 'package:path/path.dart' as path;
import 'use_flag_test_helper.dart';
main(List<String> args) async {
if (!isAOTRuntime) {
return; // Running in JIT: AOT binaries not available.
}
if (Platform.isAndroid) {
return; // SDK tree and dart_bootstrap not available on the test device.
}
// These are the tools we need to be available to run on a given platform:
if (!await testExecutable(genSnapshot)) {
throw "Cannot run test as $genSnapshot not available";
}
if (!await testExecutable(aotRuntime)) {
throw "Cannot run test as $aotRuntime not available";
}
if (!File(platformDill).existsSync()) {
throw "Cannot run test as $platformDill does not exist";
}
await withTempDir('dwarf-flag-test', (String tempDir) async {
final cwDir = path.dirname(Platform.script.toFilePath());
final script = path.join(cwDir, 'use_dwarf_stack_traces_flag_program.dart');
final scriptDill = path.join(tempDir, 'flag_program.dill');
// Compile script to Kernel IR.
await run(genKernel, <String>[
'--aot',
'--platform=$platformDill',
'-o',
scriptDill,
script,
]);
// Run the AOT compiler with/without Dwarf stack traces.
final scriptDwarfSnapshot = path.join(tempDir, 'dwarf.so');
final scriptNonDwarfSnapshot = path.join(tempDir, 'non_dwarf.so');
final scriptDwarfDebugInfo = path.join(tempDir, 'debug_info.so');
await Future.wait(<Future>[
run(genSnapshot, <String>[
// We test --dwarf-stack-traces-mode, not --dwarf-stack-traces, because
// the latter is a handler that sets the former and also may change
// other flags. This way, we limit the difference between the two
// snapshots and also directly test the flag saved as a VM global flag.
'--dwarf-stack-traces-mode',
'--save-debugging-info=$scriptDwarfDebugInfo',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptDwarfSnapshot',
scriptDill,
]),
run(genSnapshot, <String>[
'--no-dwarf-stack-traces-mode',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptNonDwarfSnapshot',
scriptDill,
]),
]);
// Run the resulting Dwarf-AOT compiled script.
final dwarfTrace1 = await runError(aotRuntime, <String>[
'--dwarf-stack-traces-mode',
scriptDwarfSnapshot,
scriptDill,
]);
final dwarfTrace2 = await runError(aotRuntime, <String>[
'--no-dwarf-stack-traces-mode',
scriptDwarfSnapshot,
scriptDill,
]);
// Run the resulting non-Dwarf-AOT compiled script.
final nonDwarfTrace1 = await runError(aotRuntime, <String>[
'--dwarf-stack-traces-mode',
scriptNonDwarfSnapshot,
scriptDill,
]);
final nonDwarfTrace2 = await runError(aotRuntime, <String>[
'--no-dwarf-stack-traces-mode',
scriptNonDwarfSnapshot,
scriptDill,
]);
// Ensure the result is based off the flag passed to gen_snapshot, not
// the one passed to the runtime.
Expect.deepEquals(nonDwarfTrace1, nonDwarfTrace2);
// For DWARF stack traces, we can't guarantee that the stack traces are
// textually equal on all platforms, but if we retrieve the PC offsets
// out of the stack trace, those should be equal.
final tracePCOffsets1 = collectPCOffsets(dwarfTrace1);
final tracePCOffsets2 = collectPCOffsets(dwarfTrace2);
Expect.deepEquals(tracePCOffsets1, tracePCOffsets2);
// Check that translating the DWARF stack trace (without internal frames)
// matches the symbolic stack trace.
final dwarf = Dwarf.fromFile(scriptDwarfDebugInfo)!;
// Check that build IDs match for traces.
Expect.isNotNull(dwarf.buildId);
print('Dwarf build ID: "${dwarf.buildId!}"');
// We should never generate an all-zero build ID.
Expect.notEquals(dwarf.buildId, "00000000000000000000000000000000");
// This is a common failure case as well, when HashBitsContainer ends up
// hashing over seemingly empty sections.
Expect.notEquals(dwarf.buildId, "01000000010000000100000001000000");
final buildId1 = buildId(dwarfTrace1);
Expect.isFalse(buildId1.isEmpty);
print('Trace 1 build ID: "${buildId1}"');
Expect.equals(dwarf.buildId, buildId1);
final buildId2 = buildId(dwarfTrace2);
Expect.isFalse(buildId2.isEmpty);
print('Trace 2 build ID: "${buildId2}"');
Expect.equals(dwarf.buildId, buildId2);
final translatedDwarfTrace1 = await Stream.fromIterable(dwarfTrace1)
.transform(DwarfStackTraceDecoder(dwarf))
.toList();
final translatedStackFrames = onlySymbolicFrameLines(translatedDwarfTrace1);
final originalStackFrames = onlySymbolicFrameLines(nonDwarfTrace1);
print('Stack frames from translated non-symbolic stack trace:');
translatedStackFrames.forEach(print);
print('');
print('Stack frames from original symbolic stack trace:');
originalStackFrames.forEach(print);
print('');
Expect.isTrue(translatedStackFrames.length > 0);
Expect.isTrue(originalStackFrames.length > 0);
// In symbolic mode, we don't store column information to avoid an increase
// in size of CodeStackMaps. Thus, we need to strip any columns from the
// translated non-symbolic stack to compare them via equality.
final columnStrippedTranslated = removeColumns(translatedStackFrames);
print('Stack frames from translated non-symbolic stack trace, no columns:');
columnStrippedTranslated.forEach(print);
print('');
Expect.deepEquals(columnStrippedTranslated, originalStackFrames);
// Since we compiled directly to ELF, there should be a DSO base address
// in the stack trace header and 'virt' markers in the stack frames.
// The offsets of absolute addresses from their respective DSO base
// should be the same for both traces.
final dsoBase1 = dsoBaseAddresses(dwarfTrace1).single;
final dsoBase2 = dsoBaseAddresses(dwarfTrace2).single;
final absTrace1 = absoluteAddresses(dwarfTrace1);
final absTrace2 = absoluteAddresses(dwarfTrace2);
final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1);
final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2);
Expect.deepEquals(relocatedFromDso1, relocatedFromDso2);
// The relocated addresses marked with 'virt' should match between the
// different runs, and they should also match the relocated address
// calculated from the PCOffset for each frame as well as the relocated
// address for each frame calculated using the respective DSO base.
final virtTrace1 = explicitVirtualAddresses(dwarfTrace1);
final virtTrace2 = explicitVirtualAddresses(dwarfTrace2);
Expect.deepEquals(virtTrace1, virtTrace2);
Expect.deepEquals(
virtTrace1, tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)));
Expect.deepEquals(
virtTrace2, tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)));
Expect.deepEquals(virtTrace1, relocatedFromDso1);
Expect.deepEquals(virtTrace2, relocatedFromDso2);
});
}
final _buildIdRE = RegExp(r"build_id: '([a-f\d]+)'");
String buildId(Iterable<String> lines) {
for (final line in lines) {
final match = _buildIdRE.firstMatch(line);
if (match != null) {
return match.group(1)!;
}
}
return '';
}
final _symbolicFrameRE = RegExp(r'^#\d+\s+');
Iterable<String> onlySymbolicFrameLines(Iterable<String> lines) {
return lines.where((line) => _symbolicFrameRE.hasMatch(line));
}
final _columnsRE = RegExp(r'[(](.*:\d+):\d+[)]');
Iterable<String> removeColumns(Iterable<String> lines) sync* {
for (final line in lines) {
final match = _columnsRE.firstMatch(line);
if (match != null) {
yield line.replaceRange(match.start, match.end, '(${match.group(1)!})');
} else {
yield line;
}
}
}
Iterable<int> parseUsingAddressRegExp(RegExp re, Iterable<String> lines) sync* {
for (final line in lines) {
final match = re.firstMatch(line);
if (match != null) {
yield int.parse(match.group(1)!, 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);