Commit 0b4d398c40

Jakub Konka <kubkon@jakubkonka.com>
2023-11-08 17:35:56
elf: streamline codepaths for different linker modes (object, ar, exe/dyn)
1 parent a16e670
Changed files (1)
src
link
src/link/Elf.zig
@@ -959,6 +959,9 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     } else null;
     const gc_sections = self.base.options.gc_sections orelse false;
 
+    // --verbose-link
+    if (self.base.options.verbose_link) try self.dumpArgv(comp);
+
     var csu = try CsuObjects.init(arena, self.base.options, comp);
     const compiler_rt_path: ?[]const u8 = blk: {
         if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
@@ -966,247 +969,9 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
         break :blk null;
     };
 
-    // --verbose-link
-    if (self.base.options.verbose_link) {
-        var argv = std.ArrayList([]const u8).init(arena);
-
-        try argv.append("zig");
-        try argv.append("ld");
-
-        try argv.append("-o");
-        try argv.append(full_out_path);
-
-        if (self.base.options.entry) |entry| {
-            try argv.append("--entry");
-            try argv.append(entry);
-        }
-
-        if (self.base.options.dynamic_linker) |path| {
-            try argv.append("-dynamic-linker");
-            try argv.append(path);
-        }
-
-        if (self.base.options.soname) |name| {
-            try argv.append("-soname");
-            try argv.append(name);
-        }
-
-        for (self.base.options.rpath_list) |rpath| {
-            try argv.append("-rpath");
-            try argv.append(rpath);
-        }
-
-        if (self.base.options.each_lib_rpath) {
-            for (self.base.options.lib_dirs) |lib_dir_path| {
-                try argv.append("-rpath");
-                try argv.append(lib_dir_path);
-            }
-            for (self.base.options.objects) |obj| {
-                if (Compilation.classifyFileExt(obj.path) == .shared_library) {
-                    const lib_dir_path = std.fs.path.dirname(obj.path) orelse continue;
-                    if (obj.loption) continue;
-
-                    try argv.append("-rpath");
-                    try argv.append(lib_dir_path);
-                }
-            }
-        }
-
-        if (self.base.options.stack_size_override) |ss| {
-            try argv.append("-z");
-            try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{ss}));
-        }
-
-        if (self.base.options.image_base_override) |image_base| {
-            try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base}));
-        }
-
-        if (gc_sections) {
-            try argv.append("--gc-sections");
-        }
-
-        if (self.base.options.print_gc_sections) {
-            try argv.append("--print-gc-sections");
-        }
-
-        if (self.base.options.eh_frame_hdr) {
-            try argv.append("--eh-frame-hdr");
-        }
-
-        if (self.base.options.rdynamic) {
-            try argv.append("--export-dynamic");
-        }
-
-        if (self.base.options.strip) {
-            try argv.append("-s");
-        }
-
-        if (self.base.options.z_notext) {
-            try argv.append("-z");
-            try argv.append("notext");
-        }
-
-        if (self.base.options.z_nocopyreloc) {
-            try argv.append("-z");
-            try argv.append("nocopyreloc");
-        }
-
-        if (self.base.options.z_now) {
-            try argv.append("-z");
-            try argv.append("now");
-        }
-
-        if (self.isStatic()) {
-            try argv.append("-static");
-        } else if (self.isDynLib()) {
-            try argv.append("-shared");
-        }
-
-        if (self.base.options.pie and self.isExe()) {
-            try argv.append("-pie");
-        }
-
-        // csu prelude
-        if (csu.crt0) |v| try argv.append(v);
-        if (csu.crti) |v| try argv.append(v);
-        if (csu.crtbegin) |v| try argv.append(v);
-
-        for (self.base.options.lib_dirs) |lib_dir| {
-            try argv.append("-L");
-            try argv.append(lib_dir);
-        }
-
-        if (self.base.options.link_libc) {
-            if (self.base.options.libc_installation) |libc_installation| {
-                try argv.append("-L");
-                try argv.append(libc_installation.crt_dir.?);
-            }
-        }
-
-        var whole_archive = false;
-        for (self.base.options.objects) |obj| {
-            if (obj.must_link and !whole_archive) {
-                try argv.append("-whole-archive");
-                whole_archive = true;
-            } else if (!obj.must_link and whole_archive) {
-                try argv.append("-no-whole-archive");
-                whole_archive = false;
-            }
-
-            if (obj.loption) {
-                assert(obj.path[0] == ':');
-                try argv.append("-l");
-            }
-            try argv.append(obj.path);
-        }
-        if (whole_archive) {
-            try argv.append("-no-whole-archive");
-            whole_archive = false;
-        }
-
-        for (comp.c_object_table.keys()) |key| {
-            try argv.append(key.status.success.object_path);
-        }
-
-        if (module_obj_path) |p| {
-            try argv.append(p);
-        }
-
-        // TSAN
-        if (self.base.options.tsan) {
-            try argv.append(comp.tsan_static_lib.?.full_object_path);
-        }
-
-        // libc
-        if (!self.base.options.skip_linker_dependencies and
-            !self.base.options.link_libc)
-        {
-            if (comp.libc_static_lib) |lib| {
-                try argv.append(lib.full_object_path);
-            }
-        }
-
-        // stack-protector.
-        // Related: https://github.com/ziglang/zig/issues/7265
-        if (comp.libssp_static_lib) |ssp| {
-            try argv.append(ssp.full_object_path);
-        }
-
-        // Shared libraries.
-        // Worst-case, we need an --as-needed argument for every lib, as well
-        // as one before and one after.
-        try argv.ensureUnusedCapacity(self.base.options.system_libs.keys().len * 2 + 2);
-        argv.appendAssumeCapacity("--as-needed");
-        var as_needed = true;
-
-        for (self.base.options.system_libs.values()) |lib_info| {
-            const lib_as_needed = !lib_info.needed;
-            switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) {
-                0b00, 0b11 => {},
-                0b01 => {
-                    argv.appendAssumeCapacity("--no-as-needed");
-                    as_needed = false;
-                },
-                0b10 => {
-                    argv.appendAssumeCapacity("--as-needed");
-                    as_needed = true;
-                },
-            }
-            argv.appendAssumeCapacity(lib_info.path.?);
-        }
-
-        if (!as_needed) {
-            argv.appendAssumeCapacity("--as-needed");
-            as_needed = true;
-        }
-
-        // libc++ dep
-        if (self.base.options.link_libcpp) {
-            try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
-            try argv.append(comp.libcxx_static_lib.?.full_object_path);
-        }
-
-        // libunwind dep
-        if (self.base.options.link_libunwind) {
-            try argv.append(comp.libunwind_static_lib.?.full_object_path);
-        }
-
-        // libc dep
-        if (self.base.options.link_libc) {
-            if (self.base.options.libc_installation != null) {
-                const needs_grouping = self.base.options.link_mode == .Static;
-                if (needs_grouping) try argv.append("--start-group");
-                try argv.appendSlice(target_util.libcFullLinkFlags(target));
-                if (needs_grouping) try argv.append("--end-group");
-            } else if (target.isGnuLibC()) {
-                for (glibc.libs) |lib| {
-                    const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{
-                        comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
-                    });
-                    try argv.append(lib_path);
-                }
-                try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a"));
-            } else if (target.isMusl()) {
-                try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) {
-                    .Static => "libc.a",
-                    .Dynamic => "libc.so",
-                }));
-            }
-        }
-
-        // compiler-rt
-        if (compiler_rt_path) |p| {
-            try argv.append(p);
-        }
-
-        // crt postlude
-        if (csu.crtend) |v| try argv.append(v);
-        if (csu.crtn) |v| try argv.append(v);
-
-        Compilation.dump_argv(argv.items);
-    }
-
     if (self.zigObjectPtr()) |zig_object| try zig_object.flushModule(self);
+    if (self.isStaticLib()) return self.flushStaticLib(comp, module_obj_path);
+    if (self.isObject()) return self.flushObject(comp, module_obj_path);
 
     // Here we will parse input positional and library files (if referenced).
     // This will roughly match in any linker backend we support.
@@ -1232,6 +997,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     // rpaths
     var rpath_table = std.StringArrayHashMap(void).init(self.base.allocator);
     defer rpath_table.deinit();
+
     for (self.base.options.rpath_list) |rpath| {
         _ = try rpath_table.put(rpath, {});
     }
@@ -1380,8 +1146,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
             try self.handleAndReportParseError(obj.path, err, &parse_ctx);
     }
 
-    if (self.isStaticLib()) return self.flushStaticLib();
-
     // Init all objects
     for (self.objects.items) |index| {
         try self.file(index).?.object.init(self);
@@ -1410,7 +1174,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
 
     // If we haven't already, create a linker-generated input file comprising of
     // linker-defined synthetic symbols only such as `_DYNAMIC`, etc.
-    if (self.linker_defined_index == null and !self.isRelocatable()) {
+    if (self.linker_defined_index == null) {
         const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
         self.files.set(index, .{ .linker_defined = .{ .index = index } });
         self.linker_defined_index = index;
@@ -1424,8 +1188,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     self.resolveSymbols();
     self.markEhFrameAtomsDead();
 
-    if (self.isObject()) return self.flushObject();
-
     try self.convertCommonSymbols();
     self.markImportsExports();
 
@@ -1519,145 +1281,490 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     }
 }
 
-pub fn flushStaticLib(self: *Elf) link.File.FlushError!void {
-    const gpa = self.base.allocator;
+pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void {
+    const gpa = self.base.allocator;
+
+    var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
+    defer positionals.deinit();
+
+    try positionals.ensureUnusedCapacity(self.base.options.objects.len);
+    positionals.appendSliceAssumeCapacity(self.base.options.objects);
+
+    // This is a set of object files emitted by clang in a single `build-exe` invocation.
+    // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
+    // in this set.
+    for (comp.c_object_table.keys()) |key| {
+        try positionals.append(.{ .path = key.status.success.object_path });
+    }
+
+    if (module_obj_path) |path| try positionals.append(.{ .path = path });
+
+    for (positionals.items) |obj| {
+        var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined };
+        self.parsePositional(obj.path, obj.must_link, &parse_ctx) catch |err|
+            try self.handleAndReportParseError(obj.path, err, &parse_ctx);
+    }
+
+    // First, we flush relocatable object file generated with our backends.
+    if (self.zigObjectPtr()) |zig_object| {
+        zig_object.resolveSymbols(self);
+        zig_object.claimUnresolvedObject(self);
+
+        try self.initSymtab();
+        try self.initShStrtab();
+        try self.sortShdrs();
+        try zig_object.addAtomsToRelaSections(self);
+        try self.updateSectionSizesObject();
+
+        try self.allocateNonAllocSections();
+
+        if (build_options.enable_logging) {
+            state_log.debug("{}", .{self.dumpState()});
+        }
+
+        try self.writeSyntheticSectionsObject();
+        try self.writeShdrTable();
+        try self.writeElfHeader();
+    }
+
+    var files = std.ArrayList(File.Index).init(gpa);
+    defer files.deinit();
+    try files.ensureTotalCapacityPrecise(self.objects.items.len + 1);
+    // Note to self: we currently must have ZigObject written out first as we write the object
+    // file into the same file descriptor and then re-read its contents.
+    // TODO implement writing ZigObject to a buffer instead of file.
+    if (self.zigObjectPtr()) |zig_object| files.appendAssumeCapacity(zig_object.index);
+    for (self.objects.items) |index| files.appendAssumeCapacity(index);
+
+    // Update ar symtab from parsed objects
+    var ar_symtab: Archive.ArSymtab = .{};
+    defer ar_symtab.deinit(gpa);
+
+    for (files.items) |index| {
+        try self.file(index).?.updateArSymtab(&ar_symtab, self);
+    }
+
+    ar_symtab.sort();
+
+    // Save object paths in filenames strtab.
+    var ar_strtab: Archive.ArStrtab = .{};
+    defer ar_strtab.deinit(gpa);
+
+    for (files.items) |index| {
+        const file_ptr = self.file(index).?;
+        try file_ptr.updateArStrtab(gpa, &ar_strtab);
+        file_ptr.updateArSize(self);
+    }
+
+    // Update file offsets of contributing objects.
+    const total_size: usize = blk: {
+        var pos: usize = elf.ARMAG.len;
+        pos += @sizeOf(elf.ar_hdr) + ar_symtab.size(.p64);
+
+        if (ar_strtab.size() > 0) {
+            pos = mem.alignForward(usize, pos, 2);
+            pos += @sizeOf(elf.ar_hdr) + ar_strtab.size();
+        }
+
+        for (files.items) |index| {
+            const file_ptr = self.file(index).?;
+            const state = switch (file_ptr) {
+                .zig_object => |x| &x.output_ar_state,
+                .object => |x| &x.output_ar_state,
+                else => unreachable,
+            };
+            pos = mem.alignForward(usize, pos, 2);
+            state.file_off = pos;
+            pos += @sizeOf(elf.ar_hdr) + (math.cast(usize, state.size) orelse return error.Overflow);
+        }
+
+        break :blk pos;
+    };
+
+    if (build_options.enable_logging) {
+        state_log.debug("ar_symtab\n{}\n", .{ar_symtab.fmt(self)});
+        state_log.debug("ar_strtab\n{}\n", .{ar_strtab});
+    }
+
+    var buffer = std.ArrayList(u8).init(gpa);
+    defer buffer.deinit();
+    try buffer.ensureTotalCapacityPrecise(total_size);
+
+    // Write magic
+    try buffer.writer().writeAll(elf.ARMAG);
+
+    // Write symtab
+    try ar_symtab.write(.p64, self, buffer.writer());
+
+    // Write strtab
+    if (ar_strtab.size() > 0) {
+        if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
+        try ar_strtab.write(buffer.writer());
+    }
+
+    // Write object files
+    for (files.items) |index| {
+        if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
+        try self.file(index).?.writeAr(self, buffer.writer());
+    }
+
+    assert(buffer.items.len == total_size);
+
+    try self.base.file.?.setEndPos(total_size);
+    try self.base.file.?.pwriteAll(buffer.items, 0);
+}
+
+pub fn flushObject(self: *Elf, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void {
+    const gpa = self.base.allocator;
+
+    var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
+    defer positionals.deinit();
+    try positionals.ensureUnusedCapacity(self.base.options.objects.len);
+    positionals.appendSliceAssumeCapacity(self.base.options.objects);
+
+    // This is a set of object files emitted by clang in a single `build-exe` invocation.
+    // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
+    // in this set.
+    for (comp.c_object_table.keys()) |key| {
+        try positionals.append(.{ .path = key.status.success.object_path });
+    }
+
+    if (module_obj_path) |path| try positionals.append(.{ .path = path });
+
+    for (positionals.items) |obj| {
+        var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined };
+        self.parsePositional(obj.path, obj.must_link, &parse_ctx) catch |err|
+            try self.handleAndReportParseError(obj.path, err, &parse_ctx);
+    }
+
+    // Init all objects
+    for (self.objects.items) |index| {
+        try self.file(index).?.object.init(self);
+    }
+
+    // Now, we are ready to resolve the symbols across all input files.
+    // We will first resolve the files in the ZigObject, next in the parsed
+    // input Object files.
+    // Any qualifing unresolved symbol will be upgraded to an absolute, weak
+    // symbol for potential resolution at load-time.
+    self.resolveSymbols();
+    self.markEhFrameAtomsDead();
+    self.claimUnresolvedObject();
+
+    try self.initSectionsObject();
+    try self.sortShdrs();
+    if (self.zigObjectPtr()) |zig_object| {
+        try zig_object.addAtomsToRelaSections(self);
+    }
+    for (self.objects.items) |index| {
+        const object = self.file(index).?.object;
+        try object.addAtomsToOutputSections(self);
+        try object.addAtomsToRelaSections(self);
+    }
+    try self.updateSectionSizesObject();
+
+    try self.allocateAllocSectionsObject();
+    try self.allocateNonAllocSections();
+    self.allocateAtoms();
+
+    if (build_options.enable_logging) {
+        state_log.debug("{}", .{self.dumpState()});
+    }
+
+    try self.writeAtomsObject();
+    try self.writeSyntheticSectionsObject();
+    try self.writeShdrTable();
+    try self.writeElfHeader();
+}
+
+/// --verbose-link output
+fn dumpArgv(self: *Elf, comp: *Compilation) !void {
+    var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
+    defer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
+
+    const target = self.base.options.target;
+    const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
+    const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
+    const module_obj_path: ?[]const u8 = if (self.base.intermediary_basename) |path| blk: {
+        if (fs.path.dirname(full_out_path)) |dirname| {
+            break :blk try fs.path.join(arena, &.{ dirname, path });
+        } else {
+            break :blk path;
+        }
+    } else null;
+    const gc_sections = self.base.options.gc_sections orelse false;
+
+    var csu = try CsuObjects.init(arena, self.base.options, comp);
+    const compiler_rt_path: ?[]const u8 = blk: {
+        if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
+        if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
+        break :blk null;
+    };
+
+    var argv = std.ArrayList([]const u8).init(arena);
+
+    try argv.append("zig");
+
+    if (self.isStaticLib()) {
+        try argv.append("ar");
+    } else {
+        try argv.append("ld");
+    }
+
+    if (self.isObject()) {
+        try argv.append("-r");
+    }
+
+    try argv.append("-o");
+    try argv.append(full_out_path);
+
+    if (self.isRelocatable()) {
+        for (self.base.options.objects) |obj| {
+            try argv.append(obj.path);
+        }
+
+        for (comp.c_object_table.keys()) |key| {
+            try argv.append(key.status.success.object_path);
+        }
+
+        if (module_obj_path) |p| {
+            try argv.append(p);
+        }
+    } else {
+        if (!self.isStatic()) {
+            if (self.base.options.dynamic_linker) |path| {
+                try argv.append("-dynamic-linker");
+                try argv.append(path);
+            }
+        }
+
+        if (self.isDynLib()) {
+            if (self.base.options.soname) |name| {
+                try argv.append("-soname");
+                try argv.append(name);
+            }
+        }
+
+        if (self.base.options.entry) |entry| {
+            try argv.append("--entry");
+            try argv.append(entry);
+        }
+
+        for (self.base.options.rpath_list) |rpath| {
+            try argv.append("-rpath");
+            try argv.append(rpath);
+        }
+
+        if (self.base.options.each_lib_rpath) {
+            for (self.base.options.lib_dirs) |lib_dir_path| {
+                try argv.append("-rpath");
+                try argv.append(lib_dir_path);
+            }
+            for (self.base.options.objects) |obj| {
+                if (Compilation.classifyFileExt(obj.path) == .shared_library) {
+                    const lib_dir_path = std.fs.path.dirname(obj.path) orelse continue;
+                    if (obj.loption) continue;
+
+                    try argv.append("-rpath");
+                    try argv.append(lib_dir_path);
+                }
+            }
+        }
+
+        if (self.base.options.stack_size_override) |ss| {
+            try argv.append("-z");
+            try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{ss}));
+        }
+
+        if (self.base.options.image_base_override) |image_base| {
+            try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base}));
+        }
+
+        if (gc_sections) {
+            try argv.append("--gc-sections");
+        }
+
+        if (self.base.options.print_gc_sections) {
+            try argv.append("--print-gc-sections");
+        }
 
-    // First, we flush relocatable object file generated with our backends.
-    if (self.zigObjectPtr()) |zig_object| {
-        zig_object.resolveSymbols(self);
-        zig_object.claimUnresolvedObject(self);
+        if (self.base.options.eh_frame_hdr) {
+            try argv.append("--eh-frame-hdr");
+        }
 
-        try self.initSymtab();
-        try self.initShStrtab();
-        try self.sortShdrs();
-        try zig_object.addAtomsToRelaSections(self);
-        try self.updateSectionSizesObject();
+        if (self.base.options.rdynamic) {
+            try argv.append("--export-dynamic");
+        }
 
-        try self.allocateNonAllocSections();
+        if (self.base.options.z_notext) {
+            try argv.append("-z");
+            try argv.append("notext");
+        }
 
-        if (build_options.enable_logging) {
-            state_log.debug("{}", .{self.dumpState()});
+        if (self.base.options.z_nocopyreloc) {
+            try argv.append("-z");
+            try argv.append("nocopyreloc");
         }
 
-        try self.writeSyntheticSectionsObject();
-        try self.writeShdrTable();
-        try self.writeElfHeader();
-    }
+        if (self.base.options.z_now) {
+            try argv.append("-z");
+            try argv.append("now");
+        }
 
-    var files = std.ArrayList(File.Index).init(gpa);
-    defer files.deinit();
-    try files.ensureTotalCapacityPrecise(self.objects.items.len + 1);
-    // Note to self: we currently must have ZigObject written out first as we write the object
-    // file into the same file descriptor and then re-read its contents.
-    // TODO implement writing ZigObject to a buffer instead of file.
-    if (self.zigObjectPtr()) |zig_object| files.appendAssumeCapacity(zig_object.index);
-    for (self.objects.items) |index| files.appendAssumeCapacity(index);
+        if (self.isStatic()) {
+            try argv.append("-static");
+        } else if (self.isDynLib()) {
+            try argv.append("-shared");
+        }
 
-    // Update ar symtab from parsed objects
-    var ar_symtab: Archive.ArSymtab = .{};
-    defer ar_symtab.deinit(gpa);
+        if (self.base.options.pie and self.isExe()) {
+            try argv.append("-pie");
+        }
 
-    for (files.items) |index| {
-        try self.file(index).?.updateArSymtab(&ar_symtab, self);
-    }
+        if (self.base.options.strip) {
+            try argv.append("-s");
+        }
 
-    ar_symtab.sort();
+        // csu prelude
+        if (csu.crt0) |v| try argv.append(v);
+        if (csu.crti) |v| try argv.append(v);
+        if (csu.crtbegin) |v| try argv.append(v);
 
-    // Save object paths in filenames strtab.
-    var ar_strtab: Archive.ArStrtab = .{};
-    defer ar_strtab.deinit(gpa);
+        for (self.base.options.lib_dirs) |lib_dir| {
+            try argv.append("-L");
+            try argv.append(lib_dir);
+        }
 
-    for (files.items) |index| {
-        const file_ptr = self.file(index).?;
-        try file_ptr.updateArStrtab(gpa, &ar_strtab);
-        file_ptr.updateArSize(self);
-    }
+        if (self.base.options.link_libc) {
+            if (self.base.options.libc_installation) |libc_installation| {
+                try argv.append("-L");
+                try argv.append(libc_installation.crt_dir.?);
+            }
+        }
 
-    // Update file offsets of contributing objects.
-    const total_size: usize = blk: {
-        var pos: usize = elf.ARMAG.len;
-        pos += @sizeOf(elf.ar_hdr) + ar_symtab.size(.p64);
+        var whole_archive = false;
+        for (self.base.options.objects) |obj| {
+            if (obj.must_link and !whole_archive) {
+                try argv.append("-whole-archive");
+                whole_archive = true;
+            } else if (!obj.must_link and whole_archive) {
+                try argv.append("-no-whole-archive");
+                whole_archive = false;
+            }
 
-        if (ar_strtab.size() > 0) {
-            pos = mem.alignForward(usize, pos, 2);
-            pos += @sizeOf(elf.ar_hdr) + ar_strtab.size();
+            if (obj.loption) {
+                assert(obj.path[0] == ':');
+                try argv.append("-l");
+            }
+            try argv.append(obj.path);
         }
-
-        for (files.items) |index| {
-            const file_ptr = self.file(index).?;
-            const state = switch (file_ptr) {
-                .zig_object => |x| &x.output_ar_state,
-                .object => |x| &x.output_ar_state,
-                else => unreachable,
-            };
-            pos = mem.alignForward(usize, pos, 2);
-            state.file_off = pos;
-            pos += @sizeOf(elf.ar_hdr) + (math.cast(usize, state.size) orelse return error.Overflow);
+        if (whole_archive) {
+            try argv.append("-no-whole-archive");
+            whole_archive = false;
         }
 
-        break :blk pos;
-    };
+        for (comp.c_object_table.keys()) |key| {
+            try argv.append(key.status.success.object_path);
+        }
 
-    if (build_options.enable_logging) {
-        state_log.debug("ar_symtab\n{}\n", .{ar_symtab.fmt(self)});
-        state_log.debug("ar_strtab\n{}\n", .{ar_strtab});
-    }
+        if (module_obj_path) |p| {
+            try argv.append(p);
+        }
 
-    var buffer = std.ArrayList(u8).init(gpa);
-    defer buffer.deinit();
-    try buffer.ensureTotalCapacityPrecise(total_size);
+        // TSAN
+        if (self.base.options.tsan) {
+            try argv.append(comp.tsan_static_lib.?.full_object_path);
+        }
 
-    // Write magic
-    try buffer.writer().writeAll(elf.ARMAG);
+        // libc
+        if (!self.base.options.skip_linker_dependencies and
+            !self.base.options.link_libc)
+        {
+            if (comp.libc_static_lib) |lib| {
+                try argv.append(lib.full_object_path);
+            }
+        }
 
-    // Write symtab
-    try ar_symtab.write(.p64, self, buffer.writer());
+        // stack-protector.
+        // Related: https://github.com/ziglang/zig/issues/7265
+        if (comp.libssp_static_lib) |ssp| {
+            try argv.append(ssp.full_object_path);
+        }
 
-    // Write strtab
-    if (ar_strtab.size() > 0) {
-        if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
-        try ar_strtab.write(buffer.writer());
-    }
+        // Shared libraries.
+        // Worst-case, we need an --as-needed argument for every lib, as well
+        // as one before and one after.
+        try argv.ensureUnusedCapacity(self.base.options.system_libs.keys().len * 2 + 2);
+        argv.appendAssumeCapacity("--as-needed");
+        var as_needed = true;
 
-    // Write object files
-    for (files.items) |index| {
-        if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
-        try self.file(index).?.writeAr(self, buffer.writer());
-    }
+        for (self.base.options.system_libs.values()) |lib_info| {
+            const lib_as_needed = !lib_info.needed;
+            switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) {
+                0b00, 0b11 => {},
+                0b01 => {
+                    argv.appendAssumeCapacity("--no-as-needed");
+                    as_needed = false;
+                },
+                0b10 => {
+                    argv.appendAssumeCapacity("--as-needed");
+                    as_needed = true;
+                },
+            }
+            argv.appendAssumeCapacity(lib_info.path.?);
+        }
 
-    assert(buffer.items.len == total_size);
+        if (!as_needed) {
+            argv.appendAssumeCapacity("--as-needed");
+            as_needed = true;
+        }
 
-    try self.base.file.?.setEndPos(total_size);
-    try self.base.file.?.pwriteAll(buffer.items, 0);
-}
+        // libc++ dep
+        if (self.base.options.link_libcpp) {
+            try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
+            try argv.append(comp.libcxx_static_lib.?.full_object_path);
+        }
 
-pub fn flushObject(self: *Elf) link.File.FlushError!void {
-    self.claimUnresolvedObject();
+        // libunwind dep
+        if (self.base.options.link_libunwind) {
+            try argv.append(comp.libunwind_static_lib.?.full_object_path);
+        }
 
-    try self.initSectionsObject();
-    try self.sortShdrs();
-    if (self.zigObjectPtr()) |zig_object| {
-        try zig_object.addAtomsToRelaSections(self);
-    }
-    for (self.objects.items) |index| {
-        const object = self.file(index).?.object;
-        try object.addAtomsToOutputSections(self);
-        try object.addAtomsToRelaSections(self);
-    }
-    try self.updateSectionSizesObject();
+        // libc dep
+        if (self.base.options.link_libc) {
+            if (self.base.options.libc_installation != null) {
+                const needs_grouping = self.base.options.link_mode == .Static;
+                if (needs_grouping) try argv.append("--start-group");
+                try argv.appendSlice(target_util.libcFullLinkFlags(target));
+                if (needs_grouping) try argv.append("--end-group");
+            } else if (target.isGnuLibC()) {
+                for (glibc.libs) |lib| {
+                    const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{
+                        comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
+                    });
+                    try argv.append(lib_path);
+                }
+                try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a"));
+            } else if (target.isMusl()) {
+                try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) {
+                    .Static => "libc.a",
+                    .Dynamic => "libc.so",
+                }));
+            }
+        }
 
-    try self.allocateAllocSectionsObject();
-    try self.allocateNonAllocSections();
-    self.allocateAtoms();
+        // compiler-rt
+        if (compiler_rt_path) |p| {
+            try argv.append(p);
+        }
 
-    if (build_options.enable_logging) {
-        state_log.debug("{}", .{self.dumpState()});
+        // crt postlude
+        if (csu.crtend) |v| try argv.append(v);
+        if (csu.crtn) |v| try argv.append(v);
     }
 
-    try self.writeAtomsObject();
-    try self.writeSyntheticSectionsObject();
-    try self.writeShdrTable();
-    try self.writeElfHeader();
+    Compilation.dump_argv(argv.items);
 }
 
 const ParseError = error{
@@ -3651,8 +3758,10 @@ fn setDynamicSection(self: *Elf, rpaths: []const []const u8) !void {
         try self.dynamic.addNeeded(shared_object, self);
     }
 
-    if (self.base.options.soname) |soname| {
-        try self.dynamic.setSoname(soname, self);
+    if (self.isDynLib()) {
+        if (self.base.options.soname) |soname| {
+            try self.dynamic.setSoname(soname, self);
+        }
     }
 
     try self.dynamic.setRpath(rpaths, self);