| // 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; |
| } |
| } |
| } |