Skip to content

Commit 1299404

Browse files
sstricklCommit Queue
authored andcommitted
[vm] Add compact unwinding info to ARM64 Mach-O snapshots.
For other architectures, a __eh_frame section is generated like for non-MacOS platforms. TEST=vm/dart/unwinding_information_test Issue: #60307 Change-Id: I46c6ff1357c222f73a129e1becd9b12b3fb6ef9c Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-mac-release-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-dwarf-linux-product-x64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try,vm-fuchsia-release-x64-try,vm-fuchsia-release-arm64-try,vm-aot-linux-release-simarm_x64-try,vm-gcc-linux-try,vm-ubsan-linux-release-arm64-try,vm-aot-win-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/433020 Reviewed-by: Slava Egorov <[email protected]> Reviewed-by: Ryan Macnak <[email protected]> Commit-Queue: Tess Strickland <[email protected]>
1 parent a04c0d4 commit 1299404

File tree

10 files changed

+583
-180
lines changed

10 files changed

+583
-180
lines changed

runtime/platform/mach_o.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,112 @@ struct cs_code_directory {
537537
// 8-byte aligned (like blobs) and the hash data is 16-byte aligned.
538538
};
539539

540+
// Compact unwinding information constants for encodings.
541+
542+
// Architecture-independent constants for encodings.
543+
544+
// A shorthand for the zero-value encoding that denotes that the associated
545+
// memory space does not contain function instructions.
546+
static constexpr uint32_t UNWIND_INFO_ENCODING_NONE = 0;
547+
548+
static constexpr uint32_t UNWIND_INFO_ENCODING_IS_NOT_FUNCTION_START =
549+
0x80000000;
550+
static constexpr uint32_t UNWIND_INFO_ENCODING_HAS_LSDA = 0x40000000;
551+
static constexpr uint32_t UNWIND_INFO_ENCODING_PERSONALITY_MASK = 0x30000000;
552+
553+
// Currently compact unwinding information is only generated for ARM64, so only
554+
// the constants used by the MachO writer are included below.
555+
556+
// ARM64-specific constants for encodings.
557+
static constexpr uint32_t UNWIND_INFO_ENCODING_ARM64_MODE_MASK = 0x0F000000;
558+
559+
// A standard ARM64 prologue where FP/LR are immediately pushed on the
560+
// stack and then SP is copied to FP. If there are any non-volatile
561+
// registers saved, they are saved in pairs right below the FP/LR pair
562+
// in register number order.
563+
//
564+
// In the MachO writer, this is the only non-zero encoding used and
565+
// no non-volatile register pairs are recorded as being saved, so
566+
// the appropriate constants for each pair and for other encodings are elided.
567+
static constexpr uint32_t UNWIND_INFO_ENCODING_ARM64_MODE_FRAME = 0x04000000;
568+
569+
// Note that fields ending in section_offset are offsets into the unwind info
570+
// section as a whole, whereas fields ending in page_offset are offsets into
571+
// the specific second level page (e.g., the page header starts at offset 0).
572+
573+
struct unwind_info_lsda_index {
574+
uint32_t function_offset;
575+
uint32_t lsda_offset;
576+
};
577+
578+
struct unwind_info_first_level_page_index {
579+
uint32_t function_offset;
580+
uint32_t second_level_page_section_offset;
581+
uint32_t lsda_index_section_offset;
582+
};
583+
584+
// Second level pages have two formats: a compressed and a "regular" format.
585+
// As the Mach-O writer only creates a single second level page with four
586+
// entries currently, it uses the regular format as it is simpler.
587+
588+
static constexpr uint32_t UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE = 2;
589+
590+
struct unwind_info_regular_second_level_page_entry {
591+
uint32_t function_offset;
592+
uint32_t encoding;
593+
};
594+
595+
struct unwind_info_regular_second_level_page_header {
596+
uint32_t kind; // UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE
597+
uint16_t entry_page_offset;
598+
uint16_t entry_count;
599+
};
600+
601+
static const size_t UNWIND_INFO_SECOND_LEVEL_PAGE_MAX_SIZE = 4 * KB;
602+
603+
static constexpr uint32_t UNWIND_INFO_REGULAR_SECOND_LEVEL_PAGE_MAX_ENTRIES =
604+
(UNWIND_INFO_SECOND_LEVEL_PAGE_MAX_SIZE -
605+
sizeof(unwind_info_regular_second_level_page_header)) /
606+
sizeof(unwind_info_regular_second_level_page_entry);
607+
608+
constexpr size_t UnwindInfoRegularSecondLevelPageSize(intptr_t entries) {
609+
return sizeof(unwind_info_regular_second_level_page_header) +
610+
sizeof(unwind_info_regular_second_level_page_entry) * entries;
611+
}
612+
613+
static constexpr uint32_t UNWIND_INFO_VERSION = 1;
614+
615+
struct unwind_info_header {
616+
uint32_t version;
617+
uint32_t common_encodings_section_offset;
618+
uint32_t common_encodings_count;
619+
uint32_t personalities_section_offset;
620+
uint32_t personalities_count;
621+
uint32_t first_level_page_indices_section_offset;
622+
uint32_t first_level_page_indices_count;
623+
624+
// Note that the last first level page indices is a sentinel that contains
625+
// the end of the covered space as its function offset and the end
626+
// of the lsda array as its lsda_index_section_offset.
627+
//
628+
// sentinel_index = first_level_page_indices_count - 1
629+
//
630+
// lsda_size =
631+
// first_level_pages[sentinel_index].lsda_index_section_offset -
632+
// first_level_pages[0].lsda_index_section_offset
633+
//
634+
// lsda_count = lsda_size / sizeof(unwind_info_lsda_index)
635+
// second_level_page_count = sentinel_index;
636+
637+
// Variadic payload of unwind info section after unwind_info_header:
638+
// uint32_t common_encodings[common_encodings_count]
639+
// uint32_t personalities[personalities_count]
640+
// unwind_info_first_level_page_index
641+
// first_level_pages[first_level_page_indices_count]
642+
// unwind_info_lsda_index lsda[lsda_count]
643+
// ... regular and compressed second level pages ...
644+
};
645+
540646
#pragma pack(pop)
541647

542648
} // namespace mach_o

runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
import "dart:io";
1313

1414
import 'package:expect/expect.dart';
15-
import 'package:native_stack_traces/elf.dart';
1615
import 'package:native_stack_traces/src/dwarf_container.dart';
17-
import 'package:native_stack_traces/src/macho.dart';
1816
import 'package:path/path.dart' as path;
1917

2018
import 'use_flag_test_helper.dart';
@@ -62,76 +60,6 @@ Future<void> main(List<String> args) async {
6260
});
6361
}
6462

65-
const commonGenSnapshotArgs = <String>[
66-
// Make sure that the runs are deterministic so we can depend on the same
67-
// snapshot being generated each time.
68-
'--deterministic',
69-
];
70-
71-
enum SnapshotType {
72-
elf,
73-
machoDylib,
74-
assembly;
75-
76-
String get kindString {
77-
switch (this) {
78-
case elf:
79-
return 'app-aot-elf';
80-
case machoDylib:
81-
return 'app-aot-macho-dylib';
82-
case assembly:
83-
return 'app-aot-assembly';
84-
}
85-
}
86-
87-
String get fileArgumentName {
88-
switch (this) {
89-
case elf:
90-
return 'elf';
91-
case machoDylib:
92-
return 'macho';
93-
case assembly:
94-
return 'assembly';
95-
}
96-
}
97-
98-
DwarfContainer? fromFile(String filename) {
99-
switch (this) {
100-
case elf:
101-
return Elf.fromFile(filename);
102-
case machoDylib:
103-
return MachO.fromFile(filename);
104-
case assembly:
105-
return Elf.fromFile(filename) ?? MachO.fromFile(filename);
106-
}
107-
}
108-
109-
@override
110-
String toString() => name;
111-
}
112-
113-
Future<void> createSnapshot(
114-
String scriptDill,
115-
SnapshotType snapshotType,
116-
String finalPath, [
117-
List<String> extraArgs = const [],
118-
]) async {
119-
String output = finalPath;
120-
if (snapshotType == SnapshotType.assembly) {
121-
output = path.withoutExtension(finalPath) + '.S';
122-
}
123-
await run(genSnapshot, <String>[
124-
...commonGenSnapshotArgs,
125-
...extraArgs,
126-
'--snapshot-kind=${snapshotType.kindString}',
127-
'--${snapshotType.fileArgumentName}=$output',
128-
scriptDill,
129-
]);
130-
if (snapshotType == SnapshotType.assembly) {
131-
await assembleSnapshot(output, finalPath);
132-
}
133-
}
134-
13563
Future<List<String>?> retrieveDebugMap(
13664
SnapshotType snapshotType,
13765
String snapshotPath,
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This test checks that the compact unwinding information is appropriately
6+
// generated for Mac ARM64 snapshots.
7+
8+
// OtherResources=use_save_debugging_info_flag_program.dart
9+
10+
import "dart:io";
11+
12+
import 'package:expect/expect.dart';
13+
import 'package:native_stack_traces/src/dwarf_container.dart';
14+
import 'package:path/path.dart' as path;
15+
16+
import 'use_flag_test_helper.dart';
17+
18+
Future<void> main(List<String> args) async {
19+
if (!isAOTRuntime) {
20+
return; // Running in JIT: AOT binaries not available.
21+
}
22+
23+
// Currently, this test only checks compact unwinding information, which
24+
// is only generated on Mac ARM64 binaries.
25+
if (!Platform.isMacOS || !buildDir.endsWith('ARM64')) {
26+
return;
27+
}
28+
29+
// These are the tools we need to be available to run on a given platform:
30+
if (!await testExecutable(genSnapshot)) {
31+
throw "Cannot run test as $genSnapshot not available";
32+
}
33+
if (!await testExecutable(dartPrecompiledRuntime)) {
34+
throw "Cannot run test as $dartPrecompiledRuntime not available";
35+
}
36+
if (!File(platformDill).existsSync()) {
37+
throw "Cannot run test as $platformDill does not exist";
38+
}
39+
40+
await withTempDir('unwinding-information', (String tempDir) async {
41+
final cwDir = path.dirname(Platform.script.toFilePath());
42+
final script = path.join(
43+
cwDir,
44+
'use_save_debugging_info_flag_program.dart',
45+
);
46+
final scriptDill = path.join(tempDir, 'flag_program.dill');
47+
48+
// Compile script to Kernel IR.
49+
await run(genKernel, <String>[
50+
'--aot',
51+
'--platform=$platformDill',
52+
'-o',
53+
scriptDill,
54+
script,
55+
]);
56+
57+
await checkMachO(tempDir, scriptDill);
58+
});
59+
}
60+
61+
Future<List<String>> retrieveUnwindInfo(
62+
SnapshotType snapshotType,
63+
String snapshotPath,
64+
) async {
65+
if (snapshotType != SnapshotType.machoDylib) {
66+
throw ArgumentError("Unhandled snapshot type");
67+
}
68+
final objdump = llvmTool('llvm-objdump');
69+
if (objdump == null) {
70+
throw StateError('Expected llvm-objdump in buildutils');
71+
}
72+
return await runOutput(objdump, ['--macho', '-u', snapshotPath]);
73+
}
74+
75+
Future<void> checkSnapshotType(
76+
String tempDir,
77+
String scriptDill,
78+
SnapshotType snapshotType,
79+
) async {
80+
final scriptUnstrippedSnapshot = path.join(
81+
tempDir,
82+
'unstripped-$snapshotType.so',
83+
);
84+
await createSnapshot(scriptDill, snapshotType, scriptUnstrippedSnapshot);
85+
final unstrippedCase = TestCase(
86+
snapshotType,
87+
scriptUnstrippedSnapshot,
88+
snapshotType.fromFile(scriptUnstrippedSnapshot)!,
89+
await retrieveUnwindInfo(snapshotType, scriptUnstrippedSnapshot),
90+
);
91+
92+
final scriptStrippedSnapshot = path.join(
93+
tempDir,
94+
'stripped-$snapshotType.so',
95+
);
96+
await createSnapshot(scriptDill, snapshotType, scriptStrippedSnapshot, [
97+
'--strip',
98+
]);
99+
final strippedCase = TestCase(
100+
snapshotType,
101+
scriptStrippedSnapshot,
102+
snapshotType.fromFile(scriptStrippedSnapshot)!,
103+
await retrieveUnwindInfo(snapshotType, scriptStrippedSnapshot),
104+
);
105+
106+
checkCases(unstrippedCase, strippedCase);
107+
}
108+
109+
Future<void> checkMachO(String tempDir, String scriptDill) async {
110+
await checkSnapshotType(tempDir, scriptDill, SnapshotType.machoDylib);
111+
}
112+
113+
class TestCase {
114+
final SnapshotType snapshotType;
115+
final String snapshotPath;
116+
final DwarfContainer container;
117+
final List<String> unwindInfo;
118+
119+
TestCase(
120+
this.snapshotType,
121+
this.snapshotPath,
122+
this.container,
123+
this.unwindInfo,
124+
);
125+
}
126+
127+
void checkCases(TestCase unstripped, TestCase stripped) {
128+
Expect.isNotEmpty(unstripped.unwindInfo);
129+
Expect.deepEquals(unstripped.unwindInfo, stripped.unwindInfo);
130+
}

0 commit comments

Comments
 (0)