Commit d1446565a1

Jakub Konka <kubkon@jakubkonka.com>
2023-10-05 17:53:04
elf: re-enable dynamic linking codepaths
1 parent 04a8f21
src/link/Elf/file.zig
@@ -2,7 +2,7 @@ pub const File = union(enum) {
     zig_module: *ZigModule,
     linker_defined: *LinkerDefined,
     object: *Object,
-    // shared_object: *SharedObject,
+    shared_object: *SharedObject,
 
     pub fn index(file: File) Index {
         return switch (file) {
@@ -26,7 +26,7 @@ pub const File = union(enum) {
             .zig_module => |x| try writer.print("{s}", .{x.path}),
             .linker_defined => try writer.writeAll("(linker defined)"),
             .object => |x| try writer.print("{}", .{x.fmtPath()}),
-            // .shared_object => |x| try writer.writeAll(x.path),
+            .shared_object => |x| try writer.writeAll(x.path),
         }
     }
 
@@ -49,8 +49,7 @@ pub const File = union(enum) {
     pub fn symbolRank(file: File, sym: elf.Elf64_Sym, in_archive: bool) u32 {
         const base: u3 = blk: {
             if (sym.st_shndx == elf.SHN_COMMON) break :blk if (in_archive) 6 else 5;
-            // if (file == .shared or in_archive) break :blk switch (sym.st_bind()) {
-            if (in_archive) break :blk switch (sym.st_bind()) {
+            if (file == .shared_object or in_archive) break :blk switch (sym.st_bind()) {
                 elf.STB_GLOBAL => 3,
                 else => 4,
             };
@@ -92,6 +91,7 @@ pub const File = union(enum) {
     pub fn atoms(file: File) []const Atom.Index {
         return switch (file) {
             .linker_defined => unreachable,
+            .shared_object => unreachable,
             .zig_module => |x| x.atoms.items,
             .object => |x| x.atoms.items,
         };
@@ -100,6 +100,7 @@ pub const File = union(enum) {
     pub fn locals(file: File) []const Symbol.Index {
         return switch (file) {
             .linker_defined => unreachable,
+            .shared_object => unreachable,
             inline else => |x| x.locals(),
         };
     }
@@ -117,7 +118,7 @@ pub const File = union(enum) {
         zig_module: ZigModule,
         linker_defined: LinkerDefined,
         object: Object,
-        // shared_object: SharedObject,
+        shared_object: SharedObject,
     };
 };
 
@@ -129,6 +130,6 @@ const Atom = @import("Atom.zig");
 const Elf = @import("../Elf.zig");
 const LinkerDefined = @import("LinkerDefined.zig");
 const Object = @import("Object.zig");
-// const SharedObject = @import("SharedObject.zig");
+const SharedObject = @import("SharedObject.zig");
 const Symbol = @import("Symbol.zig");
 const ZigModule = @import("ZigModule.zig");
src/link/Elf/SharedObject.zig
@@ -0,0 +1,361 @@
+path: []const u8,
+data: []const u8,
+index: File.Index,
+
+header: ?elf.Elf64_Ehdr = null,
+shdrs: std.ArrayListUnmanaged(ElfShdr) = .{},
+symtab: []align(1) const elf.Elf64_Sym = &[0]elf.Elf64_Sym{},
+strtab: []const u8 = &[0]u8{},
+/// Version symtab contains version strings of the symbols if present.
+versyms: std.ArrayListUnmanaged(elf.Elf64_Versym) = .{},
+verstrings: std.ArrayListUnmanaged(u32) = .{},
+
+dynamic_sect_index: ?u16 = null,
+versym_sect_index: ?u16 = null,
+verdef_sect_index: ?u16 = null,
+
+symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+aliases: ?std.ArrayListUnmanaged(u32) = null,
+
+needed: bool,
+alive: bool,
+
+output_symtab_size: Elf.SymtabSize = .{},
+
+pub fn isSharedObject(file: std.fs.File) bool {
+    const reader = file.reader();
+    const header = reader.readStruct(elf.Elf64_Ehdr) catch return false;
+    defer file.seekTo(0) catch {};
+    if (!mem.eql(u8, header.e_ident[0..4], "\x7fELF")) return false;
+    if (header.e_ident[elf.EI_VERSION] != 1) return false;
+    if (header.e_type != elf.ET.DYN) return false;
+    return true;
+}
+
+pub fn deinit(self: *SharedObject, allocator: Allocator) void {
+    self.versyms.deinit(allocator);
+    self.verstrings.deinit(allocator);
+    self.symbols.deinit(allocator);
+    if (self.aliases) |*aliases| aliases.deinit(allocator);
+    self.shdrs.deinit(allocator);
+}
+
+pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
+    const gpa = elf_file.base.allocator;
+    var stream = std.io.fixedBufferStream(self.data);
+    const reader = stream.reader();
+
+    self.header = try reader.readStruct(elf.Elf64_Ehdr);
+
+    var dynsym_index: ?u16 = null;
+    const shdrs = @as(
+        [*]align(1) const elf.Elf64_Shdr,
+        @ptrCast(self.data.ptr + self.header.?.e_shoff),
+    )[0..self.header.?.e_shnum];
+    try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len);
+
+    for (shdrs, 0..) |shdr, i| {
+        self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr));
+        switch (shdr.sh_type) {
+            elf.SHT_DYNSYM => dynsym_index = @as(u16, @intCast(i)),
+            elf.SHT_DYNAMIC => self.dynamic_sect_index = @as(u16, @intCast(i)),
+            elf.SHT_GNU_VERSYM => self.versym_sect_index = @as(u16, @intCast(i)),
+            elf.SHT_GNU_VERDEF => self.verdef_sect_index = @as(u16, @intCast(i)),
+            else => {},
+        }
+    }
+
+    if (dynsym_index) |index| {
+        const shdr = self.shdrs.items[index];
+        const symtab = self.shdrContents(index);
+        const nsyms = @divExact(symtab.len, @sizeOf(elf.Elf64_Sym));
+        self.symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(symtab.ptr))[0..nsyms];
+        self.strtab = self.shdrContents(@as(u16, @intCast(shdr.sh_link)));
+    }
+
+    try self.parseVersions(elf_file);
+    try self.initSymtab(elf_file);
+}
+
+fn parseVersions(self: *SharedObject, elf_file: *Elf) !void {
+    const gpa = elf_file.base.allocator;
+
+    try self.verstrings.resize(gpa, 2);
+    self.verstrings.items[elf.VER_NDX_LOCAL] = 0;
+    self.verstrings.items[elf.VER_NDX_GLOBAL] = 0;
+
+    if (self.verdef_sect_index) |shndx| {
+        const verdefs = self.shdrContents(shndx);
+        const nverdefs = self.verdefNum();
+        try self.verstrings.resize(gpa, self.verstrings.items.len + nverdefs);
+
+        var i: u32 = 0;
+        var offset: u32 = 0;
+        while (i < nverdefs) : (i += 1) {
+            const verdef = @as(*align(1) const elf.Elf64_Verdef, @ptrCast(verdefs.ptr + offset)).*;
+            defer offset += verdef.vd_next;
+            if (verdef.vd_flags == elf.VER_FLG_BASE) continue; // Skip BASE entry
+            const vda_name = if (verdef.vd_cnt > 0)
+                @as(*align(1) const elf.Elf64_Verdaux, @ptrCast(verdefs.ptr + offset + verdef.vd_aux)).vda_name
+            else
+                0;
+            self.verstrings.items[verdef.vd_ndx] = vda_name;
+        }
+    }
+
+    try self.versyms.ensureTotalCapacityPrecise(gpa, self.symtab.len);
+
+    if (self.versym_sect_index) |shndx| {
+        const versyms_raw = self.shdrContents(shndx);
+        const nversyms = @divExact(versyms_raw.len, @sizeOf(elf.Elf64_Versym));
+        const versyms = @as([*]align(1) const elf.Elf64_Versym, @ptrCast(versyms_raw.ptr))[0..nversyms];
+        for (versyms) |ver| {
+            const normalized_ver = if (ver & elf.VERSYM_VERSION >= self.verstrings.items.len - 1)
+                elf.VER_NDX_GLOBAL
+            else
+                ver;
+            self.versyms.appendAssumeCapacity(normalized_ver);
+        }
+    } else for (0..self.symtab.len) |_| {
+        self.versyms.appendAssumeCapacity(elf.VER_NDX_GLOBAL);
+    }
+}
+
+fn initSymtab(self: *SharedObject, elf_file: *Elf) !void {
+    const gpa = elf_file.base.allocator;
+
+    try self.symbols.ensureTotalCapacityPrecise(gpa, self.symtab.len);
+
+    for (self.symtab, 0..) |sym, i| {
+        const hidden = self.versyms.items[i] & elf.VERSYM_HIDDEN != 0;
+        const name = self.getString(sym.st_name);
+        // We need to garble up the name so that we don't pick this symbol
+        // during symbol resolution. Thank you GNU!
+        const off = if (hidden) blk: {
+            const full_name = try std.fmt.allocPrint(gpa, "{s}@{s}", .{
+                name,
+                self.versionString(self.versyms.items[i]),
+            });
+            defer gpa.free(full_name);
+            break :blk try elf_file.strtab.insert(gpa, full_name);
+        } else try elf_file.strtab.insert(gpa, name);
+        const gop = try elf_file.getOrCreateGlobal(off);
+        self.symbols.addOneAssumeCapacity().* = gop.index;
+    }
+}
+
+pub fn resolveSymbols(self: *SharedObject, elf_file: *Elf) void {
+    for (self.globals(), 0..) |index, i| {
+        const esym_index = @as(u32, @intCast(i));
+        const this_sym = self.symtab[esym_index];
+
+        if (this_sym.st_shndx == elf.SHN_UNDEF) continue;
+
+        const global = elf_file.symbol(index);
+        if (self.asFile().symbolRank(this_sym, false) < global.symbolRank(elf_file)) {
+            global.value = this_sym.st_value;
+            global.atom_index = 0;
+            global.esym_index = esym_index;
+            global.version_index = self.versyms.items[esym_index];
+            global.file_index = self.index;
+        }
+    }
+}
+
+pub fn resetGlobals(self: *SharedObject, elf_file: *Elf) void {
+    for (self.globals()) |index| {
+        const global = elf_file.symbol(index);
+        const off = global.name_offset;
+        global.* = .{};
+        global.name_offset = off;
+    }
+}
+
+pub fn markLive(self: *SharedObject, elf_file: *Elf) void {
+    for (self.globals(), 0..) |index, i| {
+        const sym = self.symtab[i];
+        if (sym.st_shndx != elf.SHN_UNDEF) continue;
+
+        const global = elf_file.symbol(index);
+        const file = global.file(elf_file) orelse continue;
+        const should_drop = switch (file) {
+            .shared_object => |sh| !sh.needed and sym.st_bind() == elf.STB_WEAK,
+            else => false,
+        };
+        if (!should_drop and !file.isAlive()) {
+            file.setAlive();
+            file.markLive(elf_file);
+        }
+    }
+}
+
+pub fn updateSymtabSize(self: *SharedObject, elf_file: *Elf) void {
+    for (self.globals()) |global_index| {
+        const global = elf_file.symbol(global_index);
+        if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
+        if (global.isLocal()) continue;
+        global.flags.output_symtab = true;
+        self.output_symtab_size.nglobals += 1;
+    }
+}
+
+pub fn writeSymtab(self: *SharedObject, elf_file: *Elf, ctx: anytype) void {
+    var iglobal = ctx.iglobal;
+    for (self.globals()) |global_index| {
+        const global = elf_file.symbol(global_index);
+        if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
+        if (!global.flags.output_symtab) continue;
+        global.setOutputSym(elf_file, &ctx.symtab[iglobal]);
+        iglobal += 1;
+    }
+}
+
+pub fn globals(self: SharedObject) []const Symbol.Index {
+    return self.symbols.items;
+}
+
+pub fn shdrContents(self: SharedObject, index: u16) []const u8 {
+    const shdr = self.shdrs.items[index];
+    return self.data[shdr.sh_offset..][0..shdr.sh_size];
+}
+
+pub fn getString(self: SharedObject, off: u32) [:0]const u8 {
+    assert(off < self.strtab.len);
+    return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);
+}
+
+pub fn versionString(self: SharedObject, index: elf.Elf64_Versym) [:0]const u8 {
+    const off = self.verstrings.items[index & elf.VERSYM_VERSION];
+    return self.getString(off);
+}
+
+pub fn asFile(self: *SharedObject) File {
+    return .{ .shared_object = self };
+}
+
+fn dynamicTable(self: *SharedObject) []align(1) const elf.Elf64_Dyn {
+    const shndx = self.dynamic_sect_index orelse return &[0]elf.Elf64_Dyn{};
+    const raw = self.shdrContents(shndx);
+    const num = @divExact(raw.len, @sizeOf(elf.Elf64_Dyn));
+    return @as([*]align(1) const elf.Elf64_Dyn, @ptrCast(raw.ptr))[0..num];
+}
+
+fn verdefNum(self: *SharedObject) u32 {
+    const entries = self.dynamicTable();
+    for (entries) |entry| switch (entry.d_tag) {
+        elf.DT_VERDEFNUM => return @as(u32, @intCast(entry.d_val)),
+        else => {},
+    };
+    return 0;
+}
+
+pub fn soname(self: *SharedObject) []const u8 {
+    const entries = self.dynamicTable();
+    for (entries) |entry| switch (entry.d_tag) {
+        elf.DT_SONAME => return self.getString(@as(u32, @intCast(entry.d_val))),
+        else => {},
+    };
+    return std.fs.path.basename(self.path);
+}
+
+pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void {
+    assert(self.aliases == null);
+
+    const SortAlias = struct {
+        pub fn lessThan(ctx: *Elf, lhs: Symbol.Index, rhs: Symbol.Index) bool {
+            const lhs_sym = ctx.symbol(lhs).elfSym(ctx);
+            const rhs_sym = ctx.symbol(rhs).elfSym(ctx);
+            return lhs_sym.st_value < rhs_sym.st_value;
+        }
+    };
+
+    const gpa = elf_file.base.allocator;
+    var aliases = std.ArrayList(Symbol.Index).init(gpa);
+    defer aliases.deinit();
+    try aliases.ensureTotalCapacityPrecise(self.globals().len);
+
+    for (self.globals()) |index| {
+        const global = elf_file.symbol(index);
+        const global_file = global.file(elf_file) orelse continue;
+        if (global_file.index() != self.index) continue;
+        aliases.appendAssumeCapacity(index);
+    }
+
+    std.mem.sort(u32, aliases.items, elf_file, SortAlias.lessThan);
+
+    self.aliases = aliases.moveToUnmanaged();
+}
+
+pub fn symbolAliases(self: *SharedObject, index: u32, elf_file: *Elf) []const u32 {
+    assert(self.aliases != null);
+
+    const symbol = elf_file.symbol(index).elfSym(elf_file);
+    const aliases = self.aliases.?;
+
+    const start = for (aliases.items, 0..) |alias, i| {
+        const alias_sym = elf_file.symbol(alias).elfSym(elf_file);
+        if (symbol.st_value == alias_sym.st_value) break i;
+    } else aliases.items.len;
+
+    const end = for (aliases.items[start..], 0..) |alias, i| {
+        const alias_sym = elf_file.symbol(alias).elfSym(elf_file);
+        if (symbol.st_value < alias_sym.st_value) break i + start;
+    } else aliases.items.len;
+
+    return aliases.items[start..end];
+}
+
+pub fn format(
+    self: SharedObject,
+    comptime unused_fmt_string: []const u8,
+    options: std.fmt.FormatOptions,
+    writer: anytype,
+) !void {
+    _ = self;
+    _ = unused_fmt_string;
+    _ = options;
+    _ = writer;
+    @compileError("do not format shared objects directly");
+}
+
+pub fn fmtSymtab(self: SharedObject, elf_file: *Elf) std.fmt.Formatter(formatSymtab) {
+    return .{ .data = .{
+        .shared = self,
+        .elf_file = elf_file,
+    } };
+}
+
+const FormatContext = struct {
+    shared: SharedObject,
+    elf_file: *Elf,
+};
+
+fn formatSymtab(
+    ctx: FormatContext,
+    comptime unused_fmt_string: []const u8,
+    options: std.fmt.FormatOptions,
+    writer: anytype,
+) !void {
+    _ = unused_fmt_string;
+    _ = options;
+    const shared = ctx.shared;
+    try writer.writeAll("  globals\n");
+    for (shared.symbols.items) |index| {
+        const global = ctx.elf_file.symbol(index);
+        try writer.print("    {}\n", .{global.fmt(ctx.elf_file)});
+    }
+}
+
+const SharedObject = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const elf = std.elf;
+const log = std.log.scoped(.elf);
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Elf = @import("../Elf.zig");
+const ElfShdr = @import("Object.zig").ElfShdr;
+const File = @import("file.zig").File;
+const Symbol = @import("Symbol.zig");
src/link/Elf/Symbol.zig
@@ -32,7 +32,7 @@ extra_index: u32 = 0,
 
 pub fn isAbs(symbol: Symbol, elf_file: *Elf) bool {
     const file_ptr = symbol.file(elf_file).?;
-    // if (file_ptr == .shared) return symbol.sourceSymbol(elf_file).st_shndx == elf.SHN_ABS;
+    if (file_ptr == .shared_object) return symbol.elfSym(elf_file).st_shndx == elf.SHN_ABS;
     return !symbol.flags.import and symbol.atom(elf_file) == null and symbol.outputShndx() == null and
         file_ptr != .linker_defined;
 }
@@ -52,8 +52,8 @@ pub fn isIFunc(symbol: Symbol, elf_file: *Elf) bool {
 
 pub fn @"type"(symbol: Symbol, elf_file: *Elf) u4 {
     const s_sym = symbol.elfSym(elf_file);
-    // const file_ptr = symbol.file(elf_file).?;
-    // if (s_sym.st_type() == elf.STT_GNU_IFUNC and file_ptr == .shared) return elf.STT_FUNC;
+    const file_ptr = symbol.file(elf_file).?;
+    if (s_sym.st_type() == elf.STT_GNU_IFUNC and file_ptr == .shared_object) return elf.STT_FUNC;
     return s_sym.st_type();
 }
 
@@ -74,7 +74,7 @@ pub fn elfSym(symbol: Symbol, elf_file: *Elf) elf.Elf64_Sym {
     switch (file_ptr) {
         .zig_module => |x| return x.elfSym(symbol.esym_index).*,
         .linker_defined => |x| return x.symtab.items[symbol.esym_index],
-        .object => |x| return x.symtab[symbol.esym_index],
+        inline else => |x| return x.symtab[symbol.esym_index],
     }
 }
 
@@ -88,23 +88,18 @@ pub fn symbolRank(symbol: Symbol, elf_file: *Elf) u32 {
     return file_ptr.symbolRank(sym, in_archive);
 }
 
-pub fn address(symbol: Symbol, opts: struct {
-    plt: bool = true,
-}, elf_file: *Elf) u64 {
-    _ = elf_file;
-    _ = opts;
-    // if (symbol.flags.copy_rel) {
-    //     return elf_file.sectionAddress(elf_file.copy_rel_sect_index.?) + symbol.value;
-    // }
-    // if (symbol.flags.plt and opts.plt) {
-    //     const extra = symbol.getExtra(elf_file).?;
-    //     if (!symbol.flags.is_canonical and symbol.flags.got) {
-    //         // We have a non-lazy bound function pointer, use that!
-    //         return elf_file.getPltGotEntryAddress(extra.plt_got);
-    //     }
-    //     // Lazy-bound function it is!
-    //     return elf_file.getPltEntryAddress(extra.plt);
-    // }
+pub fn address(symbol: Symbol, opts: struct { plt: bool = true }, elf_file: *Elf) u64 {
+    if (symbol.flags.has_copy_rel) {
+        return symbol.copyRelAddress(elf_file);
+    }
+    if (symbol.flags.has_plt and opts.plt) {
+        if (!symbol.flags.is_canonical and symbol.flags.has_got) {
+            // We have a non-lazy bound function pointer, use that!
+            return symbol.pltGotAddress(elf_file);
+        }
+        // Lazy-bound function it is!
+        return symbol.pltAddress(elf_file);
+    }
     return symbol.value;
 }
 
@@ -115,6 +110,33 @@ pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 {
     return entry.address(elf_file);
 }
 
+pub fn pltGotAddress(symbol: Symbol, elf_file: *Elf) u64 {
+    if (!(symbol.flags.has_plt and symbol.flags.has_got and !symbol.flags.is_canonical)) return 0;
+    const extras = symbol.extra(elf_file).?;
+    const shdr = elf_file.shdrs.items[elf_file.plt_got_section_index.?];
+    return shdr.sh_addr + extras.plt_got * 16;
+}
+
+pub fn pltAddress(symbol: Symbol, elf_file: *Elf) u64 {
+    if (!symbol.flags.has_plt) return 0;
+    const extras = symbol.extra(elf_file).?;
+    const shdr = elf_file.shdrs.items[elf_file.plt_section_index.?];
+    return shdr.sh_addr + extras.plt * 16 + PltSection.preamble_size;
+}
+
+pub fn gotPltAddress(symbol: Symbol, elf_file: *Elf) u64 {
+    if (!symbol.flags.has_plt) return 0;
+    const extras = symbol.extra(elf_file).?;
+    const shdr = elf_file.shdrs.items[elf_file.got_plt_section_index.?];
+    return shdr.sh_addr + extras.plt * 8 + GotPltSection.preamble_size;
+}
+
+pub fn copyRelAddress(symbol: Symbol, elf_file: *Elf) u64 {
+    if (!symbol.flags.has_copy_rel) return 0;
+    const shdr = elf_file.shdrs.items[elf_file.copy_rel_section_index.?];
+    return shdr.sh_addr + symbol.value;
+}
+
 const GetOrCreateGotEntryResult = struct {
     found_existing: bool,
     index: GotSection.Index,
@@ -149,17 +171,18 @@ pub fn tlsDescAddress(symbol: Symbol, elf_file: *Elf) u64 {
     return entry.address(elf_file);
 }
 
-// pub fn alignment(symbol: Symbol, elf_file: *Elf) !u64 {
-//     const file = symbol.getFile(elf_file) orelse return 0;
-//     const shared = file.shared;
-//     const s_sym = symbol.getSourceSymbol(elf_file);
-//     const shdr = shared.getShdrs()[s_sym.st_shndx];
-//     const alignment = @max(1, shdr.sh_addralign);
-//     return if (s_sym.st_value == 0)
-//         alignment
-//     else
-//         @min(alignment, try std.math.powi(u64, 2, @ctz(s_sym.st_value)));
-// }
+pub fn dsoAlignment(symbol: Symbol, elf_file: *Elf) !u64 {
+    const file_ptr = symbol.file(elf_file) orelse return 0;
+    assert(file_ptr == .shared_object);
+    const shared_object = file_ptr.shared_object;
+    const esym = symbol.elfSym(elf_file);
+    const shdr = shared_object.shdrs.items[esym.st_shndx];
+    const alignment = @max(1, shdr.sh_addralign);
+    return if (esym.st_value == 0)
+        alignment
+    else
+        @min(alignment, try std.math.powi(u64, 2, @ctz(esym.st_value)));
+}
 
 pub fn addExtra(symbol: *Symbol, extras: Extra, elf_file: *Elf) !void {
     symbol.extra_index = try elf_file.addSymbolExtra(extras);
@@ -183,22 +206,22 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void {
     const st_bind: u8 = blk: {
         if (symbol.isLocal()) break :blk 0;
         if (symbol.flags.weak) break :blk elf.STB_WEAK;
-        // if (file_ptr == .shared) break :blk elf.STB_GLOBAL;
+        if (file_ptr == .shared_object) break :blk elf.STB_GLOBAL;
         break :blk esym.st_bind();
     };
     const st_shndx = blk: {
-        // if (symbol.flags.copy_rel) break :blk elf_file.copy_rel_sect_index.?;
-        // if (file_ptr == .shared or s_sym.st_shndx == elf.SHN_UNDEF) break :blk elf.SHN_UNDEF;
+        if (symbol.flags.has_copy_rel) break :blk elf_file.copy_rel_section_index.?;
+        if (file_ptr == .shared_object or esym.st_shndx == elf.SHN_UNDEF) break :blk elf.SHN_UNDEF;
         if (symbol.atom(elf_file) == null and file_ptr != .linker_defined)
             break :blk elf.SHN_ABS;
         break :blk symbol.outputShndx() orelse elf.SHN_UNDEF;
     };
     const st_value = blk: {
-        // if (symbol.flags.copy_rel) break :blk symbol.address(.{}, elf_file);
-        // if (file_ptr == .shared or s_sym.st_shndx == elf.SHN_UNDEF) {
-        //     if (symbol.flags.is_canonical) break :blk symbol.address(.{}, elf_file);
-        //     break :blk 0;
-        // }
+        if (symbol.flags.has_copy_rel) break :blk symbol.address(.{}, elf_file);
+        if (file_ptr == .shared_object or esym.st_shndx == elf.SHN_UNDEF) {
+            if (symbol.flags.is_canonical) break :blk symbol.address(.{}, elf_file);
+            break :blk 0;
+        }
         if (st_shndx == elf.SHN_ABS) break :blk symbol.value;
         const shdr = &elf_file.shdrs.items[st_shndx];
         if (shdr.sh_flags & elf.SHF_TLS != 0 and file_ptr != .linker_defined)
@@ -254,9 +277,10 @@ fn formatName(
     switch (symbol.version_index & elf.VERSYM_VERSION) {
         elf.VER_NDX_LOCAL, elf.VER_NDX_GLOBAL => {},
         else => {
-            unreachable;
-            // const shared = symbol.getFile(elf_file).?.shared;
-            // try writer.print("@{s}", .{shared.getVersionString(symbol.version_index)});
+            const file_ptr = symbol.file(elf_file).?;
+            assert(file_ptr == .shared_object);
+            const shared_object = file_ptr.shared_object;
+            try writer.print("@{s}", .{shared_object.versionString(symbol.version_index)});
         },
     }
 }
@@ -312,9 +336,12 @@ pub const Flags = packed struct {
     /// Whether this symbol is weak.
     weak: bool = false,
 
-    /// Whether the symbol makes into the output symtab or not.
+    /// Whether the symbol makes into the output symtab.
     output_symtab: bool = false,
 
+    /// Whether the symbol has entry in dynamic symbol table.
+    has_dynamic: bool = false,
+
     /// Whether the symbol contains GOT indirection.
     needs_got: bool = false,
     has_got: bool = false,
@@ -328,7 +355,6 @@ pub const Flags = packed struct {
     /// Whether the symbol contains COPYREL directive.
     needs_copy_rel: bool = false,
     has_copy_rel: bool = false,
-    has_dynamic: bool = false,
 
     /// Whether the symbol contains TLSGD indirection.
     needs_tlsgd: bool = false,
@@ -365,8 +391,10 @@ const Atom = @import("Atom.zig");
 const Elf = @import("../Elf.zig");
 const File = @import("file.zig").File;
 const GotSection = synthetic_sections.GotSection;
+const GotPltSection = synthetic_sections.GotPltSection;
 const LinkerDefined = @import("LinkerDefined.zig");
-// const Object = @import("Object.zig");
-// const SharedObject = @import("SharedObject.zig");
+const Object = @import("Object.zig");
+const PltSection = synthetic_sections.PltSection;
+const SharedObject = @import("SharedObject.zig");
 const Symbol = @This();
 const ZigModule = @import("ZigModule.zig");
src/link/Elf/synthetic_sections.zig
@@ -1,3 +1,225 @@
+pub const DynamicSection = struct {
+    soname: ?u32 = null,
+    needed: std.ArrayListUnmanaged(u32) = .{},
+    rpath: u32 = 0,
+
+    pub fn deinit(dt: *DynamicSection, allocator: Allocator) void {
+        dt.needed.deinit(allocator);
+    }
+
+    pub fn addNeeded(dt: *DynamicSection, shared: *SharedObject, elf_file: *Elf) !void {
+        const gpa = elf_file.base.allocator;
+        const off = try elf_file.dynstrtab.insert(gpa, shared.getSoname());
+        try dt.needed.append(gpa, off);
+    }
+
+    pub fn setRpath(dt: *DynamicSection, rpath_list: []const []const u8, elf_file: *Elf) !void {
+        if (rpath_list.len == 0) return;
+        const gpa = elf_file.base.allocator;
+        var rpath = std.ArrayList(u8).init(gpa);
+        defer rpath.deinit();
+        for (rpath_list, 0..) |path, i| {
+            if (i > 0) try rpath.append(':');
+            try rpath.appendSlice(path);
+        }
+        dt.rpath = try elf_file.dynstrtab.insert(gpa, rpath.items);
+    }
+
+    pub fn setSoname(dt: *DynamicSection, soname: []const u8, elf_file: *Elf) !void {
+        dt.soname = try elf_file.dynstrtab.insert(elf_file.base.allocator, soname);
+    }
+
+    fn getFlags(dt: DynamicSection, elf_file: *Elf) ?u64 {
+        _ = dt;
+        var flags: u64 = 0;
+        if (elf_file.base.options.z_now) {
+            flags |= elf.DF_BIND_NOW;
+        }
+        for (elf_file.got.entries.items) |entry| switch (entry.tag) {
+            .gottp => {
+                flags |= elf.DF_STATIC_TLS;
+                break;
+            },
+            else => {},
+        };
+        if (elf_file.has_text_reloc) {
+            flags |= elf.DF_TEXTREL;
+        }
+        return if (flags > 0) flags else null;
+    }
+
+    fn getFlags1(dt: DynamicSection, elf_file: *Elf) ?u64 {
+        _ = dt;
+        var flags_1: u64 = 0;
+        if (elf_file.base.options.z_now) {
+            flags_1 |= elf.DF_1_NOW;
+        }
+        if (elf_file.base.options.pie) {
+            flags_1 |= elf.DF_1_PIE;
+        }
+        // if (elf_file.base.options.z_nodlopen) {
+        //     flags_1 |= elf.DF_1_NOOPEN;
+        // }
+        return if (flags_1 > 0) flags_1 else null;
+    }
+
+    pub fn size(dt: DynamicSection, elf_file: *Elf) usize {
+        var nentries: usize = 0;
+        nentries += dt.needed.items.len; // NEEDED
+        if (dt.soname != null) nentries += 1; // SONAME
+        if (dt.rpath > 0) nentries += 1; // RUNPATH
+        if (elf_file.sectionByName(".init") != null) nentries += 1; // INIT
+        if (elf_file.sectionByName(".fini") != null) nentries += 1; // FINI
+        if (elf_file.sectionByName(".init_array") != null) nentries += 2; // INIT_ARRAY
+        if (elf_file.sectionByName(".fini_array") != null) nentries += 2; // FINI_ARRAY
+        if (elf_file.rela_dyn_section_index != null) nentries += 3; // RELA
+        if (elf_file.rela_plt_section_index != null) nentries += 3; // JMPREL
+        if (elf_file.got_plt_section_index != null) nentries += 1; // PLTGOT
+        nentries += 1; // HASH
+        if (elf_file.gnu_hash_section_index != null) nentries += 1; // GNU_HASH
+        if (elf_file.has_text_reloc) nentries += 1; // TEXTREL
+        nentries += 1; // SYMTAB
+        nentries += 1; // SYMENT
+        nentries += 1; // STRTAB
+        nentries += 1; // STRSZ
+        if (elf_file.versym_section_index != null) nentries += 1; // VERSYM
+        if (elf_file.verneed_section_index != null) nentries += 2; // VERNEED
+        if (dt.getFlags(elf_file) != null) nentries += 1; // FLAGS
+        if (dt.getFlags1(elf_file) != null) nentries += 1; // FLAGS_1
+        if (!elf_file.isDynLib()) nentries += 1; // DEBUG
+        nentries += 1; // NULL
+        return nentries * @sizeOf(elf.Elf64_Dyn);
+    }
+
+    pub fn write(dt: DynamicSection, elf_file: *Elf, writer: anytype) !void {
+        // NEEDED
+        for (dt.needed.items) |off| {
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_NEEDED, .d_val = off });
+        }
+
+        if (dt.soname) |off| {
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SONAME, .d_val = off });
+        }
+
+        // RUNPATH
+        // TODO add option in Options to revert to old RPATH tag
+        if (dt.rpath > 0) {
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RUNPATH, .d_val = dt.rpath });
+        }
+
+        // INIT
+        if (elf_file.sectionByName(".init")) |shndx| {
+            const addr = elf_file.shdrs.items[shndx].sh_addr;
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT, .d_val = addr });
+        }
+
+        // FINI
+        if (elf_file.sectionByName(".fini")) |shndx| {
+            const addr = elf_file.shdrs.items[shndx].sh_addr;
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI, .d_val = addr });
+        }
+
+        // INIT_ARRAY
+        if (elf_file.sectionByName(".init_array")) |shndx| {
+            const shdr = elf_file.shdrs.items[shndx];
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT_ARRAY, .d_val = shdr.sh_addr });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT_ARRAYSZ, .d_val = shdr.sh_size });
+        }
+
+        // FINI_ARRAY
+        if (elf_file.sectionByName(".fini_array")) |shndx| {
+            const shdr = elf_file.shdrs.items[shndx];
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI_ARRAY, .d_val = shdr.sh_addr });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI_ARRAYSZ, .d_val = shdr.sh_size });
+        }
+
+        // RELA
+        if (elf_file.rela_dyn_section_index) |shndx| {
+            const shdr = elf_file.shdrs.items[shndx];
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELA, .d_val = shdr.sh_addr });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELASZ, .d_val = shdr.sh_size });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELAENT, .d_val = shdr.sh_entsize });
+        }
+
+        // JMPREL
+        if (elf_file.rela_plt_section_index) |shndx| {
+            const shdr = elf_file.shdrs.items[shndx];
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_JMPREL, .d_val = shdr.sh_addr });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTRELSZ, .d_val = shdr.sh_size });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTREL, .d_val = elf.DT_RELA });
+        }
+
+        // PLTGOT
+        if (elf_file.got_plt_section_index) |shndx| {
+            const addr = elf_file.shdrs.items[shndx].sh_addr;
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTGOT, .d_val = addr });
+        }
+
+        {
+            assert(elf_file.hash_section_index != null);
+            const addr = elf_file.shdrs.items[elf_file.hash_section_index.?].sh_addr;
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_HASH, .d_val = addr });
+        }
+
+        if (elf_file.gnu_hash_section_index) |shndx| {
+            const addr = elf_file.shdrs.items[shndx].sh_addr;
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_GNU_HASH, .d_val = addr });
+        }
+
+        // TEXTREL
+        if (elf_file.has_text_reloc) {
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_TEXTREL, .d_val = 0 });
+        }
+
+        // SYMTAB + SYMENT
+        {
+            assert(elf_file.dynsymtab_section_index != null);
+            const shdr = elf_file.shdrs.items[elf_file.dynsymtab_section_index.?];
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SYMTAB, .d_val = shdr.sh_addr });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SYMENT, .d_val = shdr.sh_entsize });
+        }
+
+        // STRTAB + STRSZ
+        {
+            assert(elf_file.dynstrtab_section_index != null);
+            const shdr = elf_file.shdrs.items[elf_file.dynstrtab_section_index.?];
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_STRTAB, .d_val = shdr.sh_addr });
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_STRSZ, .d_val = shdr.sh_size });
+        }
+
+        // VERSYM
+        if (elf_file.versym_section_index) |shndx| {
+            const addr = elf_file.shdrs.items[shndx].sh_addr;
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_VERSYM, .d_val = addr });
+        }
+
+        // VERNEED + VERNEEDNUM
+        if (elf_file.verneed_section_index) |shndx| {
+            const addr = elf_file.shdrs.items[shndx].sh_addr;
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_VERNEED, .d_val = addr });
+            try writer.writeStruct(elf.Elf64_Dyn{
+                .d_tag = elf.DT_VERNEEDNUM,
+                .d_val = elf_file.verneed.verneed.items.len,
+            });
+        }
+
+        // FLAGS
+        if (dt.getFlags(elf_file)) |flags| {
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FLAGS, .d_val = flags });
+        }
+        // FLAGS_1
+        if (dt.getFlags1(elf_file)) |flags_1| {
+            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FLAGS_1, .d_val = flags_1 });
+        }
+
+        // DEBUG
+        if (!elf_file.isDynLib()) try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_DEBUG, .d_val = 0 });
+
+        // NULL
+        try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_NULL, .d_val = 0 });
+    }
+};
+
 pub const GotSection = struct {
     entries: std.ArrayListUnmanaged(Entry) = .{},
     output_symtab_size: Elf.SymtabSize = .{},
@@ -72,44 +294,57 @@ pub const GotSection = struct {
         return index;
     }
 
-    // pub fn addTlsGdSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
-    //     const index = got.next_index;
-    //     const symbol = elf_file.getSymbol(sym_index);
-    //     if (symbol.flags.import or elf_file.options.output_mode == .lib) got.needs_rela = true;
-    //     if (symbol.getExtra(elf_file)) |extra| {
-    //         var new_extra = extra;
-    //         new_extra.tlsgd = index;
-    //         symbol.setExtra(new_extra, elf_file);
-    //     } else try symbol.addExtra(.{ .tlsgd = index }, elf_file);
-    //     try got.symbols.append(elf_file.base.allocator, .{ .tlsgd = sym_index });
-    //     got.next_index += 2;
-    // }
-
-    // pub fn addGotTpSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
-    //     const index = got.next_index;
-    //     const symbol = elf_file.getSymbol(sym_index);
-    //     if (symbol.flags.import or elf_file.options.output_mode == .lib) got.needs_rela = true;
-    //     if (symbol.getExtra(elf_file)) |extra| {
-    //         var new_extra = extra;
-    //         new_extra.gottp = index;
-    //         symbol.setExtra(new_extra, elf_file);
-    //     } else try symbol.addExtra(.{ .gottp = index }, elf_file);
-    //     try got.symbols.append(elf_file.base.allocator, .{ .gottp = sym_index });
-    //     got.next_index += 1;
-    // }
-
-    // pub fn addTlsDescSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
-    //     const index = got.next_index;
-    //     const symbol = elf_file.getSymbol(sym_index);
-    //     got.needs_rela = true;
-    //     if (symbol.getExtra(elf_file)) |extra| {
-    //         var new_extra = extra;
-    //         new_extra.tlsdesc = index;
-    //         symbol.setExtra(new_extra, elf_file);
-    //     } else try symbol.addExtra(.{ .tlsdesc = index }, elf_file);
-    //     try got.symbols.append(elf_file.base.allocator, .{ .tlsdesc = sym_index });
-    //     got.next_index += 2;
-    // }
+    pub fn addTlsLdSymbol(got: *GotSection, elf_file: *Elf) !void {
+        assert(got.flags.needs_tlsld);
+        const index = try got.allocateEntry(elf_file.base.allocator);
+        const entry = &got.entries.items[index];
+        entry.tag = .tlsld;
+        entry.symbol_index = undefined; // unused
+        got.flags.needs_rela = true;
+        got.tlsld_index = index;
+    }
+
+    pub fn addTlsGdSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
+        const index = try got.allocateEntry(elf_file.base.allocator);
+        const entry = &got.entries.items[index];
+        entry.tag = .tlsgd;
+        entry.symbol_index = sym_index;
+        const symbol = elf_file.symbol(sym_index);
+        if (symbol.flags.import or elf_file.isDynLib()) got.flags.needs_rela = true;
+        if (symbol.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.tlsgd = index;
+            symbol.setExtra(new_extra, elf_file);
+        } else try symbol.addExtra(.{ .tlsgd = index }, elf_file);
+    }
+
+    pub fn addGotTpSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
+        const index = try got.allocateEntry(elf_file.base.allocator);
+        const entry = &got.entries.items[index];
+        entry.tag = .gottp;
+        entry.symbol_index = sym_index;
+        const symbol = elf_file.symbol(sym_index);
+        if (symbol.flags.import or elf_file.isDynLib()) got.flags.needs_rela = true;
+        if (symbol.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.gottp = index;
+            symbol.setExtra(new_extra, elf_file);
+        } else try symbol.addExtra(.{ .gottp = index }, elf_file);
+    }
+
+    pub fn addTlsDescSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
+        const index = try got.allocateEntry(elf_file.base.allocator);
+        const entry = &got.entries.items[index];
+        entry.tag = .tlsdesc;
+        entry.symbol_index = sym_index;
+        const symbol = elf_file.symbol(sym_index);
+        got.flags.needs_rela = true;
+        if (symbol.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.tlsdesc = index;
+            symbol.setExtra(new_extra, elf_file);
+        } else try symbol.addExtra(.{ .tlsdesc = index }, elf_file);
+    }
 
     pub fn size(got: GotSection, elf_file: *Elf) usize {
         var s: usize = 0;
@@ -119,260 +354,207 @@ pub const GotSection = struct {
         return s;
     }
 
-    pub fn writeEntry(got: *GotSection, elf_file: *Elf, index: Index) !void {
-        const entry_size: u16 = elf_file.archPtrWidthBytes();
-        // if (got.dirty) {
-        //     const needed_size = got.size(elf_file);
-        //     try elf_file.growAllocSection(elf_file.got_section_index.?, needed_size);
-        //     got.dirty = false;
-        // }
+    pub fn write(got: GotSection, elf_file: *Elf, writer: anytype) !void {
+        const is_dyn_lib = elf_file.isDynLib();
+        const apply_relocs = true; // TODO add user option for this
+
+        for (got.entries.items) |entry| {
+            const symbol = elf_file.symbol(entry.symbol_index);
+            switch (entry.tag) {
+                .got => {
+                    const value = blk: {
+                        const value = symbol.address(.{ .plt = false }, elf_file);
+                        if (symbol.flags.import) break :blk 0;
+                        if (symbol.isIFunc(elf_file))
+                            break :blk if (apply_relocs) value else 0;
+                        if (elf_file.base.options.pic and !symbol.isAbs(elf_file))
+                            break :blk if (apply_relocs) value else 0;
+                        break :blk value;
+                    };
+                    try writeInt(value, elf_file, writer);
+                },
+                .tlsld => {
+                    try writeInt(if (is_dyn_lib) @as(u64, 0) else 1, elf_file, writer);
+                    try writeInt(0, elf_file, writer);
+                },
+                .tlsgd => {
+                    if (symbol.flags.import) {
+                        try writeInt(0, elf_file, writer);
+                        try writeInt(0, elf_file, writer);
+                    } else {
+                        try writeInt(if (is_dyn_lib) @as(u64, 0) else 1, elf_file, writer);
+                        const offset = symbol.address(.{}, elf_file) - elf_file.dtpAddress();
+                        try writeInt(offset, elf_file, writer);
+                    }
+                },
+                .gottp => {
+                    if (symbol.flags.import) {
+                        try writeInt(0, elf_file, writer);
+                    } else if (is_dyn_lib) {
+                        const offset = if (apply_relocs)
+                            symbol.address(.{}, elf_file) - elf_file.tlsAddress()
+                        else
+                            0;
+                        try writeInt(offset, elf_file, writer);
+                    } else {
+                        const offset = @as(i64, @intCast(symbol.address(.{}, elf_file))) -
+                            @as(i64, @intCast(elf_file.tpAddress()));
+                        try writeInt(offset, elf_file, writer);
+                    }
+                },
+                .tlsdesc => {
+                    try writeInt(0, elf_file, writer);
+                    try writeInt(0, elf_file, writer);
+                },
+            }
+        }
+    }
+
+    fn writeInt(value: anytype, elf_file: *Elf, writer: anytype) !void {
+        const entry_size = elf_file.archPtrWidthBytes();
         const endian = elf_file.base.options.target.cpu.arch.endian();
-        const entry = got.entries.items[index];
-        const shdr = &elf_file.shdrs.items[elf_file.got_section_index.?];
-        const off = shdr.sh_offset + @as(u64, entry_size) * entry.cell_index;
-        const vaddr = shdr.sh_addr + @as(u64, entry_size) * entry.cell_index;
-        const value = elf_file.symbol(entry.symbol_index).value;
         switch (entry_size) {
-            2 => {
-                var buf: [2]u8 = undefined;
-                std.mem.writeInt(u16, &buf, @as(u16, @intCast(value)), endian);
-                try elf_file.base.file.?.pwriteAll(&buf, off);
-            },
-            4 => {
-                var buf: [4]u8 = undefined;
-                std.mem.writeInt(u32, &buf, @as(u32, @intCast(value)), endian);
-                try elf_file.base.file.?.pwriteAll(&buf, off);
-            },
-            8 => {
-                var buf: [8]u8 = undefined;
-                std.mem.writeInt(u64, &buf, value, endian);
-                try elf_file.base.file.?.pwriteAll(&buf, off);
-
-                if (elf_file.base.child_pid) |pid| {
-                    switch (builtin.os.tag) {
-                        .linux => {
-                            var local_vec: [1]std.os.iovec_const = .{.{
-                                .iov_base = &buf,
-                                .iov_len = buf.len,
-                            }};
-                            var remote_vec: [1]std.os.iovec_const = .{.{
-                                .iov_base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(vaddr)))),
-                                .iov_len = buf.len,
-                            }};
-                            const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0);
-                            switch (std.os.errno(rc)) {
-                                .SUCCESS => assert(rc == buf.len),
-                                else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}),
-                            }
-                        },
-                        else => return error.HotSwapUnavailableOnHostOperatingSystem,
-                    }
-                }
-            },
+            2 => try writer.writeInt(u16, @intCast(value), endian),
+            4 => try writer.writeInt(u32, @intCast(value), endian),
+            8 => try writer.writeInt(u64, @intCast(value), endian),
             else => unreachable,
         }
     }
 
-    pub fn write(got: GotSection, elf_file: *Elf, writer: anytype) !void {
-        const entry_size: u16 = elf_file.archPtrWidthBytes();
-        const endian = elf_file.base.options.target.cpu.arch.endian();
+    pub fn addRela(got: GotSection, elf_file: *Elf) !void {
+        const is_dyn_lib = elf_file.isDynLib();
+        try elf_file.rela_dyn.ensureUnusedCapacity(elf_file.base.allocator, got.numRela(elf_file));
+
         for (got.entries.items) |entry| {
-            const value = elf_file.symbol(entry.symbol_index).value;
-            switch (entry_size) {
-                2 => try writer.writeInt(u16, @intCast(value), endian),
-                4 => try writer.writeInt(u32, @intCast(value), endian),
-                8 => try writer.writeInt(u64, @intCast(value), endian),
-                else => unreachable,
+            const symbol = switch (entry.tag) {
+                .tlsld => null,
+                inline else => elf_file.symbol(entry.symbol_index),
+            };
+            const extra = if (symbol) |s| s.extra(elf_file).? else null;
+
+            switch (entry.tag) {
+                .got => {
+                    const offset = symbol.?.gotAddress(elf_file);
+                    if (symbol.?.flags.import) {
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .sym = extra.?.dynamic,
+                            .type = elf.R_X86_64_GLOB_DAT,
+                        });
+                        continue;
+                    }
+                    if (symbol.?.isIFunc(elf_file)) {
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .type = elf.R_X86_64_IRELATIVE,
+                            .addend = @intCast(symbol.?.address(.{ .plt = false }, elf_file)),
+                        });
+                        continue;
+                    }
+                    if (elf_file.base.options.pic and !symbol.?.isAbs(elf_file)) {
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .type = elf.R_X86_64_RELATIVE,
+                            .addend = @intCast(symbol.?.address(.{ .plt = false }, elf_file)),
+                        });
+                    }
+                },
+
+                .tlsld => {
+                    if (is_dyn_lib) {
+                        const offset = entry.address(elf_file);
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .type = elf.R_X86_64_DTPMOD64,
+                        });
+                    }
+                },
+
+                .tlsgd => {
+                    const offset = symbol.?.tlsGdAddress(elf_file);
+                    if (symbol.?.flags.import) {
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .sym = extra.?.dynamic,
+                            .type = elf.R_X86_64_DTPMOD64,
+                        });
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset + 8,
+                            .sym = extra.?.dynamic,
+                            .type = elf.R_X86_64_DTPOFF64,
+                        });
+                    } else if (is_dyn_lib) {
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .sym = extra.?.dynamic,
+                            .type = elf.R_X86_64_DTPMOD64,
+                        });
+                    }
+                },
+
+                .gottp => {
+                    const offset = symbol.?.gotTpAddress(elf_file);
+                    if (symbol.?.flags.import) {
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .sym = extra.?.dynamic,
+                            .type = elf.R_X86_64_TPOFF64,
+                        });
+                    } else if (is_dyn_lib) {
+                        elf_file.addRelaDynAssumeCapacity(.{
+                            .offset = offset,
+                            .type = elf.R_X86_64_TPOFF64,
+                            .addend = @intCast(symbol.?.address(.{}, elf_file) - elf_file.tlsAddress()),
+                        });
+                    }
+                },
+
+                .tlsdesc => {
+                    const offset = symbol.?.tlsDescAddress(elf_file);
+                    elf_file.addRelaDynAssumeCapacity(.{
+                        .offset = offset,
+                        .sym = extra.?.dynamic,
+                        .type = elf.R_X86_64_TLSDESC,
+                    });
+                },
             }
         }
     }
 
-    // pub fn write(got: GotSection, elf_file: *Elf, writer: anytype) !void {
-    //     const is_shared = elf_file.options.output_mode == .lib;
-    //     const apply_relocs = elf_file.options.apply_dynamic_relocs;
-
-    //     for (got.symbols.items) |sym| {
-    //         const symbol = elf_file.getSymbol(sym.getIndex());
-    //         switch (sym) {
-    //             .got => {
-    //                 const value: u64 = blk: {
-    //                     const value = symbol.getAddress(.{ .plt = false }, elf_file);
-    //                     if (symbol.flags.import) break :blk 0;
-    //                     if (symbol.isIFunc(elf_file))
-    //                         break :blk if (apply_relocs) value else 0;
-    //                     if (elf_file.options.pic and !symbol.isAbs(elf_file))
-    //                         break :blk if (apply_relocs) value else 0;
-    //                     break :blk value;
-    //                 };
-    //                 try writer.writeIntLittle(u64, value);
-    //             },
-
-    //             .tlsgd => {
-    //                 if (symbol.flags.import) {
-    //                     try writer.writeIntLittle(u64, 0);
-    //                     try writer.writeIntLittle(u64, 0);
-    //                 } else {
-    //                     try writer.writeIntLittle(u64, if (is_shared) @as(u64, 0) else 1);
-    //                     const offset = symbol.getAddress(.{}, elf_file) - elf_file.getDtpAddress();
-    //                     try writer.writeIntLittle(u64, offset);
-    //                 }
-    //             },
-
-    //             .gottp => {
-    //                 if (symbol.flags.import) {
-    //                     try writer.writeIntLittle(u64, 0);
-    //                 } else if (is_shared) {
-    //                     const offset = if (apply_relocs)
-    //                         symbol.getAddress(.{}, elf_file) - elf_file.getTlsAddress()
-    //                     else
-    //                         0;
-    //                     try writer.writeIntLittle(u64, offset);
-    //                 } else {
-    //                     const offset = @as(i64, @intCast(symbol.getAddress(.{}, elf_file))) -
-    //                         @as(i64, @intCast(elf_file.getTpAddress()));
-    //                     try writer.writeIntLittle(u64, @as(u64, @bitCast(offset)));
-    //                 }
-    //             },
-
-    //             .tlsdesc => {
-    //                 try writer.writeIntLittle(u64, 0);
-    //                 try writer.writeIntLittle(u64, 0);
-    //             },
-    //         }
-    //     }
-
-    //     if (got.emit_tlsld) {
-    //         try writer.writeIntLittle(u64, if (is_shared) @as(u64, 0) else 1);
-    //         try writer.writeIntLittle(u64, 0);
-    //     }
-    // }
-
-    // pub fn addRela(got: GotSection, elf_file: *Elf) !void {
-    //     const is_shared = elf_file.options.output_mode == .lib;
-    //     try elf_file.rela_dyn.ensureUnusedCapacity(elf_file.base.allocator, got.numRela(elf_file));
-
-    //     for (got.symbols.items) |sym| {
-    //         const symbol = elf_file.getSymbol(sym.getIndex());
-    //         const extra = symbol.getExtra(elf_file).?;
-
-    //         switch (sym) {
-    //             .got => {
-    //                 const offset = symbol.gotAddress(elf_file);
-
-    //                 if (symbol.flags.import) {
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset,
-    //                         .sym = extra.dynamic,
-    //                         .type = elf.R_X86_64_GLOB_DAT,
-    //                     });
-    //                     continue;
-    //                 }
-
-    //                 if (symbol.isIFunc(elf_file)) {
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset,
-    //                         .type = elf.R_X86_64_IRELATIVE,
-    //                         .addend = @intCast(symbol.getAddress(.{ .plt = false }, elf_file)),
-    //                     });
-    //                     continue;
-    //                 }
-
-    //                 if (elf_file.options.pic and !symbol.isAbs(elf_file)) {
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset,
-    //                         .type = elf.R_X86_64_RELATIVE,
-    //                         .addend = @intCast(symbol.getAddress(.{ .plt = false }, elf_file)),
-    //                     });
-    //                 }
-    //             },
-
-    //             .tlsgd => {
-    //                 const offset = symbol.getTlsGdAddress(elf_file);
-    //                 if (symbol.flags.import) {
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset,
-    //                         .sym = extra.dynamic,
-    //                         .type = elf.R_X86_64_DTPMOD64,
-    //                     });
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset + 8,
-    //                         .sym = extra.dynamic,
-    //                         .type = elf.R_X86_64_DTPOFF64,
-    //                     });
-    //                 } else if (is_shared) {
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset,
-    //                         .sym = extra.dynamic,
-    //                         .type = elf.R_X86_64_DTPMOD64,
-    //                     });
-    //                 }
-    //             },
-
-    //             .gottp => {
-    //                 const offset = symbol.getGotTpAddress(elf_file);
-    //                 if (symbol.flags.import) {
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset,
-    //                         .sym = extra.dynamic,
-    //                         .type = elf.R_X86_64_TPOFF64,
-    //                     });
-    //                 } else if (is_shared) {
-    //                     elf_file.addRelaDynAssumeCapacity(.{
-    //                         .offset = offset,
-    //                         .type = elf.R_X86_64_TPOFF64,
-    //                         .addend = @intCast(symbol.getAddress(.{}, elf_file) - elf_file.getTlsAddress()),
-    //                     });
-    //                 }
-    //             },
-
-    //             .tlsdesc => {
-    //                 const offset = symbol.getTlsDescAddress(elf_file);
-    //                 elf_file.addRelaDynAssumeCapacity(.{
-    //                     .offset = offset,
-    //                     .sym = extra.dynamic,
-    //                     .type = elf.R_X86_64_TLSDESC,
-    //                 });
-    //             },
-    //         }
-    //     }
-
-    //     if (is_shared and got.emit_tlsld) {
-    //         const offset = elf_file.getTlsLdAddress();
-    //         elf_file.addRelaDynAssumeCapacity(.{
-    //             .offset = offset,
-    //             .type = elf.R_X86_64_DTPMOD64,
-    //         });
-    //     }
-    // }
-
-    // pub fn numRela(got: GotSection, elf_file: *Elf) usize {
-    //     const is_shared = elf_file.options.output_mode == .lib;
-    //     var num: usize = 0;
-    //     for (got.symbols.items) |sym| {
-    //         const symbol = elf_file.symbol(sym.index());
-    //         switch (sym) {
-    //             .got => if (symbol.flags.import or
-    //                 symbol.isIFunc(elf_file) or (elf_file.options.pic and !symbol.isAbs(elf_file)))
-    //             {
-    //                 num += 1;
-    //             },
-
-    //             .tlsgd => if (symbol.flags.import) {
-    //                 num += 2;
-    //             } else if (is_shared) {
-    //                 num += 1;
-    //             },
-
-    //             .gottp => if (symbol.flags.import or is_shared) {
-    //                 num += 1;
-    //             },
-
-    //             .tlsdesc => num += 1,
-    //         }
-    //     }
-    //     if (is_shared and got.emit_tlsld) num += 1;
-    //     return num;
-    // }
+    pub fn numRela(got: GotSection, elf_file: *Elf) usize {
+        const is_dyn_lib = elf_file.isDynLib();
+        var num: usize = 0;
+        for (got.entries.items) |entry| {
+            const symbol = switch (entry.tag) {
+                .tlsld => null,
+                inline else => elf_file.symbol(entry.symbol_index),
+            };
+            switch (entry.tag) {
+                .got => if (symbol.?.flags.import or
+                    symbol.?.isIFunc(elf_file) or (elf_file.base.options.pic and !symbol.?.isAbs(elf_file)))
+                {
+                    num += 1;
+                },
+
+                .tlsld => if (is_dyn_lib) {
+                    num += 1;
+                },
+
+                .tlsgd => if (symbol.?.flags.import) {
+                    num += 2;
+                } else if (is_dyn_lib) {
+                    num += 1;
+                },
+
+                .gottp => if (symbol.?.flags.import or is_dyn_lib) {
+                    num += 1;
+                },
+
+                .tlsdesc => num += 1,
+            }
+        }
+        return num;
+    }
 
     pub fn updateSymtabSize(got: *GotSection, elf_file: *Elf) void {
         _ = elf_file;
@@ -382,8 +564,11 @@ pub const GotSection = struct {
     pub fn updateStrtab(got: GotSection, elf_file: *Elf) !void {
         const gpa = elf_file.base.allocator;
         for (got.entries.items) |entry| {
-            const symbol = elf_file.symbol(entry.symbol_index);
-            const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol.name(elf_file), @tagName(entry.tag) });
+            const symbol_name = switch (entry.tag) {
+                .tlsld => "",
+                inline else => elf_file.symbol(entry.symbol_index).name(elf_file),
+            };
+            const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol_name, @tagName(entry.tag) });
             defer gpa.free(name);
             _ = try elf_file.strtab.insert(gpa, name);
         }
@@ -392,14 +577,18 @@ pub const GotSection = struct {
     pub fn writeSymtab(got: GotSection, elf_file: *Elf, ctx: anytype) !void {
         const gpa = elf_file.base.allocator;
         for (got.entries.items, ctx.ilocal..) |entry, ilocal| {
-            const symbol = elf_file.symbol(entry.symbol_index);
-            const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol.name(elf_file), @tagName(entry.tag) });
+            const symbol = switch (entry.tag) {
+                .tlsld => null,
+                inline else => elf_file.symbol(entry.symbol_index),
+            };
+            const symbol_name = switch (entry.tag) {
+                .tlsld => "",
+                inline else => symbol.?.name(elf_file),
+            };
+            const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol_name, @tagName(entry.tag) });
             defer gpa.free(name);
             const st_name = try elf_file.strtab.insert(gpa, name);
-            const st_value = switch (entry.tag) {
-                .got => symbol.gotAddress(elf_file),
-                else => unreachable,
-            };
+            const st_value = entry.address(elf_file);
             const st_size: u64 = entry.len() * elf_file.archPtrWidthBytes();
             ctx.symtab[ilocal] = .{
                 .st_name = st_name,
@@ -443,12 +632,697 @@ pub const GotSection = struct {
     }
 };
 
+pub const PltSection = struct {
+    symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+    output_symtab_size: Elf.SymtabSize = .{},
+
+    pub const preamble_size = 32;
+
+    pub fn deinit(plt: *PltSection, allocator: Allocator) void {
+        plt.symbols.deinit(allocator);
+    }
+
+    pub fn addSymbol(plt: *PltSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
+        const index = @as(u32, @intCast(plt.symbols.items.len));
+        const symbol = elf_file.symbol(sym_index);
+        if (symbol.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.plt = index;
+            symbol.setExtra(new_extra, elf_file);
+        } else try symbol.addExtra(.{ .plt = index }, elf_file);
+        try plt.symbols.append(elf_file.base.allocator, sym_index);
+    }
+
+    pub fn size(plt: PltSection) usize {
+        return preamble_size + plt.symbols.items.len * 16;
+    }
+
+    pub fn write(plt: PltSection, elf_file: *Elf, writer: anytype) !void {
+        const plt_addr = elf_file.shdrs.items[elf_file.plt_section_index.?].sh_addr;
+        const got_plt_addr = elf_file.shdrs.items[elf_file.got_plt_section_index.?].sh_addr;
+        var preamble = [_]u8{
+            0xf3, 0x0f, 0x1e, 0xfa, // endbr64
+            0x41, 0x53, // push r11
+            0xff, 0x35, 0x00, 0x00, 0x00, 0x00, // push qword ptr [rip] -> .got.plt[1]
+            0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip] -> .got.plt[2]
+        };
+        var disp = @as(i64, @intCast(got_plt_addr + 8)) - @as(i64, @intCast(plt_addr + 8)) - 4;
+        mem.writeIntLittle(i32, preamble[8..][0..4], @as(i32, @intCast(disp)));
+        disp = @as(i64, @intCast(got_plt_addr + 16)) - @as(i64, @intCast(plt_addr + 14)) - 4;
+        mem.writeIntLittle(i32, preamble[14..][0..4], @as(i32, @intCast(disp)));
+        try writer.writeAll(&preamble);
+        try writer.writeByteNTimes(0xcc, preamble_size - preamble.len);
+
+        for (plt.symbols.items, 0..) |sym_index, i| {
+            const sym = elf_file.symbol(sym_index);
+            const target_addr = sym.gotPltAddress(elf_file);
+            const source_addr = sym.pltAddress(elf_file);
+            disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 12)) - 4;
+            var entry = [_]u8{
+                0xf3, 0x0f, 0x1e, 0xfa, // endbr64
+                0x41, 0xbb, 0x00, 0x00, 0x00, 0x00, // mov r11d, N
+                0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip] -> .got.plt[N]
+            };
+            mem.writeIntLittle(i32, entry[6..][0..4], @as(i32, @intCast(i)));
+            mem.writeIntLittle(i32, entry[12..][0..4], @as(i32, @intCast(disp)));
+            try writer.writeAll(&entry);
+        }
+    }
+
+    pub fn addRela(plt: PltSection, elf_file: *Elf) !void {
+        try elf_file.rela_plt.ensureUnusedCapacity(elf_file.base.allocator, plt.numRela());
+        for (plt.symbols.items) |sym_index| {
+            const sym = elf_file.symbol(sym_index);
+            assert(sym.flags.import);
+            const extra = sym.extra(elf_file).?;
+            const r_offset = sym.gotPltAddress(elf_file);
+            const r_sym: u64 = extra.dynamic;
+            const r_type: u32 = elf.R_X86_64_JUMP_SLOT;
+            elf_file.rela_plt.appendAssumeCapacity(.{
+                .r_offset = r_offset,
+                .r_info = (r_sym << 32) | r_type,
+                .r_addend = 0,
+            });
+        }
+    }
+
+    pub fn numRela(plt: PltSection) usize {
+        return plt.symbols.items.len;
+    }
+
+    pub fn updateSymtabSize(plt: *PltSection, elf_file: *Elf) void {
+        _ = elf_file;
+        plt.output_symtab_size.nlocals = @as(u32, @intCast(plt.symbols.items.len));
+    }
+
+    pub fn updateStrtab(plt: PltSection, elf_file: *Elf) !void {
+        const gpa = elf_file.base.allocator;
+        for (plt.symbols.items) |sym_index| {
+            const sym = elf_file.symbol(sym_index);
+            const name = try std.fmt.allocPrint(gpa, "{s}$plt", .{sym.name(elf_file)});
+            defer gpa.free(name);
+            _ = try elf_file.strtab.insert(gpa, name);
+        }
+    }
+
+    pub fn writeSymtab(plt: PltSection, elf_file: *Elf, ctx: anytype) !void {
+        const gpa = elf_file.base.allocator;
+
+        var ilocal = ctx.ilocal;
+        for (plt.symbols.items) |sym_index| {
+            const sym = elf_file.symbol(sym_index);
+            const name = try std.fmt.allocPrint(gpa, "{s}$plt", .{sym.name(elf_file)});
+            defer gpa.free(name);
+            const st_name = try elf_file.strtab.insert(gpa, name);
+            ctx.symtab[ilocal] = .{
+                .st_name = st_name,
+                .st_info = elf.STT_FUNC,
+                .st_other = 0,
+                .st_shndx = elf_file.plt_section_index.?,
+                .st_value = sym.pltAddress(elf_file),
+                .st_size = 16,
+            };
+            ilocal += 1;
+        }
+    }
+};
+
+pub const GotPltSection = struct {
+    pub const preamble_size = 24;
+
+    pub fn size(got_plt: GotPltSection, elf_file: *Elf) usize {
+        _ = got_plt;
+        return preamble_size + elf_file.plt.symbols.items.len * 8;
+    }
+
+    pub fn write(got_plt: GotPltSection, elf_file: *Elf, writer: anytype) !void {
+        _ = got_plt;
+        {
+            // [0]: _DYNAMIC
+            const symbol = elf_file.symbol(elf_file.dynamic_index.?);
+            try writer.writeIntLittle(u64, symbol.value);
+        }
+        // [1]: 0x0
+        // [2]: 0x0
+        try writer.writeIntLittle(u64, 0x0);
+        try writer.writeIntLittle(u64, 0x0);
+        if (elf_file.plt_section_index) |shndx| {
+            const plt_addr = elf_file.shdrs.items[shndx].sh_addr;
+            for (0..elf_file.plt.symbols.items.len) |_| {
+                // [N]: .plt
+                try writer.writeIntLittle(u64, plt_addr);
+            }
+        }
+    }
+};
+
+pub const PltGotSection = struct {
+    symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+    output_symtab_size: Elf.SymtabSize = .{},
+
+    pub fn deinit(plt_got: *PltGotSection, allocator: Allocator) void {
+        plt_got.symbols.deinit(allocator);
+    }
+
+    pub fn addSymbol(plt_got: *PltGotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
+        const index = @as(u32, @intCast(plt_got.symbols.items.len));
+        const symbol = elf_file.symbol(sym_index);
+        if (symbol.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.plt_got = index;
+            symbol.setExtra(new_extra, elf_file);
+        } else try symbol.addExtra(.{ .plt_got = index }, elf_file);
+        try plt_got.symbols.append(elf_file.base.allocator, sym_index);
+    }
+
+    pub fn size(plt_got: PltGotSection) usize {
+        return plt_got.symbols.items.len * 16;
+    }
+
+    pub fn write(plt_got: PltGotSection, elf_file: *Elf, writer: anytype) !void {
+        for (plt_got.symbols.items) |sym_index| {
+            const sym = elf_file.symbol(sym_index);
+            const target_addr = sym.gotAddress(elf_file);
+            const source_addr = sym.pltGotAddress(elf_file);
+            const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 6)) - 4;
+            var entry = [_]u8{
+                0xf3, 0x0f, 0x1e, 0xfa, // endbr64
+                0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip] -> .got[N]
+                0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
+            };
+            mem.writeIntLittle(i32, entry[6..][0..4], @as(i32, @intCast(disp)));
+            try writer.writeAll(&entry);
+        }
+    }
+
+    pub fn updateSymtabSize(plt_got: *PltGotSection, elf_file: *Elf) void {
+        _ = elf_file;
+        plt_got.output_symtab_size.nlocals = @as(u32, @intCast(plt_got.symbols.items.len));
+    }
+
+    pub fn updateStrtab(plt_got: PltGotSection, elf_file: *Elf) !void {
+        const gpa = elf_file.base.allocator;
+        for (plt_got.symbols.items) |sym_index| {
+            const sym = elf_file.symbol(sym_index);
+            const name = try std.fmt.allocPrint(gpa, "{s}$pltgot", .{sym.name(elf_file)});
+            defer gpa.free(name);
+            _ = try elf_file.strtab.insert(gpa, name);
+        }
+    }
+
+    pub fn writeSymtab(plt_got: PltGotSection, elf_file: *Elf, ctx: anytype) !void {
+        const gpa = elf_file.base.allocator;
+        var ilocal = ctx.ilocal;
+        for (plt_got.symbols.items) |sym_index| {
+            const sym = elf_file.symbol(sym_index);
+            const name = try std.fmt.allocPrint(gpa, "{s}$pltgot", .{sym.name(elf_file)});
+            defer gpa.free(name);
+            const st_name = try elf_file.strtab.insert(gpa, name);
+            ctx.symtab[ilocal] = .{
+                .st_name = st_name,
+                .st_info = elf.STT_FUNC,
+                .st_other = 0,
+                .st_shndx = elf_file.plt_got_section_index.?,
+                .st_value = sym.pltGotAddress(elf_file),
+                .st_size = 16,
+            };
+            ilocal += 1;
+        }
+    }
+};
+
+pub const CopyRelSection = struct {
+    symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+
+    pub fn deinit(copy_rel: *CopyRelSection, allocator: Allocator) void {
+        copy_rel.symbols.deinit(allocator);
+    }
+
+    pub fn addSymbol(copy_rel: *CopyRelSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
+        const index = @as(u32, @intCast(copy_rel.symbols.items.len));
+        const symbol = elf_file.symbol(sym_index);
+        symbol.flags.import = true;
+        symbol.flags.@"export" = true;
+        symbol.flags.has_copy_rel = true;
+        symbol.flags.weak = false;
+
+        if (symbol.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.copy_rel = index;
+            symbol.setExtra(new_extra, elf_file);
+        } else try symbol.addExtra(.{ .copy_rel = index }, elf_file);
+        try copy_rel.symbols.append(elf_file.base.allocator, sym_index);
+
+        const shared_object = symbol.file(elf_file).?.shared_object;
+        if (shared_object.aliases == null) {
+            try shared_object.initSymbolAliases(elf_file);
+        }
+
+        const aliases = shared_object.symbolAliases(sym_index, elf_file);
+        for (aliases) |alias| {
+            if (alias == sym_index) continue;
+            const alias_sym = elf_file.symbol(alias);
+            alias_sym.flags.import = true;
+            alias_sym.flags.@"export" = true;
+            alias_sym.flags.has_copy_rel = true;
+            alias_sym.flags.needs_copy_rel = true;
+            alias_sym.flags.weak = false;
+            try elf_file.dynsym.addSymbol(alias, elf_file);
+        }
+    }
+
+    pub fn updateSectionSize(copy_rel: CopyRelSection, shndx: u16, elf_file: *Elf) !void {
+        const shdr = &elf_file.shdrs.items[shndx];
+        for (copy_rel.symbols.items) |sym_index| {
+            const symbol = elf_file.symbol(sym_index);
+            const shared_object = symbol.file(elf_file).?.shared_object;
+            const alignment = try symbol.dsoAlignment(elf_file);
+            symbol.value = mem.alignForward(u64, shdr.sh_size, alignment);
+            shdr.sh_addralign = @max(shdr.sh_addralign, alignment);
+            shdr.sh_size = symbol.value + symbol.elfSym(elf_file).st_size;
+
+            const aliases = shared_object.symbolAliases(sym_index, elf_file);
+            for (aliases) |alias| {
+                if (alias == sym_index) continue;
+                const alias_sym = elf_file.symbol(alias);
+                alias_sym.value = symbol.value;
+            }
+        }
+    }
+
+    pub fn addRela(copy_rel: CopyRelSection, elf_file: *Elf) !void {
+        try elf_file.rela_dyn.ensureUnusedCapacity(elf_file.base.allocator, copy_rel.numRela());
+        for (copy_rel.symbols.items) |sym_index| {
+            const sym = elf_file.symbol(sym_index);
+            assert(sym.flags.import and sym.flags.has_copy_rel);
+            const extra = sym.extra(elf_file).?;
+            elf_file.addRelaDynAssumeCapacity(.{
+                .offset = sym.address(.{}, elf_file),
+                .sym = extra.dynamic,
+                .type = elf.R_X86_64_COPY,
+            });
+        }
+    }
+
+    pub fn numRela(copy_rel: CopyRelSection) usize {
+        return copy_rel.symbols.items.len;
+    }
+};
+
+pub const DynsymSection = struct {
+    entries: std.ArrayListUnmanaged(Entry) = .{},
+
+    pub const Entry = struct {
+        /// Index of the symbol which gets privilege of getting a dynamic treatment
+        symbol_index: Symbol.Index,
+        /// Offset into .dynstrtab
+        off: u32,
+    };
+
+    pub fn deinit(dynsym: *DynsymSection, allocator: Allocator) void {
+        dynsym.entries.deinit(allocator);
+    }
+
+    pub fn addSymbol(dynsym: *DynsymSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
+        const gpa = elf_file.base.allocator;
+        const index = @as(u32, @intCast(dynsym.entries.items.len + 1));
+        const sym = elf_file.symbol(sym_index);
+        sym.flags.has_dynamic = true;
+        if (sym.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.dynamic = index;
+            sym.setExtra(new_extra, elf_file);
+        } else try sym.addExtra(.{ .dynamic = index }, elf_file);
+        const off = try elf_file.dynstrtab.insert(gpa, sym.name(elf_file));
+        try dynsym.entries.append(gpa, .{ .symbol_index = sym_index, .off = off });
+    }
+
+    pub fn sort(dynsym: *DynsymSection, elf_file: *Elf) void {
+        const Sort = struct {
+            pub fn lessThan(ctx: *Elf, lhs: Entry, rhs: Entry) bool {
+                const lhs_sym = ctx.symbol(lhs.symbol_index);
+                const rhs_sym = ctx.symbol(rhs.symbol_index);
+
+                if (lhs_sym.flags.@"export" != rhs_sym.flags.@"export") {
+                    return rhs_sym.flags.@"export";
+                }
+
+                // TODO cache hash values
+                const nbuckets = ctx.gnu_hash.num_buckets;
+                const lhs_hash = GnuHashSection.hasher(lhs_sym.name(ctx)) % nbuckets;
+                const rhs_hash = GnuHashSection.hasher(rhs_sym.name(ctx)) % nbuckets;
+
+                if (lhs_hash == rhs_hash)
+                    return lhs_sym.extra(ctx).?.dynamic < rhs_sym.extra(ctx).?.dynamic;
+                return lhs_hash < rhs_hash;
+            }
+        };
+
+        var num_exports: u32 = 0;
+        for (dynsym.entries.items) |entry| {
+            const sym = elf_file.symbol(entry.symbol_index);
+            if (sym.flags.@"export") num_exports += 1;
+        }
+
+        elf_file.gnu_hash.num_buckets = @divTrunc(num_exports, GnuHashSection.load_factor) + 1;
+
+        std.mem.sort(Entry, dynsym.entries.items, elf_file, Sort.lessThan);
+
+        for (dynsym.entries.items, 1..) |entry, index| {
+            const sym = elf_file.symbol(entry.symbol_index);
+            var extra = sym.extra(elf_file).?;
+            extra.dynamic = @as(u32, @intCast(index));
+            sym.setExtra(extra, elf_file);
+        }
+    }
+
+    pub fn size(dynsym: DynsymSection) usize {
+        return dynsym.count() * @sizeOf(elf.Elf64_Sym);
+    }
+
+    pub fn count(dynsym: DynsymSection) u32 {
+        return @as(u32, @intCast(dynsym.entries.items.len + 1));
+    }
+
+    pub fn write(dynsym: DynsymSection, elf_file: *Elf, writer: anytype) !void {
+        try writer.writeStruct(Elf.null_sym);
+        for (dynsym.entries.items) |entry| {
+            const sym = elf_file.symbol(entry.symbol_index);
+            var out_sym: elf.Elf64_Sym = Elf.null_sym;
+            sym.setOutputSym(elf_file, &out_sym);
+            out_sym.st_name = entry.off;
+            try writer.writeStruct(out_sym);
+        }
+    }
+};
+
+pub const HashSection = struct {
+    buffer: std.ArrayListUnmanaged(u8) = .{},
+
+    pub fn deinit(hs: *HashSection, allocator: Allocator) void {
+        hs.buffer.deinit(allocator);
+    }
+
+    pub fn generate(hs: *HashSection, elf_file: *Elf) !void {
+        if (elf_file.dynsym.count() == 1) return;
+
+        const gpa = elf_file.base.allocator;
+        const nsyms = elf_file.dynsym.count();
+
+        var buckets = try gpa.alloc(u32, nsyms);
+        defer gpa.free(buckets);
+        @memset(buckets, 0);
+
+        var chains = try gpa.alloc(u32, nsyms);
+        defer gpa.free(chains);
+        @memset(chains, 0);
+
+        for (elf_file.dynsym.entries.items, 1..) |entry, i| {
+            const name = elf_file.dynstrtab.getAssumeExists(entry.off);
+            const hash = hasher(name) % buckets.len;
+            chains[@as(u32, @intCast(i))] = buckets[hash];
+            buckets[hash] = @as(u32, @intCast(i));
+        }
+
+        try hs.buffer.ensureTotalCapacityPrecise(gpa, (2 + nsyms * 2) * 4);
+        hs.buffer.writer(gpa).writeIntLittle(u32, @as(u32, @intCast(nsyms))) catch unreachable;
+        hs.buffer.writer(gpa).writeIntLittle(u32, @as(u32, @intCast(nsyms))) catch unreachable;
+        hs.buffer.writer(gpa).writeAll(mem.sliceAsBytes(buckets)) catch unreachable;
+        hs.buffer.writer(gpa).writeAll(mem.sliceAsBytes(chains)) catch unreachable;
+    }
+
+    pub inline fn size(hs: HashSection) usize {
+        return hs.buffer.items.len;
+    }
+
+    pub fn hasher(name: [:0]const u8) u32 {
+        var h: u32 = 0;
+        var g: u32 = 0;
+        for (name) |c| {
+            h = (h << 4) + c;
+            g = h & 0xf0000000;
+            if (g > 0) h ^= g >> 24;
+            h &= ~g;
+        }
+        return h;
+    }
+};
+
+pub const GnuHashSection = struct {
+    num_buckets: u32 = 0,
+    num_bloom: u32 = 1,
+    num_exports: u32 = 0,
+
+    pub const load_factor = 8;
+    pub const header_size = 16;
+    pub const bloom_shift = 26;
+
+    fn getExports(elf_file: *Elf) []const DynsymSection.Entry {
+        const start = for (elf_file.dynsym.entries.items, 0..) |entry, i| {
+            const sym = elf_file.symbol(entry.symbol_index);
+            if (sym.flags.@"export") break i;
+        } else elf_file.dynsym.entries.items.len;
+        return elf_file.dynsym.entries.items[start..];
+    }
+
+    inline fn bitCeil(x: u64) u64 {
+        if (@popCount(x) == 1) return x;
+        return @as(u64, @intCast(@as(u128, 1) << (64 - @clz(x))));
+    }
+
+    pub fn calcSize(hash: *GnuHashSection, elf_file: *Elf) !void {
+        hash.num_exports = @as(u32, @intCast(getExports(elf_file).len));
+        if (hash.num_exports > 0) {
+            const num_bits = hash.num_exports * 12;
+            hash.num_bloom = @as(u32, @intCast(bitCeil(@divTrunc(num_bits, 64))));
+        }
+    }
+
+    pub fn size(hash: GnuHashSection) usize {
+        return header_size + hash.num_bloom * 8 + hash.num_buckets * 4 + hash.num_exports * 4;
+    }
+
+    pub fn write(hash: GnuHashSection, elf_file: *Elf, writer: anytype) !void {
+        const exports = getExports(elf_file);
+        const export_off = elf_file.dynsym.count() - hash.num_exports;
+
+        var counting = std.io.countingWriter(writer);
+        const cwriter = counting.writer();
+
+        try cwriter.writeIntLittle(u32, hash.num_buckets);
+        try cwriter.writeIntLittle(u32, export_off);
+        try cwriter.writeIntLittle(u32, hash.num_bloom);
+        try cwriter.writeIntLittle(u32, bloom_shift);
+
+        const gpa = elf_file.base.allocator;
+        const hashes = try gpa.alloc(u32, exports.len);
+        defer gpa.free(hashes);
+        const indices = try gpa.alloc(u32, exports.len);
+        defer gpa.free(indices);
+
+        // Compose and write the bloom filter
+        const bloom = try gpa.alloc(u64, hash.num_bloom);
+        defer gpa.free(bloom);
+        @memset(bloom, 0);
+
+        for (exports, 0..) |entry, i| {
+            const sym = elf_file.symbol(entry.symbol_index);
+            const h = hasher(sym.name(elf_file));
+            hashes[i] = h;
+            indices[i] = h % hash.num_buckets;
+            const idx = @divTrunc(h, 64) % hash.num_bloom;
+            bloom[idx] |= @as(u64, 1) << @as(u6, @intCast(h % 64));
+            bloom[idx] |= @as(u64, 1) << @as(u6, @intCast((h >> bloom_shift) % 64));
+        }
+
+        try cwriter.writeAll(mem.sliceAsBytes(bloom));
+
+        // Fill in the hash bucket indices
+        const buckets = try gpa.alloc(u32, hash.num_buckets);
+        defer gpa.free(buckets);
+        @memset(buckets, 0);
+
+        for (0..hash.num_exports) |i| {
+            if (buckets[indices[i]] == 0) {
+                buckets[indices[i]] = @as(u32, @intCast(i + export_off));
+            }
+        }
+
+        try cwriter.writeAll(mem.sliceAsBytes(buckets));
+
+        // Finally, write the hash table
+        const table = try gpa.alloc(u32, hash.num_exports);
+        defer gpa.free(table);
+        @memset(table, 0);
+
+        for (0..hash.num_exports) |i| {
+            const h = hashes[i];
+            if (i == exports.len - 1 or indices[i] != indices[i + 1]) {
+                table[i] = h | 1;
+            } else {
+                table[i] = h & ~@as(u32, 1);
+            }
+        }
+
+        try cwriter.writeAll(mem.sliceAsBytes(table));
+
+        assert(counting.bytes_written == hash.size());
+    }
+
+    pub fn hasher(name: [:0]const u8) u32 {
+        var h: u32 = 5381;
+        for (name) |c| {
+            h = (h << 5) +% h +% c;
+        }
+        return h;
+    }
+};
+
+pub const VerneedSection = struct {
+    verneed: std.ArrayListUnmanaged(elf.Elf64_Verneed) = .{},
+    vernaux: std.ArrayListUnmanaged(elf.Elf64_Vernaux) = .{},
+    index: elf.Elf64_Versym = elf.VER_NDX_GLOBAL + 1,
+
+    pub fn deinit(vern: *VerneedSection, allocator: Allocator) void {
+        vern.verneed.deinit(allocator);
+        vern.vernaux.deinit(allocator);
+    }
+
+    pub fn generate(vern: *VerneedSection, elf_file: *Elf) !void {
+        const dynsyms = elf_file.dynsym.entries.items;
+        var versyms = elf_file.versym.items;
+
+        const VersionedSymbol = struct {
+            /// Index in the output version table
+            index: usize,
+            /// Index of the defining this symbol version shared object file
+            shared_object: File.Index,
+            /// Version index
+            version_index: elf.Elf64_Versym,
+
+            fn soname(this: @This(), ctx: *Elf) []const u8 {
+                const shared_object = ctx.file(this.shared_object).?.shared_object;
+                return shared_object.soname();
+            }
+
+            fn versionString(this: @This(), ctx: *Elf) [:0]const u8 {
+                const shared_object = ctx.file(this.shared_object).?.shared_object;
+                return shared_object.versionString(this.version_index);
+            }
+
+            pub fn lessThan(ctx: *Elf, lhs: @This(), rhs: @This()) bool {
+                if (lhs.shared_object == rhs.shared_object) return lhs.version_index < rhs.version_index;
+                return mem.lessThan(u8, lhs.soname(ctx), rhs.soname(ctx));
+            }
+        };
+
+        const gpa = elf_file.base.allocator;
+        var verneed = std.ArrayList(VersionedSymbol).init(gpa);
+        defer verneed.deinit();
+        try verneed.ensureTotalCapacity(dynsyms.len);
+
+        for (dynsyms, 1..) |entry, i| {
+            const symbol = elf_file.symbol(entry.symbol_index);
+            if (symbol.flags.import and symbol.version_index & elf.VERSYM_VERSION > elf.VER_NDX_GLOBAL) {
+                const shared_object = symbol.file(elf_file).?.shared_object;
+                verneed.appendAssumeCapacity(.{
+                    .index = i,
+                    .shared_object = shared_object.index,
+                    .version_index = symbol.version_index,
+                });
+            }
+        }
+
+        mem.sort(VersionedSymbol, verneed.items, elf_file, VersionedSymbol.lessThan);
+
+        var last = verneed.items[0];
+        var last_verneed = try vern.addVerneed(last.soname(elf_file), elf_file);
+        var last_vernaux = try vern.addVernaux(last_verneed, last.versionString(elf_file), elf_file);
+        versyms[last.index] = last_vernaux.vna_other;
+
+        for (verneed.items[1..]) |ver| {
+            if (ver.shared_object == last.shared_object) {
+                if (ver.version_index != last.version_index) {
+                    last_vernaux = try vern.addVernaux(last_verneed, ver.versionString(elf_file), elf_file);
+                }
+            } else {
+                last_verneed = try vern.addVerneed(ver.soname(elf_file), elf_file);
+                last_vernaux = try vern.addVernaux(last_verneed, ver.versionString(elf_file), elf_file);
+            }
+            last = ver;
+            versyms[ver.index] = last_vernaux.vna_other;
+        }
+
+        // Fixup offsets
+        var count: usize = 0;
+        var verneed_off: u32 = 0;
+        var vernaux_off: u32 = @as(u32, @intCast(vern.verneed.items.len)) * @sizeOf(elf.Elf64_Verneed);
+        for (vern.verneed.items, 0..) |*vsym, vsym_i| {
+            if (vsym_i < vern.verneed.items.len - 1) vsym.vn_next = @sizeOf(elf.Elf64_Verneed);
+            vsym.vn_aux = vernaux_off - verneed_off;
+            var inner_off: u32 = 0;
+            for (vern.vernaux.items[count..][0..vsym.vn_cnt], 0..) |*vaux, vaux_i| {
+                if (vaux_i < vsym.vn_cnt - 1) vaux.vna_next = @sizeOf(elf.Elf64_Vernaux);
+                inner_off += @sizeOf(elf.Elf64_Vernaux);
+            }
+            vernaux_off += inner_off;
+            verneed_off += @sizeOf(elf.Elf64_Verneed);
+            count += vsym.vn_cnt;
+        }
+    }
+
+    fn addVerneed(vern: *VerneedSection, soname: []const u8, elf_file: *Elf) !*elf.Elf64_Verneed {
+        const gpa = elf_file.base.allocator;
+        const sym = try vern.verneed.addOne(gpa);
+        sym.* = .{
+            .vn_version = 1,
+            .vn_cnt = 0,
+            .vn_file = try elf_file.dynstrtab.insert(gpa, soname),
+            .vn_aux = 0,
+            .vn_next = 0,
+        };
+        return sym;
+    }
+
+    fn addVernaux(
+        vern: *VerneedSection,
+        verneed_sym: *elf.Elf64_Verneed,
+        version: [:0]const u8,
+        elf_file: *Elf,
+    ) !elf.Elf64_Vernaux {
+        const gpa = elf_file.base.allocator;
+        const sym = try vern.vernaux.addOne(gpa);
+        sym.* = .{
+            .vna_hash = HashSection.hasher(version),
+            .vna_flags = 0,
+            .vna_other = vern.index,
+            .vna_name = try elf_file.dynstrtab.insert(gpa, version),
+            .vna_next = 0,
+        };
+        verneed_sym.vn_cnt += 1;
+        vern.index += 1;
+        return sym.*;
+    }
+
+    pub fn size(vern: VerneedSection) usize {
+        return vern.verneed.items.len * @sizeOf(elf.Elf64_Verneed) + vern.vernaux.items.len * @sizeOf(elf.Elf64_Vernaux);
+    }
+
+    pub fn write(vern: VerneedSection, writer: anytype) !void {
+        try writer.writeAll(mem.sliceAsBytes(vern.verneed.items));
+        try writer.writeAll(mem.sliceAsBytes(vern.vernaux.items));
+    }
+};
+
 const assert = std.debug.assert;
 const builtin = @import("builtin");
 const elf = std.elf;
+const mem = std.mem;
 const log = std.log.scoped(.link);
 const std = @import("std");
 
 const Allocator = std.mem.Allocator;
 const Elf = @import("../Elf.zig");
+const File = @import("file.zig").File;
+const SharedObject = @import("SharedObject.zig");
 const Symbol = @import("Symbol.zig");
src/link/Elf.zig
@@ -14,6 +14,7 @@ files: std.MultiArrayList(File.Entry) = .{},
 zig_module_index: ?File.Index = null,
 linker_defined_index: ?File.Index = null,
 objects: std.ArrayListUnmanaged(File.Index) = .{},
+shared_objects: std.ArrayListUnmanaged(File.Index) = .{},
 
 /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
 /// Same order as in the file.
@@ -61,10 +62,34 @@ default_sym_version: elf.Elf64_Versym,
 shstrtab: StringTable(.strtab) = .{},
 /// .strtab buffer
 strtab: StringTable(.strtab) = .{},
-
-/// Representation of the GOT table as committed to the file.
+/// Dynamic symbol table. Only populated and emitted when linking dynamically.
+dynsym: DynsymSection = .{},
+/// .dynstrtab buffer
+dynstrtab: StringTable(.dynstrtab) = .{},
+/// Version symbol table. Only populated and emitted when linking dynamically.
+versym: std.ArrayListUnmanaged(elf.Elf64_Versym) = .{},
+/// .verneed section
+verneed: VerneedSection = .{},
+/// .got section
 got: GotSection = .{},
+/// .rela.dyn section
 rela_dyn: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{},
+/// .dynamic section
+dynamic: DynamicSection = .{},
+/// .hash section
+hash: HashSection = .{},
+/// .gnu.hash section
+gnu_hash: GnuHashSection = .{},
+/// .plt section
+plt: PltSection = .{},
+/// .got.plt section
+got_plt: GotPltSection = .{},
+/// .plt.got section
+plt_got: PltGotSection = .{},
+/// .copyrel section
+copy_rel: CopyRelSection = .{},
+/// .rela.plt section
+rela_plt: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{},
 
 /// Tracked section headers
 text_section_index: ?u16 = null,
@@ -73,18 +98,30 @@ data_section_index: ?u16 = null,
 bss_section_index: ?u16 = null,
 tdata_section_index: ?u16 = null,
 tbss_section_index: ?u16 = null,
+debug_info_section_index: ?u16 = null,
+debug_abbrev_section_index: ?u16 = null,
+debug_str_section_index: ?u16 = null,
+debug_aranges_section_index: ?u16 = null,
+debug_line_section_index: ?u16 = null,
+
+copy_rel_section_index: ?u16 = null,
+dynamic_section_index: ?u16 = null,
+dynstrtab_section_index: ?u16 = null,
+dynsymtab_section_index: ?u16 = null,
 eh_frame_section_index: ?u16 = null,
 eh_frame_hdr_section_index: ?u16 = null,
-dynamic_section_index: ?u16 = null,
+hash_section_index: ?u16 = null,
+gnu_hash_section_index: ?u16 = null,
 got_section_index: ?u16 = null,
 got_plt_section_index: ?u16 = null,
+interp_section_index: ?u16 = null,
 plt_section_index: ?u16 = null,
+plt_got_section_index: ?u16 = null,
 rela_dyn_section_index: ?u16 = null,
-debug_info_section_index: ?u16 = null,
-debug_abbrev_section_index: ?u16 = null,
-debug_str_section_index: ?u16 = null,
-debug_aranges_section_index: ?u16 = null,
-debug_line_section_index: ?u16 = null,
+rela_plt_section_index: ?u16 = null,
+versym_section_index: ?u16 = null,
+verneed_section_index: ?u16 = null,
+
 shstrtab_section_index: ?u16 = null,
 strtab_section_index: ?u16 = null,
 symtab_section_index: ?u16 = null,
@@ -316,10 +353,11 @@ pub fn deinit(self: *Elf) void {
         .zig_module => data.zig_module.deinit(gpa),
         .linker_defined => data.linker_defined.deinit(gpa),
         .object => data.object.deinit(gpa),
-        // .shared_object => data.shared_object.deinit(gpa),
+        .shared_object => data.shared_object.deinit(gpa),
     };
     self.files.deinit(gpa);
     self.objects.deinit(gpa);
+    self.shared_objects.deinit(gpa);
 
     self.shdrs.deinit(gpa);
     self.phdr_to_shdr_table.deinit(gpa);
@@ -333,7 +371,6 @@ pub fn deinit(self: *Elf) void {
     self.symbols.deinit(gpa);
     self.symbols_extra.deinit(gpa);
     self.symbols_free_list.deinit(gpa);
-    self.got.deinit(gpa);
     self.resolver.deinit(gpa);
     self.start_stop_indexes.deinit(gpa);
 
@@ -369,7 +406,19 @@ pub fn deinit(self: *Elf) void {
     self.comdat_groups.deinit(gpa);
     self.comdat_groups_owners.deinit(gpa);
     self.comdat_groups_table.deinit(gpa);
+
+    self.got.deinit(gpa);
+    self.plt.deinit(gpa);
+    self.plt_got.deinit(gpa);
+    self.dynsym.deinit(gpa);
+    self.dynstrtab.deinit(gpa);
+    self.dynamic.deinit(gpa);
+    self.hash.deinit(gpa);
+    self.versym.deinit(gpa);
+    self.verneed.deinit(gpa);
+    self.copy_rel.deinit(gpa);
     self.rela_dyn.deinit(gpa);
+    self.rela_plt.deinit(gpa);
 }
 
 pub fn getDeclVAddr(self: *Elf, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo) !u64 {
@@ -1268,6 +1317,24 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
         try dw.flushModule(self.base.options.module.?);
     }
 
+    // Dedup shared objects
+    {
+        var seen_dsos = std.StringHashMap(void).init(gpa);
+        defer seen_dsos.deinit();
+        try seen_dsos.ensureTotalCapacity(@as(u32, @intCast(self.shared_objects.items.len)));
+
+        var i: usize = 0;
+        while (i < self.shared_objects.items.len) {
+            const index = self.shared_objects.items[i];
+            const shared_object = self.file(index).?.shared_object;
+            const soname = shared_object.soname();
+            const gop = seen_dsos.getOrPutAssumeCapacity(soname);
+            if (gop.found_existing) {
+                _ = self.shared_objects.orderedRemove(i);
+            } else i += 1;
+        }
+    }
+
     // 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) {
@@ -1677,6 +1744,7 @@ fn resolveSymbols(self: *Elf) void {
     if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self);
     // Resolve symbols on the set of all objects and shared objects (even if some are unneeded).
     for (self.objects.items) |index| self.file(index).?.resolveSymbols(self);
+    for (self.shared_objects.items) |index| self.file(index).?.resolveSymbols(self);
 
     // Mark live objects.
     self.markLive();
@@ -1684,6 +1752,7 @@ fn resolveSymbols(self: *Elf) void {
     // Reset state of all globals after marking live objects.
     if (self.zig_module_index) |index| self.file(index).?.resetGlobals(self);
     for (self.objects.items) |index| self.file(index).?.resetGlobals(self);
+    for (self.shared_objects.items) |index| self.file(index).?.resetGlobals(self);
 
     // Prune dead objects and shared objects.
     var i: usize = 0;
@@ -1693,6 +1762,13 @@ fn resolveSymbols(self: *Elf) void {
             _ = self.objects.orderedRemove(i);
         } else i += 1;
     }
+    i = 0;
+    while (i < self.shared_objects.items.len) {
+        const index = self.shared_objects.items[i];
+        if (!self.file(index).?.isAlive()) {
+            _ = self.shared_objects.orderedRemove(i);
+        } else i += 1;
+    }
 
     // Dedup comdat groups.
     for (self.objects.items) |index| {
@@ -1728,6 +1804,7 @@ fn resolveSymbols(self: *Elf) void {
     // Re-resolve the symbols.
     if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self);
     for (self.objects.items) |index| self.file(index).?.resolveSymbols(self);
+    for (self.shared_objects.items) |index| self.file(index).?.resolveSymbols(self);
 }
 
 /// Traverses all objects and shared objects marking any object referenced by
@@ -1740,6 +1817,10 @@ fn markLive(self: *Elf) void {
         const file_ptr = self.file(index).?;
         if (file_ptr.isAlive()) file_ptr.markLive(self);
     }
+    for (self.shared_objects.items) |index| {
+        const file_ptr = self.file(index).?;
+        if (file_ptr.isAlive()) file_ptr.markLive(self);
+    }
 }
 
 fn markEhFrameAtomsDead(self: *Elf) void {
@@ -1759,10 +1840,10 @@ fn markImportsExports(self: *Elf) void {
                 const file_ptr = global.file(elf_file) orelse continue;
                 const vis = @as(elf.STV, @enumFromInt(global.elfSym(elf_file).st_other));
                 if (vis == .HIDDEN) continue;
-                // if (file == .shared and !global.isAbs(self)) {
-                //     global.flags.import = true;
-                //     continue;
-                // }
+                if (file_ptr == .shared_object and !global.isAbs(elf_file)) {
+                    global.flags.import = true;
+                    continue;
+                }
                 if (file_ptr.index() == file_index) {
                     global.flags.@"export" = true;
                     if (elf_file.isDynLib() and vis != .PROTECTED) {
@@ -1773,6 +1854,17 @@ fn markImportsExports(self: *Elf) void {
         }
     }.mark;
 
+    if (!self.isDynLib()) {
+        for (self.shared_objects.items) |index| {
+            for (self.file(index).?.globals()) |global_index| {
+                const global = self.symbol(global_index);
+                const file_ptr = global.file(self) orelse continue;
+                const vis = @as(elf.STV, @enumFromInt(global.elfSym(self).st_other));
+                if (file_ptr != .shared_object and vis != .HIDDEN) global.flags.@"export" = true;
+            }
+        }
+    }
+
     if (self.zig_module_index) |index| {
         mark(self, index);
     }
@@ -1820,12 +1912,51 @@ fn scanRelocs(self: *Elf) !void {
 
     try self.reportUndefined(&undefs);
 
-    for (self.symbols.items, 0..) |*sym, sym_index| {
+    for (self.symbols.items, 0..) |*sym, i| {
+        const index = @as(u32, @intCast(i));
+        if (!sym.isLocal() and !sym.flags.has_dynamic) {
+            log.debug("'{s}' is non-local", .{sym.name(self)});
+            try self.dynsym.addSymbol(index, self);
+        }
         if (sym.flags.needs_got) {
             log.debug("'{s}' needs GOT", .{sym.name(self)});
-            _ = try self.got.addGotSymbol(@intCast(sym_index), self);
-            sym.flags.has_got = true;
+            _ = try self.got.addGotSymbol(index, self);
+        }
+        if (sym.flags.needs_plt) {
+            if (sym.flags.is_canonical) {
+                log.debug("'{s}' needs CPLT", .{sym.name(self)});
+                sym.flags.@"export" = true;
+                try self.plt.addSymbol(index, self);
+            } else if (sym.flags.needs_got) {
+                log.debug("'{s}' needs PLTGOT", .{sym.name(self)});
+                try self.plt_got.addSymbol(index, self);
+            } else {
+                log.debug("'{s}' needs PLT", .{sym.name(self)});
+                try self.plt.addSymbol(index, self);
+            }
+        }
+        if (sym.flags.needs_copy_rel and !sym.flags.has_copy_rel) {
+            log.debug("'{s}' needs COPYREL", .{sym.name(self)});
+            try self.copy_rel.addSymbol(index, self);
+        }
+        if (sym.flags.needs_tlsgd) {
+            log.debug("'{s}' needs TLSGD", .{sym.name(self)});
+            try self.got.addTlsGdSymbol(index, self);
         }
+        if (sym.flags.needs_gottp) {
+            log.debug("'{s}' needs GOTTP", .{sym.name(self)});
+            try self.got.addGotTpSymbol(index, self);
+        }
+        if (sym.flags.needs_tlsdesc) {
+            log.debug("'{s}' needs TLSDESC", .{sym.name(self)});
+            try self.dynsym.addSymbol(index, self);
+            try self.got.addTlsDescSymbol(index, self);
+        }
+    }
+
+    if (self.got.flags.needs_tlsld) {
+        log.debug("program needs TLSLD", .{});
+        try self.got.addTlsLdSymbol(self);
     }
 }
 
@@ -2567,12 +2698,12 @@ fn writeHeader(self: *Elf) !void {
 
     assert(index == 16);
 
-    const elf_type = switch (self.base.options.effectiveOutputMode()) {
-        .Exe => elf.ET.EXEC,
-        .Obj => elf.ET.REL,
+    const elf_type: elf.ET = switch (self.base.options.effectiveOutputMode()) {
+        .Exe => if (self.base.options.pic) .DYN else .EXEC,
+        .Obj => .REL,
         .Lib => switch (self.base.options.link_mode) {
-            .Static => elf.ET.REL,
-            .Dynamic => elf.ET.DYN,
+            .Static => @as(elf.ET, .REL),
+            .Dynamic => .DYN,
         },
     };
     mem.writeInt(u16, hdr_buf[index..][0..2], @intFromEnum(elf_type), endian);
@@ -2819,8 +2950,8 @@ fn updateDeclCode(
                 esym.st_value = atom_ptr.value;
 
                 log.debug("  (writing new offset table entry)", .{});
-                const extra = sym.extra(self).?;
-                try self.got.writeEntry(self, extra.got);
+                // const extra = sym.extra(self).?;
+                // try self.got.writeEntry(self, extra.got);
             }
         } else if (code.len < old_size) {
             atom_ptr.shrink(self);
@@ -2834,7 +2965,8 @@ fn updateDeclCode(
 
         sym.flags.needs_got = true;
         const gop = try sym.getOrCreateGotEntry(sym_index, self);
-        try self.got.writeEntry(self, gop.index);
+        _ = gop;
+        // try self.got.writeEntry(self, gop.index);
     }
 
     if (self.base.child_pid) |pid| {
@@ -3070,7 +3202,8 @@ fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol.
 
     local_sym.flags.needs_got = true;
     const gop = try local_sym.getOrCreateGotEntry(symbol_index, self);
-    try self.got.writeEntry(self, gop.index);
+    _ = gop;
+    // try self.got.writeEntry(self, gop.index);
 
     const section_offset = atom_ptr.value - self.phdrs.items[phdr_index].p_vaddr;
     const file_offset = self.shdrs.items[output_section_index].sh_offset + section_offset;
@@ -3480,13 +3613,145 @@ fn initSections(self: *Elf) !void {
         }
     }
 
-    if (self.got.entries.items.len > 0 and self.got_section_index == null) {
+    if (self.got.entries.items.len > 0) {
         self.got_section_index = try self.addSection(.{
             .name = ".got",
             .type = elf.SHT_PROGBITS,
             .flags = elf.SHF_ALLOC | elf.SHF_WRITE,
             .addralign = ptr_size,
         });
+        self.got_plt_section_index = try self.addSection(.{
+            .name = ".got.plt",
+            .type = elf.SHT_PROGBITS,
+            .flags = elf.SHF_ALLOC | elf.SHF_WRITE,
+            .addralign = @alignOf(u64),
+        });
+    }
+
+    const needs_rela_dyn = blk: {
+        if (self.got.flags.needs_rela or self.got.flags.needs_tlsld or
+            self.copy_rel.symbols.items.len > 0) break :blk true;
+        for (self.objects.items) |index| {
+            if (self.file(index).?.object.num_dynrelocs > 0) break :blk true;
+        }
+        break :blk false;
+    };
+    if (needs_rela_dyn) {
+        self.rela_dyn_section_index = try self.addSection(.{
+            .name = ".rela.dyn",
+            .type = elf.SHT_RELA,
+            .flags = elf.SHF_ALLOC,
+            .addralign = @alignOf(elf.Elf64_Rela),
+            .entsize = @sizeOf(elf.Elf64_Rela),
+        });
+    }
+
+    if (self.plt.symbols.items.len > 0) {
+        self.plt_section_index = try self.addSection(.{
+            .name = ".plt",
+            .type = elf.SHT_PROGBITS,
+            .flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR,
+            .addralign = 16,
+        });
+        self.rela_plt_section_index = try self.addSection(.{
+            .name = ".rela.plt",
+            .type = elf.SHT_RELA,
+            .flags = elf.SHF_ALLOC,
+            .addralign = @alignOf(elf.Elf64_Rela),
+            .entsize = @sizeOf(elf.Elf64_Rela),
+        });
+    }
+
+    if (self.plt_got.symbols.items.len > 0) {
+        self.plt_got_section_index = try self.addSection(.{
+            .name = ".plt.got",
+            .type = elf.SHT_PROGBITS,
+            .flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR,
+            .addralign = 16,
+        });
+    }
+
+    if (self.copy_rel.symbols.items.len > 0) {
+        self.copy_rel_section_index = try self.addSection(.{
+            .name = ".copyrel",
+            .type = elf.SHT_NOBITS,
+            .flags = elf.SHF_ALLOC | elf.SHF_WRITE,
+        });
+    }
+
+    const needs_interp = blk: {
+        // On Ubuntu with musl-gcc, we get a weird combo of options looking like this:
+        // -dynamic-linker=<path> -static
+        // In this case, if we do generate .interp section and segment, we will get
+        // a segfault in the dynamic linker trying to load a binary that is static
+        // and doesn't contain .dynamic section.
+        if (self.isStatic() and !self.base.options.pie) break :blk false;
+        break :blk self.base.options.dynamic_linker != null;
+    };
+    if (needs_interp) {
+        self.interp_section_index = try self.addSection(.{
+            .name = ".interp",
+            .type = elf.SHT_PROGBITS,
+            .flags = elf.SHF_ALLOC,
+            .addralign = 1,
+        });
+    }
+
+    if (self.isDynLib() or self.shared_objects.items.len > 0 or self.base.options.pie) {
+        self.dynstrtab_section_index = try self.addSection(.{
+            .name = ".dynstr",
+            .flags = elf.SHF_ALLOC,
+            .type = elf.SHT_STRTAB,
+            .entsize = 1,
+            .addralign = 1,
+        });
+        self.dynamic_section_index = try self.addSection(.{
+            .name = ".dynamic",
+            .flags = elf.SHF_ALLOC | elf.SHF_WRITE,
+            .type = elf.SHT_DYNAMIC,
+            .entsize = @sizeOf(elf.Elf64_Dyn),
+            .addralign = @alignOf(elf.Elf64_Dyn),
+        });
+        self.dynsymtab_section_index = try self.addSection(.{
+            .name = ".dynsym",
+            .flags = elf.SHF_ALLOC,
+            .type = elf.SHT_DYNSYM,
+            .addralign = @alignOf(elf.Elf64_Sym),
+            .entsize = @sizeOf(elf.Elf64_Sym),
+        });
+        self.hash_section_index = try self.addSection(.{
+            .name = ".hash",
+            .flags = elf.SHF_ALLOC,
+            .type = elf.SHT_HASH,
+            .addralign = 4,
+            .entsize = 4,
+        });
+        self.gnu_hash_section_index = try self.addSection(.{
+            .name = ".gnu.hash",
+            .flags = elf.SHF_ALLOC,
+            .type = elf.SHT_GNU_HASH,
+            .addralign = 8,
+        });
+
+        const needs_versions = for (self.dynsym.entries.items) |entry| {
+            const sym = self.symbol(entry.symbol_index);
+            if (sym.flags.import and sym.version_index & elf.VERSYM_VERSION > elf.VER_NDX_GLOBAL) break true;
+        } else false;
+        if (needs_versions) {
+            self.versym_section_index = try self.addSection(.{
+                .name = ".gnu.version",
+                .flags = elf.SHF_ALLOC,
+                .type = elf.SHT_GNU_VERSYM,
+                .addralign = @alignOf(elf.Elf64_Versym),
+                .entsize = @sizeOf(elf.Elf64_Versym),
+            });
+            self.verneed_section_index = try self.addSection(.{
+                .name = ".gnu.version_r",
+                .flags = elf.SHF_ALLOC,
+                .type = elf.SHT_GNU_VERNEED,
+                .addralign = @alignOf(elf.Elf64_Verneed),
+            });
+        }
     }
 
     if (self.symtab_section_index == null) {
@@ -3665,6 +3930,20 @@ fn sortSections(self: *Elf) !void {
         &self.symtab_section_index,
         &self.strtab_section_index,
         &self.shstrtab_section_index,
+        &self.interp_section_index,
+        &self.dynamic_section_index,
+        &self.dynsymtab_section_index,
+        &self.dynstrtab_section_index,
+        &self.hash_section_index,
+        &self.gnu_hash_section_index,
+        &self.plt_section_index,
+        &self.got_plt_section_index,
+        &self.plt_got_section_index,
+        &self.rela_dyn_section_index,
+        &self.rela_plt_section_index,
+        &self.copy_rel_section_index,
+        &self.versym_section_index,
+        &self.verneed_section_index,
     }) |maybe_index| {
         if (maybe_index.*) |*index| {
             index.* = backlinks[index.*];
@@ -3675,6 +3954,47 @@ fn sortSections(self: *Elf) !void {
         const shdr = &self.shdrs.items[index];
         shdr.sh_link = self.strtab_section_index.?;
     }
+
+    if (self.dynamic_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynstrtab_section_index.?;
+    }
+
+    if (self.dynsymtab_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynstrtab_section_index.?;
+    }
+
+    if (self.hash_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynsymtab_section_index.?;
+    }
+
+    if (self.gnu_hash_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynsymtab_section_index.?;
+    }
+
+    if (self.versym_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynsymtab_section_index.?;
+    }
+
+    if (self.verneed_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynstrtab_section_index.?;
+    }
+
+    if (self.rela_dyn_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynsymtab_section_index orelse 0;
+    }
+
+    if (self.rela_plt_section_index) |index| {
+        const shdr = &self.shdrs.items[index];
+        shdr.sh_link = self.dynsymtab_section_index.?;
+        shdr.sh_info = self.plt_section_index.?;
+    }
 }
 
 fn updateSectionSizes(self: *Elf) !void {
@@ -3683,21 +4003,77 @@ fn updateSectionSizes(self: *Elf) !void {
     }
 
     if (self.eh_frame_section_index) |index| {
-        const shdr = &self.shdrs.items[index];
-        shdr.sh_size = try eh_frame.calcEhFrameSize(self);
-        shdr.sh_addralign = @alignOf(u64);
+        self.shdrs.items[index].sh_size = try eh_frame.calcEhFrameSize(self);
     }
 
     if (self.eh_frame_hdr_section_index) |index| {
-        const shdr = &self.shdrs.items[index];
-        shdr.sh_size = eh_frame.calcEhFrameHdrSize(self);
-        shdr.sh_addralign = @alignOf(u32);
+        self.shdrs.items[index].sh_size = eh_frame.calcEhFrameHdrSize(self);
     }
 
     if (self.got_section_index) |index| {
         self.shdrs.items[index].sh_size = self.got.size(self);
     }
 
+    if (self.plt_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.plt.size();
+    }
+
+    if (self.got_plt_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.got_plt.size(self);
+    }
+
+    if (self.plt_got_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.plt_got.size();
+    }
+
+    if (self.rela_dyn_section_index) |shndx| {
+        var num = self.got.numRela(self) + self.copy_rel.numRela();
+        for (self.objects.items) |index| {
+            num += self.file(index).?.object.num_dynrelocs;
+        }
+        self.shdrs.items[shndx].sh_size = num * @sizeOf(elf.Elf64_Rela);
+    }
+
+    if (self.rela_plt_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.plt.numRela() * @sizeOf(elf.Elf64_Rela);
+    }
+
+    if (self.copy_rel_section_index) |index| {
+        try self.copy_rel.updateSectionSize(index, self);
+    }
+
+    if (self.interp_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.base.options.dynamic_linker.?.len + 1;
+    }
+
+    if (self.hash_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.hash.size();
+    }
+
+    if (self.gnu_hash_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.gnu_hash.size();
+    }
+
+    if (self.dynamic_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.dynamic.size(self);
+    }
+
+    if (self.dynsymtab_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.dynsym.size();
+    }
+
+    if (self.dynstrtab_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.dynstrtab.buffer.items.len;
+    }
+
+    if (self.versym_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.versym.items.len * @sizeOf(elf.Elf64_Versym);
+    }
+
+    if (self.verneed_section_index) |index| {
+        self.shdrs.items[index].sh_size = self.verneed.size();
+    }
+
     if (self.symtab_section_index != null) {
         try self.updateSymtabSize();
     }
@@ -3708,13 +4084,17 @@ fn updateSectionSizes(self: *Elf) !void {
         if (self.got_section_index) |_| {
             try self.got.updateStrtab(self);
         }
+        if (self.plt_section_index) |_| {
+            try self.plt.updateStrtab(self);
+        }
+        if (self.plt_got_section_index) |_| {
+            try self.plt_got.updateStrtab(self);
+        }
         self.shdrs.items[index].sh_size = self.strtab.buffer.items.len;
-        // try self.growNonAllocSection(index, self.strtab.buffer.items.len, 1, false);
     }
 
     if (self.shstrtab_section_index) |index| {
         self.shdrs.items[index].sh_size = self.shstrtab.buffer.items.len;
-        // try self.growNonAllocSection(index, self.shstrtab.buffer.items.len, 1, false);
     }
 }
 
@@ -3729,18 +4109,18 @@ fn initPhdrs(self: *Elf) !void {
     });
 
     // Add INTERP phdr if required
-    // if (self.interp_sect_index) |index| {
-    //     const shdr = self.sections.items(.shdr)[index];
-    //     _ = try self.addPhdr(.{
-    //         .type = elf.PT_INTERP,
-    //         .flags = elf.PF_R,
-    //         .@"align" = 1,
-    //         .offset = shdr.sh_offset,
-    //         .addr = shdr.sh_addr,
-    //         .filesz = shdr.sh_size,
-    //         .memsz = shdr.sh_size,
-    //     });
-    // }
+    if (self.interp_section_index) |index| {
+        const shdr = self.shdrs.items[index];
+        _ = try self.addPhdr(.{
+            .type = elf.PT_INTERP,
+            .flags = elf.PF_R,
+            .@"align" = 1,
+            .offset = shdr.sh_offset,
+            .addr = shdr.sh_addr,
+            .filesz = shdr.sh_size,
+            .memsz = shdr.sh_size,
+        });
+    }
 
     // Add LOAD phdrs
     const slice = self.shdrs.items;
@@ -3806,18 +4186,18 @@ fn initPhdrs(self: *Elf) !void {
     }
 
     // Add DYNAMIC phdr
-    // if (self.dynamic_sect_index) |index| {
-    //     const shdr = self.sections.items(.shdr)[index];
-    //     _ = try self.addPhdr(.{
-    //         .type = elf.PT_DYNAMIC,
-    //         .flags = elf.PF_R | elf.PF_W,
-    //         .@"align" = shdr.sh_addralign,
-    //         .offset = shdr.sh_offset,
-    //         .addr = shdr.sh_addr,
-    //         .memsz = shdr.sh_size,
-    //         .filesz = shdr.sh_size,
-    //     });
-    // }
+    if (self.dynamic_section_index) |index| {
+        const shdr = self.shdrs.items[index];
+        _ = try self.addPhdr(.{
+            .type = elf.PT_DYNAMIC,
+            .flags = elf.PF_R | elf.PF_W,
+            .@"align" = shdr.sh_addralign,
+            .offset = shdr.sh_offset,
+            .addr = shdr.sh_addr,
+            .memsz = shdr.sh_size,
+            .filesz = shdr.sh_size,
+        });
+    }
 
     // Add PT_GNU_EH_FRAME phdr if required.
     if (self.eh_frame_hdr_section_index) |index| {
@@ -4113,6 +4493,16 @@ fn updateSymtabSize(self: *Elf) !void {
         sizes.nlocals += self.got.output_symtab_size.nlocals;
     }
 
+    if (self.plt_section_index) |_| {
+        self.plt.updateSymtabSize(self);
+        sizes.nlocals += self.plt.output_symtab_size.nlocals;
+    }
+
+    if (self.plt_got_section_index) |_| {
+        self.plt_got.updateSymtabSize(self);
+        sizes.nlocals += self.plt_got.output_symtab_size.nlocals;
+    }
+
     if (self.linker_defined_index) |index| {
         const linker_defined = self.file(index).?.linker_defined;
         linker_defined.updateSymtabSize(self);
@@ -4127,18 +4517,70 @@ fn updateSymtabSize(self: *Elf) !void {
         .p32 => @sizeOf(elf.Elf32_Sym),
         .p64 => @sizeOf(elf.Elf64_Sym),
     };
-    // const sym_align: u16 = switch (self.ptr_width) {
-    //     .p32 => @alignOf(elf.Elf32_Sym),
-    //     .p64 => @alignOf(elf.Elf64_Sym),
-    // };
     const needed_size = (sizes.nlocals + sizes.nglobals + 1) * sym_size;
     shdr.sh_size = needed_size;
-    // try self.growNonAllocSection(self.symtab_section_index.?, needed_size, sym_align, false);
 }
 
 fn writeSyntheticSections(self: *Elf) !void {
     const gpa = self.base.allocator;
 
+    if (self.interp_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try gpa.alloc(u8, shdr.sh_size);
+        defer gpa.free(buffer);
+        const dylinker = self.base.options.dynamic_linker.?;
+        @memcpy(buffer[0..dylinker.len], dylinker);
+        buffer[dylinker.len] = 0;
+        try self.base.file.?.pwriteAll(buffer, shdr.sh_offset);
+    }
+
+    if (self.hash_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        try self.base.file.?.pwriteAll(self.hash.buffer.items, shdr.sh_offset);
+    }
+
+    if (self.gnu_hash_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, self.gnu_hash.size());
+        defer buffer.deinit();
+        try self.gnu_hash.write(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.versym_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.versym.items), shdr.sh_offset);
+    }
+
+    if (self.verneed_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, self.verneed.size());
+        defer buffer.deinit();
+        try self.verneed.write(buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.dynamic_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, self.dynamic.size(self));
+        defer buffer.deinit();
+        try self.dynamic.write(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.dynsymtab_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, self.dynsym.size());
+        defer buffer.deinit();
+        try self.dynsym.write(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.dynstrtab_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        try self.base.file.?.pwriteAll(self.dynstrtab.buffer.items, shdr.sh_offset);
+    }
+
     if (self.eh_frame_section_index) |shndx| {
         const shdr = self.shdrs.items[shndx];
         var buffer = try std.ArrayList(u8).initCapacity(gpa, shdr.sh_size);
@@ -4163,6 +4605,44 @@ fn writeSyntheticSections(self: *Elf) !void {
         try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
     }
 
+    if (self.rela_dyn_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        try self.got.addRela(self);
+        try self.copy_rel.addRela(self);
+        self.sortRelaDyn();
+        try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.rela_dyn.items), shdr.sh_offset);
+    }
+
+    if (self.plt_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, self.plt.size());
+        defer buffer.deinit();
+        try self.plt.write(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.got_plt_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, self.got_plt.size(self));
+        defer buffer.deinit();
+        try self.got_plt.write(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.plt_got_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, self.plt_got.size());
+        defer buffer.deinit();
+        try self.plt_got.write(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.rela_plt_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        try self.plt.addRela(self);
+        try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.rela_plt.items), shdr.sh_offset);
+    }
+
     if (self.shstrtab_section_index) |index| {
         const shdr = self.shdrs.items[index];
         try self.base.file.?.pwriteAll(self.shstrtab.buffer.items, shdr.sh_offset);
@@ -4213,11 +4693,27 @@ fn writeSymtab(self: *Elf) !void {
         ctx.iglobal += object.output_symtab_size.nglobals;
     }
 
+    for (self.shared_objects.items) |index| {
+        const shared_object = self.file(index).?.shared_object;
+        shared_object.writeSymtab(self, ctx);
+        ctx.iglobal += shared_object.output_symtab_size.nglobals;
+    }
+
     if (self.got_section_index) |_| {
         try self.got.writeSymtab(self, ctx);
         ctx.ilocal += self.got.output_symtab_size.nlocals;
     }
 
+    if (self.plt_section_index) |_| {
+        try self.plt.writeSymtab(self, ctx);
+        ctx.ilocal += self.plt.output_symtab_size.nlocals;
+    }
+
+    if (self.plt_got_section_index) |_| {
+        try self.plt_got.writeSymtab(self, ctx);
+        ctx.ilocal += self.plt_got.output_symtab_size.nlocals;
+    }
+
     if (self.linker_defined_index) |index| {
         const linker_defined = self.file(index).?.linker_defined;
         linker_defined.writeSymtab(self, ctx);
@@ -4576,7 +5072,7 @@ pub fn isStatic(self: Elf) bool {
 }
 
 pub fn isDynLib(self: Elf) bool {
-    return self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic;
+    return self.base.options.effectiveOutputMode() == .Lib and self.base.options.link_mode == .Dynamic;
 }
 
 fn addPhdr(self: *Elf, opts: struct {
@@ -4723,6 +5219,7 @@ pub fn file(self: *Elf, index: File.Index) ?File {
         .linker_defined => .{ .linker_defined = &self.files.items(.data)[index].linker_defined },
         .zig_module => .{ .zig_module = &self.files.items(.data)[index].zig_module },
         .object => .{ .object = &self.files.items(.data)[index].object },
+        .shared_object => .{ .shared_object = &self.files.items(.data)[index].shared_object },
     };
 }
 
@@ -5077,6 +5574,16 @@ fn fmtDumpState(
         });
     }
 
+    for (self.shared_objects.items) |index| {
+        const shared_object = self.file(index).?.shared_object;
+        try writer.print("shared_object({d}) : ", .{index});
+        try writer.print("{s}", .{shared_object.path});
+        try writer.print(" : needed({})", .{shared_object.needed});
+        if (!shared_object.alive) try writer.writeAll(" : [*]");
+        try writer.writeByte('\n');
+        try writer.print("{}\n", .{shared_object.fmtSymtab(self)});
+    }
+
     if (self.linker_defined_index) |index| {
         const linker_defined = self.file(index).?.linker_defined;
         try writer.print("linker_defined({d}) : (linker defined)\n", .{index});
@@ -5227,10 +5734,16 @@ const Archive = @import("Elf/Archive.zig");
 pub const Atom = @import("Elf/Atom.zig");
 const Cache = std.Build.Cache;
 const Compilation = @import("../Compilation.zig");
+const CopyRelSection = synthetic_sections.CopyRelSection;
+const DynamicSection = synthetic_sections.DynamicSection;
+const DynsymSection = synthetic_sections.DynsymSection;
 const Dwarf = @import("Dwarf.zig");
 const Elf = @This();
 const File = @import("Elf/file.zig").File;
+const GnuHashSection = synthetic_sections.GnuHashSection;
 const GotSection = synthetic_sections.GotSection;
+const GotPltSection = synthetic_sections.GotPltSection;
+const HashSection = synthetic_sections.HashSection;
 const LinkerDefined = @import("Elf/LinkerDefined.zig");
 const Liveness = @import("../Liveness.zig");
 const LlvmObject = @import("../codegen/llvm.zig").Object;
@@ -5238,10 +5751,14 @@ const Module = @import("../Module.zig");
 const Object = @import("Elf/Object.zig");
 const InternPool = @import("../InternPool.zig");
 const Package = @import("../Package.zig");
+const PltSection = synthetic_sections.PltSection;
+const PltGotSection = synthetic_sections.PltGotSection;
+const SharedObject = @import("Elf/SharedObject.zig");
 const Symbol = @import("Elf/Symbol.zig");
 const StringTable = @import("strtab.zig").StringTable;
 const TableSection = @import("table_section.zig").TableSection;
 const Type = @import("../type.zig").Type;
 const TypedValue = @import("../TypedValue.zig");
 const Value = @import("../value.zig").Value;
+const VerneedSection = synthetic_sections.VerneedSection;
 const ZigModule = @import("Elf/ZigModule.zig");
CMakeLists.txt
@@ -591,6 +591,7 @@ set(ZIG_STAGE2_SOURCES
     "${CMAKE_SOURCE_DIR}/src/link/Elf/Atom.zig"
     "${CMAKE_SOURCE_DIR}/src/link/Elf/LinkerDefined.zig"
     "${CMAKE_SOURCE_DIR}/src/link/Elf/Object.zig"
+    "${CMAKE_SOURCE_DIR}/src/link/Elf/SharedObject.zig"
     "${CMAKE_SOURCE_DIR}/src/link/Elf/Symbol.zig"
     "${CMAKE_SOURCE_DIR}/src/link/Elf/ZigModule.zig"
     "${CMAKE_SOURCE_DIR}/src/link/Elf/eh_frame.zig"