macho: dedup LC emitting logic

Fix path written to `LC_ID_DYLIB` to include the current CWD (if any).
This commit is contained in:
Jakub Konka 2022-12-14 11:46:46 +01:00
parent 0d92fcf6a5
commit db2052bc35
4 changed files with 371 additions and 621 deletions

View File

@ -585,10 +585,13 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/ZldAtom.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/zld.zig"
"${CMAKE_SOURCE_DIR}/src/link/Plan9.zig"

View File

@ -20,6 +20,7 @@ const dead_strip = @import("MachO/dead_strip.zig");
const fat = @import("MachO/fat.zig");
const link = @import("../link.zig");
const llvm_backend = @import("../codegen/llvm.zig");
const load_commands = @import("MachO/load_commands.zig");
const target_util = @import("../target.zig");
const trace = @import("../tracy.zig").trace;
const zld = @import("MachO/zld.zig");
@ -265,9 +266,6 @@ pub const SymbolWithLoc = struct {
/// actual_capacity + (actual_capacity / ideal_factor)
const ideal_factor = 3;
/// Default path to dyld
pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
/// In order for a slice of bytes to be considered eligible to keep metadata pointing at
/// it as a possible place to put new symbols, it must have enough room for this many bytes
/// (plus extra for reserved capacity).
@ -561,17 +559,24 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
var ncmds: u32 = 0;
try self.writeLinkeditSegmentData(&ncmds, lc_writer);
try writeDylinkerLC(&ncmds, lc_writer);
try load_commands.writeDylinkerLC(&ncmds, lc_writer);
self.writeMainLC(&ncmds, lc_writer) catch |err| switch (err) {
error.MissingMainEntrypoint => {
self.error_flags.no_entry_point_found = true;
},
else => |e| return e,
};
if (self.base.options.output_mode == .Exe) blk: {
const seg_id = self.header_segment_cmd_index.?;
const seg = self.segments.items[seg_id];
const global = self.getEntryPoint() catch |err| switch (err) {
error.MissingMainEntrypoint => {
self.error_flags.no_entry_point_found = true;
break :blk;
},
else => |e| return e,
};
const sym = self.getSymbol(global);
try load_commands.writeMainLC(@intCast(u32, sym.n_value - seg.vmaddr), &self.base.options, &ncmds, lc_writer);
}
try self.writeDylibIdLC(&ncmds, lc_writer);
try self.writeRpathLCs(&ncmds, lc_writer);
try load_commands.writeDylibIdLC(self.base.allocator, &self.base.options, &ncmds, lc_writer);
try load_commands.writeRpathLCs(self.base.allocator, &self.base.options, &ncmds, lc_writer);
{
try lc_writer.writeStruct(macho.source_version_command{
@ -581,7 +586,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
ncmds += 1;
}
try self.writeBuildVersionLC(&ncmds, lc_writer);
try load_commands.writeBuildVersionLC(&self.base.options, &ncmds, lc_writer);
{
std.crypto.random.bytes(&self.uuid.uuid);
@ -589,7 +594,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
ncmds += 1;
}
try self.writeLoadDylibLCs(&ncmds, lc_writer);
try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), &ncmds, lc_writer);
const target = self.base.options.target;
const requires_codesig = blk: {
@ -1702,195 +1707,6 @@ pub fn resolveDyldStubBinder(self: *MachO) !void {
try self.writePtrWidthAtom(got_atom);
}
pub fn writeDylinkerLC(ncmds: *u32, lc_writer: anytype) !void {
const name_len = mem.sliceTo(default_dyld_path, 0).len;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.dylinker_command) + name_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.dylinker_command{
.cmd = .LOAD_DYLINKER,
.cmdsize = cmdsize,
.name = @sizeOf(macho.dylinker_command),
});
try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0));
const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
pub fn writeMainLC(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
if (self.base.options.output_mode != .Exe) return;
const seg_id = self.header_segment_cmd_index.?;
const seg = self.segments.items[seg_id];
const global = try self.getEntryPoint();
const sym = self.getSymbol(global);
try lc_writer.writeStruct(macho.entry_point_command{
.cmd = .MAIN,
.cmdsize = @sizeOf(macho.entry_point_command),
.entryoff = @intCast(u32, sym.n_value - seg.vmaddr),
.stacksize = self.base.options.stack_size_override orelse 0,
});
ncmds.* += 1;
}
const WriteDylibLCCtx = struct {
cmd: macho.LC,
name: []const u8,
timestamp: u32 = 2,
current_version: u32 = 0x10000,
compatibility_version: u32 = 0x10000,
};
pub fn writeDylibLC(ctx: WriteDylibLCCtx, ncmds: *u32, lc_writer: anytype) !void {
const name_len = ctx.name.len + 1;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.dylib_command) + name_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.dylib_command{
.cmd = ctx.cmd,
.cmdsize = cmdsize,
.dylib = .{
.name = @sizeOf(macho.dylib_command),
.timestamp = ctx.timestamp,
.current_version = ctx.current_version,
.compatibility_version = ctx.compatibility_version,
},
});
try lc_writer.writeAll(ctx.name);
try lc_writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
pub fn writeDylibIdLC(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
if (self.base.options.output_mode != .Lib) return;
const install_name = self.base.options.install_name orelse self.base.options.emit.?.sub_path;
const curr = self.base.options.version orelse std.builtin.Version{
.major = 1,
.minor = 0,
.patch = 0,
};
const compat = self.base.options.compatibility_version orelse std.builtin.Version{
.major = 1,
.minor = 0,
.patch = 0,
};
try writeDylibLC(.{
.cmd = .ID_DYLIB,
.name = install_name,
.current_version = curr.major << 16 | curr.minor << 8 | curr.patch,
.compatibility_version = compat.major << 16 | compat.minor << 8 | compat.patch,
}, ncmds, lc_writer);
}
const RpathIterator = struct {
buffer: []const []const u8,
table: std.StringHashMap(void),
count: usize = 0,
fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator {
return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) };
}
fn deinit(it: *RpathIterator) void {
it.table.deinit();
}
fn next(it: *RpathIterator) !?[]const u8 {
while (true) {
if (it.count >= it.buffer.len) return null;
const rpath = it.buffer[it.count];
it.count += 1;
const gop = try it.table.getOrPut(rpath);
if (gop.found_existing) continue;
return rpath;
}
}
};
pub fn writeRpathLCs(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
const gpa = self.base.allocator;
var it = RpathIterator.init(gpa, self.base.options.rpath_list);
defer it.deinit();
while (try it.next()) |rpath| {
const rpath_len = rpath.len + 1;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.rpath_command) + rpath_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.rpath_command{
.cmdsize = cmdsize,
.path = @sizeOf(macho.rpath_command),
});
try lc_writer.writeAll(rpath);
try lc_writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
}
pub fn writeBuildVersionLC(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
const platform_version = blk: {
const ver = self.base.options.target.os.version_range.semver.min;
const platform_version = ver.major << 16 | ver.minor << 8;
break :blk platform_version;
};
const sdk_version = if (self.base.options.native_darwin_sdk) |sdk| blk: {
const ver = sdk.version;
const sdk_version = ver.major << 16 | ver.minor << 8;
break :blk sdk_version;
} else platform_version;
const is_simulator_abi = self.base.options.target.abi == .simulator;
try lc_writer.writeStruct(macho.build_version_command{
.cmdsize = cmdsize,
.platform = switch (self.base.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,
.ntools = 1,
});
try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
.tool = .LD,
.version = 0x0,
}));
ncmds.* += 1;
}
pub fn writeLoadDylibLCs(self: *MachO, ncmds: *u32, lc_writer: anytype) !void {
for (self.referenced_dylibs.keys()) |id| {
const dylib = self.dylibs.items[id];
const dylib_id = dylib.id orelse unreachable;
try writeDylibLC(.{
.cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB,
.name = dylib_id.name,
.timestamp = dylib_id.timestamp,
.current_version = dylib_id.current_version,
.compatibility_version = dylib_id.compatibility_version,
}, ncmds, lc_writer);
}
}
pub fn deinit(self: *MachO) void {
const gpa = self.base.allocator;
@ -2976,98 +2792,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
}
}
pub inline fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {
const darwin_path_max = 1024;
const name_len = if (assume_max_path_len) darwin_path_max else std.mem.len(name) + 1;
return mem.alignForwardGeneric(u64, cmd_size + name_len, @alignOf(u64));
}
fn calcLCsSize(self: *MachO, assume_max_path_len: bool) !u32 {
const gpa = self.base.allocator;
var sizeofcmds: u64 = 0;
for (self.segments.items) |seg| {
sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64);
}
// LC_DYLD_INFO_ONLY
sizeofcmds += @sizeOf(macho.dyld_info_command);
// LC_FUNCTION_STARTS
if (self.text_section_index != null) {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
// LC_DATA_IN_CODE
sizeofcmds += @sizeOf(macho.linkedit_data_command);
// LC_SYMTAB
sizeofcmds += @sizeOf(macho.symtab_command);
// LC_DYSYMTAB
sizeofcmds += @sizeOf(macho.dysymtab_command);
// LC_LOAD_DYLINKER
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.dylinker_command),
mem.sliceTo(default_dyld_path, 0),
false,
);
// LC_MAIN
if (self.base.options.output_mode == .Exe) {
sizeofcmds += @sizeOf(macho.entry_point_command);
}
// LC_ID_DYLIB
if (self.base.options.output_mode == .Lib) {
sizeofcmds += blk: {
const install_name = self.base.options.install_name orelse self.base.options.emit.?.sub_path;
break :blk calcInstallNameLen(
@sizeOf(macho.dylib_command),
install_name,
assume_max_path_len,
);
};
}
// LC_RPATH
{
var it = RpathIterator.init(gpa, self.base.options.rpath_list);
defer it.deinit();
while (try it.next()) |rpath| {
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.rpath_command),
rpath,
assume_max_path_len,
);
}
}
// LC_SOURCE_VERSION
sizeofcmds += @sizeOf(macho.source_version_command);
// LC_BUILD_VERSION
sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
// LC_UUID
sizeofcmds += @sizeOf(macho.uuid_command);
// LC_LOAD_DYLIB
for (self.referenced_dylibs.keys()) |id| {
const dylib = self.dylibs.items[id];
const dylib_id = dylib.id orelse unreachable;
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.dylib_command),
dylib_id.name,
assume_max_path_len,
);
}
// LC_CODE_SIGNATURE
{
const target = self.base.options.target;
const requires_codesig = blk: {
if (self.base.options.entitlements) |_| break :blk true;
if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator))
break :blk true;
break :blk false;
};
if (requires_codesig) {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
}
return @intCast(u32, sizeofcmds);
}
pub fn calcPagezeroSize(self: *MachO) u64 {
fn calcPagezeroSize(self: *MachO) u64 {
const pagezero_vmsize = self.base.options.pagezero_size orelse default_pagezero_vmsize;
const aligned_pagezero_vmsize = mem.alignBackwardGeneric(u64, pagezero_vmsize, self.page_size);
if (self.base.options.output_mode == .Lib) return 0;
@ -3079,23 +2804,6 @@ pub fn calcPagezeroSize(self: *MachO) u64 {
return aligned_pagezero_vmsize;
}
pub fn calcMinHeaderPad(self: *MachO) !u64 {
var padding: u32 = (try self.calcLCsSize(false)) + (self.base.options.headerpad_size orelse 0);
log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
if (self.base.options.headerpad_max_install_names) {
var min_headerpad_size: u32 = try self.calcLCsSize(true);
log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
min_headerpad_size + @sizeOf(macho.mach_header_64),
});
padding = @max(padding, min_headerpad_size);
}
const offset = @sizeOf(macho.mach_header_64) + padding;
log.debug("actual headerpad size 0x{x}", .{offset});
return offset;
}
fn allocateSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: struct {
size: u64 = 0,
alignment: u32 = 0,

View File

@ -0,0 +1,325 @@
const std = @import("std");
const assert = std.debug.assert;
const link = @import("../../link.zig");
const log = std.log.scoped(.link);
const macho = std.macho;
const mem = std.mem;
const Allocator = mem.Allocator;
const Dylib = @import("Dylib.zig");
pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {
const darwin_path_max = 1024;
const name_len = if (assume_max_path_len) darwin_path_max else std.mem.len(name) + 1;
return mem.alignForwardGeneric(u64, cmd_size + name_len, @alignOf(u64));
}
const CalcLCsSizeCtx = struct {
segments: []const macho.segment_command_64,
dylibs: []const Dylib,
referenced_dylibs: []u16,
wants_function_starts: bool = true,
};
fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
var has_text_segment: bool = false;
var sizeofcmds: u64 = 0;
for (ctx.segments) |seg| {
sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64);
if (mem.eql(u8, seg.segName(), "__TEXT")) {
has_text_segment = true;
}
}
// LC_DYLD_INFO_ONLY
sizeofcmds += @sizeOf(macho.dyld_info_command);
// LC_FUNCTION_STARTS
if (has_text_segment and ctx.wants_function_starts) |_| {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
// LC_DATA_IN_CODE
sizeofcmds += @sizeOf(macho.linkedit_data_command);
// LC_SYMTAB
sizeofcmds += @sizeOf(macho.symtab_command);
// LC_DYSYMTAB
sizeofcmds += @sizeOf(macho.dysymtab_command);
// LC_LOAD_DYLINKER
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.dylinker_command),
mem.sliceTo(default_dyld_path, 0),
false,
);
// LC_MAIN
if (options.output_mode == .Exe) {
sizeofcmds += @sizeOf(macho.entry_point_command);
}
// LC_ID_DYLIB
if (options.output_mode == .Lib and options.link_mode == .Dynamic) {
sizeofcmds += blk: {
const emit = options.emit.?;
const install_name = options.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path});
defer if (options.install_name == null) gpa.free(install_name);
break :blk calcInstallNameLen(
@sizeOf(macho.dylib_command),
install_name,
assume_max_path_len,
);
};
}
// LC_RPATH
{
var it = RpathIterator.init(gpa, options.rpath_list);
defer it.deinit();
while (try it.next()) |rpath| {
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.rpath_command),
rpath,
assume_max_path_len,
);
}
}
// LC_SOURCE_VERSION
sizeofcmds += @sizeOf(macho.source_version_command);
// LC_BUILD_VERSION
sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
// LC_UUID
sizeofcmds += @sizeOf(macho.uuid_command);
// LC_LOAD_DYLIB
for (ctx.referenced_dylibs) |id| {
const dylib = ctx.dylibs[id];
const dylib_id = dylib.id orelse unreachable;
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.dylib_command),
dylib_id.name,
assume_max_path_len,
);
}
// LC_CODE_SIGNATURE
{
const target = options.target;
const requires_codesig = blk: {
if (options.entitlements) |_| break :blk true;
if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator))
break :blk true;
break :blk false;
};
if (requires_codesig) {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
}
return @intCast(u32, sizeofcmds);
}
pub fn calcMinHeaderPad(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx) !u64 {
var padding: u32 = (try calcLCsSize(gpa, options, ctx, false)) + (options.headerpad_size orelse 0);
log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
if (options.headerpad_max_install_names) {
var min_headerpad_size: u32 = try calcLCsSize(gpa, options, ctx, true);
log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
min_headerpad_size + @sizeOf(macho.mach_header_64),
});
padding = @max(padding, min_headerpad_size);
}
const offset = @sizeOf(macho.mach_header_64) + padding;
log.debug("actual headerpad size 0x{x}", .{offset});
return offset;
}
pub fn writeDylinkerLC(ncmds: *u32, lc_writer: anytype) !void {
const name_len = mem.sliceTo(default_dyld_path, 0).len;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.dylinker_command) + name_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.dylinker_command{
.cmd = .LOAD_DYLINKER,
.cmdsize = cmdsize,
.name = @sizeOf(macho.dylinker_command),
});
try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0));
const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
const WriteDylibLCCtx = struct {
cmd: macho.LC,
name: []const u8,
timestamp: u32 = 2,
current_version: u32 = 0x10000,
compatibility_version: u32 = 0x10000,
};
fn writeDylibLC(ctx: WriteDylibLCCtx, ncmds: *u32, lc_writer: anytype) !void {
const name_len = ctx.name.len + 1;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.dylib_command) + name_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.dylib_command{
.cmd = ctx.cmd,
.cmdsize = cmdsize,
.dylib = .{
.name = @sizeOf(macho.dylib_command),
.timestamp = ctx.timestamp,
.current_version = ctx.current_version,
.compatibility_version = ctx.compatibility_version,
},
});
try lc_writer.writeAll(ctx.name);
try lc_writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
pub fn writeDylibIdLC(gpa: Allocator, options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void {
assert(options.output_mode == .Lib and options.link_mode == .Dynamic);
const emit = options.emit.?;
const install_name = options.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path});
defer if (options.install_name == null) gpa.free(install_name);
const curr = options.version orelse std.builtin.Version{
.major = 1,
.minor = 0,
.patch = 0,
};
const compat = options.compatibility_version orelse std.builtin.Version{
.major = 1,
.minor = 0,
.patch = 0,
};
try writeDylibLC(.{
.cmd = .ID_DYLIB,
.name = install_name,
.current_version = curr.major << 16 | curr.minor << 8 | curr.patch,
.compatibility_version = compat.major << 16 | compat.minor << 8 | compat.patch,
}, ncmds, lc_writer);
}
pub fn writeMainLC(entryoff: u32, options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void {
assert(options.output_mode == .Exe);
try lc_writer.writeStruct(macho.entry_point_command{
.cmd = .MAIN,
.cmdsize = @sizeOf(macho.entry_point_command),
.entryoff = entryoff,
.stacksize = options.stack_size_override orelse 0,
});
ncmds.* += 1;
}
const RpathIterator = struct {
buffer: []const []const u8,
table: std.StringHashMap(void),
count: usize = 0,
fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator {
return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) };
}
fn deinit(it: *RpathIterator) void {
it.table.deinit();
}
fn next(it: *RpathIterator) !?[]const u8 {
while (true) {
if (it.count >= it.buffer.len) return null;
const rpath = it.buffer[it.count];
it.count += 1;
const gop = try it.table.getOrPut(rpath);
if (gop.found_existing) continue;
return rpath;
}
}
};
pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void {
var it = RpathIterator.init(gpa, options.rpath_list);
defer it.deinit();
while (try it.next()) |rpath| {
const rpath_len = rpath.len + 1;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.rpath_command) + rpath_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.rpath_command{
.cmdsize = cmdsize,
.path = @sizeOf(macho.rpath_command),
});
try lc_writer.writeAll(rpath);
try lc_writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
}
pub fn writeBuildVersionLC(options: *const link.Options, ncmds: *u32, 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 = ver.major << 16 | ver.minor << 8;
break :blk platform_version;
};
const sdk_version = if (options.native_darwin_sdk) |sdk| blk: {
const ver = sdk.version;
const sdk_version = ver.major << 16 | ver.minor << 8;
break :blk sdk_version;
} 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,
.ntools = 1,
});
try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
.tool = .LD,
.version = 0x0,
}));
ncmds.* += 1;
}
pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, ncmds: *u32, lc_writer: anytype) !void {
for (referenced) |index| {
const dylib = dylibs[index];
const dylib_id = dylib.id orelse unreachable;
try writeDylibLC(.{
.cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB,
.name = dylib_id.name,
.timestamp = dylib_id.timestamp,
.current_version = dylib_id.current_version,
.compatibility_version = dylib_id.compatibility_version,
}, ncmds, lc_writer);
}
}
pub fn writeSourceVersionLC(ncmds: *u32, lc_writer: anytype) !void {
try lc_writer.writeStruct(macho.source_version_command{
.cmdsize = @sizeOf(macho.source_version_command),
.version = 0x0,
});
ncmds.* += 1;
}

View File

@ -13,6 +13,7 @@ const bind = @import("bind.zig");
const dead_strip = @import("dead_strip.zig");
const fat = @import("fat.zig");
const link = @import("../../link.zig");
const load_commands = @import("load_commands.zig");
const thunks = @import("thunks.zig");
const trace = @import("../../tracy.zig").trace;
@ -34,7 +35,7 @@ pub const Zld = struct {
gpa: Allocator,
file: fs.File,
page_size: u16,
options: link.Options,
options: *const link.Options,
objects: std.ArrayListUnmanaged(Object) = .{},
archives: std.ArrayListUnmanaged(Archive) = .{},
@ -1227,195 +1228,6 @@ pub const Zld = struct {
}
}
fn writeDylinkerLC(ncmds: *u32, lc_writer: anytype) !void {
const name_len = mem.sliceTo(MachO.default_dyld_path, 0).len;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.dylinker_command) + name_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.dylinker_command{
.cmd = .LOAD_DYLINKER,
.cmdsize = cmdsize,
.name = @sizeOf(macho.dylinker_command),
});
try lc_writer.writeAll(mem.sliceTo(MachO.default_dyld_path, 0));
const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
fn writeMainLC(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
if (self.options.output_mode != .Exe) return;
const seg_id = self.getSegmentByName("__TEXT").?;
const seg = self.segments.items[seg_id];
const global = self.getEntryPoint();
const sym = self.getSymbol(global);
try lc_writer.writeStruct(macho.entry_point_command{
.cmd = .MAIN,
.cmdsize = @sizeOf(macho.entry_point_command),
.entryoff = @intCast(u32, sym.n_value - seg.vmaddr),
.stacksize = self.options.stack_size_override orelse 0,
});
ncmds.* += 1;
}
const WriteDylibLCCtx = struct {
cmd: macho.LC,
name: []const u8,
timestamp: u32 = 2,
current_version: u32 = 0x10000,
compatibility_version: u32 = 0x10000,
};
fn writeDylibLC(ctx: WriteDylibLCCtx, ncmds: *u32, lc_writer: anytype) !void {
const name_len = ctx.name.len + 1;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.dylib_command) + name_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.dylib_command{
.cmd = ctx.cmd,
.cmdsize = cmdsize,
.dylib = .{
.name = @sizeOf(macho.dylib_command),
.timestamp = ctx.timestamp,
.current_version = ctx.current_version,
.compatibility_version = ctx.compatibility_version,
},
});
try lc_writer.writeAll(ctx.name);
try lc_writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
fn writeDylibIdLC(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
if (self.options.output_mode != .Lib) return;
const install_name = self.options.install_name orelse self.options.emit.?.sub_path;
const curr = self.options.version orelse std.builtin.Version{
.major = 1,
.minor = 0,
.patch = 0,
};
const compat = self.options.compatibility_version orelse std.builtin.Version{
.major = 1,
.minor = 0,
.patch = 0,
};
try writeDylibLC(.{
.cmd = .ID_DYLIB,
.name = install_name,
.current_version = curr.major << 16 | curr.minor << 8 | curr.patch,
.compatibility_version = compat.major << 16 | compat.minor << 8 | compat.patch,
}, ncmds, lc_writer);
}
const RpathIterator = struct {
buffer: []const []const u8,
table: std.StringHashMap(void),
count: usize = 0,
fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator {
return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) };
}
fn deinit(it: *RpathIterator) void {
it.table.deinit();
}
fn next(it: *RpathIterator) !?[]const u8 {
while (true) {
if (it.count >= it.buffer.len) return null;
const rpath = it.buffer[it.count];
it.count += 1;
const gop = try it.table.getOrPut(rpath);
if (gop.found_existing) continue;
return rpath;
}
}
};
fn writeRpathLCs(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
const gpa = self.gpa;
var it = RpathIterator.init(gpa, self.options.rpath_list);
defer it.deinit();
while (try it.next()) |rpath| {
const rpath_len = rpath.len + 1;
const cmdsize = @intCast(u32, mem.alignForwardGeneric(
u64,
@sizeOf(macho.rpath_command) + rpath_len,
@sizeOf(u64),
));
try lc_writer.writeStruct(macho.rpath_command{
.cmdsize = cmdsize,
.path = @sizeOf(macho.rpath_command),
});
try lc_writer.writeAll(rpath);
try lc_writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
if (padding > 0) {
try lc_writer.writeByteNTimes(0, padding);
}
ncmds.* += 1;
}
}
fn writeBuildVersionLC(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
const platform_version = blk: {
const ver = self.options.target.os.version_range.semver.min;
const platform_version = ver.major << 16 | ver.minor << 8;
break :blk platform_version;
};
const sdk_version = if (self.options.native_darwin_sdk) |sdk| blk: {
const ver = sdk.version;
const sdk_version = ver.major << 16 | ver.minor << 8;
break :blk sdk_version;
} else platform_version;
const is_simulator_abi = self.options.target.abi == .simulator;
try lc_writer.writeStruct(macho.build_version_command{
.cmdsize = cmdsize,
.platform = switch (self.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,
.ntools = 1,
});
try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
.tool = .LD,
.version = 0x0,
}));
ncmds.* += 1;
}
fn writeLoadDylibLCs(self: *Zld, ncmds: *u32, lc_writer: anytype) !void {
for (self.referenced_dylibs.keys()) |id| {
const dylib = self.dylibs.items[id];
const dylib_id = dylib.id orelse unreachable;
try writeDylibLC(.{
.cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB,
.name = dylib_id.name,
.timestamp = dylib_id.timestamp,
.current_version = dylib_id.current_version,
.compatibility_version = dylib_id.compatibility_version,
}, ncmds, lc_writer);
}
}
pub fn deinit(self: *Zld) void {
const gpa = self.gpa;
@ -1516,110 +1328,6 @@ pub const Zld = struct {
}
}
fn calcLCsSize(self: *Zld, assume_max_path_len: bool) !u32 {
const gpa = self.gpa;
var sizeofcmds: u64 = 0;
for (self.segments.items) |seg| {
sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64);
}
// LC_DYLD_INFO_ONLY
sizeofcmds += @sizeOf(macho.dyld_info_command);
// LC_FUNCTION_STARTS
if (self.getSectionByName("__TEXT", "__text")) |_| {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
// LC_DATA_IN_CODE
sizeofcmds += @sizeOf(macho.linkedit_data_command);
// LC_SYMTAB
sizeofcmds += @sizeOf(macho.symtab_command);
// LC_DYSYMTAB
sizeofcmds += @sizeOf(macho.dysymtab_command);
// LC_LOAD_DYLINKER
sizeofcmds += MachO.calcInstallNameLen(
@sizeOf(macho.dylinker_command),
mem.sliceTo(MachO.default_dyld_path, 0),
false,
);
// LC_MAIN
if (self.options.output_mode == .Exe) {
sizeofcmds += @sizeOf(macho.entry_point_command);
}
// LC_ID_DYLIB
if (self.options.output_mode == .Lib) {
sizeofcmds += blk: {
const install_name = self.options.install_name orelse self.options.emit.?.sub_path;
break :blk MachO.calcInstallNameLen(
@sizeOf(macho.dylib_command),
install_name,
assume_max_path_len,
);
};
}
// LC_RPATH
{
var it = RpathIterator.init(gpa, self.options.rpath_list);
defer it.deinit();
while (try it.next()) |rpath| {
sizeofcmds += MachO.calcInstallNameLen(
@sizeOf(macho.rpath_command),
rpath,
assume_max_path_len,
);
}
}
// LC_SOURCE_VERSION
sizeofcmds += @sizeOf(macho.source_version_command);
// LC_BUILD_VERSION
sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
// LC_UUID
sizeofcmds += @sizeOf(macho.uuid_command);
// LC_LOAD_DYLIB
for (self.referenced_dylibs.keys()) |id| {
const dylib = self.dylibs.items[id];
const dylib_id = dylib.id orelse unreachable;
sizeofcmds += MachO.calcInstallNameLen(
@sizeOf(macho.dylib_command),
dylib_id.name,
assume_max_path_len,
);
}
// LC_CODE_SIGNATURE
{
const target = self.options.target;
const requires_codesig = blk: {
if (self.options.entitlements) |_| break :blk true;
if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator))
break :blk true;
break :blk false;
};
if (requires_codesig) {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
}
return @intCast(u32, sizeofcmds);
}
fn calcMinHeaderPad(self: *Zld) !u64 {
var padding: u32 = (try self.calcLCsSize(false)) + (self.options.headerpad_size orelse 0);
log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
if (self.options.headerpad_max_install_names) {
var min_headerpad_size: u32 = try self.calcLCsSize(true);
log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
min_headerpad_size + @sizeOf(macho.mach_header_64),
});
padding = @max(padding, min_headerpad_size);
}
const offset = @sizeOf(macho.mach_header_64) + padding;
log.debug("actual headerpad size 0x{x}", .{offset});
return offset;
}
pub fn allocateSymbol(self: *Zld) !u32 {
try self.locals.ensureUnusedCapacity(self.gpa, 1);
log.debug(" (allocating symbol index {d})", .{self.locals.items.len});
@ -1842,7 +1550,11 @@ pub const Zld = struct {
fn allocateSegments(self: *Zld) !void {
for (self.segments.items) |*segment, segment_index| {
const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT");
const base_size = if (is_text_segment) try self.calcMinHeaderPad() else 0;
const base_size = if (is_text_segment) try load_commands.calcMinHeaderPad(self.gpa, self.options, .{
.segments = self.segments.items,
.dylibs = self.dylibs.items,
.referenced_dylibs = self.referenced_dylibs.keys(),
}) else 0;
try self.allocateSegment(@intCast(u8, segment_index), base_size);
}
}
@ -3734,7 +3446,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
defer tracy.end();
const gpa = macho_file.base.allocator;
const options = macho_file.base.options;
const options = &macho_file.base.options;
const target = options.target;
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
@ -3884,7 +3596,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
macho_file.base.file = try directory.handle.createFile(sub_path, .{
.truncate = true,
.read = true,
.mode = link.determineMode(options),
.mode = link.determineMode(options.*),
});
}
var zld = Zld{
@ -4301,20 +4013,22 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
}
}
try Zld.writeDylinkerLC(&ncmds, lc_writer);
try zld.writeMainLC(&ncmds, lc_writer);
try zld.writeDylibIdLC(&ncmds, lc_writer);
try zld.writeRpathLCs(&ncmds, lc_writer);
try load_commands.writeDylinkerLC(&ncmds, lc_writer);
{
try lc_writer.writeStruct(macho.source_version_command{
.cmdsize = @sizeOf(macho.source_version_command),
.version = 0x0,
});
ncmds += 1;
if (zld.options.output_mode == .Exe) {
const seg_id = zld.getSegmentByName("__TEXT").?;
const seg = zld.segments.items[seg_id];
const global = zld.getEntryPoint();
const sym = zld.getSymbol(global);
try load_commands.writeMainLC(@intCast(u32, sym.n_value - seg.vmaddr), options, &ncmds, lc_writer);
} else {
assert(zld.options.output_mode == .Lib);
try load_commands.writeDylibIdLC(zld.gpa, zld.options, &ncmds, lc_writer);
}
try zld.writeBuildVersionLC(&ncmds, lc_writer);
try load_commands.writeRpathLCs(zld.gpa, zld.options, &ncmds, lc_writer);
try load_commands.writeSourceVersionLC(&ncmds, lc_writer);
try load_commands.writeBuildVersionLC(zld.options, &ncmds, lc_writer);
{
var uuid_lc = macho.uuid_command{
@ -4326,7 +4040,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
ncmds += 1;
}
try zld.writeLoadDylibLCs(&ncmds, lc_writer);
try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), &ncmds, lc_writer);
const requires_codesig = blk: {
if (options.entitlements) |_| break :blk true;