Skip to content

Commit 1f5fd27

Browse files
sstricklCommit Queue
authored andcommitted
[vm] Add --macho-min-os-version flag for macOS/iOS targets.
The command line flag allows the user to specify the minimum MacOS or iOS version used in the build version load command for the Mach-O dynamic library snapshot. If not specified, the minimum OS version specified in macOS Mach-O snapshots is 10.15 (Catalina) and the minimum OS version specified in iOS Mach-O snapshots is 13. This CL also changes the targeted OS version in the build version load command to be the same as the minimum OS version. Adds support to parsing build version load commands to pkg/native_stack_traces's Mach-O parser in order to test the existence and contents of build version load commands in vm/dart/unobfuscated_static_symbols. TEST=vm/dart/unobfuscated_static_symbols Issue: #60307 Change-Id: I3ee3ba34297d3261be7e3a1d2fb3c1da1ef0ef05 Cq-Include-Trybots: luci.dart.try:vm-aot-mac-debug-x64-try,vm-aot-mac-debug-arm64-try,vm-aot-linux-debug-arm64-try,vm-aot-linux-debug-x64-try,pkg-mac-release-try,pkg-mac-release-arm64-try,pkg-linux-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/438901 Reviewed-by: Ryan Macnak <[email protected]> Commit-Queue: Tess Strickland <[email protected]>
1 parent 560b35a commit 1f5fd27

File tree

11 files changed

+419
-106
lines changed

11 files changed

+419
-106
lines changed

pkg/native_stack_traces/lib/src/macho.dart

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class LoadCommand {
125125
static const LC_SYMTAB = 0x2;
126126
static const LC_SEGMENT_64 = 0x19;
127127
static const LC_UUID = 0x1b;
128+
static const LC_BUILD_VERSION = 0x32;
128129

129130
static LoadCommand fromReader(Reader reader) {
130131
final start = reader.offset; // cmdsize includes size of cmd and cmdsize.
@@ -143,6 +144,9 @@ class LoadCommand {
143144
case LC_UUID:
144145
command = UuidCommand.fromReader(reader, cmd, cmdsize);
145146
break;
147+
case LC_BUILD_VERSION:
148+
command = BuildVersionCommand.fromReader(reader, cmd, cmdsize);
149+
break;
146150
default:
147151
break;
148152
}
@@ -351,6 +355,161 @@ class UuidCommand extends LoadCommand {
351355
}
352356
}
353357

358+
class Version {
359+
final int x;
360+
final int y;
361+
final int z;
362+
363+
const Version(this.x, this.y, this.z);
364+
365+
static Version fromInt(int v) {
366+
final x = (v & 0xffffffff) >> 16;
367+
final y = (v & 0xffff) >> 8;
368+
final z = v & 0xff;
369+
return Version(x, y, z);
370+
}
371+
372+
@override
373+
int get hashCode => Object.hash(x, y, z);
374+
375+
@override
376+
bool operator ==(Object other) {
377+
if (identical(this, other)) return true;
378+
return other is Version && x == other.x && y == other.y && z == other.z;
379+
}
380+
381+
@override
382+
String toString() => '$x.$y.$z';
383+
}
384+
385+
enum Platform {
386+
PLATFORM_UNKNOWN(0, 'Unknown'),
387+
PLATFORM_ANY(0xFFFFFFFF, 'any'),
388+
PLATFORM_MACOS(0x1, 'MacOS'),
389+
PLATFORM_IOS(0x2, 'iOS'),
390+
PLATFORM_TVOS(0x3, 'TVOS'),
391+
PLATFORM_WATCHOS(0x4, 'WatchOS'),
392+
PLATFORM_IOSSIMULATOR(0x7, 'iOS Simulator'),
393+
PLATFORM_TVOSSIMULATOR(0x8, 'TVOS Simulator'),
394+
PLATFORM_WATCHOSSIMULATOR(0x9, 'WatchOS Simulator');
395+
396+
final int id;
397+
final String description;
398+
399+
const Platform(this.id, this.description);
400+
401+
static Platform? fromId(int id) {
402+
for (final platform in values) {
403+
if (platform.id == id) {
404+
return platform;
405+
}
406+
}
407+
return null;
408+
}
409+
410+
@override
411+
String toString() => description;
412+
}
413+
414+
enum BuildTool {
415+
TOOL_CLANG(1, 'clang'),
416+
TOOL_SWIFT(2, 'Swift'),
417+
TOOL_LD(3, 'ld'),
418+
TOOL_LLD(4, 'lld');
419+
420+
final int id;
421+
final String description;
422+
423+
const BuildTool(this.id, this.description);
424+
425+
static BuildTool? fromId(int id) {
426+
for (final tool in values) {
427+
if (tool.id == id) {
428+
return tool;
429+
}
430+
}
431+
return null;
432+
}
433+
434+
@override
435+
String toString() => description;
436+
}
437+
438+
class BuildToolVersion {
439+
final int _toolId;
440+
final BuildTool? tool;
441+
final Version version;
442+
443+
const BuildToolVersion._(this._toolId, this.tool, this.version);
444+
445+
factory BuildToolVersion.fromReader(Reader reader) {
446+
final id = _readMachOUint32(reader);
447+
final tool = BuildTool.fromId(id);
448+
final version = Version.fromInt(_readMachOUint32(reader));
449+
return BuildToolVersion._(id, tool, version);
450+
}
451+
452+
void writeToStringBuffer(StringBuffer buffer) {
453+
buffer
454+
..write(' - ')
455+
..write(tool ?? 'Unknown (0x${paddedHex(_toolId, 4)})')
456+
..write(' (')
457+
..write(version)
458+
..writeln(')');
459+
}
460+
461+
@override
462+
String toString() {
463+
final buffer = StringBuffer();
464+
writeToStringBuffer(buffer);
465+
return buffer.toString();
466+
}
467+
}
468+
469+
class BuildVersionCommand extends LoadCommand {
470+
final int _platformId;
471+
final Platform? platform;
472+
final Version minOS;
473+
final Version sdk;
474+
final List<BuildToolVersion> toolVersions;
475+
476+
BuildVersionCommand._(super.cmd, super.cmdsize, this._platformId,
477+
this.platform, this.minOS, this.sdk, this.toolVersions)
478+
: super._();
479+
480+
static BuildVersionCommand fromReader(Reader reader, int cmd, int cmdsize) {
481+
final platformId = _readMachOUint32(reader);
482+
final platform = Platform.fromId(platformId);
483+
final minOS = Version.fromInt(_readMachOUint32(reader));
484+
final sdk = Version.fromInt(_readMachOUint32(reader));
485+
final toolCount = _readMachOUint32(reader);
486+
final toolVersions = <BuildToolVersion>[];
487+
for (int i = 0; i < toolCount; i++) {
488+
toolVersions.add(BuildToolVersion.fromReader(reader));
489+
}
490+
return BuildVersionCommand._(
491+
cmd, cmdsize, platformId, platform, minOS, sdk, toolVersions);
492+
}
493+
494+
@override
495+
void writeToStringBuffer(StringBuffer buffer) {
496+
buffer
497+
..writeln('Build version:')
498+
..write(' Platform: ')
499+
..writeln(platform ?? 'Unknown (0x${paddedHex(_platformId, 4)})')
500+
..write(' Minimum OS: ')
501+
..writeln(minOS)
502+
..write(' Minimum SDK: ')
503+
..writeln(sdk);
504+
if (toolVersions.isNotEmpty) {
505+
buffer.writeln(' Tools:');
506+
for (final v in toolVersions) {
507+
v.writeToStringBuffer(buffer);
508+
}
509+
}
510+
}
511+
}
512+
354513
class MachOHeader {
355514
final int magic;
356515
final int cputype;
@@ -529,6 +688,9 @@ class MachO extends DwarfContainer {
529688
Reader.fromTypedData(reader.bdata,
530689
wordSize: _header.wordSize, endian: _header.endian);
531690

691+
Iterable<T> commandsWhereType<T extends LoadCommand>() =>
692+
_commands.whereType<T>();
693+
532694
@override
533695
String? get architecture => CpuType.fromCode(_header.cputype)?.dartName;
534696

runtime/tests/vm/dart/unobfuscated_static_symbols_test.dart

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import "dart:io";
1313

1414
import 'package:expect/expect.dart';
1515
import 'package:native_stack_traces/src/dwarf_container.dart';
16+
import 'package:native_stack_traces/src/macho.dart' as macho;
1617
import 'package:path/path.dart' as path;
1718

1819
import 'use_flag_test_helper.dart';
@@ -85,20 +86,33 @@ Future<List<String>?> retrieveDebugMap(
8586
return await runOutput(dsymutil, ['--dump-debug-map', snapshotPath]);
8687
}
8788

89+
final hasMinOSVersionOption = Platform.isMacOS || Platform.isIOS;
90+
final expectedVersion = hasMinOSVersionOption ? macho.Version(1, 2, 3) : null;
91+
8892
Future<void> checkSnapshotType(
8993
String tempDir,
9094
String scriptDill,
9195
SnapshotType snapshotType,
9296
) async {
97+
final commonOptions = <String>[];
98+
if (hasMinOSVersionOption && snapshotType == SnapshotType.machoDylib) {
99+
commonOptions.add('--macho-min-os-version=$expectedVersion');
100+
}
93101
// Run the AOT compiler without Dwarf stack trace, once without obfuscation,
94102
// once with obfuscation, and once with obfuscation and saving debugging
95103
// information.
96104
final scriptUnobfuscatedSnapshot = path.join(
97105
tempDir,
98106
'unobfuscated-$snapshotType.so',
99107
);
100-
await createSnapshot(scriptDill, snapshotType, scriptUnobfuscatedSnapshot);
108+
await createSnapshot(
109+
scriptDill,
110+
snapshotType,
111+
scriptUnobfuscatedSnapshot,
112+
commonOptions,
113+
);
101114
final unobfuscatedCase = TestCase(
115+
snapshotType,
102116
scriptUnobfuscatedSnapshot,
103117
snapshotType.fromFile(scriptUnobfuscatedSnapshot)!,
104118
debugMap: await retrieveDebugMap(snapshotType, scriptUnobfuscatedSnapshot),
@@ -109,9 +123,11 @@ Future<void> checkSnapshotType(
109123
'obfuscated-only-$snapshotType.so',
110124
);
111125
await createSnapshot(scriptDill, snapshotType, scriptObfuscatedOnlySnapshot, [
126+
...commonOptions,
112127
'--obfuscate',
113128
]);
114129
final obfuscatedOnlyCase = TestCase(
130+
snapshotType,
115131
scriptObfuscatedOnlySnapshot,
116132
snapshotType.fromFile(scriptObfuscatedOnlySnapshot)!,
117133
debugMap: await retrieveDebugMap(
@@ -135,10 +151,12 @@ Future<void> checkSnapshotType(
135151
'obfuscated-debug-$snapshotType.so',
136152
);
137153
await createSnapshot(scriptDill, snapshotType, scriptObfuscatedSnapshot, [
154+
...commonOptions,
138155
'--obfuscate',
139156
'--save-debugging-info=$scriptDebuggingInfo',
140157
]);
141158
obfuscatedCase = TestCase(
159+
snapshotType,
142160
scriptObfuscatedSnapshot,
143161
snapshotType.fromFile(scriptObfuscatedSnapshot)!,
144162
debuggingInfoContainer: snapshotType.fromFile(scriptDebuggingInfo)!,
@@ -154,11 +172,13 @@ Future<void> checkSnapshotType(
154172
'obfuscated-separate-debug-$snapshotType.so',
155173
);
156174
await createSnapshot(scriptDill, snapshotType, scriptStrippedSnapshot, [
175+
...commonOptions,
157176
'--strip',
158177
'--obfuscate',
159178
'--save-debugging-info=$scriptSeparateDebuggingInfo',
160179
]);
161180
strippedCase = TestCase(
181+
snapshotType,
162182
scriptStrippedSnapshot,
163183
/*container=*/ null, // No static symbols in stripped snapshot.
164184
debuggingInfoContainer: snapshotType.fromFile(
@@ -192,12 +212,14 @@ Future<void> checkAssembly(String tempDir, String scriptDill) async {
192212
}
193213

194214
class TestCase {
215+
final SnapshotType type;
195216
final String snapshotPath;
196217
final DwarfContainer? container;
197218
final DwarfContainer? debuggingInfoContainer;
198219
final List<String>? debugMap;
199220

200221
TestCase(
222+
this.type,
201223
this.snapshotPath,
202224
this.container, {
203225
this.debuggingInfoContainer,
@@ -222,6 +244,7 @@ Future<void> checkCases(
222244
unstrippedObfuscateds.map((c) => c.debugMap!).toList(),
223245
);
224246
}
247+
checkMachOSnapshots(unobfuscated, obfuscateds);
225248
}
226249

227250
Future<void> checkTraces(
@@ -412,3 +435,31 @@ void checkDebugMaps(List<String> expected, List<List<String>> cases) {
412435
}
413436
}
414437
}
438+
439+
// Checks for MachO snapshots (not separate debugging information).
440+
void checkMachOSnapshots(TestCase unobfuscated, List<TestCase> obfuscateds) {
441+
checkMachOSnapshot(unobfuscated);
442+
obfuscateds.forEach(checkMachOSnapshot);
443+
}
444+
445+
void checkMachOSnapshot(TestCase testCase) {
446+
// The checks below are only for snapshots, not for debugging information.
447+
final snapshot = testCase.container;
448+
if (snapshot is! macho.MachO) return;
449+
final buildVersion = snapshot
450+
.commandsWhereType<macho.BuildVersionCommand>()
451+
.singleOrNull;
452+
final expectedPlatform = Platform.isMacOS
453+
? macho.Platform.PLATFORM_MACOS
454+
: Platform.isIOS
455+
? macho.Platform.PLATFORM_IOS
456+
: null;
457+
Expect.equals(expectedPlatform, buildVersion?.platform);
458+
if (testCase.type == SnapshotType.machoDylib) {
459+
Expect.equals(expectedVersion, buildVersion?.minOS);
460+
Expect.equals(expectedVersion, buildVersion?.sdk);
461+
if (buildVersion != null) {
462+
Expect.isEmpty(buildVersion.toolVersions);
463+
}
464+
}
465+
}

runtime/tests/vm/dart/use_dwarf_stack_traces_flag_deferred_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import "dart:io";
1616

1717
import 'package:native_stack_traces/native_stack_traces.dart';
1818
import 'package:native_stack_traces/src/constants.dart' show rootLoadingUnitId;
19-
import 'package:native_stack_traces/src/macho.dart';
19+
import 'package:native_stack_traces/src/macho.dart'
20+
show emptyMachOForArchitecture, MachO;
2021
import 'package:path/path.dart' as path;
2122
import 'package:test/test.dart';
2223

runtime/tests/vm/dart/use_dwarf_stack_traces_flag_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import "dart:async";
1010
import "dart:io";
1111

1212
import 'package:native_stack_traces/native_stack_traces.dart';
13-
import 'package:native_stack_traces/src/macho.dart';
13+
import 'package:native_stack_traces/src/macho.dart'
14+
show emptyMachOForArchitecture, MachO;
1415
import 'package:path/path.dart' as path;
1516
import 'package:test/test.dart';
1617

0 commit comments

Comments
 (0)