authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-08-28 20:55:45+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-08-29 11:45:03+02:00
log2e28ab153c39df77403eb64f282589349f05921d
tree217c88b51804fc416b844e2055d3d78d97324705
parentec03619dcfa025442f6a70cbdfeafd5882591b64

macho: parse platform info from each object file into Platform struct


5 files changed, 203 insertions(+), 39 deletions(-)

src/link/MachO.zig+15-4
......@@ -585,7 +585,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
585585 try lc_writer.writeStruct(macho.source_version_command{
586586 .version = 0,
587587 });
588 try load_commands.writeBuildVersionLC(&self.base.options, lc_writer);
588 {
589 const platform = load_commands.Platform.fromOptions(&self.base.options);
590 const sdk_version: ?std.SemanticVersion = self.base.options.darwin_sdk_version orelse blk: {
591 if (self.base.options.sysroot) |path| break :blk load_commands.inferSdkVersionFromSdkPath(path);
592 break :blk null;
593 };
594 if (platform.isBuildVersionCompatible()) {
595 try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
596 } else {
597 try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
598 }
599 }
589600
590601 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
591602 try lc_writer.writeStruct(self.uuid_cmd);
......@@ -797,7 +808,7 @@ fn parseObject(
797808 const self_cpu_arch = self.base.options.target.cpu.arch;
798809
799810 if (self_cpu_arch != cpu_arch) {
800 error_ctx.* = .{ .detected_arch = cpu_arch };
811 error_ctx.detected_arch = cpu_arch;
801812 return error.InvalidArch;
802813 }
803814}
......@@ -894,7 +905,7 @@ fn parseArchive(
894905 else => unreachable,
895906 };
896907 if (cpu_arch != parsed_cpu_arch) {
897 error_ctx.* = .{ .detected_arch = parsed_cpu_arch };
908 error_ctx.detected_arch = parsed_cpu_arch;
898909 return error.InvalidArch;
899910 }
900911 }
......@@ -959,7 +970,7 @@ fn parseDylib(
959970 else => unreachable,
960971 };
961972 if (self_cpu_arch != cpu_arch) {
962 error_ctx.* = .{ .detected_arch = cpu_arch };
973 error_ctx.detected_arch = cpu_arch;
963974 return error.InvalidArch;
964975 }
965976
src/link/MachO/Dylib.zig+4-4
......@@ -217,7 +217,7 @@ const TargetMatcher = struct {
217217 target: CrossTarget,
218218 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
219219
220 fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher {
220 pub fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher {
221221 var self = TargetMatcher{
222222 .allocator = allocator,
223223 .target = target,
......@@ -239,7 +239,7 @@ const TargetMatcher = struct {
239239 return self;
240240 }
241241
242 fn deinit(self: *TargetMatcher) void {
242 pub fn deinit(self: *TargetMatcher) void {
243243 for (self.target_strings.items) |t| {
244244 self.allocator.free(t);
245245 }
......@@ -263,7 +263,7 @@ const TargetMatcher = struct {
263263 };
264264 }
265265
266 fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 {
266 pub fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 {
267267 const cpu_arch = cpuArchToAppleString(target.cpu_arch.?);
268268 const os_tag = @tagName(target.os_tag.?);
269269 const target_abi = abiToAppleString(target.abi orelse .none);
......@@ -291,7 +291,7 @@ const TargetMatcher = struct {
291291 return hasValue(archs, cpuArchToAppleString(self.target.cpu_arch.?));
292292 }
293293
294 fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
294 pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
295295 var arena = std.heap.ArenaAllocator.init(self.allocator);
296296 defer arena.deinit();
297297
src/link/MachO/Object.zig+21
......@@ -940,6 +940,26 @@ pub fn parseDwarfInfo(self: Object) DwarfInfo {
940940 return di;
941941}
942942
943/// Returns Options.Platform composed from the first encountered build version type load command:
944/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
945pub fn getPlatform(self: Object) ?Platform {
946 var it = LoadCommandIterator{
947 .ncmds = self.header.ncmds,
948 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
949 };
950 while (it.next()) |cmd| {
951 switch (cmd.cmd()) {
952 .BUILD_VERSION,
953 .VERSION_MIN_MACOSX,
954 .VERSION_MIN_IPHONEOS,
955 .VERSION_MIN_TVOS,
956 .VERSION_MIN_WATCHOS,
957 => return Platform.fromLoadCommand(cmd),
958 else => {},
959 }
960 } else return null;
961}
962
943963pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {
944964 const size = @as(usize, @intCast(sect.size));
945965 return self.contents[sect.offset..][0..size];
......@@ -1089,5 +1109,6 @@ const Atom = @import("Atom.zig");
10891109const DwarfInfo = @import("DwarfInfo.zig");
10901110const LoadCommandIterator = macho.LoadCommandIterator;
10911111const MachO = @import("../MachO.zig");
1112const Platform = @import("load_commands.zig").Platform;
10921113const SymbolWithLoc = MachO.SymbolWithLoc;
10931114const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/load_commands.zig+145-27
......@@ -76,8 +76,14 @@ fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx
7676 }
7777 // LC_SOURCE_VERSION
7878 sizeofcmds += @sizeOf(macho.source_version_command);
79 // LC_BUILD_VERSION
80 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
79 // LC_BUILD_VERSION or LC_VERSION_MIN_
80 if (Platform.fromOptions(options).isBuildVersionCompatible()) {
81 // LC_BUILD_VERSION
82 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
83 } else {
84 // LC_VERSION_MIN_
85 sizeofcmds += @sizeOf(macho.version_min_command);
86 }
8187 // LC_UUID
8288 sizeofcmds += @sizeOf(macho.uuid_command);
8389 // LC_LOAD_DYLIB
......@@ -252,33 +258,28 @@ pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: an
252258 }
253259}
254260
255pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !void {
256 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
257 const platform_version = blk: {
258 const ver = options.target.os.version_range.semver.min;
259 const platform_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8));
260 break :blk platform_version;
261 };
262 const sdk_version: ?std.SemanticVersion = options.darwin_sdk_version orelse blk: {
263 if (options.sysroot) |path| break :blk inferSdkVersionFromSdkPath(path);
264 break :blk null;
261pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
262 const cmd: macho.LC = switch (platform.os_tag) {
263 .macos => .VERSION_MIN_MACOSX,
264 .ios => .VERSION_MIN_IPHONEOS,
265 .tvos => .VERSION_MIN_TVOS,
266 .watchos => .VERSION_MIN_WATCHOS,
267 else => unreachable,
265268 };
266 const sdk_version_value: u32 = if (sdk_version) |ver|
267 @intCast(ver.major << 16 | ver.minor << 8)
268 else
269 platform_version;
270 const is_simulator_abi = options.target.abi == .simulator;
269 try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{
270 .cmd = cmd,
271 .version = platform.toAppleVersion(),
272 .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
273 }));
274}
275
276pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
277 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
271278 try lc_writer.writeStruct(macho.build_version_command{
272279 .cmdsize = cmdsize,
273 .platform = switch (options.target.os.tag) {
274 .macos => .MACOS,
275 .ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS,
276 .watchos => if (is_simulator_abi) macho.PLATFORM.WATCHOSSIMULATOR else macho.PLATFORM.WATCHOS,
277 .tvos => if (is_simulator_abi) macho.PLATFORM.TVOSSIMULATOR else macho.PLATFORM.TVOS,
278 else => unreachable,
279 },
280 .minos = platform_version,
281 .sdk = sdk_version_value,
280 .platform = platform.toApplePlatform(),
281 .minos = platform.toAppleVersion(),
282 .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
282283 .ntools = 1,
283284 });
284285 try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
......@@ -301,7 +302,124 @@ pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: an
301302 }
302303}
303304
304fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
305pub const Platform = struct {
306 os_tag: std.Target.Os.Tag,
307 abi: std.Target.Abi,
308 version: std.SemanticVersion,
309
310 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
311 /// the extracted minimum platform version.
312 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
313 switch (lc.cmd()) {
314 .BUILD_VERSION => {
315 const cmd = lc.cast(macho.build_version_command).?;
316 return .{
317 .os_tag = switch (cmd.platform) {
318 .MACOS => .macos,
319 .IOS, .IOSSIMULATOR => .ios,
320 .TVOS, .TVOSSIMULATOR => .tvos,
321 .WATCHOS, .WATCHOSSIMULATOR => .watchos,
322 else => @panic("TODO"),
323 },
324 .abi = switch (cmd.platform) {
325 .IOSSIMULATOR,
326 .TVOSSIMULATOR,
327 .WATCHOSSIMULATOR,
328 => .simulator,
329 else => .none,
330 },
331 .version = appleVersionToSemanticVersion(cmd.minos),
332 };
333 },
334 .VERSION_MIN_MACOSX,
335 .VERSION_MIN_IPHONEOS,
336 .VERSION_MIN_TVOS,
337 .VERSION_MIN_WATCHOS,
338 => {
339 const cmd = lc.cast(macho.version_min_command).?;
340 return .{
341 .os_tag = switch (lc.cmd()) {
342 .VERSION_MIN_MACOSX => .macos,
343 .VERSION_MIN_IPHONEOS => .ios,
344 .VERSION_MIN_TVOS => .tvos,
345 .VERSION_MIN_WATCHOS => .watchos,
346 else => unreachable,
347 },
348 .abi = .none,
349 .version = appleVersionToSemanticVersion(cmd.version),
350 };
351 },
352 else => unreachable,
353 }
354 }
355
356 pub fn fromOptions(options: *const link.Options) Platform {
357 return .{
358 .os_tag = options.target.os.tag,
359 .abi = options.target.abi,
360 .version = options.target.os.version_range.semver.min,
361 };
362 }
363
364 pub fn toAppleVersion(plat: Platform) u32 {
365 return semanticVersionToAppleVersion(plat.version);
366 }
367
368 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
369 return switch (plat.os_tag) {
370 .macos => .MACOS,
371 .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
372 .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
373 .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
374 else => unreachable,
375 };
376 }
377
378 pub fn isBuildVersionCompatible(plat: Platform) bool {
379 inline for (supported_platforms) |sup_plat| {
380 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
381 return sup_plat[2] <= plat.toAppleVersion();
382 }
383 }
384 return false;
385 }
386
387 pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
388 const major = version.major;
389 const minor = version.minor;
390 const patch = version.patch;
391 return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
392 }
393
394 inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
395 return .{
396 .major = @as(u16, @truncate(version >> 16)),
397 .minor = @as(u8, @truncate(version >> 8)),
398 .patch = @as(u8, @truncate(version)),
399 };
400 }
401};
402
403const SupportedPlatforms = struct {
404 std.Target.Os.Tag,
405 std.Target.Abi,
406 u32, // Min platform version for which to emit LC_BUILD_VERSION
407 u32, // Min supported platform version
408 ?[]const u8, // Env var to look for
409};
410
411// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
412const supported_platforms = [_]SupportedPlatforms{
413 .{ .macos, .none, 0xA0E00, 0xA0800, "MACOSX_DEPLOYMENT_TARGET" },
414 .{ .ios, .none, 0xC0000, 0x70000, "IPHONEOS_DEPLOYMENT_TARGET" },
415 .{ .tvos, .none, 0xC0000, 0x70000, "TVOS_DEPLOYMENT_TARGET" },
416 .{ .watchos, .none, 0x50000, 0x20000, "WATCHOS_DEPLOYMENT_TARGET" },
417 .{ .ios, .simulator, 0xD0000, 0x80000, null },
418 .{ .tvos, .simulator, 0xD0000, 0x80000, null },
419 .{ .watchos, .simulator, 0x60000, 0x20000, null },
420};
421
422pub fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion {
305423 const stem = std.fs.path.stem(path);
306424 const start = for (stem, 0..) |c, i| {
307425 if (std.ascii.isDigit(c)) break i;
src/link/MachO/zld.zig+18-4
......@@ -345,10 +345,13 @@ pub fn linkWithZld(
345345 parent: u16,
346346 }, .Dynamic).init(arena);
347347
348 var parse_error_ctx: union {
349 none: void,
348 var parse_error_ctx: struct {
350349 detected_arch: std.Target.Cpu.Arch,
351 } = .{ .none = {} };
350 detected_os: std.Target.Os.Tag,
351 } = .{
352 .detected_arch = undefined,
353 .detected_os = undefined,
354 };
352355
353356 for (positionals.items) |obj| {
354357 const in_file = try std.fs.cwd().openFile(obj.path, .{});
......@@ -586,7 +589,18 @@ pub fn linkWithZld(
586589 try lc_writer.writeStruct(macho.source_version_command{
587590 .version = 0,
588591 });
589 try load_commands.writeBuildVersionLC(&macho_file.base.options, lc_writer);
592 {
593 const platform = load_commands.Platform.fromOptions(&macho_file.base.options);
594 const sdk_version: ?std.SemanticVersion = macho_file.base.options.darwin_sdk_version orelse blk: {
595 if (macho_file.base.options.sysroot) |path| break :blk load_commands.inferSdkVersionFromSdkPath(path);
596 break :blk null;
597 };
598 if (platform.isBuildVersionCompatible()) {
599 try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
600 } else {
601 try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
602 }
603 }
590604
591605 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
592606 try lc_writer.writeStruct(macho_file.uuid_cmd);