blob: 091087d219611fc8d6567eb9fc58cad1359d1f81 [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 AOT compiler can generate debugging information
// for stripped ELF output, and that using the debugging information to look
// up stripped stack trace information matches the non-stripped version.
// OtherResources=use_dwarf_stack_traces_flag_program.dart
import "dart:io";
import "dart:math";
import "dart:typed_data";
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('save-debug-info-flag-test', (String tempDir) async {
final cwDir = path.dirname(Platform.script.toFilePath());
// We can just reuse the program for the use_dwarf_stack_traces test.
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 Dwarf stack traces, once without stripping,
// once with stripping, and once with stripping and saving debugging
// information.
final scriptWholeSnapshot = path.join(tempDir, 'whole.so');
await run(genSnapshot, <String>[
'--dwarf-stack-traces',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptWholeSnapshot',
scriptDill,
]);
final scriptStrippedOnlySnapshot = path.join(tempDir, 'stripped_only.so');
await run(genSnapshot, <String>[
'--dwarf-stack-traces',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptStrippedOnlySnapshot',
'--strip',
scriptDill,
]);
final scriptStrippedSnapshot = path.join(tempDir, 'stripped.so');
final scriptDebuggingInfo = path.join(tempDir, 'debug.so');
await run(genSnapshot, <String>[
'--dwarf-stack-traces',
'--snapshot-kind=app-aot-elf',
'--elf=$scriptStrippedSnapshot',
'--strip',
'--save-debugging-info=$scriptDebuggingInfo',
scriptDill,
]);
// Run the resulting scripts, saving the stack traces.
final wholeTrace = await runError(aotRuntime, <String>[
scriptWholeSnapshot,
scriptDill,
]);
final wholeOffsets = collectPCOffsets(wholeTrace);
final strippedOnlyTrace = await runError(aotRuntime, <String>[
scriptStrippedOnlySnapshot,
scriptDill,
]);
final strippedOnlyOffsets = collectPCOffsets(strippedOnlyTrace);
final strippedTrace = await runError(aotRuntime, <String>[
scriptStrippedSnapshot,
scriptDill,
]);
final strippedOffsets = collectPCOffsets(strippedTrace);
// The retrieved offsets should be the same for all runs.
Expect.deepEquals(wholeOffsets, strippedOffsets);
Expect.deepEquals(strippedOnlyOffsets, strippedOffsets);
// Stripped output should not change when --save-debugging-info is used.
compareSnapshots(scriptStrippedOnlySnapshot, scriptStrippedSnapshot);
print('');
print("Original stack trace:");
strippedTrace.forEach(print);
final debugDwarf = Dwarf.fromFile(scriptDebuggingInfo)!;
final wholeDwarf = Dwarf.fromFile(scriptWholeSnapshot)!;
final fromDebug = await Stream.fromIterable(strippedTrace)
.transform(DwarfStackTraceDecoder(debugDwarf))
.toList();
print("\nStack trace converted using separate debugging info:");
print(fromDebug.join('\n'));
final fromWhole = await Stream.fromIterable(strippedTrace)
.transform(DwarfStackTraceDecoder(wholeDwarf))
.toList();
print("\nStack trace converted using unstripped ELF file:");
print(fromWhole.join('\n'));
Expect.deepEquals(fromDebug, fromWhole);
});
}
void compareSnapshots(String file1, String file2) {
final bytes1 = File(file1).readAsBytesSync();
final bytes2 = File(file2).readAsBytesSync();
final diff = diffBinary(bytes1, bytes2);
if (diff.isNotEmpty) {
print("\nFound differences between $file1 and $file2:");
printDiff(diff);
}
Expect.equals(bytes1.length, bytes2.length);
Expect.equals(0, diff.length);
}
Map<int, List<int>> diffBinary(Uint8List bytes1, Uint8List bytes2) {
final ret = Map<int, List<int>>();
final len = min(bytes1.length, bytes2.length);
for (var i = 0; i < len; i++) {
if (bytes1[i] != bytes2[i]) {
ret[i] = <int>[bytes1[i], bytes2[i]];
}
}
if (bytes1.length > len) {
for (var i = len; i < bytes1.length; i++) {
ret[i] = <int>[bytes1[i], -1];
}
} else if (bytes2.length > len) {
for (var i = len; i < bytes2.length; i++) {
ret[i] = <int>[-1, bytes2[i]];
}
}
return ret;
}
void printDiff(Map<int, List<int>> map, [int maxOutput = 100]) {
int lines = 0;
for (var index in map.keys) {
final pair = map[index]!;
if (pair[0] == -1) {
print('$index: <>, ${pair[1]}');
lines++;
} else if (pair[1] == -1) {
print('$index: ${pair[0]}, <>');
lines++;
} else {
print('$index: ${pair[0]}, ${pair[1]}');
lines++;
}
if (lines >= maxOutput) {
return;
}
}
}