Commit 6ad5db030c

Jakub Konka <kubkon@jakubkonka.com>
2023-09-08 18:12:53
elf: store GOT index in symbol extra array; use GotSection for GOT
1 parent 9691d1a
Changed files (9)
src/arch/aarch64/CodeGen.zig
@@ -4316,8 +4316,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
             if (self.bin_file.cast(link.File.Elf)) |elf_file| {
                 const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
                 const sym = elf_file.symbol(sym_index);
-                _ = try sym.getOrCreateOffsetTableEntry(elf_file);
-                const got_addr = @as(u32, @intCast(sym.getOffsetTableAddress(elf_file)));
+                _ = try sym.getOrCreateGotEntry(elf_file);
+                const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file)));
                 try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr });
             } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
                 const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
src/arch/arm/CodeGen.zig
@@ -4296,8 +4296,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
             if (self.bin_file.cast(link.File.Elf)) |elf_file| {
                 const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
                 const sym = elf_file.symbol(sym_index);
-                _ = try sym.getOrCreateOffsetTableEntry(elf_file);
-                const got_addr = @as(u32, @intCast(sym.getOffsetTableAddress(elf_file)));
+                _ = try sym.getOrCreateGotEntry(elf_file);
+                const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file)));
                 try self.genSetReg(Type.usize, .lr, .{ .memory = got_addr });
             } else if (self.bin_file.cast(link.File.MachO)) |_| {
                 unreachable; // unsupported architecture for MachO
src/arch/riscv64/CodeGen.zig
@@ -1749,8 +1749,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
                 .func => |func| {
                     const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
                     const sym = elf_file.symbol(sym_index);
-                    _ = try sym.getOrCreateOffsetTableEntry(elf_file);
-                    const got_addr = @as(u32, @intCast(sym.getOffsetTableAddress(elf_file)));
+                    _ = try sym.getOrCreateGotEntry(elf_file);
+                    const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file)));
                     try self.genSetReg(Type.usize, .ra, .{ .memory = got_addr });
                     _ = try self.addInst(.{
                         .tag = .jalr,
src/arch/sparc64/CodeGen.zig
@@ -1351,8 +1351,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
                     const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
                         const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
                         const sym = elf_file.symbol(sym_index);
-                        _ = try sym.getOrCreateOffsetTableEntry(elf_file);
-                        break :blk @as(u32, @intCast(sym.getOffsetTableAddress(elf_file)));
+                        _ = try sym.getOrCreateGotEntry(elf_file);
+                        break :blk @as(u32, @intCast(sym.gotAddress(elf_file)));
                     } else unreachable;
 
                     try self.genSetReg(Type.usize, .o7, .{ .memory = got_addr });
src/arch/x86_64/CodeGen.zig
@@ -8151,8 +8151,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
             if (self.bin_file.cast(link.File.Elf)) |elf_file| {
                 const sym_index = try elf_file.getOrCreateMetadataForDecl(owner_decl);
                 const sym = elf_file.symbol(sym_index);
-                _ = try sym.getOrCreateOffsetTableEntry(elf_file);
-                const got_addr = sym.getOffsetTableAddress(elf_file);
+                _ = try sym.getOrCreateGotEntry(elf_file);
+                const got_addr = sym.gotAddress(elf_file);
                 try self.asmMemory(.{ ._, .call }, Memory.sib(.qword, .{
                     .base = .{ .reg = .ds },
                     .disp = @intCast(got_addr),
@@ -10218,8 +10218,8 @@ fn genLazySymbolRef(
         const sym_index = elf_file.getOrCreateMetadataForLazySymbol(lazy_sym) catch |err|
             return self.fail("{s} creating lazy symbol", .{@errorName(err)});
         const sym = elf_file.symbol(sym_index);
-        _ = try sym.getOrCreateOffsetTableEntry(elf_file);
-        const got_addr = sym.getOffsetTableAddress(elf_file);
+        _ = try sym.getOrCreateGotEntry(elf_file);
+        const got_addr = sym.gotAddress(elf_file);
         const got_mem =
             Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(got_addr) });
         switch (tag) {
src/link/Elf/Symbol.zig
@@ -84,24 +84,6 @@ pub fn symbolRank(symbol: Symbol, elf_file: *Elf) u32 {
     return file_ptr.symbolRank(sym, in_archive);
 }
 
-/// If entry already exists, returns index to it.
-/// Otherwise, creates a new entry in the Global Offset Table for this Symbol.
-pub fn getOrCreateOffsetTableEntry(self: Symbol, elf_file: *Elf) !Symbol.Index {
-    if (elf_file.got_table.lookup.get(self.index)) |index| return index;
-    const index = try elf_file.got_table.allocateEntry(elf_file.base.allocator, self.index);
-    elf_file.got_table_count_dirty = true;
-    return index;
-}
-
-pub fn getOffsetTableAddress(self: Symbol, elf_file: *Elf) u64 {
-    const got_entry_index = elf_file.got_table.lookup.get(self.index).?;
-    const target = elf_file.base.options.target;
-    const ptr_bits = target.ptrBitWidth();
-    const ptr_bytes: u64 = @divExact(ptr_bits, 8);
-    const got = elf_file.program_headers.items[elf_file.phdr_got_index.?];
-    return got.p_vaddr + got_entry_index * ptr_bytes;
-}
-
 pub fn address(symbol: Symbol, opts: struct {
     plt: bool = true,
 }, elf_file: *Elf) u64 {
@@ -122,11 +104,21 @@ pub fn address(symbol: Symbol, opts: struct {
     return symbol.value;
 }
 
-// pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 {
-//     if (!symbol.flags.got) return 0;
-//     const extra = symbol.extra(elf_file).?;
-//     return elf_file.gotEntryAddress(extra.got);
-// }
+pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 {
+    if (!symbol.flags.got) return 0;
+    const extras = symbol.extra(elf_file).?;
+    const entry = elf_file.got.entries.items[extras.got];
+    return entry.address(elf_file);
+}
+
+pub fn getOrCreateGotEntry(symbol: *Symbol, elf_file: *Elf) !GotSection.Index {
+    const index = if (symbol.flags.got)
+        symbol.extra(elf_file).?.got
+    else
+        try elf_file.got.addGotSymbol(symbol.index, elf_file);
+    symbol.flags.got = true;
+    return index;
+}
 
 // pub fn tlsGdAddress(symbol: Symbol, elf_file: *Elf) u64 {
 //     if (!symbol.flags.tlsgd) return 0;
@@ -159,7 +151,7 @@ pub fn address(symbol: Symbol, opts: struct {
 // }
 
 pub fn addExtra(symbol: *Symbol, extras: Extra, elf_file: *Elf) !void {
-    symbol.extra = try elf_file.addSymbolExtra(extras);
+    symbol.extra_index = try elf_file.addSymbolExtra(extras);
 }
 
 pub fn extra(symbol: Symbol, elf_file: *Elf) ?Extra {
@@ -347,10 +339,12 @@ pub const Index = u32;
 const assert = std.debug.assert;
 const elf = std.elf;
 const std = @import("std");
+const synthetic_sections = @import("synthetic_sections.zig");
 
 const Atom = @import("Atom.zig");
 const Elf = @import("../Elf.zig");
 const File = @import("file.zig").File;
+const GotSection = synthetic_sections.GotSection;
 const LinkerDefined = @import("LinkerDefined.zig");
 // const Object = @import("Object.zig");
 // const SharedObject = @import("SharedObject.zig");
src/link/Elf/synthetic_sections.zig
@@ -0,0 +1,469 @@
+pub const GotSection = struct {
+    entries: std.ArrayListUnmanaged(Entry) = .{},
+    needs_rela: bool = false,
+    dirty: bool = false,
+    output_symtab_size: Elf.SymtabSize = .{},
+
+    pub const Index = u32;
+
+    const Tag = enum {
+        got,
+        tlsld,
+        tlsgd,
+        gottp,
+        tlsdesc,
+    };
+
+    const Entry = struct {
+        tag: Tag,
+        symbol_index: Symbol.Index,
+        cell_index: Index,
+
+        /// Returns how many indexes in the GOT this entry uses.
+        pub inline fn len(entry: Entry) usize {
+            return switch (entry.tag) {
+                .got, .gottp => 1,
+                .tlsld, .tlsgd, .tlsdesc => 2,
+            };
+        }
+
+        pub fn address(entry: Entry, elf_file: *Elf) u64 {
+            const ptr_bytes = @as(u64, elf_file.archPtrWidthBytes());
+            const shdr = &elf_file.sections.items(.shdr)[elf_file.got_section_index.?];
+            return shdr.sh_addr + @as(u64, entry.cell_index) * ptr_bytes;
+        }
+    };
+
+    pub fn deinit(got: *GotSection, allocator: Allocator) void {
+        got.entries.deinit(allocator);
+    }
+
+    fn allocateEntry(got: *GotSection, allocator: Allocator) !Index {
+        try got.entries.ensureUnusedCapacity(allocator, 1);
+        // TODO add free list
+        const index = @as(Index, @intCast(got.entries.items.len));
+        const entry = got.entries.addOneAssumeCapacity();
+        const cell_index: Index = if (index > 0) blk: {
+            const last = got.entries.items[index - 1];
+            break :blk last.cell_index + @as(Index, @intCast(last.len()));
+        } else 0;
+        entry.* = .{ .tag = undefined, .symbol_index = undefined, .cell_index = cell_index };
+        got.dirty = true;
+        return index;
+    }
+
+    pub fn addGotSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !Index {
+        const index = try got.allocateEntry(elf_file.base.allocator);
+        const entry = &got.entries.items[index];
+        entry.tag = .got;
+        entry.symbol_index = sym_index;
+        const symbol = elf_file.symbol(sym_index);
+        if (symbol.flags.import or symbol.isIFunc(elf_file) or (elf_file.base.options.pic and !symbol.isAbs(elf_file)))
+            got.needs_rela = true;
+        if (symbol.extra(elf_file)) |extra| {
+            var new_extra = extra;
+            new_extra.got = index;
+            symbol.setExtra(new_extra, elf_file);
+        } else try symbol.addExtra(.{ .got = index }, elf_file);
+        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 size(got: GotSection, elf_file: *Elf) usize {
+        var s: usize = 0;
+        for (got.entries.items) |entry| {
+            s += elf_file.archPtrWidthBytes() * entry.len();
+        }
+        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;
+        }
+        const endian = elf_file.base.options.target.cpu.arch.endian();
+        const entry = got.entries.items[index];
+        const shdr = &elf_file.sections.items(.shdr)[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,
+                    }
+                }
+            },
+            else => unreachable,
+        }
+    }
+
+    // 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 calcSymtabSize(got: *GotSection, elf_file: *Elf) !void {
+        got.output_symtab_size.nlocals = @as(u32, @intCast(got.symbols.items.len));
+        for (got.symbols.items) |sym| {
+            const suffix_len = switch (sym) {
+                .tlsgd => "$tlsgd".len,
+                .got => "$got".len,
+                .gottp => "$gottp".len,
+                .tlsdesc => "$tlsdesc".len,
+            };
+            const symbol = elf_file.getSymbol(sym.getIndex());
+            const name_len = symbol.getName(elf_file).len;
+            got.output_symtab_size.strsize += @as(u32, @intCast(name_len + suffix_len + 1));
+        }
+
+        if (got.emit_tlsld) {
+            got.output_symtab_size.nlocals += 1;
+            got.output_symtab_size.strsize += @as(u32, @intCast("$tlsld".len + 1));
+        }
+    }
+
+    pub fn writeSymtab(got: GotSection, elf_file: *Elf, ctx: anytype) !void {
+        const gpa = elf_file.base.allocator;
+
+        var ilocal = ctx.ilocal;
+        for (got.symbols.items) |sym| {
+            const suffix = switch (sym) {
+                .tlsgd => "$tlsgd",
+                .got => "$got",
+                .gottp => "$gottp",
+                .tlsdesc => "$tlsdesc",
+            };
+            const symbol = elf_file.getSymbol(sym.getIndex());
+            const name = try std.fmt.allocPrint(gpa, "{s}{s}", .{ symbol.getName(elf_file), suffix });
+            defer gpa.free(name);
+            const st_name = try ctx.strtab.insert(gpa, name);
+            const st_value = switch (sym) {
+                // .tlsgd => symbol.tlsGdAddress(elf_file),
+                .got => symbol.gotAddress(elf_file),
+                // .gottp => symbol.gotTpAddress(elf_file),
+                // .tlsdesc => symbol.tlsDescAddress(elf_file),
+                else => unreachable,
+            };
+            const st_size: u64 = switch (sym) {
+                .tlsgd, .tlsdesc => 16,
+                .got, .gottp => 8,
+            };
+            ctx.symtab[ilocal] = .{
+                .st_name = st_name,
+                .st_info = elf.STT_OBJECT,
+                .st_other = 0,
+                .st_shndx = elf_file.got_section_index.?,
+                .st_value = st_value,
+                .st_size = st_size,
+            };
+            ilocal += 1;
+        }
+
+        // if (got.emit_tlsld) {
+        //     const st_name = try ctx.strtab.insert(gpa, "$tlsld");
+        //     ctx.symtab[ilocal] = .{
+        //         .st_name = st_name,
+        //         .st_info = elf.STT_OBJECT,
+        //         .st_other = 0,
+        //         .st_shndx = elf_file.got_sect_index.?,
+        //         .st_value = elf_file.getTlsLdAddress(),
+        //         .st_size = 16,
+        //     };
+        //     ilocal += 1;
+        // }
+    }
+
+    const FormatCtx = struct {
+        got: GotSection,
+        elf_file: *Elf,
+    };
+
+    pub fn fmt(got: GotSection, elf_file: *Elf) std.fmt.Formatter(format2) {
+        return .{ .data = .{ .got = got, .elf_file = elf_file } };
+    }
+
+    pub fn format2(
+        ctx: FormatCtx,
+        comptime unused_fmt_string: []const u8,
+        options: std.fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        _ = options;
+        _ = unused_fmt_string;
+        try writer.writeAll("GOT\n");
+        for (ctx.got.entries.items) |entry| {
+            const symbol = ctx.elf_file.symbol(entry.symbol_index);
+            try writer.print("  {d}@0x{x} => {d}@0x{x} ({s})\n", .{
+                entry.cell_index,
+                entry.address(ctx.elf_file),
+                entry.symbol_index,
+                symbol.address(.{}, ctx.elf_file),
+                symbol.name(ctx.elf_file),
+            });
+        }
+    }
+};
+
+const assert = std.debug.assert;
+const builtin = @import("builtin");
+const elf = std.elf;
+const log = std.log.scoped(.link);
+const std = @import("std");
+
+const Allocator = std.mem.Allocator;
+const Elf = @import("../Elf.zig");
+const Symbol = @import("Symbol.zig");
src/link/Elf.zig
@@ -42,6 +42,8 @@ shstrtab: StringTable(.strtab) = .{},
 /// .strtab buffer
 strtab: StringTable(.strtab) = .{},
 
+got: GotSection = .{},
+
 symtab_section_index: ?u16 = null,
 text_section_index: ?u16 = null,
 rodata_section_index: ?u16 = null,
@@ -56,18 +58,16 @@ shstrtab_section_index: ?u16 = null,
 strtab_section_index: ?u16 = null,
 
 symbols: std.ArrayListUnmanaged(Symbol) = .{},
+symbols_extra: std.ArrayListUnmanaged(u32) = .{},
 resolver: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index) = .{},
 unresolved: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
 
 symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .{},
 
-got_table: TableSection(Symbol.Index) = .{},
-
 phdr_table_dirty: bool = false,
 shdr_table_dirty: bool = false,
 shstrtab_dirty: bool = false,
 strtab_dirty: bool = false,
-got_table_count_dirty: bool = false,
 
 debug_strtab_dirty: bool = false,
 debug_abbrev_section_dirty: bool = false,
@@ -146,6 +146,8 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
 
     // Index 0 is always a null symbol.
     try self.symbols.append(allocator, .{});
+    // Index 0 is always a null symbol.
+    try self.symbols_extra.append(allocator, 0);
     // Allocate atom index 0 to null atom
     try self.atoms.append(allocator, .{});
     // Append null file at index 0
@@ -234,8 +236,9 @@ pub fn deinit(self: *Elf) void {
     self.shstrtab.deinit(gpa);
     self.strtab.deinit(gpa);
     self.symbols.deinit(gpa);
+    self.symbols_extra.deinit(gpa);
     self.symbols_free_list.deinit(gpa);
-    self.got_table.deinit(gpa);
+    self.got.deinit(gpa);
     self.resolver.deinit(gpa);
     self.unresolved.deinit(gpa);
 
@@ -1249,7 +1252,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     assert(!self.shstrtab_dirty);
     assert(!self.strtab_dirty);
     assert(!self.debug_strtab_dirty);
-    assert(!self.got_table_count_dirty);
+    assert(!self.got.dirty);
 }
 
 fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !void {
@@ -2087,7 +2090,7 @@ fn freeDeclMetadata(self: *Elf, sym_index: Symbol.Index) void {
     log.debug("adding %{d} to local symbols free list", .{sym_index});
     self.symbols_free_list.append(self.base.allocator, sym_index) catch {};
     self.symbols.items[sym_index] = .{};
-    self.got_table.freeEntry(self.base.allocator, sym_index);
+    // TODO free GOT entry here
 }
 
 pub fn freeDecl(self: *Elf, decl_index: Module.Decl.Index) void {
@@ -2226,9 +2229,8 @@ fn updateDeclCode(
                 esym.st_value = atom_ptr.value;
 
                 log.debug("  (writing new offset table entry)", .{});
-                const got_entry_index = self.got_table.lookup.get(sym_index).?;
-                self.got_table.entries.items[got_entry_index] = sym_index;
-                try self.writeOffsetTableEntry(got_entry_index);
+                const extra = sym.extra(self).?;
+                try self.got.writeEntry(self, extra.got);
             }
         } else if (code.len < old_size) {
             atom_ptr.shrink(self);
@@ -2245,8 +2247,8 @@ fn updateDeclCode(
         sym.value = atom_ptr.value;
         esym.st_value = atom_ptr.value;
 
-        const got_entry_index = try sym.getOrCreateOffsetTableEntry(self);
-        try self.writeOffsetTableEntry(got_entry_index);
+        const got_index = try sym.getOrCreateGotEntry(self);
+        try self.got.writeEntry(self, got_index);
     }
 
     const phdr_index = self.sections.items(.phdr_index)[shdr_index];
@@ -2477,8 +2479,8 @@ fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol.
     local_sym.value = atom_ptr.value;
     local_esym.st_value = atom_ptr.value;
 
-    const got_entry_index = try local_sym.getOrCreateOffsetTableEntry(self);
-    try self.writeOffsetTableEntry(got_entry_index);
+    const got_index = try local_sym.getOrCreateGotEntry(self);
+    try self.got.writeEntry(self, got_index);
 
     const section_offset = atom_ptr.value - self.program_headers.items[phdr_index].p_vaddr;
     const file_offset = self.sections.items(.shdr)[local_sym.output_section_index].sh_offset + section_offset;
@@ -2712,61 +2714,6 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
     }
 }
 
-fn writeOffsetTableEntry(self: *Elf, index: @TypeOf(self.got_table).Index) !void {
-    const entry_size: u16 = self.archPtrWidthBytes();
-    if (self.got_table_count_dirty) {
-        const needed_size = self.got_table.entries.items.len * entry_size;
-        try self.growAllocSection(self.got_section_index.?, needed_size);
-        self.got_table_count_dirty = false;
-    }
-    const endian = self.base.options.target.cpu.arch.endian();
-    const shdr = &self.sections.items(.shdr)[self.got_section_index.?];
-    const off = shdr.sh_offset + @as(u64, entry_size) * index;
-    const phdr = &self.program_headers.items[self.phdr_got_index.?];
-    const vaddr = phdr.p_vaddr + @as(u64, entry_size) * index;
-    const got_entry = self.got_table.entries.items[index];
-    const got_value = self.symbol(got_entry).value;
-    switch (entry_size) {
-        2 => {
-            var buf: [2]u8 = undefined;
-            mem.writeInt(u16, &buf, @as(u16, @intCast(got_value)), endian);
-            try self.base.file.?.pwriteAll(&buf, off);
-        },
-        4 => {
-            var buf: [4]u8 = undefined;
-            mem.writeInt(u32, &buf, @as(u32, @intCast(got_value)), endian);
-            try self.base.file.?.pwriteAll(&buf, off);
-        },
-        8 => {
-            var buf: [8]u8 = undefined;
-            mem.writeInt(u64, &buf, got_value, endian);
-            try self.base.file.?.pwriteAll(&buf, off);
-
-            if (self.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,
-                }
-            }
-        },
-        else => unreachable,
-    }
-}
-
 fn updateSymtabSize(self: *Elf) !void {
     var sizes = SymtabSize{};
 
@@ -2858,8 +2805,8 @@ fn ptrWidthBytes(self: Elf) u8 {
 
 /// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes
 /// in a 32-bit ELF file.
-fn archPtrWidthBytes(self: Elf) u8 {
-    return @as(u8, @intCast(self.base.options.target.ptrBitWidth() / 8));
+pub fn archPtrWidthBytes(self: Elf) u8 {
+    return @as(u8, @intCast(@divExact(self.base.options.target.ptrBitWidth(), 8)));
 }
 
 fn progHeaderTo32(phdr: elf.Elf64_Phdr) elf.Elf32_Phdr {
@@ -3179,6 +3126,50 @@ pub fn addSymbol(self: *Elf) !Symbol.Index {
     return index;
 }
 
+pub fn addSymbolExtra(self: *Elf, extra: Symbol.Extra) !u32 {
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    try self.symbols_extra.ensureUnusedCapacity(self.base.allocator, fields.len);
+    return self.addSymbolExtraAssumeCapacity(extra);
+}
+
+pub fn addSymbolExtraAssumeCapacity(self: *Elf, extra: Symbol.Extra) u32 {
+    const index = @as(u32, @intCast(self.symbols_extra.items.len));
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    inline for (fields) |field| {
+        self.symbols_extra.appendAssumeCapacity(switch (field.type) {
+            u32 => @field(extra, field.name),
+            else => @compileError("bad field type"),
+        });
+    }
+    return index;
+}
+
+pub fn symbolExtra(self: *Elf, index: u32) ?Symbol.Extra {
+    if (index == 0) return null;
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    var i: usize = index;
+    var result: Symbol.Extra = undefined;
+    inline for (fields) |field| {
+        @field(result, field.name) = switch (field.type) {
+            u32 => self.symbols_extra.items[i],
+            else => @compileError("bad field type"),
+        };
+        i += 1;
+    }
+    return result;
+}
+
+pub fn setSymbolExtra(self: *Elf, index: u32, extra: Symbol.Extra) void {
+    assert(index > 0);
+    const fields = @typeInfo(Symbol.Extra).Struct.fields;
+    inline for (fields, 0..) |field, i| {
+        self.symbols_extra.items[index + i] = switch (field.type) {
+            u32 => @field(extra, field.name),
+            else => @compileError("bad field type"),
+        };
+    }
+}
+
 const GetOrPutGlobalResult = struct {
     found_existing: bool,
     index: Symbol.Index,
@@ -3227,6 +3218,7 @@ fn fmtDumpState(
         try writer.print("linker_defined({d}) : (linker defined)\n", .{index});
         try writer.print("{}\n", .{linker_defined.fmtSymtab(self)});
     }
+    try writer.print("{}\n", .{self.got.fmt(self)});
 }
 
 const default_entry_addr = 0x8000000;
@@ -3311,6 +3303,7 @@ const lldMain = @import("../main.zig").lldMain;
 const musl = @import("../musl.zig");
 const target_util = @import("../target.zig");
 const trace = @import("../tracy.zig").trace;
+const synthetic_sections = @import("Elf/synthetic_sections.zig");
 
 const Air = @import("../Air.zig");
 const Allocator = std.mem.Allocator;
@@ -3320,6 +3313,7 @@ const Compilation = @import("../Compilation.zig");
 const Dwarf = @import("Dwarf.zig");
 const Elf = @This();
 const File = @import("Elf/file.zig").File;
+const GotSection = synthetic_sections.GotSection;
 const LinkerDefined = @import("Elf/LinkerDefined.zig");
 const Liveness = @import("../Liveness.zig");
 const LlvmObject = @import("../codegen/llvm.zig").Object;
src/codegen.zig
@@ -856,8 +856,8 @@ fn genDeclRef(
     if (bin_file.cast(link.File.Elf)) |elf_file| {
         const sym_index = try elf_file.getOrCreateMetadataForDecl(decl_index);
         const sym = elf_file.symbol(sym_index);
-        _ = try sym.getOrCreateOffsetTableEntry(elf_file);
-        return GenResult.mcv(.{ .memory = sym.getOffsetTableAddress(elf_file) });
+        _ = try sym.getOrCreateGotEntry(elf_file);
+        return GenResult.mcv(.{ .memory = sym.gotAddress(elf_file) });
     } else if (bin_file.cast(link.File.MachO)) |macho_file| {
         const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index);
         const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;