blob: 466df022223f960004f3701f2f25099fd31d23cc [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));
}