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

This commit is contained in:
Jakub Konka
2023-08-28 20:55:45 +02:00
parent ec03619dcf
commit 2e28ab153c
5 changed files with 203 additions and 39 deletions

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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");

View File

@@ -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;

View File

@@ -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);