diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 81a63b6f9e..cd842fcf31 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -871,17 +871,19 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { // If we're compiling native and we can find libSystem.B.{dylib, tbd}, // we link against that instead of embedded libSystem.B.tbd file. - const libc_stub_path = blk: { - if (self.base.options.is_native_os) { - if (try resolveLib(arena, lib_dirs.items, "System", .lib)) |full_path| { - break :blk full_path; - } + var link_native_libsystem = false; + if (self.base.options.is_native_os) { + if (try resolveLib(arena, lib_dirs.items, "System", .lib)) |full_path| { + try libs.append(full_path); + link_native_libsystem = true; } - - break :blk try comp.zig_lib_directory.join(arena, &[_][]const u8{ + } + if (!link_native_libsystem) { + const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "darwin", "libSystem.B.tbd", }); - }; + try positionals.append(full_path); + } // frameworks var framework_dirs = std.ArrayList([]const u8).init(arena); @@ -935,6 +937,10 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try argv.append("-o"); try argv.append(full_out_path); + if (link_native_libsystem) { + try argv.append("-lSystem"); + } + for (search_lib_names.items) |l_name| { try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name})); } @@ -950,7 +956,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { .syslibroot = self.base.options.sysroot, .libs = libs.items, .rpaths = rpaths.items, - .libc_stub_path = libc_stub_path, }); break :outer; diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 2ecd2a20ed..a60a45c6ed 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -45,8 +45,8 @@ id: ?Id = null, /// a symbol is referenced by an object file. symbols: std.StringArrayHashMapUnmanaged(void) = .{}, -// TODO add parsing re-exported libs from binary dylibs -dependent_libs: std.StringArrayHashMapUnmanaged(void) = .{}, +/// Array list of all dependent libs of this dylib. +dependent_libs: std.ArrayListUnmanaged(Id) = .{}, pub const Id = struct { name: []const u8, @@ -54,15 +54,28 @@ pub const Id = struct { current_version: u32, compatibility_version: u32, - pub fn default(name: []const u8) Id { - return .{ - .name = name, + pub fn default(allocator: *Allocator, name: []const u8) !Id { + return Id{ + .name = try allocator.dupe(u8, name), .timestamp = 2, .current_version = 0x10000, .compatibility_version = 0x10000, }; } + pub fn fromLoadCommand(allocator: *Allocator, lc: GenericCommandWithData(macho.dylib_command)) !Id { + const dylib = lc.inner.dylib; + const dylib_name = @ptrCast([*:0]const u8, lc.data[dylib.name - @sizeOf(macho.dylib_command) ..]); + const name = try allocator.dupe(u8, mem.spanZ(dylib_name)); + + return Id{ + .name = name, + .timestamp = dylib.timestamp, + .current_version = dylib.current_version, + .compatibility_version = dylib.compatibility_version, + }; + } + pub fn deinit(id: *Id, allocator: *Allocator) void { allocator.free(id.name); } @@ -129,12 +142,12 @@ pub const Error = error{ UnsupportedCpuArchitecture, } || fs.File.OpenError || std.os.PReadError || Id.ParseError; -pub fn createAndParseFromPath( - allocator: *Allocator, - arch: Arch, - path: []const u8, - syslibroot: ?[]const u8, -) Error!?[]*Dylib { +pub const CreateOpts = struct { + syslibroot: ?[]const u8 = null, + id: ?Id = null, +}; + +pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, path: []const u8, opts: CreateOpts) Error!?[]*Dylib { const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { error.FileNotFound => return null, else => |e| return e, @@ -152,7 +165,7 @@ pub fn createAndParseFromPath( .arch = arch, .name = name, .file = file, - .syslibroot = syslibroot, + .syslibroot = opts.syslibroot, }; dylib.parse() catch |err| switch (err) { @@ -171,6 +184,20 @@ pub fn createAndParseFromPath( else => |e| return e, }; + if (opts.id) |id| { + if (dylib.id.?.current_version < id.compatibility_version) { + log.warn("found dylib is incompatible with the required minimum version", .{}); + log.warn(" | dylib: {s}", .{id.name}); + log.warn(" | required minimum version: {}", .{id.compatibility_version}); + log.warn(" | dylib version: {}", .{dylib.id.?.current_version}); + + // TODO maybe this should be an error and facilitate auto-cleanup? + dylib.deinit(); + allocator.destroy(dylib); + return null; + } + } + var dylibs = std.ArrayList(*Dylib).init(allocator); defer dylibs.deinit(); @@ -191,8 +218,8 @@ pub fn deinit(self: *Dylib) void { } self.symbols.deinit(self.allocator); - for (self.dependent_libs.keys()) |key| { - self.allocator.free(key); + for (self.dependent_libs.items) |*id| { + id.deinit(self.allocator); } self.dependent_libs.deinit(self.allocator); @@ -287,6 +314,8 @@ fn readFatStruct(reader: anytype, comptime T: type) !T { } fn readLoadCommands(self: *Dylib, reader: anytype) !void { + const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; + try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds); var i: u16 = 0; @@ -302,6 +331,13 @@ fn readLoadCommands(self: *Dylib, reader: anytype) !void { macho.LC_ID_DYLIB => { self.id_cmd_index = i; }, + macho.LC_REEXPORT_DYLIB => { + if (should_lookup_reexports) { + // Parse install_name to dependent dylib. + const id = try Id.fromLoadCommand(self.allocator, cmd.Dylib); + try self.dependent_libs.append(self.allocator, id); + } + }, else => { log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); }, @@ -313,22 +349,10 @@ fn readLoadCommands(self: *Dylib, reader: anytype) !void { fn parseId(self: *Dylib) !void { const index = self.id_cmd_index orelse { log.debug("no LC_ID_DYLIB load command found; using hard-coded defaults...", .{}); - self.id = Id.default(try self.allocator.dupe(u8, self.name.?)); + self.id = try Id.default(self.allocator, self.name.?); return; }; - const id_cmd = self.load_commands.items[index].Dylib; - const dylib = id_cmd.inner.dylib; - - // TODO should we compare the name from the dylib's id with the user-specified one? - const dylib_name = @ptrCast([*:0]const u8, id_cmd.data[dylib.name - @sizeOf(macho.dylib_command) ..]); - const name = try self.allocator.dupe(u8, mem.spanZ(dylib_name)); - - self.id = .{ - .name = name, - .timestamp = dylib.timestamp, - .current_version = dylib.current_version, - .compatibility_version = dylib.compatibility_version, - }; + self.id = try Id.fromLoadCommand(self.allocator, self.load_commands.items[index].Dylib); } fn parseSymbols(self: *Dylib) !void { @@ -380,7 +404,7 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void { const umbrella_lib = lib_stub.inner[0]; - var id = Id.default(try self.allocator.dupe(u8, umbrella_lib.install_name)); + var id = try Id.default(self.allocator, umbrella_lib.install_name); if (umbrella_lib.current_version) |version| { try id.parseCurrentVersion(version); } @@ -470,7 +494,9 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void { } log.debug(" | {s}", .{lib}); - try self.dependent_libs.put(self.allocator, try self.allocator.dupe(u8, lib), {}); + + const dep_id = try Id.default(self.allocator, lib); + try self.dependent_libs.append(self.allocator, dep_id); } } } @@ -478,36 +504,40 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void { } pub fn parseDependentLibs(self: *Dylib, out: *std.ArrayList(*Dylib)) !void { - outer: for (self.dependent_libs.keys()) |lib| { - const dirname = fs.path.dirname(lib) orelse { - log.warn("unable to resolve dependency {s}", .{lib}); - continue; + outer: for (self.dependent_libs.items) |id| { + const has_ext = blk: { + const basename = fs.path.basename(id.name); + break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; }; - const filename = fs.path.basename(lib); - const without_ext = if (mem.lastIndexOfScalar(u8, filename, '.')) |index| - filename[0..index] - else - filename; + const extension = if (has_ext) fs.path.extension(id.name) else ""; + const without_ext = if (has_ext) blk: { + const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable; + break :blk id.name[0..index]; + } else id.name; - for (&[_][]const u8{ "dylib", "tbd" }) |ext| { - const with_ext = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ + for (&[_][]const u8{ extension, ".tbd" }) |ext| { + const with_ext = try std.fmt.allocPrint(self.allocator, "{s}{s}", .{ without_ext, ext, }); defer self.allocator.free(with_ext); - const lib_path = if (self.syslibroot) |syslibroot| - try fs.path.join(self.allocator, &.{ syslibroot, dirname, with_ext }) + const full_path = if (self.syslibroot) |syslibroot| + try fs.path.join(self.allocator, &.{ syslibroot, with_ext }) else - try fs.path.join(self.allocator, &.{ dirname, with_ext }); + with_ext; + defer if (self.syslibroot) |_| self.allocator.free(full_path); - log.debug("trying dependency at fully resolved path {s}", .{lib_path}); + log.debug("trying dependency at fully resolved path {s}", .{full_path}); const dylibs = (try createAndParseFromPath( self.allocator, self.arch.?, - lib_path, - self.syslibroot, + full_path, + .{ + .id = id, + .syslibroot = self.syslibroot, + }, )) orelse { continue; }; @@ -516,7 +546,7 @@ pub fn parseDependentLibs(self: *Dylib, out: *std.ArrayList(*Dylib)) !void { continue :outer; } else { - log.warn("unable to resolve dependency {s}", .{lib}); + log.warn("unable to resolve dependency {s}", .{id.name}); } } } diff --git a/src/link/MachO/Symbol.zig b/src/link/MachO/Symbol.zig index c58af27672..8da4704909 100644 --- a/src/link/MachO/Symbol.zig +++ b/src/link/MachO/Symbol.zig @@ -111,7 +111,8 @@ pub const Unresolved = struct { base: Symbol, /// File where this symbol was referenced. - file: *Object, + /// null means synthetic, e.g., dyld_stub_binder. + file: ?*Object = null, pub const base_type: Symbol.Type = .unresolved; }; diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 56462c1140..87dcdcd35d 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -38,7 +38,6 @@ objects: std.ArrayListUnmanaged(*Object) = .{}, archives: std.ArrayListUnmanaged(*Archive) = .{}, dylibs: std.ArrayListUnmanaged(*Dylib) = .{}, -libsystem_dylib_index: ?u16 = null, next_dylib_ordinal: u16 = 1, load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, @@ -199,7 +198,6 @@ const LinkArgs = struct { syslibroot: ?[]const u8, libs: []const []const u8, rpaths: []const []const u8, - libc_stub_path: []const u8, }; pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: LinkArgs) !void { @@ -240,7 +238,6 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L try self.populateMetadata(); try self.parseInputFiles(files, args.syslibroot); try self.parseLibs(args.libs, args.syslibroot); - try self.parseLibSystem(args.libc_stub_path, args.syslibroot); try self.resolveSymbols(); try self.resolveStubsAndGotEntries(); try self.updateMetadata(); @@ -280,7 +277,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8, syslibroot: ?[]const u self.allocator, self.arch.?, full_path, - syslibroot, + .{ .syslibroot = syslibroot }, )) |dylibs| { defer self.allocator.free(dylibs); try self.dylibs.appendSlice(self.allocator, dylibs); @@ -297,7 +294,7 @@ fn parseLibs(self: *Zld, libs: []const []const u8, syslibroot: ?[]const u8) !voi self.allocator, self.arch.?, lib, - syslibroot, + .{ .syslibroot = syslibroot }, )) |dylibs| { defer self.allocator.free(dylibs); try self.dylibs.appendSlice(self.allocator, dylibs); @@ -313,36 +310,6 @@ fn parseLibs(self: *Zld, libs: []const []const u8, syslibroot: ?[]const u8) !voi } } -fn parseLibSystem(self: *Zld, libc_stub_path: []const u8, syslibroot: ?[]const u8) !void { - const dylibs = (try Dylib.createAndParseFromPath( - self.allocator, - self.arch.?, - libc_stub_path, - syslibroot, - )) orelse return error.FailedToParseLibSystem; - defer self.allocator.free(dylibs); - - assert(dylibs.len == 1); // More than one dylib output from parsing libSystem! - const dylib = dylibs[0]; - - self.libsystem_dylib_index = @intCast(u16, self.dylibs.items.len); - try self.dylibs.append(self.allocator, dylib); - - // Add LC_LOAD_DYLIB load command. - dylib.ordinal = self.next_dylib_ordinal; - const dylib_id = dylib.id orelse unreachable; - var dylib_cmd = try createLoadDylibCommand( - self.allocator, - dylib_id.name, - dylib_id.timestamp, - dylib_id.current_version, - dylib_id.compatibility_version, - ); - errdefer dylib_cmd.deinit(self.allocator); - try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); - self.next_dylib_ordinal += 1; -} - fn mapAndUpdateSections( self: *Zld, object: *Object, @@ -1656,6 +1623,21 @@ fn resolveSymbols(self: *Zld) !void { } self.unresolved.clearRetainingCapacity(); + // Put dyld_stub_binder as an unresolved special symbol. + { + const name = try self.allocator.dupe(u8, "dyld_stub_binder"); + errdefer self.allocator.free(name); + const undef = try self.allocator.create(Symbol.Unresolved); + errdefer self.allocator.destroy(undef); + undef.* = .{ + .base = .{ + .@"type" = .unresolved, + .name = name, + }, + }; + try unresolved.append(&undef.base); + } + var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator); defer referenced.deinit(); @@ -1664,9 +1646,7 @@ fn resolveSymbols(self: *Zld) !void { const proxy = inner: { for (self.dylibs.items) |dylib, i| { const proxy = (try dylib.createProxy(undef.name)) orelse continue; - if (self.libsystem_dylib_index.? != @intCast(u16, i)) { // LibSystem gets load command seperately. - try referenced.put(dylib, {}); - } + try referenced.put(dylib, {}); break :inner proxy; } if (mem.eql(u8, undef.name, "___dso_handle")) { @@ -1681,7 +1661,6 @@ fn resolveSymbols(self: *Zld) !void { .@"type" = .proxy, .name = name, }, - .file = null, }; break :inner &proxy.base; } @@ -1717,21 +1696,13 @@ fn resolveSymbols(self: *Zld) !void { if (self.unresolved.count() > 0) { for (self.unresolved.values()) |undef| { log.err("undefined reference to symbol '{s}'", .{undef.name}); - log.err(" | referenced in {s}", .{ - undef.cast(Symbol.Unresolved).?.file.name.?, - }); + if (undef.cast(Symbol.Unresolved).?.file) |file| { + log.err(" | referenced in {s}", .{file.name.?}); + } } return error.UndefinedSymbolReference; } - - // Finally put dyld_stub_binder as an Import - const libsystem_dylib = self.dylibs.items[self.libsystem_dylib_index.?]; - const proxy = (try libsystem_dylib.createProxy("dyld_stub_binder")) orelse { - log.err("undefined reference to symbol 'dyld_stub_binder'", .{}); - return error.UndefinedSymbolReference; - }; - try self.imports.putNoClobber(self.allocator, proxy.name, proxy); } fn resolveStubsAndGotEntries(self: *Zld) !void { @@ -3173,33 +3144,3 @@ pub fn parseName(name: *const [16]u8) []const u8 { const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; return name[0..len]; } - -fn printSymbols(self: *Zld) void { - log.debug("globals", .{}); - for (self.globals.values()) |value| { - const sym = value.cast(Symbol.Regular) orelse unreachable; - log.debug(" | {s} @ {*}", .{ sym.base.name, value }); - log.debug(" => alias of {*}", .{sym.base.alias}); - log.debug(" => linkage {s}", .{sym.linkage}); - log.debug(" => defined in {s}", .{sym.file.name.?}); - } - for (self.objects.items) |object| { - log.debug("locals in {s}", .{object.name.?}); - for (object.symbols.items) |sym| { - log.debug(" | {s} @ {*}", .{ sym.name, sym }); - log.debug(" => alias of {*}", .{sym.alias}); - if (sym.cast(Symbol.Regular)) |reg| { - log.debug(" => linkage {s}", .{reg.linkage}); - } else { - log.debug(" => unresolved", .{}); - } - } - } - log.debug("proxies", .{}); - for (self.imports.values()) |value| { - const sym = value.cast(Symbol.Proxy) orelse unreachable; - log.debug(" | {s} @ {*}", .{ sym.base.name, value }); - log.debug(" => alias of {*}", .{sym.base.alias}); - log.debug(" => defined in libSystem.B.dylib", .{}); - } -}