macho: re-enable --verbose-link

This commit is contained in:
Jakub Konka 2024-01-10 09:55:40 +01:00
parent dd0addab1f
commit 7588eeccea

View File

@ -1,5 +1,4 @@
base: File,
entry_name: ?[]const u8,
/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path.
llvm_object: ?*LlvmObject = null,
@ -47,9 +46,9 @@ atoms: std.ArrayListUnmanaged(Atom) = .{},
sdk_layout: ?SdkLayout,
/// Size of the __PAGEZERO segment.
pagezero_vmsize: u64,
pagezero_vmsize: ?u64,
/// Minimum space for future expansion of the load commands.
headerpad_size: u32,
headerpad_size: ?u32,
/// Set enough space as if all paths were MATPATHLEN.
headerpad_max_install_names: bool,
/// Remove dylibs that are unreachable by the entry point or exported symbols.
@ -61,6 +60,8 @@ install_name: ?[]const u8,
/// Path to entitlements file.
entitlements: ?[]const u8,
compatibility_version: ?std.SemanticVersion,
/// Entry name
entry_name: ?[]const u8,
/// Hot-code swapping state.
hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
@ -128,8 +129,8 @@ pub fn createEmpty(
.build_id = options.build_id,
.rpath_list = options.rpath_list,
},
.pagezero_vmsize = options.pagezero_size orelse default_pagezero_vmsize,
.headerpad_size = options.headerpad_size orelse default_headerpad_size,
.pagezero_vmsize = options.pagezero_size,
.headerpad_size = options.headerpad_size,
.headerpad_max_install_names = options.headerpad_max_install_names,
.dead_strip_dylibs = options.dead_strip_dylibs,
.sdk_layout = options.darwin_sdk_layout,
@ -249,9 +250,173 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
/// --verbose-link output
fn dumpArgv(self: *MachO, comp: *Compilation) !void {
_ = self;
_ = comp;
@panic("TODO dumpArgv");
const gpa = self.base.comp.gpa;
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const target = self.base.comp.root_mod.resolved_target.result;
const directory = self.base.emit.directory;
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: {
if (fs.path.dirname(full_out_path)) |dirname| {
break :blk try fs.path.join(arena, &.{ dirname, path });
} else {
break :blk path;
}
} else null;
var argv = std.ArrayList([]const u8).init(arena);
try argv.append("zig");
if (self.base.isStaticLib()) {
try argv.append("ar");
} else {
try argv.append("ld");
}
if (self.base.isObject()) {
try argv.append("-r");
}
try argv.append("-o");
try argv.append(full_out_path);
if (self.base.isRelocatable()) {
for (comp.objects) |obj| {
try argv.append(obj.path);
}
for (comp.c_object_table.keys()) |key| {
try argv.append(key.status.success.object_path);
}
if (module_obj_path) |p| {
try argv.append(p);
}
} else {
if (!self.base.isStatic()) {
try argv.append("-dynamic");
}
if (self.base.isDynLib()) {
try argv.append("-dylib");
if (self.install_name) |install_name| {
try argv.append("-install_name");
try argv.append(install_name);
}
}
{
const platform = Platform.fromTarget(target);
try argv.append("-platform_version");
try argv.append(@tagName(platform.os_tag));
try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
const sdk_version: ?std.SemanticVersion = self.inferSdkVersion();
if (sdk_version) |ver| {
try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
} else {
try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
}
}
if (comp.sysroot) |syslibroot| {
try argv.append("-syslibroot");
try argv.append(syslibroot);
}
for (self.base.rpath_list) |rpath| {
try argv.append("-rpath");
try argv.append(rpath);
}
if (self.pagezero_vmsize) |size| {
try argv.append("-pagezero_size");
try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
}
if (self.headerpad_size) |size| {
try argv.append("-headerpad_size");
try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
}
if (self.headerpad_max_install_names) {
try argv.append("-headerpad_max_install_names");
}
if (self.base.gc_sections) {
try argv.append("-dead_strip");
}
if (self.dead_strip_dylibs) {
try argv.append("-dead_strip_dylibs");
}
if (self.entry_name) |entry_name| {
try argv.appendSlice(&.{ "-e", entry_name });
}
for (comp.objects) |obj| {
// TODO: verify this
if (obj.must_link) {
try argv.append("-force_load");
}
try argv.append(obj.path);
}
for (comp.c_object_table.keys()) |key| {
try argv.append(key.status.success.object_path);
}
if (module_obj_path) |p| {
try argv.append(p);
}
if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path);
if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path);
if (comp.config.link_libcpp) {
try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
try argv.append(comp.libcxx_static_lib.?.full_object_path);
}
try argv.append("-o");
try argv.append(full_out_path);
try argv.append("-lSystem");
for (comp.system_libs.keys()) |l_name| {
const info = comp.system_libs.get(l_name).?;
const arg = if (info.needed)
try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
else if (info.weak)
try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name})
else
try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
try argv.append(arg);
}
for (self.frameworks) |framework| {
const name = std.fs.path.stem(framework.path);
const arg = if (framework.needed)
try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
else if (framework.weak)
try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
else
try std.fmt.allocPrint(arena, "-framework {s}", .{name});
try argv.append(arg);
}
if (self.base.isDynLib() and self.base.allow_shlib_undefined) {
try argv.append("-undefined");
try argv.append("dynamic_lookup");
}
}
Compilation.dump_argv(argv.items);
}
/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
@ -1034,38 +1199,6 @@ pub const Section = struct {
free_list: std.ArrayListUnmanaged(Atom.Index) = .{},
};
const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata);
const LazySymbolMetadata = struct {
const State = enum { unused, pending_flush, flushed };
text_atom: Atom.Index = undefined,
data_const_atom: Atom.Index = undefined,
text_state: State = .unused,
data_const_state: State = .unused,
};
const DeclMetadata = struct {
atom: Atom.Index,
section: u8,
/// A list of all exports aliases of this Decl.
/// TODO do we actually need this at all?
exports: std.ArrayListUnmanaged(u32) = .{},
fn getExport(m: DeclMetadata, macho_file: *const MachO, name: []const u8) ?u32 {
for (m.exports.items) |exp| {
if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp }))) return exp;
}
return null;
}
fn getExportPtr(m: *DeclMetadata, macho_file: *MachO, name: []const u8) ?*u32 {
for (m.exports.items) |*exp| {
if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp.* }))) return exp;
}
return null;
}
};
const HotUpdateState = struct {
mach_task: ?std.os.darwin.MachTask = null,
};
@ -1091,18 +1224,32 @@ pub const null_sym = macho.nlist_64{
};
pub const Platform = struct {
platform: macho.PLATFORM,
version: Version,
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 lc_cmd = lc.cast(macho.build_version_command).?;
const cmd = lc.cast(macho.build_version_command).?;
return .{
.platform = lc_cmd.platform,
.version = .{ .value = lc_cmd.minos },
.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,
@ -1110,22 +1257,45 @@ pub const Platform = struct {
.VERSION_MIN_TVOS,
.VERSION_MIN_WATCHOS,
=> {
const lc_cmd = lc.cast(macho.version_min_command).?;
const cmd = lc.cast(macho.version_min_command).?;
return .{
.platform = switch (lc.cmd()) {
.VERSION_MIN_MACOSX => .MACOS,
.VERSION_MIN_IPHONEOS => .IOS,
.VERSION_MIN_TVOS => .TVOS,
.VERSION_MIN_WATCHOS => .WATCHOS,
.os_tag = switch (lc.cmd()) {
.VERSION_MIN_MACOSX => .macos,
.VERSION_MIN_IPHONEOS => .ios,
.VERSION_MIN_TVOS => .tvos,
.VERSION_MIN_WATCHOS => .watchos,
else => unreachable,
},
.version = .{ .value = lc_cmd.version },
.abi = .none,
.version = appleVersionToSemanticVersion(cmd.version),
};
},
else => unreachable,
}
}
pub fn fromTarget(target: std.Target) Platform {
return .{
.os_tag = target.os.tag,
.abi = target.abi,
.version = 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.platform) {
@ -1134,76 +1304,152 @@ pub const Platform = struct {
}
return false;
}
};
pub const Version = struct {
value: u32,
pub fn major(v: Version) u16 {
return @as(u16, @truncate(v.value >> 16));
}
pub fn minor(v: Version) u8 {
return @as(u8, @truncate(v.value >> 8));
}
pub fn patch(v: Version) u8 {
return @as(u8, @truncate(v.value));
}
pub fn parse(raw: []const u8) ?Version {
var parsed: [3]u16 = [_]u16{0} ** 3;
var count: usize = 0;
var it = std.mem.splitAny(u8, raw, ".");
while (it.next()) |comp| {
if (count >= 3) return null;
parsed[count] = std.fmt.parseInt(u16, comp, 10) catch return null;
count += 1;
pub fn isVersionMinCompatible(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[3] <= plat.toAppleVersion();
}
}
if (count == 0) return null;
const maj = parsed[0];
const min = std.math.cast(u8, parsed[1]) orelse return null;
const pat = std.math.cast(u8, parsed[2]) orelse return null;
return Version.new(maj, min, pat);
return false;
}
pub fn new(maj: u16, min: u8, pat: u8) Version {
return .{ .value = (@as(u32, @intCast(maj)) << 16) | (@as(u32, @intCast(min)) << 8) | pat };
pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) {
return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
}
pub fn format(
v: Version,
const FmtCtx = struct {
platform: Platform,
cpu_arch: std.Target.Cpu.Arch,
};
pub fn formatTarget(
ctx: FmtCtx,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.print("{d}.{d}.{d}", .{
v.major(),
v.minor(),
v.patch(),
});
try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) });
if (ctx.platform.abi != .none) {
try writer.print("-{s}", .{@tagName(ctx.platform.abi)});
}
}
/// Caller owns the memory.
pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
var buffer = std.ArrayList(u8).init(gpa);
defer buffer.deinit();
try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)});
return buffer.toOwnedSlice();
}
pub fn eqlTarget(plat: Platform, other: Platform) bool {
return plat.os_tag == other.os_tag and plat.abi == other.abi;
}
};
const SupportedPlatforms = struct {
macho.PLATFORM, // Platform identifier
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
// zig fmt: off
const supported_platforms = [_]SupportedPlatforms{
.{ .MACOS, 0xA0E00, 0xA0800, "MACOSX_DEPLOYMENT_TARGET" },
.{ .IOS, 0xC0000, 0x70000, "IPHONEOS_DEPLOYMENT_TARGET" },
.{ .TVOS, 0xC0000, 0x70000, "TVOS_DEPLOYMENT_TARGET" },
.{ .WATCHOS, 0x50000, 0x20000, "WATCHOS_DEPLOYMENT_TARGET" },
.{ .IOSSIMULATOR, 0xD0000, 0x80000, null },
.{ .TVOSSIMULATOR, 0xD0000, 0x80000, null },
.{ .WATCHOSSIMULATOR, 0x60000, 0x20000, null },
.{ .macos, .none, 0xA0E00, 0xA0800 },
.{ .ios, .none, 0xC0000, 0x70000 },
.{ .tvos, .none, 0xC0000, 0x70000 },
.{ .watchos, .none, 0x50000, 0x20000 },
.{ .ios, .simulator, 0xD0000, 0x80000 },
.{ .tvos, .simulator, 0xD0000, 0x80000 },
.{ .watchos, .simulator, 0x60000, 0x20000 },
};
// zig fmt: on
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));
}
pub 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)),
};
}
fn inferSdkVersion(self: *MachO) ?std.SemanticVersion {
const comp = self.base.comp;
const gpa = comp.gpa;
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const sdk_layout = self.sdk_layout orelse return null;
const sdk_dir = switch (sdk_layout) {
.sdk => comp.sysroot.?,
.vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
};
if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
return parseSdkVersion(ver);
} else |_| {
// Read from settings should always succeed when vendored.
if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
}
// infer from pathname
const stem = std.fs.path.stem(sdk_dir);
const start = for (stem, 0..) |c, i| {
if (std.ascii.isDigit(c)) break i;
} else stem.len;
const end = for (stem[start..], start..) |c, i| {
if (std.ascii.isDigit(c) or c == '.') continue;
break i;
} else stem.len;
return parseSdkVersion(stem[start..end]);
}
// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
// Use property `MinimalDisplayName` to determine version.
// The file/property is also available with vendored libc.
fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
return error.SdkVersionFailure;
}
// Versions reported by Apple aren't exactly semantically valid as they usually omit
// the patch component, so we parse SDK value by hand.
fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
var parsed: std.SemanticVersion = .{
.major = 0,
.minor = 0,
.patch = 0,
};
const parseNext = struct {
fn parseNext(it: anytype) ?u16 {
const nn = it.next() orelse return null;
return std.fmt.parseInt(u16, nn, 10) catch null;
}
}.parseNext;
var it = std.mem.splitAny(u8, raw, ".");
parsed.major = parseNext(&it) orelse return null;
parsed.minor = parseNext(&it) orelse return null;
parsed.patch = parseNext(&it) orelse 0;
return parsed;
}
/// When allocating, the ideal_capacity is calculated by
/// actual_capacity + (actual_capacity / ideal_factor)