Commit eca12b74b8

Jakub Konka <kubkon@jakubkonka.com>
2021-06-28 12:17:55
zld: recurse dylibs reexports when defined and desired
1 parent 5aff1d5
Changed files (4)
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;
-
-        for (&[_][]const u8{ "dylib", "tbd" }) |ext| {
-            const with_ext = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{
+        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{ 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});
         }
     }
 }
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;
 };
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", .{});
-    }
-}
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;