blob: 04a974b6f483fa3959ab8bc30aa72e880d9b8564 [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).
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));
}