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...@@ -585,7 +585,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
585 try lc_writer.writeStruct(macho.source_version_command{585 try lc_writer.writeStruct(macho.source_version_command{
586 .version = 0,586 .version = 0,
587 });587 });
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
590 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));601 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
591 try lc_writer.writeStruct(self.uuid_cmd);602 try lc_writer.writeStruct(self.uuid_cmd);
...@@ -797,7 +808,7 @@ fn parseObject(...@@ -797,7 +808,7 @@ fn parseObject(
797 const self_cpu_arch = self.base.options.target.cpu.arch;808 const self_cpu_arch = self.base.options.target.cpu.arch;
798809
799 if (self_cpu_arch != cpu_arch) {810 if (self_cpu_arch != cpu_arch) {
800 error_ctx.* = .{ .detected_arch = cpu_arch };811 error_ctx.detected_arch = cpu_arch;
801 return error.InvalidArch;812 return error.InvalidArch;
802 }813 }
803}814}
...@@ -894,7 +905,7 @@ fn parseArchive(...@@ -894,7 +905,7 @@ fn parseArchive(
894 else => unreachable,905 else => unreachable,
895 };906 };
896 if (cpu_arch != parsed_cpu_arch) {907 if (cpu_arch != parsed_cpu_arch) {
897 error_ctx.* = .{ .detected_arch = parsed_cpu_arch };908 error_ctx.detected_arch = parsed_cpu_arch;
898 return error.InvalidArch;909 return error.InvalidArch;
899 }910 }
900 }911 }
...@@ -959,7 +970,7 @@ fn parseDylib(...@@ -959,7 +970,7 @@ fn parseDylib(
959 else => unreachable,970 else => unreachable,
960 };971 };
961 if (self_cpu_arch != cpu_arch) {972 if (self_cpu_arch != cpu_arch) {
962 error_ctx.* = .{ .detected_arch = cpu_arch };973 error_ctx.detected_arch = cpu_arch;
963 return error.InvalidArch;974 return error.InvalidArch;
964 }975 }
965976
src/link/MachO/Dylib.zig+4-4
...@@ -217,7 +217,7 @@ const TargetMatcher = struct {...@@ -217,7 +217,7 @@ const TargetMatcher = struct {
217 target: CrossTarget,217 target: CrossTarget,
218 target_strings: std.ArrayListUnmanaged([]const u8) = .{},218 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
219219
220 fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher {220 pub fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher {
221 var self = TargetMatcher{221 var self = TargetMatcher{
222 .allocator = allocator,222 .allocator = allocator,
223 .target = target,223 .target = target,
...@@ -239,7 +239,7 @@ const TargetMatcher = struct {...@@ -239,7 +239,7 @@ const TargetMatcher = struct {
239 return self;239 return self;
240 }240 }
241241
242 fn deinit(self: *TargetMatcher) void {242 pub fn deinit(self: *TargetMatcher) void {
243 for (self.target_strings.items) |t| {243 for (self.target_strings.items) |t| {
244 self.allocator.free(t);244 self.allocator.free(t);
245 }245 }
...@@ -263,7 +263,7 @@ const TargetMatcher = struct {...@@ -263,7 +263,7 @@ const TargetMatcher = struct {
263 };263 };
264 }264 }
265265
266 fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 {266 pub fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 {
267 const cpu_arch = cpuArchToAppleString(target.cpu_arch.?);267 const cpu_arch = cpuArchToAppleString(target.cpu_arch.?);
268 const os_tag = @tagName(target.os_tag.?);268 const os_tag = @tagName(target.os_tag.?);
269 const target_abi = abiToAppleString(target.abi orelse .none);269 const target_abi = abiToAppleString(target.abi orelse .none);
...@@ -291,7 +291,7 @@ const TargetMatcher = struct {...@@ -291,7 +291,7 @@ const TargetMatcher = struct {
291 return hasValue(archs, cpuArchToAppleString(self.target.cpu_arch.?));291 return hasValue(archs, cpuArchToAppleString(self.target.cpu_arch.?));
292 }292 }
293293
294 fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {294 pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
295 var arena = std.heap.ArenaAllocator.init(self.allocator);295 var arena = std.heap.ArenaAllocator.init(self.allocator);
296 defer arena.deinit();296 defer arena.deinit();
297297
src/link/MachO/Object.zig+21
...@@ -940,6 +940,26 @@ pub fn parseDwarfInfo(self: Object) DwarfInfo {...@@ -940,6 +940,26 @@ pub fn parseDwarfInfo(self: Object) DwarfInfo {
940 return di;940 return di;
941}941}
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
943pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {963pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {
944 const size = @as(usize, @intCast(sect.size));964 const size = @as(usize, @intCast(sect.size));
945 return self.contents[sect.offset..][0..size];965 return self.contents[sect.offset..][0..size];
...@@ -1089,5 +1109,6 @@ const Atom = @import("Atom.zig");...@@ -1089,5 +1109,6 @@ const Atom = @import("Atom.zig");
1089const DwarfInfo = @import("DwarfInfo.zig");1109const DwarfInfo = @import("DwarfInfo.zig");
1090const LoadCommandIterator = macho.LoadCommandIterator;1110const LoadCommandIterator = macho.LoadCommandIterator;
1091const MachO = @import("../MachO.zig");1111const MachO = @import("../MachO.zig");
1112const Platform = @import("load_commands.zig").Platform;
1092const SymbolWithLoc = MachO.SymbolWithLoc;1113const SymbolWithLoc = MachO.SymbolWithLoc;
1093const UnwindInfo = @import("UnwindInfo.zig");1114const 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...@@ -76,8 +76,14 @@ fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx
76 }76 }
77 // LC_SOURCE_VERSION77 // LC_SOURCE_VERSION
78 sizeofcmds += @sizeOf(macho.source_version_command);78 sizeofcmds += @sizeOf(macho.source_version_command);
79 // LC_BUILD_VERSION79 // LC_BUILD_VERSION or LC_VERSION_MIN_
80 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);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 }
81 // LC_UUID87 // LC_UUID
82 sizeofcmds += @sizeOf(macho.uuid_command);88 sizeofcmds += @sizeOf(macho.uuid_command);
83 // LC_LOAD_DYLIB89 // LC_LOAD_DYLIB
...@@ -252,33 +258,28 @@ pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: an...@@ -252,33 +258,28 @@ pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: an
252 }258 }
253}259}
254260
255pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !void {261pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
256 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);262 const cmd: macho.LC = switch (platform.os_tag) {
257 const platform_version = blk: {263 .macos => .VERSION_MIN_MACOSX,
258 const ver = options.target.os.version_range.semver.min;264 .ios => .VERSION_MIN_IPHONEOS,
259 const platform_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8));265 .tvos => .VERSION_MIN_TVOS,
260 break :blk platform_version;266 .watchos => .VERSION_MIN_WATCHOS,
261 };267 else => unreachable,
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;
265 };268 };
266 const sdk_version_value: u32 = if (sdk_version) |ver|269 try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{
267 @intCast(ver.major << 16 | ver.minor << 8)270 .cmd = cmd,
268 else271 .version = platform.toAppleVersion(),
269 platform_version;272 .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
270 const is_simulator_abi = options.target.abi == .simulator;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);
271 try lc_writer.writeStruct(macho.build_version_command{278 try lc_writer.writeStruct(macho.build_version_command{
272 .cmdsize = cmdsize,279 .cmdsize = cmdsize,
273 .platform = switch (options.target.os.tag) {280 .platform = platform.toApplePlatform(),
274 .macos => .MACOS,281 .minos = platform.toAppleVersion(),
275 .ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS,282 .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
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,
282 .ntools = 1,283 .ntools = 1,
283 });284 });
284 try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{285 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...@@ -301,7 +302,124 @@ pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: an
301 }302 }
302}303}
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 {
305 const stem = std.fs.path.stem(path);423 const stem = std.fs.path.stem(path);
306 const start = for (stem, 0..) |c, i| {424 const start = for (stem, 0..) |c, i| {
307 if (std.ascii.isDigit(c)) break i;425 if (std.ascii.isDigit(c)) break i;
src/link/MachO/zld.zig+18-4
...@@ -345,10 +345,13 @@ pub fn linkWithZld(...@@ -345,10 +345,13 @@ pub fn linkWithZld(
345 parent: u16,345 parent: u16,
346 }, .Dynamic).init(arena);346 }, .Dynamic).init(arena);
347347
348 var parse_error_ctx: union {348 var parse_error_ctx: struct {
349 none: void,
350 detected_arch: std.Target.Cpu.Arch,349 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
353 for (positionals.items) |obj| {356 for (positionals.items) |obj| {
354 const in_file = try std.fs.cwd().openFile(obj.path, .{});357 const in_file = try std.fs.cwd().openFile(obj.path, .{});
...@@ -586,7 +589,18 @@ pub fn linkWithZld(...@@ -586,7 +589,18 @@ pub fn linkWithZld(
586 try lc_writer.writeStruct(macho.source_version_command{589 try lc_writer.writeStruct(macho.source_version_command{
587 .version = 0,590 .version = 0,
588 });591 });
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
591 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));605 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
592 try lc_writer.writeStruct(macho_file.uuid_cmd);606 try lc_writer.writeStruct(macho_file.uuid_cmd);