From 2e28ab153c39df77403eb64f282589349f05921d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 28 Aug 2023 20:55:45 +0200 Subject: [PATCH] macho: parse platform info from each object file into Platform struct --- src/link/MachO.zig | 19 +++- src/link/MachO/Dylib.zig | 8 +- src/link/MachO/Object.zig | 21 ++++ src/link/MachO/load_commands.zig | 172 ++++++++++++++++++++++++++----- src/link/MachO/zld.zig | 22 +++- 5 files changed, 203 insertions(+), 39 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6a1f21778d..5bde4575c5 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -585,7 +585,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try lc_writer.writeStruct(macho.source_version_command{ .version = 0, }); - try load_commands.writeBuildVersionLC(&self.base.options, lc_writer); + { + const platform = load_commands.Platform.fromOptions(&self.base.options); + const sdk_version: ?std.SemanticVersion = self.base.options.darwin_sdk_version orelse blk: { + if (self.base.options.sysroot) |path| break :blk load_commands.inferSdkVersionFromSdkPath(path); + break :blk null; + }; + if (platform.isBuildVersionCompatible()) { + try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer); + } else { + try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer); + } + } const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len)); try lc_writer.writeStruct(self.uuid_cmd); @@ -797,7 +808,7 @@ fn parseObject( const self_cpu_arch = self.base.options.target.cpu.arch; if (self_cpu_arch != cpu_arch) { - error_ctx.* = .{ .detected_arch = cpu_arch }; + error_ctx.detected_arch = cpu_arch; return error.InvalidArch; } } @@ -894,7 +905,7 @@ fn parseArchive( else => unreachable, }; if (cpu_arch != parsed_cpu_arch) { - error_ctx.* = .{ .detected_arch = parsed_cpu_arch }; + error_ctx.detected_arch = parsed_cpu_arch; return error.InvalidArch; } } @@ -959,7 +970,7 @@ fn parseDylib( else => unreachable, }; if (self_cpu_arch != cpu_arch) { - error_ctx.* = .{ .detected_arch = cpu_arch }; + error_ctx.detected_arch = cpu_arch; return error.InvalidArch; } diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index bee276881e..6dd7b6ae96 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -217,7 +217,7 @@ const TargetMatcher = struct { target: CrossTarget, target_strings: std.ArrayListUnmanaged([]const u8) = .{}, - fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher { + pub fn init(allocator: Allocator, target: CrossTarget) !TargetMatcher { var self = TargetMatcher{ .allocator = allocator, .target = target, @@ -239,7 +239,7 @@ const TargetMatcher = struct { return self; } - fn deinit(self: *TargetMatcher) void { + pub fn deinit(self: *TargetMatcher) void { for (self.target_strings.items) |t| { self.allocator.free(t); } @@ -263,7 +263,7 @@ const TargetMatcher = struct { }; } - fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 { + pub fn targetToAppleString(allocator: Allocator, target: CrossTarget) ![]const u8 { const cpu_arch = cpuArchToAppleString(target.cpu_arch.?); const os_tag = @tagName(target.os_tag.?); const target_abi = abiToAppleString(target.abi orelse .none); @@ -291,7 +291,7 @@ const TargetMatcher = struct { return hasValue(archs, cpuArchToAppleString(self.target.cpu_arch.?)); } - fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool { + pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool { var arena = std.heap.ArenaAllocator.init(self.allocator); defer arena.deinit(); diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 4af0c3e7aa..92f2899d8b 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -940,6 +940,26 @@ pub fn parseDwarfInfo(self: Object) DwarfInfo { return di; } +/// Returns Options.Platform composed from the first encountered build version type load command: +/// either LC_BUILD_VERSION or LC_VERSION_MIN_*. +pub fn getPlatform(self: Object) ?Platform { + var it = LoadCommandIterator{ + .ncmds = self.header.ncmds, + .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], + }; + while (it.next()) |cmd| { + switch (cmd.cmd()) { + .BUILD_VERSION, + .VERSION_MIN_MACOSX, + .VERSION_MIN_IPHONEOS, + .VERSION_MIN_TVOS, + .VERSION_MIN_WATCHOS, + => return Platform.fromLoadCommand(cmd), + else => {}, + } + } else return null; +} + pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 { const size = @as(usize, @intCast(sect.size)); return self.contents[sect.offset..][0..size]; @@ -1089,5 +1109,6 @@ const Atom = @import("Atom.zig"); const DwarfInfo = @import("DwarfInfo.zig"); const LoadCommandIterator = macho.LoadCommandIterator; const MachO = @import("../MachO.zig"); +const Platform = @import("load_commands.zig").Platform; const SymbolWithLoc = MachO.SymbolWithLoc; const UnwindInfo = @import("UnwindInfo.zig"); diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig index d7b13104bf..6b326c34d3 100644 --- a/src/link/MachO/load_commands.zig +++ b/src/link/MachO/load_commands.zig @@ -76,8 +76,14 @@ fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx } // LC_SOURCE_VERSION sizeofcmds += @sizeOf(macho.source_version_command); - // LC_BUILD_VERSION - sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); + // LC_BUILD_VERSION or LC_VERSION_MIN_ + if (Platform.fromOptions(options).isBuildVersionCompatible()) { + // LC_BUILD_VERSION + sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); + } else { + // LC_VERSION_MIN_ + sizeofcmds += @sizeOf(macho.version_min_command); + } // LC_UUID sizeofcmds += @sizeOf(macho.uuid_command); // LC_LOAD_DYLIB @@ -252,33 +258,28 @@ pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: an } } -pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !void { +pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { + const cmd: macho.LC = switch (platform.os_tag) { + .macos => .VERSION_MIN_MACOSX, + .ios => .VERSION_MIN_IPHONEOS, + .tvos => .VERSION_MIN_TVOS, + .watchos => .VERSION_MIN_WATCHOS, + else => unreachable, + }; + try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{ + .cmd = cmd, + .version = platform.toAppleVersion(), + .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(), + })); +} + +pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); - const platform_version = blk: { - const ver = options.target.os.version_range.semver.min; - const platform_version = @as(u32, @intCast(ver.major << 16 | ver.minor << 8)); - break :blk platform_version; - }; - const sdk_version: ?std.SemanticVersion = options.darwin_sdk_version orelse blk: { - if (options.sysroot) |path| break :blk inferSdkVersionFromSdkPath(path); - break :blk null; - }; - const sdk_version_value: u32 = if (sdk_version) |ver| - @intCast(ver.major << 16 | ver.minor << 8) - else - platform_version; - const is_simulator_abi = options.target.abi == .simulator; try lc_writer.writeStruct(macho.build_version_command{ .cmdsize = cmdsize, - .platform = switch (options.target.os.tag) { - .macos => .MACOS, - .ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS, - .watchos => if (is_simulator_abi) macho.PLATFORM.WATCHOSSIMULATOR else macho.PLATFORM.WATCHOS, - .tvos => if (is_simulator_abi) macho.PLATFORM.TVOSSIMULATOR else macho.PLATFORM.TVOS, - else => unreachable, - }, - .minos = platform_version, - .sdk = sdk_version_value, + .platform = platform.toApplePlatform(), + .minos = platform.toAppleVersion(), + .sdk = if (sdk_version) |ver| Platform.semanticVersionToAppleVersion(ver) else platform.toAppleVersion(), .ntools = 1, }); 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 } } -fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion { +pub const Platform = struct { + os_tag: std.Target.Os.Tag, + abi: std.Target.Abi, + version: std.SemanticVersion, + + /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to + /// the extracted minimum platform version. + pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform { + switch (lc.cmd()) { + .BUILD_VERSION => { + const cmd = lc.cast(macho.build_version_command).?; + return .{ + .os_tag = switch (cmd.platform) { + .MACOS => .macos, + .IOS, .IOSSIMULATOR => .ios, + .TVOS, .TVOSSIMULATOR => .tvos, + .WATCHOS, .WATCHOSSIMULATOR => .watchos, + else => @panic("TODO"), + }, + .abi = switch (cmd.platform) { + .IOSSIMULATOR, + .TVOSSIMULATOR, + .WATCHOSSIMULATOR, + => .simulator, + else => .none, + }, + .version = appleVersionToSemanticVersion(cmd.minos), + }; + }, + .VERSION_MIN_MACOSX, + .VERSION_MIN_IPHONEOS, + .VERSION_MIN_TVOS, + .VERSION_MIN_WATCHOS, + => { + const cmd = lc.cast(macho.version_min_command).?; + return .{ + .os_tag = switch (lc.cmd()) { + .VERSION_MIN_MACOSX => .macos, + .VERSION_MIN_IPHONEOS => .ios, + .VERSION_MIN_TVOS => .tvos, + .VERSION_MIN_WATCHOS => .watchos, + else => unreachable, + }, + .abi = .none, + .version = appleVersionToSemanticVersion(cmd.version), + }; + }, + else => unreachable, + } + } + + pub fn fromOptions(options: *const link.Options) Platform { + return .{ + .os_tag = options.target.os.tag, + .abi = options.target.abi, + .version = options.target.os.version_range.semver.min, + }; + } + + pub fn toAppleVersion(plat: Platform) u32 { + return semanticVersionToAppleVersion(plat.version); + } + + pub fn toApplePlatform(plat: Platform) macho.PLATFORM { + return switch (plat.os_tag) { + .macos => .MACOS, + .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS, + .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS, + .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS, + else => unreachable, + }; + } + + pub fn isBuildVersionCompatible(plat: Platform) bool { + inline for (supported_platforms) |sup_plat| { + if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { + return sup_plat[2] <= plat.toAppleVersion(); + } + } + return false; + } + + pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 { + const major = version.major; + const minor = version.minor; + const patch = version.patch; + return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch)); + } + + inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion { + return .{ + .major = @as(u16, @truncate(version >> 16)), + .minor = @as(u8, @truncate(version >> 8)), + .patch = @as(u8, @truncate(version)), + }; + } +}; + +const SupportedPlatforms = struct { + std.Target.Os.Tag, + std.Target.Abi, + u32, // Min platform version for which to emit LC_BUILD_VERSION + u32, // Min supported platform version + ?[]const u8, // Env var to look for +}; + +// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52 +const supported_platforms = [_]SupportedPlatforms{ + .{ .macos, .none, 0xA0E00, 0xA0800, "MACOSX_DEPLOYMENT_TARGET" }, + .{ .ios, .none, 0xC0000, 0x70000, "IPHONEOS_DEPLOYMENT_TARGET" }, + .{ .tvos, .none, 0xC0000, 0x70000, "TVOS_DEPLOYMENT_TARGET" }, + .{ .watchos, .none, 0x50000, 0x20000, "WATCHOS_DEPLOYMENT_TARGET" }, + .{ .ios, .simulator, 0xD0000, 0x80000, null }, + .{ .tvos, .simulator, 0xD0000, 0x80000, null }, + .{ .watchos, .simulator, 0x60000, 0x20000, null }, +}; + +pub fn inferSdkVersionFromSdkPath(path: []const u8) ?std.SemanticVersion { const stem = std.fs.path.stem(path); const start = for (stem, 0..) |c, i| { if (std.ascii.isDigit(c)) break i; diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig index 011158ba24..1a850b58a0 100644 --- a/src/link/MachO/zld.zig +++ b/src/link/MachO/zld.zig @@ -345,10 +345,13 @@ pub fn linkWithZld( parent: u16, }, .Dynamic).init(arena); - var parse_error_ctx: union { - none: void, + var parse_error_ctx: struct { detected_arch: std.Target.Cpu.Arch, - } = .{ .none = {} }; + detected_os: std.Target.Os.Tag, + } = .{ + .detected_arch = undefined, + .detected_os = undefined, + }; for (positionals.items) |obj| { const in_file = try std.fs.cwd().openFile(obj.path, .{}); @@ -586,7 +589,18 @@ pub fn linkWithZld( try lc_writer.writeStruct(macho.source_version_command{ .version = 0, }); - try load_commands.writeBuildVersionLC(&macho_file.base.options, lc_writer); + { + const platform = load_commands.Platform.fromOptions(&macho_file.base.options); + const sdk_version: ?std.SemanticVersion = macho_file.base.options.darwin_sdk_version orelse blk: { + if (macho_file.base.options.sysroot) |path| break :blk load_commands.inferSdkVersionFromSdkPath(path); + break :blk null; + }; + if (platform.isBuildVersionCompatible()) { + try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer); + } else { + try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer); + } + } const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len)); try lc_writer.writeStruct(macho_file.uuid_cmd);