Commit 138cecc028

Jakub Konka <kubkon@jakubkonka.com>
2021-05-16 16:32:27
zld: add prelim way of linking dylibs
The support is minimalistic in the sense that we only support actual dylib files and not stubs/tbds yet, and we also don't support re-exports just yet.
1 parent 35c694d
src/link/MachO/commands.zig
@@ -38,7 +38,9 @@ pub const LoadCommand = union(enum) {
             macho.LC_SEGMENT_64 => LoadCommand{
                 .Segment = try SegmentCommand.read(allocator, stream.reader()),
             },
-            macho.LC_DYLD_INFO, macho.LC_DYLD_INFO_ONLY => LoadCommand{
+            macho.LC_DYLD_INFO,
+            macho.LC_DYLD_INFO_ONLY,
+            => LoadCommand{
                 .DyldInfoOnly = try stream.reader().readStruct(macho.dyld_info_command),
             },
             macho.LC_SYMTAB => LoadCommand{
@@ -47,16 +49,27 @@ pub const LoadCommand = union(enum) {
             macho.LC_DYSYMTAB => LoadCommand{
                 .Dysymtab = try stream.reader().readStruct(macho.dysymtab_command),
             },
-            macho.LC_ID_DYLINKER, macho.LC_LOAD_DYLINKER, macho.LC_DYLD_ENVIRONMENT => LoadCommand{
+            macho.LC_ID_DYLINKER,
+            macho.LC_LOAD_DYLINKER,
+            macho.LC_DYLD_ENVIRONMENT,
+            => LoadCommand{
                 .Dylinker = try GenericCommandWithData(macho.dylinker_command).read(allocator, stream.reader()),
             },
-            macho.LC_ID_DYLIB, macho.LC_LOAD_WEAK_DYLIB, macho.LC_LOAD_DYLIB, macho.LC_REEXPORT_DYLIB => LoadCommand{
+            macho.LC_ID_DYLIB,
+            macho.LC_LOAD_WEAK_DYLIB,
+            macho.LC_LOAD_DYLIB,
+            macho.LC_REEXPORT_DYLIB,
+            => LoadCommand{
                 .Dylib = try GenericCommandWithData(macho.dylib_command).read(allocator, stream.reader()),
             },
             macho.LC_MAIN => LoadCommand{
                 .Main = try stream.reader().readStruct(macho.entry_point_command),
             },
-            macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => LoadCommand{
+            macho.LC_VERSION_MIN_MACOSX,
+            macho.LC_VERSION_MIN_IPHONEOS,
+            macho.LC_VERSION_MIN_WATCHOS,
+            macho.LC_VERSION_MIN_TVOS,
+            => LoadCommand{
                 .VersionMin = try stream.reader().readStruct(macho.version_min_command),
             },
             macho.LC_SOURCE_VERSION => LoadCommand{
@@ -65,7 +78,10 @@ pub const LoadCommand = union(enum) {
             macho.LC_UUID => LoadCommand{
                 .Uuid = try stream.reader().readStruct(macho.uuid_command),
             },
-            macho.LC_FUNCTION_STARTS, macho.LC_DATA_IN_CODE, macho.LC_CODE_SIGNATURE => LoadCommand{
+            macho.LC_FUNCTION_STARTS,
+            macho.LC_DATA_IN_CODE,
+            macho.LC_CODE_SIGNATURE,
+            => LoadCommand{
                 .LinkeditData = try stream.reader().readStruct(macho.linkedit_data_command),
             },
             else => LoadCommand{
src/link/MachO/Dylib.zig
@@ -0,0 +1,137 @@
+const Dylib = @This();
+
+const std = @import("std");
+const fs = std.fs;
+const log = std.log.scoped(.dylib);
+const macho = std.macho;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Symbol = @import("Symbol.zig");
+
+usingnamespace @import("commands.zig");
+
+allocator: *Allocator,
+arch: ?std.Target.Cpu.Arch = null,
+header: ?macho.mach_header_64 = null,
+file: ?fs.File = null,
+name: ?[]const u8 = null,
+
+ordinal: ?u16 = null,
+
+load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
+
+symtab_cmd_index: ?u16 = null,
+dysymtab_cmd_index: ?u16 = null,
+
+symbols: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
+
+pub fn init(allocator: *Allocator) Dylib {
+    return .{ .allocator = allocator };
+}
+
+pub fn deinit(self: *Dylib) void {
+    for (self.load_commands.items) |*lc| {
+        lc.deinit(self.allocator);
+    }
+    self.load_commands.deinit(self.allocator);
+
+    for (self.symbols.items()) |entry| {
+        entry.value.deinit(self.allocator);
+        self.allocator.destroy(entry.value);
+    }
+    self.symbols.deinit(self.allocator);
+
+    if (self.name) |name| {
+        self.allocator.free(name);
+    }
+}
+
+pub fn closeFile(self: Dylib) void {
+    if (self.file) |file| {
+        file.close();
+    }
+}
+
+pub fn parse(self: *Dylib) !void {
+    log.warn("parsing shared library '{s}'", .{self.name.?});
+
+    var reader = self.file.?.reader();
+    self.header = try reader.readStruct(macho.mach_header_64);
+
+    if (self.header.?.filetype != macho.MH_DYLIB) {
+        log.err("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype });
+        return error.MalformedDylib;
+    }
+
+    const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
+        macho.CPU_TYPE_ARM64 => .aarch64,
+        macho.CPU_TYPE_X86_64 => .x86_64,
+        else => |value| {
+            log.err("unsupported cpu architecture 0x{x}", .{value});
+            return error.UnsupportedCpuArchitecture;
+        },
+    };
+    if (this_arch != self.arch.?) {
+        log.err("mismatched cpu architecture: expected {s}, found {s}", .{ self.arch.?, this_arch });
+        return error.MismatchedCpuArchitecture;
+    }
+
+    try self.readLoadCommands(reader);
+    try self.parseSymbols();
+}
+
+pub fn readLoadCommands(self: *Dylib, reader: anytype) !void {
+    try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds);
+
+    var i: u16 = 0;
+    while (i < self.header.?.ncmds) : (i += 1) {
+        var cmd = try LoadCommand.read(self.allocator, reader);
+        switch (cmd.cmd()) {
+            macho.LC_SYMTAB => {
+                self.symtab_cmd_index = i;
+            },
+            macho.LC_DYSYMTAB => {
+                self.dysymtab_cmd_index = i;
+            },
+            else => {
+                log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()});
+            },
+        }
+        self.load_commands.appendAssumeCapacity(cmd);
+    }
+}
+
+pub fn parseSymbols(self: *Dylib) !void {
+    const index = self.symtab_cmd_index orelse return;
+    const symtab_cmd = self.load_commands.items[index].Symtab;
+
+    var symtab = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms);
+    defer self.allocator.free(symtab);
+    _ = try self.file.?.preadAll(symtab, symtab_cmd.symoff);
+    const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, symtab));
+
+    var strtab = try self.allocator.alloc(u8, symtab_cmd.strsize);
+    defer self.allocator.free(strtab);
+    _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff);
+
+    for (slice) |sym| {
+        const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx));
+
+        if (!(Symbol.isSect(sym) and Symbol.isExt(sym))) continue;
+
+        const name = try self.allocator.dupe(u8, sym_name);
+        const proxy = try self.allocator.create(Symbol.Proxy);
+        errdefer self.allocator.destroy(proxy);
+
+        proxy.* = .{
+            .base = .{
+                .@"type" = .proxy,
+                .name = name,
+            },
+            .dylib = self,
+        };
+
+        try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
+    }
+}
src/link/MachO/Symbol.zig
@@ -5,6 +5,7 @@ const macho = std.macho;
 const mem = std.mem;
 
 const Allocator = mem.Allocator;
+const Dylib = @import("Dylib.zig");
 const Object = @import("Object.zig");
 
 pub const Type = enum {
@@ -43,7 +44,7 @@ pub const Regular = struct {
     /// Whether the symbol is a weak ref.
     weak_ref: bool,
 
-    /// File where to locate this symbol.
+    /// Object file where to locate this symbol.
     file: *Object,
 
     /// Debug stab if defined.
@@ -78,8 +79,8 @@ pub const Regular = struct {
 pub const Proxy = struct {
     base: Symbol,
 
-    /// Dylib ordinal.
-    dylib: u16,
+    /// Dylib where to locate this symbol.
+    dylib: ?*Dylib = null,
 
     pub const base_type: Symbol.Type = .proxy;
 };
src/link/MachO/Zld.zig
@@ -15,6 +15,7 @@ const reloc = @import("reloc.zig");
 const Allocator = mem.Allocator;
 const Archive = @import("Archive.zig");
 const CodeSignature = @import("CodeSignature.zig");
+const Dylib = @import("Dylib.zig");
 const Object = @import("Object.zig");
 const Symbol = @import("Symbol.zig");
 const Trie = @import("Trie.zig");
@@ -35,6 +36,7 @@ stack_size: u64 = 0,
 
 objects: std.ArrayListUnmanaged(*Object) = .{},
 archives: std.ArrayListUnmanaged(*Archive) = .{},
+dylibs: std.ArrayListUnmanaged(*Dylib) = .{},
 
 load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
 
@@ -151,10 +153,18 @@ pub fn deinit(self: *Zld) void {
     }
     self.archives.deinit(self.allocator);
 
+    for (self.dylibs.items) |dylib| {
+        dylib.deinit();
+        self.allocator.destroy(dylib);
+    }
+    self.dylibs.deinit(self.allocator);
+
     self.mappings.deinit(self.allocator);
     self.unhandled_sections.deinit(self.allocator);
 
     self.globals.deinit(self.allocator);
+    self.imports.deinit(self.allocator);
+    self.unresolved.deinit(self.allocator);
     self.strtab.deinit(self.allocator);
 
     {
@@ -176,11 +186,7 @@ pub fn closeFiles(self: Zld) void {
     if (self.file) |f| f.close();
 }
 
-const LinkArgs = struct {
-    stack_size: ?u64 = null,
-};
-
-pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: LinkArgs) !void {
+pub fn link(self: *Zld, files: []const []const u8, shared_libs: []const []const u8, out_path: []const u8) !void {
     if (files.len == 0) return error.NoInputFiles;
     if (out_path.len == 0) return error.EmptyOutputPath;
 
@@ -214,10 +220,10 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L
         .read = true,
         .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777,
     });
-    self.stack_size = args.stack_size orelse 0;
 
     try self.populateMetadata();
     try self.parseInputFiles(files);
+    try self.parseDylibs(shared_libs);
     try self.resolveSymbols();
     try self.resolveStubsAndGotEntries();
     try self.updateMetadata();
@@ -315,6 +321,48 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
     }
 }
 
+fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void {
+    for (shared_libs) |lib| {
+        const dylib = try self.allocator.create(Dylib);
+        errdefer self.allocator.destroy(dylib);
+
+        dylib.* = Dylib.init(self.allocator);
+        dylib.arch = self.arch.?;
+        dylib.name = try self.allocator.dupe(u8, lib);
+        dylib.file = try fs.cwd().openFile(lib, .{});
+
+        const ordinal = @intCast(u16, self.dylibs.items.len);
+        dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
+
+        // TODO Defer parsing of the dylibs until they are actually needed
+        try dylib.parse();
+        try self.dylibs.append(self.allocator, dylib);
+
+        // Add LC_LOAD_DYLIB command
+        const cmdsize = @intCast(u32, mem.alignForwardGeneric(
+            u64,
+            @sizeOf(macho.dylib_command) + dylib.name.?.len,
+            @sizeOf(u64),
+        ));
+        // TODO Read the min version from the dylib itself.
+        const min_version = 0x0;
+        var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{
+            .cmd = macho.LC_LOAD_DYLIB,
+            .cmdsize = cmdsize,
+            .dylib = .{
+                .name = @sizeOf(macho.dylib_command),
+                .timestamp = 2, // TODO parse from the dylib.
+                .current_version = min_version,
+                .compatibility_version = min_version,
+            },
+        });
+        dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
+        mem.set(u8, dylib_cmd.data, 0);
+        mem.copy(u8, dylib_cmd.data, dylib.name.?);
+        try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
+    }
+}
+
 fn mapAndUpdateSections(
     self: *Zld,
     object_id: u16,
@@ -1398,35 +1446,51 @@ fn resolveSymbols(self: *Zld) !void {
     // Third pass, resolve symbols in dynamic libraries.
     // TODO Implement libSystem as a hard-coded library, or ship with
     // a libSystem.B.tbd definition file?
-    try self.imports.ensureCapacity(self.allocator, self.unresolved.count());
-    for (self.unresolved.items()) |entry| {
-        const proxy = try self.allocator.create(Symbol.Proxy);
-        errdefer self.allocator.destroy(proxy);
+    var unresolved = std.ArrayList(*Symbol).init(self.allocator);
+    defer unresolved.deinit();
 
-        proxy.* = .{
-            .base = .{
-                .@"type" = .proxy,
-                .name = try self.allocator.dupe(u8, entry.key),
-            },
-            .dylib = 0,
-        };
-
-        self.imports.putAssumeCapacityNoClobber(proxy.base.name, &proxy.base);
-        entry.value.alias = &proxy.base;
+    try unresolved.ensureCapacity(self.unresolved.count());
+    for (self.unresolved.items()) |entry| {
+        unresolved.appendAssumeCapacity(entry.value);
     }
     self.unresolved.clearAndFree(self.allocator);
 
-    // If there are any undefs left, flag an error.
-    if (self.unresolved.count() > 0) {
-        for (self.unresolved.items()) |entry| {
-            log.err("undefined reference to symbol '{s}'", .{entry.key});
-            log.err("    | referenced in {s}", .{
-                entry.value.cast(Symbol.Unresolved).?.file.name.?,
-            });
+    var has_undefined = false;
+    while (unresolved.popOrNull()) |undef| {
+        var found = false;
+        for (self.dylibs.items) |dylib| {
+            const proxy = dylib.symbols.get(undef.name) orelse continue;
+            try self.imports.putNoClobber(self.allocator, proxy.name, proxy);
+            undef.alias = proxy;
+            found = true;
+        }
+
+        if (!found) {
+            // TODO we currently hardcode all unresolved symbols to libSystem
+            const proxy = try self.allocator.create(Symbol.Proxy);
+            errdefer self.allocator.destroy(proxy);
+
+            proxy.* = .{
+                .base = .{
+                    .@"type" = .proxy,
+                    .name = try self.allocator.dupe(u8, undef.name),
+                },
+                .dylib = null, // TODO null means libSystem
+            };
+
+            try self.imports.putNoClobber(self.allocator, proxy.base.name, &proxy.base);
+            undef.alias = &proxy.base;
+
+            // log.err("undefined reference to symbol '{s}'", .{undef.name});
+            // log.err("    | referenced in {s}", .{
+            //     undef.cast(Symbol.Unresolved).?.file.name.?,
+            // });
+            // has_undefined = true;
         }
-        return error.UndefinedSymbolReference;
     }
 
+    if (has_undefined) return error.UndefinedSymbolReference;
+
     // Finally put dyld_stub_binder as an Import
     const dyld_stub_binder = try self.allocator.create(Symbol.Proxy);
     errdefer self.allocator.destroy(dyld_stub_binder);
@@ -1436,7 +1500,7 @@ fn resolveSymbols(self: *Zld) !void {
             .@"type" = .proxy,
             .name = try self.allocator.dupe(u8, "dyld_stub_binder"),
         },
-        .dylib = 0,
+        .dylib = null, // TODO null means libSystem
     };
 
     try self.imports.putNoClobber(
@@ -2303,7 +2367,10 @@ fn writeBindInfoTable(self: *Zld) !void {
 
         for (self.got_entries.items) |sym| {
             if (sym.cast(Symbol.Proxy)) |proxy| {
-                const dylib_ordinal = proxy.dylib + 1;
+                const dylib_ordinal = ordinal: {
+                    const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem
+                    break :ordinal dylib.ordinal.?;
+                };
                 try pointers.append(.{
                     .offset = base_offset + proxy.base.got_index.? * @sizeOf(u64),
                     .segment_id = segment_id,
@@ -2322,7 +2389,10 @@ fn writeBindInfoTable(self: *Zld) !void {
 
         const sym = self.imports.get("__tlv_bootstrap") orelse unreachable;
         const proxy = sym.cast(Symbol.Proxy) orelse unreachable;
-        const dylib_ordinal = proxy.dylib + 1;
+        const dylib_ordinal = ordinal: {
+            const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem
+            break :ordinal dylib.ordinal.?;
+        };
 
         try pointers.append(.{
             .offset = base_offset,
@@ -2364,7 +2434,11 @@ fn writeLazyBindInfoTable(self: *Zld) !void {
 
         for (self.stubs.items) |sym| {
             const proxy = sym.cast(Symbol.Proxy) orelse unreachable;
-            const dylib_ordinal = proxy.dylib + 1;
+            const dylib_ordinal = ordinal: {
+                const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem
+                break :ordinal dylib.ordinal.?;
+            };
+
             pointers.appendAssumeCapacity(.{
                 .offset = base_offset + sym.stubs_index.? * @sizeOf(u64),
                 .segment_id = segment_id,
src/link/MachO.zig
@@ -548,7 +548,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
     const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
     const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe;
     const target = self.base.options.target;
-    const stack_size = self.base.options.stack_size_override orelse 16777216;
+    const stack_size = self.base.options.stack_size_override orelse 0;
     const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
 
     const id_symlink_basename = "lld.id";
@@ -675,22 +675,114 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 zld.deinit();
             }
             zld.arch = target.cpu.arch;
+            zld.stack_size = stack_size;
 
-            var input_files = std.ArrayList([]const u8).init(self.base.allocator);
-            defer input_files.deinit();
-            // Positional arguments to the linker such as object files.
-            try input_files.appendSlice(self.base.options.objects);
+            // Positional arguments to the linker such as object files and static archives.
+            var positionals = std.ArrayList([]const u8).init(self.base.allocator);
+            defer positionals.deinit();
+
+            try positionals.appendSlice(self.base.options.objects);
             for (comp.c_object_table.items()) |entry| {
-                try input_files.append(entry.key.status.success.object_path);
+                try positionals.append(entry.key.status.success.object_path);
             }
             if (module_obj_path) |p| {
-                try input_files.append(p);
+                try positionals.append(p);
             }
-            try input_files.append(comp.compiler_rt_static_lib.?.full_object_path);
+            try positionals.append(comp.compiler_rt_static_lib.?.full_object_path);
+
             // libc++ dep
             if (self.base.options.link_libcpp) {
-                try input_files.append(comp.libcxxabi_static_lib.?.full_object_path);
-                try input_files.append(comp.libcxx_static_lib.?.full_object_path);
+                try positionals.append(comp.libcxxabi_static_lib.?.full_object_path);
+                try positionals.append(comp.libcxx_static_lib.?.full_object_path);
+            }
+
+            if (self.base.options.is_native_os) {}
+
+            // Shared libraries.
+            var shared_libs = std.ArrayList([]const u8).init(self.base.allocator);
+            defer {
+                for (shared_libs.items) |sh| {
+                    self.base.allocator.free(sh);
+                }
+                shared_libs.deinit();
+            }
+
+            var search_lib_names = std.ArrayList([]const u8).init(self.base.allocator);
+            defer search_lib_names.deinit();
+
+            const system_libs = self.base.options.system_libs.items();
+            for (system_libs) |entry| {
+                const link_lib = entry.key;
+                // By this time, we depend on these libs being dynamically linked libraries and not static libraries
+                // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
+                // case we want to avoid prepending "-l".
+                if (Compilation.classifyFileExt(link_lib) == .shared_library) {
+                    const path = try self.base.allocator.dupe(u8, link_lib);
+                    try shared_libs.append(path);
+                    continue;
+                }
+
+                try search_lib_names.append(link_lib);
+            }
+
+            for (search_lib_names.items) |l_name| {
+                // TODO text-based API, or .tbd files.
+                const l_name_ext = try std.fmt.allocPrint(self.base.allocator, "lib{s}.dylib", .{l_name});
+                defer self.base.allocator.free(l_name_ext);
+
+                var found = false;
+                if (self.base.options.syslibroot) |syslibroot| {
+                    for (self.base.options.lib_dirs) |lib_dir| {
+                        const path = try fs.path.join(self.base.allocator, &[_][]const u8{
+                            syslibroot,
+                            lib_dir,
+                            l_name_ext,
+                        });
+
+                        const tmp = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
+                            error.FileNotFound => {
+                                self.base.allocator.free(path);
+                                continue;
+                            },
+                            else => |e| return e,
+                        };
+                        defer tmp.close();
+
+                        try shared_libs.append(path);
+                        found = true;
+                        break;
+                    }
+                }
+
+                for (self.base.options.lib_dirs) |lib_dir| {
+                    const path = try fs.path.join(self.base.allocator, &[_][]const u8{ lib_dir, l_name_ext });
+
+                    const tmp = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
+                        error.FileNotFound => {
+                            self.base.allocator.free(path);
+                            continue;
+                        },
+                        else => |e| return e,
+                    };
+                    defer tmp.close();
+
+                    try shared_libs.append(path);
+                    found = true;
+                    break;
+                }
+
+                if (!found) {
+                    log.warn("library '-l{s}' not found", .{l_name});
+                    log.warn("searched paths:", .{});
+                    if (self.base.options.syslibroot) |syslibroot| {
+                        for (self.base.options.lib_dirs) |lib_dir| {
+                            log.warn("  {s}/{s}", .{ syslibroot, lib_dir });
+                        }
+                    }
+                    for (self.base.options.lib_dirs) |lib_dir| {
+                        log.warn("  {s}/", .{lib_dir});
+                    }
+                }
             }
 
             if (self.base.options.verbose_link) {
@@ -700,17 +792,28 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 try argv.append("zig");
                 try argv.append("ld");
 
-                try argv.appendSlice(input_files.items);
+                if (self.base.options.syslibroot) |syslibroot| {
+                    try argv.append("-syslibroot");
+                    try argv.append(syslibroot);
+                }
+
+                try argv.appendSlice(positionals.items);
 
                 try argv.append("-o");
                 try argv.append(full_out_path);
 
+                for (search_lib_names.items) |l_name| {
+                    try argv.append(try std.fmt.allocPrint(self.base.allocator, "-l{s}", .{l_name}));
+                }
+
+                for (self.base.options.lib_dirs) |lib_dir| {
+                    try argv.append(try std.fmt.allocPrint(self.base.allocator, "-L{s}", .{lib_dir}));
+                }
+
                 Compilation.dump_argv(argv.items);
             }
 
-            try zld.link(input_files.items, full_out_path, .{
-                .stack_size = self.base.options.stack_size_override,
-            });
+            try zld.link(positionals.items, shared_libs.items, full_out_path);
 
             break :outer;
         }
CMakeLists.txt
@@ -568,6 +568,7 @@ set(ZIG_STAGE2_SOURCES
     "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
+    "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig"
     "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"