| // 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). |
| |
| import "dart:async"; |
| import "dart:io"; |
| |
| import 'package:native_stack_traces/native_stack_traces.dart'; |
| import 'package:native_stack_traces/src/macho.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| |
| import 'use_flag_test_helper.dart'; |
| import 'use_dwarf_stack_traces_flag_helper.dart'; |
| |
| Future<void> main() async { |
| await runTests( |
| 'dwarf-flag-test', |
| path.join(sdkDir, 'runtime', 'tests', 'vm', 'dart', |
| 'use_dwarf_stack_traces_flag_program.dart'), |
| runNonDwarf, |
| runElf, |
| runAssembly); |
| } |
| |
| Future<NonDwarfState> runNonDwarf(String tempDir, String scriptDill) async { |
| final scriptNonDwarfSnapshot = path.join(tempDir, 'non_dwarf.so'); |
| |
| await 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. |
| '--no-dwarf-stack-traces-mode', |
| '--snapshot-kind=app-aot-elf', |
| '--elf=$scriptNonDwarfSnapshot', |
| scriptDill, |
| ]); |
| |
| // Run the resulting non-Dwarf-AOT compiled script. |
| final outputWithOppositeFlag = |
| (await runTestProgram(dartPrecompiledRuntime, <String>[ |
| '--dwarf-stack-traces-mode', |
| scriptNonDwarfSnapshot, |
| scriptDill, |
| ])); |
| final output = (await runTestProgram(dartPrecompiledRuntime, <String>[ |
| '--no-dwarf-stack-traces-mode', |
| scriptNonDwarfSnapshot, |
| scriptDill, |
| ])); |
| |
| return NonDwarfState(output, outputWithOppositeFlag); |
| } |
| |
| class DwarfElfState extends ElfState<Dwarf> { |
| DwarfElfState(super.snapshot, super.debugInfo, super.output, |
| super.outputWithOppositeFlag); |
| |
| @override |
| Future<void> check(Trace trace, Dwarf dwarf) => |
| compareTraces(trace, output, outputWithOppositeFlag, dwarf); |
| } |
| |
| Future<DwarfElfState> runElf(String tempDir, String scriptDill) async { |
| final snapshotPath = path.join(tempDir, 'dwarf.so'); |
| final debugInfoPath = path.join(tempDir, 'debug_info.so'); |
| await run(genSnapshot, <String>[ |
| '--dwarf-stack-traces-mode', |
| '--save-debugging-info=$debugInfoPath', |
| '--snapshot-kind=app-aot-elf', |
| '--elf=$snapshotPath', |
| scriptDill, |
| ]); |
| |
| final snapshot = Dwarf.fromFile(snapshotPath)!; |
| final debugInfo = Dwarf.fromFile(debugInfoPath)!; |
| |
| // Run the resulting Dwarf-AOT compiled script. |
| |
| final output = await runTestProgram(dartPrecompiledRuntime, |
| <String>['--dwarf-stack-traces-mode', snapshotPath, scriptDill]); |
| final outputWithOppositeFlag = await runTestProgram(dartPrecompiledRuntime, |
| <String>['--no-dwarf-stack-traces-mode', snapshotPath, scriptDill]); |
| |
| return DwarfElfState(snapshot, debugInfo, output, outputWithOppositeFlag); |
| } |
| |
| class DwarfAssemblyState extends AssemblyState<Dwarf> { |
| DwarfAssemblyState(super.snapshot, super.debugInfo, super.output, |
| super.outputWithOppositeFlag, |
| [super.singleArch, super.multiArch]); |
| |
| @override |
| Future<void> check(Trace trace, Dwarf dwarf) => |
| compareTraces(trace, output, outputWithOppositeFlag, dwarf, |
| fromAssembly: true); |
| } |
| |
| Future<DwarfAssemblyState?> runAssembly( |
| String tempDir, String scriptDill) async { |
| if (skipAssembly != false) return null; |
| |
| final asmPath = path.join(tempDir, 'dwarf_assembly.S'); |
| final debugInfoPath = path.join(tempDir, 'dwarf_assembly_info.so'); |
| final snapshotPath = path.join(tempDir, 'dwarf_assembly.so'); |
| // We get a separate .dSYM bundle on MacOS. |
| var debugSnapshotPath = snapshotPath + (Platform.isMacOS ? '.dSYM' : ''); |
| |
| await 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=$debugInfoPath', |
| '--snapshot-kind=app-aot-assembly', |
| '--assembly=$asmPath', |
| scriptDill, |
| ]); |
| |
| final debugInfo = Dwarf.fromFile(debugInfoPath)!; |
| |
| await assembleSnapshot(asmPath, snapshotPath, debug: true); |
| |
| // Run the resulting Dwarf-AOT compiled script. |
| final output = await runTestProgram(dartPrecompiledRuntime, <String>[ |
| '--dwarf-stack-traces-mode', |
| snapshotPath, |
| scriptDill, |
| ]); |
| final outputWithOppositeFlag = |
| await runTestProgram(dartPrecompiledRuntime, <String>[ |
| '--no-dwarf-stack-traces-mode', |
| snapshotPath, |
| scriptDill, |
| ]); |
| |
| // Get the shared object path inside the .dSYM after compilation on MacOS. |
| debugSnapshotPath = MachO.handleDSYM(debugSnapshotPath); |
| final snapshot = Dwarf.fromFile(debugSnapshotPath)!; |
| |
| Dwarf? singleArchSnapshot; |
| Dwarf? multiArchSnapshot; |
| if (skipUniversalBinary == false) { |
| // Create empty MachO files (just a header) for each of the possible |
| // architectures. |
| final emptyFiles = <String, String>{}; |
| for (final arch in machOArchNames.values) { |
| // Don't create an empty file for the current architecture. |
| if (arch == dartNameForCurrentArchitecture) continue; |
| final contents = emptyMachOForArchitecture(arch)!; |
| final emptyPath = path.join(tempDir, "empty_$arch.so"); |
| await File(emptyPath).writeAsBytes(contents, flush: true); |
| emptyFiles[arch] = emptyPath; |
| } |
| |
| final singleArchSnapshotPath = path.join(tempDir, "ub-single"); |
| await run(lipo, <String>[ |
| debugSnapshotPath, |
| '-create', |
| '-output', |
| singleArchSnapshotPath, |
| ]); |
| singleArchSnapshot = Dwarf.fromFile(singleArchSnapshotPath)!; |
| |
| final multiArchSnapshotPath = path.join(tempDir, "ub-multiple"); |
| await run(lipo, <String>[ |
| ...emptyFiles.values, |
| debugSnapshotPath, |
| '-create', |
| '-output', |
| multiArchSnapshotPath, |
| ]); |
| multiArchSnapshot = Dwarf.fromFile(multiArchSnapshotPath)!; |
| } |
| |
| return DwarfAssemblyState(snapshot, debugInfo, output, outputWithOppositeFlag, |
| singleArchSnapshot, multiArchSnapshot); |
| } |
| |
| Future<void> compareTraces(List<String> nonDwarfTrace, DwarfTestOutput output1, |
| DwarfTestOutput output2, Dwarf dwarf, |
| {bool fromAssembly = false}) async { |
| final header1 = StackTraceHeader.fromLines(output1.trace); |
| print('Header1 = $header1'); |
| checkHeader(header1); |
| final header2 = StackTraceHeader.fromLines(output2.trace); |
| print('Header2 = $header1'); |
| checkHeader(header2); |
| |
| // 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(output1.trace); |
| final tracePCOffsets2 = collectPCOffsets(output2.trace); |
| expect(tracePCOffsets2, equals(tracePCOffsets1)); |
| expect(tracePCOffsets1, isNotEmpty); |
| checkRootUnitAssumptions(output1, output2, dwarf, |
| sampleOffset: tracePCOffsets1.first, matchingBuildIds: !fromAssembly); |
| |
| final decoder = DwarfStackTraceDecoder(dwarf); |
| final translatedDwarfTrace1 = |
| await Stream.fromIterable(output1.trace).transform(decoder).toList(); |
| |
| checkTranslatedTrace(nonDwarfTrace, translatedDwarfTrace1); |
| |
| // 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(output1.trace).single; |
| final dsoBase2 = dsoBaseAddresses(output2.trace).single; |
| |
| final absTrace1 = absoluteAddresses(output1.trace); |
| final absTrace2 = absoluteAddresses(output2.trace); |
| |
| final relocatedFromDso1 = absTrace1.map((a) => a - dsoBase1); |
| final relocatedFromDso2 = absTrace2.map((a) => a - dsoBase2); |
| |
| expect(relocatedFromDso2, equals(relocatedFromDso1)); |
| |
| // We don't print 'virt' relocated addresses when running assembled snapshots. |
| if (fromAssembly) return; |
| |
| // 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(output1.trace); |
| final virtTrace2 = explicitVirtualAddresses(output2.trace); |
| |
| expect(virtTrace2, equals(virtTrace1)); |
| |
| expect(tracePCOffsets1.map((o) => o.virtualAddressIn(dwarf)), |
| equals(virtTrace1)); |
| expect(tracePCOffsets2.map((o) => o.virtualAddressIn(dwarf)), |
| equals(virtTrace2)); |
| |
| expect(relocatedFromDso1, equals(virtTrace1)); |
| expect(relocatedFromDso2, equals(virtTrace2)); |
| } |