Commit 61a1cefeb3

Jacob Young <jacobly0@users.noreply.github.com>
2025-11-11 19:58:06
Elf2: implement PLT
1 parent 79a9f3a
Changed files (4)
src/codegen/x86_64/Emit.zig
@@ -881,7 +881,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
             end_offset - 4,
             @enumFromInt(reloc.target.index),
             reloc.off - 4,
-            .{ .X86_64 = .PC32 },
+            .{ .X86_64 = .PLT32 },
         ) else if (emit.bin_file.cast(.macho)) |macho_file| {
             const zo = macho_file.getZigObject().?;
             const atom = zo.symbols.items[emit.atom_index].getAtom(macho_file).?;
@@ -916,7 +916,13 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
                 .r_info = @as(u64, reloc.target.index) << 32 | @intFromEnum(r_type),
                 .r_addend = reloc.off - 4,
             }, zo);
-        } else return emit.fail("TODO implement {s} reloc for {s}", .{
+        } else if (emit.bin_file.cast(.elf2)) |elf| try elf.addReloc(
+            @enumFromInt(emit.atom_index),
+            end_offset - 4,
+            @enumFromInt(reloc.target.index),
+            reloc.off - 4,
+            .{ .X86_64 = if (emit.pic) .TLSLD else unreachable },
+        ) else return emit.fail("TODO implement {s} reloc for {s}", .{
             @tagName(reloc.target.type), @tagName(emit.bin_file.tag),
         }),
         .tlv => if (emit.bin_file.cast(.elf)) |elf_file| {
@@ -933,7 +939,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
             end_offset - 4,
             @enumFromInt(reloc.target.index),
             reloc.off,
-            .{ .X86_64 = .TPOFF32 },
+            .{ .X86_64 = if (emit.pic) .DTPOFF32 else .TPOFF32 },
         ) else if (emit.bin_file.cast(.macho)) |macho_file| {
             const zo = macho_file.getZigObject().?;
             const atom = zo.symbols.items[emit.atom_index].getAtom(macho_file).?;
src/link/Elf2.zig
@@ -9,8 +9,13 @@ si: Symbol.Known,
 symtab: std.ArrayList(Symbol),
 shstrtab: StringTable,
 strtab: StringTable,
-dynsym: std.ArrayList(Symbol.Index),
+dynsym: std.AutoArrayHashMapUnmanaged(Symbol.Index, void),
 dynstr: StringTable,
+got: struct {
+    len: u32,
+    tlsld: GotIndex,
+    plt: std.AutoArrayHashMapUnmanaged(Symbol.Index, void),
+},
 needed: std.AutoArrayHashMapUnmanaged(u32, void),
 inputs: std.ArrayList(struct {
     path: std.Build.Cache.Path,
@@ -35,8 +40,6 @@ pending_uavs: std.AutoArrayHashMapUnmanaged(Node.UavMapIndex, struct {
     src_loc: Zcu.LazySrcLoc,
 }),
 relocs: std.ArrayList(Reloc),
-/// This is hiding actual bugs with global symbols! Reconsider once they are implemented correctly.
-entry_hack: Symbol.Index,
 const_prog_node: std.Progress.Node,
 synth_prog_node: std.Progress.Node,
 input_prog_node: std.Progress.Node,
@@ -156,6 +159,7 @@ pub const Node = union(enum) {
         comptime phdr: MappedFile.Node.Index = @enumFromInt(4),
         comptime text: MappedFile.Node.Index = @enumFromInt(5),
         comptime data: MappedFile.Node.Index = @enumFromInt(6),
+        comptime data_rel_ro: MappedFile.Node.Index = @enumFromInt(7),
         tls: MappedFile.Node.Index,
     };
 
@@ -179,71 +183,12 @@ pub const Section = struct {
         pub fn unwrap(ri: RelIndex) ?u32 {
             return switch (ri) {
                 .none => null,
-                else => @intFromEnum(ri) - 1,
+                _ => @intFromEnum(ri) - 1,
             };
         }
     };
 };
 
-pub const StringTable = struct {
-    map: std.HashMapUnmanaged(u32, void, StringTable.Context, std.hash_map.default_max_load_percentage),
-
-    const Context = struct {
-        slice: []const u8,
-
-        pub fn eql(_: Context, lhs_key: u32, rhs_key: u32) bool {
-            return lhs_key == rhs_key;
-        }
-
-        pub fn hash(ctx: Context, key: u32) u64 {
-            return std.hash_map.hashString(std.mem.sliceTo(ctx.slice[key..], 0));
-        }
-    };
-
-    const Adapter = struct {
-        slice: []const u8,
-
-        pub fn eql(adapter: Adapter, lhs_key: []const u8, rhs_key: u32) bool {
-            return std.mem.startsWith(u8, adapter.slice[rhs_key..], lhs_key) and
-                adapter.slice[rhs_key + lhs_key.len] == 0;
-        }
-
-        pub fn hash(_: Adapter, key: []const u8) u64 {
-            assert(std.mem.indexOfScalar(u8, key, 0) == null);
-            return std.hash_map.hashString(key);
-        }
-    };
-
-    pub fn get(st: *StringTable, elf: *Elf, si: Symbol.Index, key: []const u8) !u32 {
-        const gpa = elf.base.comp.gpa;
-        const ni = si.node(elf);
-        const slice_const = ni.sliceConst(&elf.mf);
-        const gop = try st.map.getOrPutContextAdapted(
-            gpa,
-            key,
-            StringTable.Adapter{ .slice = slice_const },
-            .{ .slice = slice_const },
-        );
-        if (gop.found_existing) return gop.key_ptr.*;
-        const old_size, const new_size = size: switch (elf.shdrPtr(si.shndx(elf))) {
-            inline else => |shdr| {
-                const old_size: u32 = @intCast(elf.targetLoad(&shdr.size));
-                const new_size: u32 = @intCast(old_size + key.len + 1);
-                elf.targetStore(&shdr.size, new_size);
-                break :size .{ old_size, new_size };
-            },
-        };
-        _, const node_size = ni.location(&elf.mf).resolve(&elf.mf);
-        if (new_size > node_size)
-            try ni.resize(&elf.mf, gpa, new_size +| new_size / MappedFile.growth_factor);
-        const slice = ni.slice(&elf.mf)[old_size..];
-        @memcpy(slice[0..key.len], key);
-        slice[key.len] = 0;
-        gop.key_ptr.* = old_size;
-        return old_size;
-    }
-};
-
 pub const Symbol = struct {
     ni: MappedFile.Node.Index,
     /// Relocations contained within this symbol
@@ -260,6 +205,11 @@ pub const Symbol = struct {
         rodata,
         text,
         data,
+        data_rel_ro,
+        got,
+        got_plt,
+        plt,
+        plt_sec,
         _,
 
         pub fn get(si: Symbol.Index, elf: *Elf) *Symbol {
@@ -333,7 +283,8 @@ pub const Symbol = struct {
             shndx: Shndx = .UNDEF,
         };
         pub fn init(si: Symbol.Index, elf: *Elf, opts: InitOptions) !void {
-            const gpa = elf.base.comp.gpa;
+            const comp = elf.base.comp;
+            const gpa = comp.gpa;
             const target_endian = elf.targetEndian();
             const name_strtab_entry = try elf.string(.strtab, opts.name);
             switch (elf.shdrPtr(elf.si.symtab.shndx(elf))) {
@@ -373,15 +324,34 @@ pub const Symbol = struct {
                     @intFromEnum(si) + 1,
                 )),
             }
-            if (opts.bind == .LOCAL or elf.si.dynsym == .null) return;
-            const dsi = elf.dynsym.items.len;
-            try elf.dynsym.append(gpa, si);
+
+            if (opts.bind == .LOCAL) return;
+            no_entry: {
+                if (std.mem.eql(u8, opts.name, entry: switch (elf.options.entry) {
+                    .default => switch (comp.config.output_mode) {
+                        .Exe => continue :entry .enabled,
+                        .Lib, .Obj => continue :entry .disabled,
+                    },
+                    .disabled => break :no_entry,
+                    .enabled => "_start",
+                    .named => |named| named,
+                })) {
+                    elf.si.entry = si;
+                    switch (elf.ehdrPtr()) {
+                        inline else => |ehdr| elf.targetStore(&ehdr.entry, @intCast(opts.value)),
+                    }
+                }
+            }
+
+            if (elf.si.dynsym == .null) return;
+            const dsi = elf.dynsym.count();
+            try elf.dynsym.putNoClobber(gpa, si, {});
             const name_dynstr_entry = try elf.string(.dynstr, opts.name);
             switch (elf.shdrPtr(elf.si.dynsym.shndx(elf))) {
                 inline else => |shdr| {
                     const old_size = elf.targetLoad(&shdr.size);
                     const ent_size = elf.targetLoad(&shdr.entsize);
-                    const new_size = ent_size * elf.dynsym.items.len;
+                    const new_size = ent_size * elf.dynsym.count();
                     if (new_size > old_size) {
                         elf.targetStore(&shdr.size, @intCast(new_size));
                         const dynsym_ni = elf.si.dynsym.node(elf);
@@ -409,13 +379,136 @@ pub const Symbol = struct {
                     if (target_endian != native_endian) std.mem.byteSwapAllFields(Sym, dynsym);
                 },
             }
+
+            if (opts.type != .FUNC or opts.shndx != .UNDEF) return;
+            const plt_index: u32 = @intCast(elf.got.plt.count());
+            try elf.got.plt.putNoClobber(gpa, si, {});
+            switch (elf.ehdrField(.machine)) {
+                else => |machine| @panic(@tagName(machine)),
+                .X86_64 => {
+                    const plt_ni = elf.si.plt.node(elf);
+                    _, const plt_node_size = plt_ni.location(&elf.mf).resolve(&elf.mf);
+                    const plt_addr = plt_addr: switch (elf.shdrPtr(elf.si.plt.shndx(elf))) {
+                        inline else => |shdr| {
+                            const old_size = 16 * (1 + plt_index);
+                            const new_size = old_size + 16;
+                            elf.targetStore(&shdr.size, new_size);
+                            if (new_size > plt_node_size) try plt_ni.resize(
+                                &elf.mf,
+                                gpa,
+                                new_size +| new_size / MappedFile.growth_factor,
+                            );
+                            const plt_slice = plt_ni.slice(&elf.mf)[old_size..new_size];
+                            @memcpy(plt_slice, &[16]u8{
+                                0xf3, 0x0f, 0x1e, 0xfa, // endbr64
+                                0x68, 0x00, 0x00, 0x00, 0x00, // push $0x0
+                                0xe9, 0x00, 0x00, 0x00, 0x00, // jmp 0
+                                0x66, 0x90, // xchg %ax,%ax
+                            });
+                            std.mem.writeInt(u32, plt_slice[5..][0..4], plt_index, target_endian);
+                            std.mem.writeInt(
+                                i32,
+                                plt_slice[10..][0..4],
+                                2 - @as(i32, @intCast(new_size)),
+                                target_endian,
+                            );
+                            break :plt_addr elf.targetLoad(&shdr.addr) + old_size;
+                        },
+                    };
+
+                    const got_plt_shndx = elf.si.got_plt.shndx(elf);
+                    const got_plt_ni = elf.si.got_plt.node(elf);
+                    _, const got_plt_node_size = got_plt_ni.location(&elf.mf).resolve(&elf.mf);
+                    const got_plt_addr = got_plt_addr: switch (elf.shdrPtr(got_plt_shndx)) {
+                        inline else => |shdr, class| {
+                            const Addr = class.ElfN().Addr;
+                            const addr_size = @sizeOf(Addr);
+                            const old_size = addr_size * (3 + plt_index);
+                            const new_size = old_size + addr_size;
+                            elf.targetStore(&shdr.size, new_size);
+                            if (new_size > got_plt_node_size) try got_plt_ni.resize(
+                                &elf.mf,
+                                gpa,
+                                new_size +| new_size / MappedFile.growth_factor,
+                            );
+                            std.mem.writeInt(
+                                Addr,
+                                got_plt_ni.slice(&elf.mf)[old_size..][0..addr_size],
+                                @intCast(plt_addr),
+                                target_endian,
+                            );
+                            break :got_plt_addr elf.targetLoad(&shdr.addr) + old_size;
+                        },
+                    };
+
+                    const plt_sec_ni = elf.si.plt_sec.node(elf);
+                    _, const plt_sec_node_size = plt_sec_ni.location(&elf.mf).resolve(&elf.mf);
+                    switch (elf.shdrPtr(elf.si.plt_sec.shndx(elf))) {
+                        inline else => |shdr| {
+                            const old_size = 16 * plt_index;
+                            const new_size = old_size + 16;
+                            elf.targetStore(&shdr.size, new_size);
+                            if (new_size > plt_sec_node_size) try plt_sec_ni.resize(
+                                &elf.mf,
+                                gpa,
+                                new_size +| new_size / MappedFile.growth_factor,
+                            );
+                            const plt_sec_slice = plt_sec_ni.slice(&elf.mf)[old_size..new_size];
+                            @memcpy(plt_sec_slice, &[16]u8{
+                                0xf3, 0x0f, 0x1e, 0xfa, // endbr64
+                                0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp *0x0(%rip)
+                                0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nopw 0x0(%rax,%rax,1)
+                            });
+                            std.mem.writeInt(
+                                i32,
+                                plt_sec_slice[6..][0..4],
+                                @intCast(@as(i64, @bitCast(
+                                    got_plt_addr -% (elf.targetLoad(&shdr.addr) + old_size + 10),
+                                ))),
+                                target_endian,
+                            );
+                        },
+                    }
+
+                    const rela_plt_si = got_plt_shndx.get(elf).rela_si;
+                    const rela_plt_ni = rela_plt_si.node(elf);
+                    _, const rela_plt_node_size = rela_plt_ni.location(&elf.mf).resolve(&elf.mf);
+                    switch (elf.shdrPtr(rela_plt_si.shndx(elf))) {
+                        inline else => |shdr, class| {
+                            const Rela = class.ElfN().Rela;
+                            const rela_size = elf.targetLoad(&shdr.entsize);
+                            const old_size = rela_size * plt_index;
+                            const new_size = old_size + rela_size;
+                            elf.targetStore(&shdr.size, new_size);
+                            if (new_size > rela_plt_node_size) try rela_plt_ni.resize(
+                                &elf.mf,
+                                gpa,
+                                new_size +| new_size / MappedFile.growth_factor,
+                            );
+                            const rela: *Rela = @ptrCast(@alignCast(
+                                rela_plt_ni.slice(&elf.mf)[@intCast(old_size)..@intCast(new_size)],
+                            ));
+                            rela.* = .{
+                                .offset = @intCast(got_plt_addr),
+                                .info = .{
+                                    .type = @intFromEnum(std.elf.R_X86_64.JUMP_SLOT),
+                                    .sym = @intCast(dsi),
+                                },
+                                .addend = 0,
+                            };
+                            if (target_endian != native_endian) std.mem.byteSwapAllFields(Rela, rela);
+                        },
+                    }
+                    try rela_plt_ni.resized(gpa, &elf.mf);
+                },
+            }
         }
 
         pub fn flushMoved(si: Symbol.Index, elf: *Elf, value: u64) void {
             switch (elf.symPtr(si)) {
                 inline else => |sym, class| {
                     elf.targetStore(&sym.value, @intCast(value));
-                    if (si == elf.entry_hack) {
+                    if (si == elf.si.entry) {
                         @branchHint(.unlikely);
                         @field(elf.ehdrPtr(), @tagName(class)).entry = sym.value;
                     }
@@ -464,10 +557,16 @@ pub const Symbol = struct {
         comptime rodata: Symbol.Index = .rodata,
         comptime text: Symbol.Index = .text,
         comptime data: Symbol.Index = .data,
+        comptime data_rel_ro: Symbol.Index = .data_rel_ro,
+        comptime got: Symbol.Index = .got,
+        comptime got_plt: Symbol.Index = .got_plt,
+        comptime plt: Symbol.Index = .plt,
+        comptime plt_sec: Symbol.Index = .plt_sec,
         dynsym: Symbol.Index,
         dynstr: Symbol.Index,
         dynamic: Symbol.Index,
         tdata: Symbol.Index,
+        entry: Symbol.Index,
     };
 
     comptime {
@@ -475,6 +574,83 @@ pub const Symbol = struct {
     }
 };
 
+pub const StringTable = struct {
+    map: std.HashMapUnmanaged(u32, void, StringTable.Context, std.hash_map.default_max_load_percentage),
+
+    const Context = struct {
+        slice: []const u8,
+
+        pub fn eql(_: Context, lhs_key: u32, rhs_key: u32) bool {
+            return lhs_key == rhs_key;
+        }
+
+        pub fn hash(ctx: Context, key: u32) u64 {
+            return std.hash_map.hashString(std.mem.sliceTo(ctx.slice[key..], 0));
+        }
+    };
+
+    const Adapter = struct {
+        slice: []const u8,
+
+        pub fn eql(adapter: Adapter, lhs_key: []const u8, rhs_key: u32) bool {
+            return std.mem.startsWith(u8, adapter.slice[rhs_key..], lhs_key) and
+                adapter.slice[rhs_key + lhs_key.len] == 0;
+        }
+
+        pub fn hash(_: Adapter, key: []const u8) u64 {
+            assert(std.mem.indexOfScalar(u8, key, 0) == null);
+            return std.hash_map.hashString(key);
+        }
+    };
+
+    pub fn get(st: *StringTable, elf: *Elf, si: Symbol.Index, key: []const u8) !u32 {
+        const gpa = elf.base.comp.gpa;
+        const ni = si.node(elf);
+        const slice_const = ni.sliceConst(&elf.mf);
+        const gop = try st.map.getOrPutContextAdapted(
+            gpa,
+            key,
+            StringTable.Adapter{ .slice = slice_const },
+            .{ .slice = slice_const },
+        );
+        if (gop.found_existing) return gop.key_ptr.*;
+        try ni.resized(gpa, &elf.mf);
+        const old_size, const new_size = size: switch (elf.shdrPtr(si.shndx(elf))) {
+            inline else => |shdr| {
+                const old_size: u32 = @intCast(elf.targetLoad(&shdr.size));
+                const new_size: u32 = @intCast(old_size + key.len + 1);
+                elf.targetStore(&shdr.size, new_size);
+                break :size .{ old_size, new_size };
+            },
+        };
+        _, const node_size = ni.location(&elf.mf).resolve(&elf.mf);
+        if (new_size > node_size)
+            try ni.resize(&elf.mf, gpa, new_size +| new_size / MappedFile.growth_factor);
+        const slice = ni.slice(&elf.mf)[old_size..];
+        @memcpy(slice[0..key.len], key);
+        slice[key.len] = 0;
+        gop.key_ptr.* = old_size;
+        return old_size;
+    }
+};
+
+pub const GotIndex = enum(u32) {
+    none = std.math.maxInt(u32),
+    _,
+
+    pub fn wrap(i: ?u32) GotIndex {
+        const gi: GotIndex = @enumFromInt(i orelse return .none);
+        assert(gi != .none);
+        return gi;
+    }
+    pub fn unwrap(gi: GotIndex) ?u32 {
+        return switch (gi) {
+            _ => @intFromEnum(gi),
+            .none => null,
+        };
+    }
+};
+
 pub const Reloc = extern struct {
     type: Reloc.Type,
     prev: Reloc.Index,
@@ -500,7 +676,6 @@ pub const Reloc = extern struct {
                 .X86_64 => .{ .X86_64 = .NONE },
             };
         }
-
         pub fn absAddr(elf: *Elf) Reloc.Type {
             return switch (elf.ehdrField(.machine)) {
                 else => unreachable,
@@ -510,7 +685,6 @@ pub const Reloc = extern struct {
                 .X86_64 => .{ .X86_64 = .@"64" },
             };
         }
-
         pub fn sizeAddr(elf: *Elf) Reloc.Type {
             return switch (elf.ehdrField(.machine)) {
                 else => unreachable,
@@ -528,7 +702,6 @@ pub const Reloc = extern struct {
                 => |machine| @unionInit(Reloc.Type, @tagName(machine), @enumFromInt(int)),
             };
         }
-
         pub fn unwrap(rt: Reloc.Type, elf: *Elf) u32 {
             return switch (elf.ehdrField(.machine)) {
                 else => unreachable,
@@ -558,7 +731,7 @@ pub const Reloc = extern struct {
             else => |ni| if (ni.hasMoved(&elf.mf)) return,
         }
         switch (reloc.target.get(elf).ni) {
-            .none => return,
+            .none => {},
             else => |ni| if (ni.hasMoved(&elf.mf)) return,
         }
         const loc_slice = loc_ni.slice(&elf.mf)[@intCast(reloc.offset)..];
@@ -568,7 +741,6 @@ pub const Reloc = extern struct {
                 const loc_sym = &symtab[@intFromEnum(reloc.loc)];
                 const loc_shndx = elf.targetLoad(&loc_sym.shndx);
                 assert(loc_shndx != std.elf.SHN_UNDEF);
-                const loc_value = elf.targetLoad(&loc_sym.value) + reloc.offset;
                 const target_sym = &symtab[@intFromEnum(reloc.target)];
                 const target_value =
                     elf.targetLoad(&target_sym.value) +% @as(u64, @bitCast(reloc.addend));
@@ -582,10 +754,28 @@ pub const Reloc = extern struct {
                             target_value,
                             target_endian,
                         ),
-                        .PC32, .PLT32 => std.mem.writeInt(
+                        .PC32 => std.mem.writeInt(
+                            i32,
+                            loc_slice[0..4],
+                            @intCast(@as(i64, @bitCast(target_value -%
+                                (elf.targetLoad(&loc_sym.value) + reloc.offset)))),
+                            target_endian,
+                        ),
+                        .PLT32 => std.mem.writeInt(
                             i32,
                             loc_slice[0..4],
-                            @intCast(@as(i64, @bitCast(target_value -% loc_value))),
+                            @intCast(@as(i64, @bitCast(
+                                if (elf.got.plt.getIndex(reloc.target)) |plt_index|
+                                    elf.targetLoad(&@field(
+                                        elf.shdrPtr(elf.si.plt_sec.shndx(elf)),
+                                        @tagName(class),
+                                    ).addr) +% 16 * plt_index +%
+                                        @as(u64, @bitCast(reloc.addend)) -%
+                                        (elf.targetLoad(&loc_sym.value) + reloc.offset)
+                                else
+                                    target_value -%
+                                        (elf.targetLoad(&loc_sym.value) + reloc.offset),
+                            ))),
                             target_endian,
                         ),
                         .@"32" => std.mem.writeInt(
@@ -600,6 +790,23 @@ pub const Reloc = extern struct {
                             @intCast(@as(i64, @bitCast(target_value))),
                             target_endian,
                         ),
+                        .TLSLD => std.mem.writeInt(
+                            i32,
+                            loc_slice[0..4],
+                            @intCast(@as(i64, @bitCast(
+                                elf.targetLoad(&symtab[@intFromEnum(elf.si.got)].value) +%
+                                    @as(u64, @bitCast(reloc.addend)) +%
+                                    @as(u64, 8) * elf.got.tlsld.unwrap().? -%
+                                    (elf.targetLoad(&loc_sym.value) + reloc.offset),
+                            ))),
+                            target_endian,
+                        ),
+                        .DTPOFF32 => std.mem.writeInt(
+                            i32,
+                            loc_slice[0..4],
+                            @intCast(@as(i64, @bitCast(target_value))),
+                            target_endian,
+                        ),
                         .TPOFF32 => {
                             const phdr = @field(elf.phdrSlice(), @tagName(class));
                             const ph = &phdr[elf.getNode(elf.ni.tls).segment];
@@ -614,13 +821,15 @@ pub const Reloc = extern struct {
                         .SIZE32 => std.mem.writeInt(
                             u32,
                             loc_slice[0..4],
-                            @intCast(elf.targetLoad(&target_sym.size)),
+                            @intCast(
+                                elf.targetLoad(&target_sym.size) +% @as(u64, @bitCast(reloc.addend)),
+                            ),
                             target_endian,
                         ),
                         .SIZE64 => std.mem.writeInt(
                             u64,
                             loc_slice[0..8],
-                            @intCast(elf.targetLoad(&target_sym.size)),
+                            elf.targetLoad(&target_sym.size) +% @as(u64, @bitCast(reloc.addend)),
                             target_endian,
                         ),
                     },
@@ -711,7 +920,7 @@ fn create(
         .big => .@"2MSB",
     };
     const osabi: std.elf.OSABI = switch (target.os.tag) {
-        else => .NONE,
+        else => if (target.abi.isGnu()) .GNU else .NONE,
         .freestanding, .other => .STANDALONE,
         .netbsd => .NETBSD,
         .illumos => .SOLARIS,
@@ -776,6 +985,7 @@ fn create(
             .dynstr = .null,
             .dynamic = .null,
             .tdata = .null,
+            .entry = .null,
         },
         .symtab = .empty,
         .shstrtab = .{
@@ -788,6 +998,11 @@ fn create(
         .dynstr = .{
             .map = .empty,
         },
+        .got = .{
+            .len = 0,
+            .tlsld = .none,
+            .plt = .empty,
+        },
         .needed = .empty,
         .inputs = .empty,
         .input_sections = .empty,
@@ -801,7 +1016,6 @@ fn create(
         }),
         .pending_uavs = .empty,
         .relocs = .empty,
-        .entry_hack = .null,
         .const_prog_node = .none,
         .synth_prog_node = .none,
         .input_prog_node = .none,
@@ -823,6 +1037,7 @@ pub fn deinit(elf: *Elf) void {
     elf.strtab.map.deinit(gpa);
     elf.dynsym.deinit(gpa);
     elf.dynstr.map.deinit(gpa);
+    elf.got.plt.deinit(gpa);
     elf.needed.deinit(gpa);
     for (elf.inputs.items) |input| if (input.member) |m| gpa.free(m);
     elf.inputs.deinit(gpa);
@@ -873,14 +1088,16 @@ fn initHeaders(
     phnum += 1;
     const data_phndx = phnum;
     phnum += 1;
-    const dynamic_phndx = if (have_dynamic_section) phndx: {
+    const tls_phndx = if (comp.config.any_non_single_threaded) phndx: {
         defer phnum += 1;
         break :phndx phnum;
     } else undefined;
-    const tls_phndx = if (comp.config.any_non_single_threaded) phndx: {
+    const dynamic_phndx = if (have_dynamic_section) phndx: {
         defer phnum += 1;
         break :phndx phnum;
     } else undefined;
+    const relro_phndx = phnum;
+    phnum += 1;
 
     const expected_nodes_len = expected_nodes_len: switch (@"type") {
         .NONE, .CORE, _ => unreachable,
@@ -888,12 +1105,14 @@ fn initHeaders(
             defer phnum = 0;
             break :expected_nodes_len 5 + phnum;
         },
-        .EXEC, .DYN => break :expected_nodes_len 5 + phnum * 2 +
-            @as(usize, 2) * @intFromBool(have_dynamic_section),
+        .EXEC, .DYN => break :expected_nodes_len 9 + phnum * 2 +
+            @as(usize, 4) * @intFromBool(have_dynamic_section),
     };
     try elf.nodes.ensureTotalCapacity(gpa, expected_nodes_len);
     try elf.shdrs.ensureTotalCapacity(gpa, shnum);
     try elf.phdrs.resize(gpa, phnum);
+    try elf.symtab.ensureTotalCapacity(gpa, 1);
+    if (have_dynamic_section) try elf.dynsym.ensureTotalCapacity(gpa, 1);
     elf.nodes.appendAssumeCapacity(.file);
 
     switch (class) {
@@ -976,10 +1195,18 @@ fn initHeaders(
         elf.nodes.appendAssumeCapacity(.{ .segment = data_phndx });
         elf.phdrs.items[data_phndx] = elf.ni.data;
 
+        assert(elf.ni.data_rel_ro == try elf.mf.addOnlyChildNode(gpa, elf.ni.data, .{
+            .alignment = elf.mf.flags.block_size,
+            .moved = true,
+            .bubbles_moved = false,
+        }));
+        elf.nodes.appendAssumeCapacity(.{ .segment = relro_phndx });
+        elf.phdrs.items[relro_phndx] = elf.ni.data_rel_ro;
+
         break :ph_vaddr switch (elf.ehdrField(.type)) {
             .NONE, .CORE, _ => unreachable,
             .REL, .DYN => 0,
-            .EXEC => switch (elf.ehdrField(.machine)) {
+            .EXEC => switch (machine) {
                 .@"386" => 0x400000,
                 .AARCH64, .X86_64 => 0x200000,
                 .PPC, .PPC64 => 0x10000000,
@@ -1070,35 +1297,48 @@ fn initHeaders(
                 if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_data);
                 ph_vaddr += @intCast(data_size);
 
-                if (have_dynamic_section) {
-                    const ph_dynamic = &phdr[dynamic_phndx];
-                    ph_dynamic.* = .{
-                        .type = .DYNAMIC,
+                if (comp.config.any_non_single_threaded) {
+                    const ph_tls = &phdr[tls_phndx];
+                    ph_tls.* = .{
+                        .type = .TLS,
                         .offset = 0,
                         .vaddr = 0,
                         .paddr = 0,
                         .filesz = 0,
                         .memsz = 0,
-                        .flags = .{ .R = true, .W = true },
-                        .@"align" = @intCast(addr_align.toByteUnits()),
+                        .flags = .{ .R = true },
+                        .@"align" = @intCast(elf.mf.flags.block_size.toByteUnits()),
                     };
-                    if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_dynamic);
+                    if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_tls);
                 }
 
-                if (comp.config.any_non_single_threaded) {
-                    const ph_tls = &phdr[tls_phndx];
-                    ph_tls.* = .{
-                        .type = .TLS,
+                if (have_dynamic_section) {
+                    const ph_dynamic = &phdr[dynamic_phndx];
+                    ph_dynamic.* = .{
+                        .type = .DYNAMIC,
                         .offset = 0,
                         .vaddr = 0,
                         .paddr = 0,
                         .filesz = 0,
                         .memsz = 0,
-                        .flags = .{ .R = true },
-                        .@"align" = @intCast(elf.mf.flags.block_size.toByteUnits()),
+                        .flags = .{ .R = true, .W = true },
+                        .@"align" = @intCast(addr_align.toByteUnits()),
                     };
-                    if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_tls);
+                    if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_dynamic);
                 }
+
+                const ph_relro = &phdr[relro_phndx];
+                ph_relro.* = .{
+                    .type = .GNU_RELRO,
+                    .offset = 0,
+                    .vaddr = 0,
+                    .paddr = 0,
+                    .filesz = 0,
+                    .memsz = 0,
+                    .flags = .{ .R = true },
+                    .@"align" = @intCast(elf.mf.flags.block_size.toByteUnits()),
+                };
+                if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Phdr, ph_relro);
             }
 
             const sh_undef: *ElfN.Shdr = @ptrCast(@alignCast(elf.ni.shdr.slice(&elf.mf)));
@@ -1117,7 +1357,6 @@ fn initHeaders(
             if (target_endian != native_endian) std.mem.byteSwapAllFields(ElfN.Shdr, sh_undef);
             elf.shdrs.appendAssumeCapacity(.{ .si = .null, .rela_si = .null, .rela_free = .none });
 
-            try elf.symtab.ensureTotalCapacity(gpa, 1);
             elf.symtab.addOneAssumeCapacity().* = .{
                 .ni = .none,
                 .loc_relocs = .none,
@@ -1163,7 +1402,9 @@ fn initHeaders(
         .entsize = 1,
         .node_align = elf.mf.flags.block_size,
     }));
-    try elf.linkSections(.symtab, .strtab);
+    switch (elf.shdrPtr(elf.si.symtab.shndx(elf))) {
+        inline else => |shdr| elf.targetStore(&shdr.link, @intFromEnum(elf.si.strtab.shndx(elf))),
+    }
     elf.si.strtab.node(elf).slice(&elf.mf)[0] = 0;
 
     assert(elf.si.rodata == try elf.addSection(elf.ni.rodata, .{
@@ -1181,7 +1422,50 @@ fn initHeaders(
         .flags = .{ .WRITE = true, .ALLOC = true },
         .addralign = elf.mf.flags.block_size,
     }));
+    assert(elf.si.data_rel_ro == try elf.addSection(elf.ni.data_rel_ro, .{
+        .name = ".data.rel.ro",
+        .flags = .{ .WRITE = true, .ALLOC = true },
+        .addralign = elf.mf.flags.block_size,
+    }));
     if (@"type" != .REL) {
+        assert(elf.si.got == try elf.addSection(elf.ni.data_rel_ro, .{
+            .name = ".got",
+            .flags = .{ .WRITE = true, .ALLOC = true },
+            .addralign = addr_align,
+        }));
+        assert(elf.si.got_plt == try elf.addSection(
+            if (elf.options.z_now) elf.ni.data_rel_ro else elf.ni.data,
+            .{
+                .name = ".got.plt",
+                .type = .PROGBITS,
+                .flags = .{ .WRITE = true, .ALLOC = true },
+                .size = switch (machine) {
+                    else => @panic(@tagName(machine)),
+                    .@"386" => 3 * 4,
+                    .X86_64 => 3 * 8,
+                },
+                .addralign = addr_align,
+            },
+        ));
+        const plt_size: std.elf.Xword, const plt_align: std.mem.Alignment, const plt_sec =
+            switch (machine) {
+                else => @panic(@tagName(machine)),
+                .X86_64 => .{ 16, .@"16", true },
+            };
+        assert(elf.si.plt == try elf.addSection(elf.ni.text, .{
+            .name = ".plt",
+            .type = .PROGBITS,
+            .flags = .{ .ALLOC = true, .EXECINSTR = true },
+            .size = plt_size,
+            .addralign = plt_align,
+            .node_align = elf.mf.flags.block_size,
+        }));
+        if (plt_sec) assert(elf.si.plt_sec == try elf.addSection(elf.ni.text, .{
+            .name = ".plt.sec",
+            .flags = .{ .ALLOC = true, .EXECINSTR = true },
+            .addralign = plt_align,
+            .node_align = elf.mf.flags.block_size,
+        }));
         if (maybe_interp) |interp| {
             const interp_ni = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{
                 .size = interp.len + 1,
@@ -1193,8 +1477,8 @@ fn initHeaders(
             elf.phdrs.items[interp_phndx] = interp_ni;
 
             const sec_interp_si = try elf.addSection(interp_ni, .{
-                .type = .PROGBITS,
                 .name = ".interp",
+                .type = .PROGBITS,
                 .flags = .{ .ALLOC = true },
                 .size = @intCast(interp.len + 1),
             });
@@ -1203,13 +1487,24 @@ fn initHeaders(
             sec_interp[interp.len] = 0;
         }
         if (have_dynamic_section) {
-            const dynamic_ni = try elf.mf.addLastChildNode(gpa, elf.ni.data, .{
+            const dynamic_ni = try elf.mf.addLastChildNode(gpa, elf.ni.data_rel_ro, .{
+                .alignment = addr_align,
                 .moved = true,
                 .bubbles_moved = false,
             });
             elf.nodes.appendAssumeCapacity(.{ .segment = dynamic_phndx });
             elf.phdrs.items[dynamic_phndx] = dynamic_ni;
 
+            elf.si.dynstr = try elf.addSection(elf.ni.rodata, .{
+                .name = ".dynstr",
+                .type = .STRTAB,
+                .flags = .{ .ALLOC = true },
+                .size = 1,
+                .entsize = 1,
+                .node_align = elf.mf.flags.block_size,
+            });
+            const dynstr_shndx = elf.si.dynstr.shndx(elf);
+            elf.dynsym.putAssumeCapacityNoClobber(.null, {});
             switch (class) {
                 .NONE, _ => unreachable,
                 inline else => |ct_class| {
@@ -1217,7 +1512,10 @@ fn initHeaders(
                     elf.si.dynsym = try elf.addSection(elf.ni.rodata, .{
                         .name = ".dynsym",
                         .type = .DYNSYM,
+                        .flags = .{ .ALLOC = true },
                         .size = @sizeOf(Sym) * 1,
+                        .link = @intFromEnum(dynstr_shndx),
+                        .info = 1,
                         .addralign = addr_align,
                         .entsize = @sizeOf(Sym),
                         .node_align = elf.mf.flags.block_size,
@@ -1237,21 +1535,66 @@ fn initHeaders(
                     );
                 },
             }
-            elf.si.dynstr = try elf.addSection(elf.ni.rodata, .{
-                .name = ".dynstr",
-                .type = .STRTAB,
-                .size = 1,
-                .entsize = 1,
+            const rela_size: std.elf.Word = switch (class) {
+                .NONE, _ => unreachable,
+                inline else => |ct_class| @sizeOf(ct_class.ElfN().Rela),
+            };
+            elf.si.got.shndx(elf).get(elf).rela_si = try elf.addSection(elf.ni.rodata, .{
+                .name = ".rela.dyn",
+                .type = .RELA,
+                .flags = .{ .ALLOC = true },
+                .link = @intFromEnum(elf.si.dynsym.shndx(elf)),
+                .addralign = addr_align,
+                .entsize = rela_size,
+                .node_align = elf.mf.flags.block_size,
+            });
+            const got_plt_shndx = elf.si.got_plt.shndx(elf);
+            got_plt_shndx.get(elf).rela_si = try elf.addSection(elf.ni.rodata, .{
+                .name = ".rela.plt",
+                .type = .RELA,
+                .flags = .{ .ALLOC = true, .INFO_LINK = true },
+                .link = @intFromEnum(elf.si.dynsym.shndx(elf)),
+                .info = @intFromEnum(got_plt_shndx),
+                .addralign = addr_align,
+                .entsize = rela_size,
                 .node_align = elf.mf.flags.block_size,
             });
             elf.si.dynamic = try elf.addSection(dynamic_ni, .{
                 .name = ".dynamic",
                 .type = .DYNAMIC,
                 .flags = .{ .ALLOC = true, .WRITE = true },
+                .link = @intFromEnum(dynstr_shndx),
+                .entsize = @intCast(addr_align.toByteUnits() * 2),
                 .node_align = addr_align,
             });
-            try elf.linkSections(elf.si.dynamic, elf.si.dynstr);
-            try elf.linkSections(elf.si.dynsym, elf.si.dynstr);
+            switch (machine) {
+                else => @panic(@tagName(machine)),
+                .X86_64 => {
+                    @memcpy(elf.si.plt.node(elf).slice(&elf.mf)[0..16], &[16]u8{
+                        0xff, 0x35, 0x00, 0x00, 0x00, 0x00, // push 0x0(%rip)
+                        0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp *0x0(%rip)
+                        0x0f, 0x1f, 0x40, 0x00, // nopl 0x0(%rax)
+                    });
+                    const plt_sym = elf.si.plt.get(elf);
+                    assert(plt_sym.loc_relocs == .none);
+                    plt_sym.loc_relocs = @enumFromInt(elf.relocs.items.len);
+                    try elf.ensureUnusedRelocCapacity(elf.si.plt, 2);
+                    elf.addRelocAssumeCapacity(
+                        elf.si.plt,
+                        2,
+                        elf.si.got_plt,
+                        8 * 1 - 4,
+                        .{ .X86_64 = .PC32 },
+                    );
+                    elf.addRelocAssumeCapacity(
+                        elf.si.plt,
+                        8,
+                        elf.si.got_plt,
+                        8 * 2 - 4,
+                        .{ .X86_64 = .PC32 },
+                    );
+                },
+            }
         }
         if (comp.config.any_non_single_threaded) {
             elf.ni.tls = try elf.mf.addLastChildNode(gpa, elf.ni.rodata, .{
@@ -1305,7 +1648,8 @@ fn getNode(elf: *const Elf, ni: MappedFile.Node.Index) Node {
 }
 fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 {
     const parent_vaddr = parent_vaddr: {
-        const parent_si = switch (elf.getNode(ni.parent(&elf.mf))) {
+        const parent_ni = ni.parent(&elf.mf);
+        const parent_si = switch (elf.getNode(parent_ni)) {
             .file => return 0,
             .ehdr, .shdr => unreachable,
             .segment => |phndx| break :parent_vaddr switch (elf.phdrSlice()) {
@@ -1630,7 +1974,7 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index {
 }
 
 pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError ||
-    std.Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, LinkFailure })!void {
+    std.Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void {
     const io = elf.base.comp.io;
     var buf: [4096]u8 = undefined;
     switch (input) {
@@ -1670,8 +2014,7 @@ fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !
     const r = &fr.interface;
 
     log.debug("loadArchive({f})", .{path.fmtEscapeString()});
-    if (!std.mem.eql(u8, try r.take(std.elf.ARMAG.len), std.elf.ARMAG))
-        return diags.failParse(path, "bad magic", .{});
+    if (!std.mem.eql(u8, try r.take(std.elf.ARMAG.len), std.elf.ARMAG)) return error.BadMagic;
     var strtab: std.Io.Writer.Allocating = .init(gpa);
     defer strtab.deinit();
     while (r.takeStruct(std.elf.ar_hdr, native_endian)) |header| {
@@ -1734,8 +2077,9 @@ fn loadObject(
 
     const ii: Node.InputIndex = @enumFromInt(elf.inputs.items.len);
     log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) });
-    const ident = try r.peek(std.elf.EI.NIDENT);
-    if (!std.mem.eql(u8, ident, elf.mf.contents[0..std.elf.EI.NIDENT]))
+    const ident = try r.peek(std.elf.EI.OSABI);
+    if (!std.mem.eql(u8, ident[0..std.elf.MAGIC.len], std.elf.MAGIC)) return error.BadMagic;
+    if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.contents[std.elf.MAGIC.len..ident.len]))
         return diags.failParse(path, "bad ident", .{});
     try elf.symtab.ensureUnusedCapacity(gpa, 1);
     try elf.inputs.ensureUnusedCapacity(gpa, 1);
@@ -1973,7 +2317,8 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void
 
     log.debug("loadDso({f})", .{path.fmtEscapeString()});
     const ident = try r.peek(std.elf.EI.NIDENT);
-    if (!std.mem.eql(u8, ident, elf.mf.contents[0..std.elf.EI.NIDENT]))
+    if (!std.mem.eql(u8, ident[0..std.elf.MAGIC.len], std.elf.MAGIC)) return error.BadMagic;
+    if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.contents[std.elf.MAGIC.len..ident.len]))
         return diags.failParse(path, "bad ident", .{});
     const target_endian = elf.targetEndian();
     switch (elf.identClass()) {
@@ -2053,7 +2398,8 @@ pub fn prelink(elf: *Elf, prog_node: std.Progress.Node) !void {
     };
 }
 fn prelinkInner(elf: *Elf) !void {
-    const gpa = elf.base.comp.gpa;
+    const comp = elf.base.comp;
+    const gpa = comp.gpa;
     try elf.symtab.ensureUnusedCapacity(gpa, 1);
     try elf.inputs.ensureUnusedCapacity(gpa, 1);
     const zcu_name = try std.fmt.allocPrint(gpa, "{s}_zcu", .{
@@ -2071,11 +2417,18 @@ fn prelinkInner(elf: *Elf) !void {
         .NONE, _ => unreachable,
         inline else => |ct_class| {
             const ElfN = ct_class.ElfN();
+            const flags: ElfN.Addr = if (elf.options.z_now) std.elf.DF_BIND_NOW else 0;
+            const flags_1: ElfN.Addr = if (elf.options.z_now) std.elf.DF_1_NOW else 0;
             const needed_len = elf.needed.count();
-            const dynamic_len = needed_len + @intFromBool(elf.options.soname != null) + 5;
+            const dynamic_len = needed_len + @intFromBool(elf.options.soname != null) +
+                @intFromBool(flags != 0) + @intFromBool(flags_1 != 0) +
+                @intFromBool(comp.config.output_mode == .Exe) + 12;
             const dynamic_size: u32 = @intCast(@sizeOf(ElfN.Addr) * 2 * dynamic_len);
             const dynamic_ni = elf.si.dynamic.node(elf);
             try dynamic_ni.resize(&elf.mf, gpa, dynamic_size);
+            switch (elf.shdrPtr(elf.si.dynamic.shndx(elf))) {
+                inline else => |shdr| elf.targetStore(&shdr.size, dynamic_size),
+            }
             const sec_dynamic = dynamic_ni.slice(&elf.mf);
             const dynamic_entries: [][2]ElfN.Addr = @ptrCast(@alignCast(sec_dynamic));
             var dynamic_index: usize = 0;
@@ -2088,14 +2441,41 @@ fn prelinkInner(elf: *Elf) !void {
                 dynamic_entries[dynamic_index] = .{ std.elf.DT_SONAME, try elf.string(.dynstr, soname) };
                 dynamic_index += 1;
             }
-            dynamic_entries[dynamic_index..][0..5].* = .{
-                .{ std.elf.DT_SYMTAB, 0 },
+            if (flags != 0) {
+                dynamic_entries[dynamic_index] = .{ std.elf.DT_FLAGS, flags };
+                dynamic_index += 1;
+            }
+            if (flags_1 != 0) {
+                dynamic_entries[dynamic_index] = .{ std.elf.DT_FLAGS_1, flags_1 };
+                dynamic_index += 1;
+            }
+            if (comp.config.output_mode == .Exe) {
+                dynamic_entries[dynamic_index] = .{ std.elf.DT_DEBUG, 0 };
+                dynamic_index += 1;
+            }
+            const rela_dyn_si = elf.si.got.shndx(elf).get(elf).rela_si;
+            const rela_plt_si = elf.si.got_plt.shndx(elf).get(elf).rela_si;
+            dynamic_entries[dynamic_index..][0..12].* = .{
+                .{ std.elf.DT_RELA, @intCast(elf.computeNodeVAddr(rela_dyn_si.node(elf))) },
+                .{ std.elf.DT_RELASZ, elf.targetLoad(
+                    &@field(elf.shdrPtr(rela_dyn_si.shndx(elf)), @tagName(ct_class)).size,
+                ) },
+                .{ std.elf.DT_RELAENT, @sizeOf(ElfN.Rela) },
+                .{ std.elf.DT_JMPREL, @intCast(elf.computeNodeVAddr(rela_plt_si.node(elf))) },
+                .{ std.elf.DT_PLTRELSZ, elf.targetLoad(
+                    &@field(elf.shdrPtr(rela_plt_si.shndx(elf)), @tagName(ct_class)).size,
+                ) },
+                .{ std.elf.DT_PLTGOT, @intCast(elf.computeNodeVAddr(elf.si.got_plt.node(elf))) },
+                .{ std.elf.DT_PLTREL, std.elf.DT_RELA },
+                .{ std.elf.DT_SYMTAB, @intCast(elf.computeNodeVAddr(elf.si.dynsym.node(elf))) },
                 .{ std.elf.DT_SYMENT, @sizeOf(ElfN.Sym) },
-                .{ std.elf.DT_STRTAB, 0 },
-                .{ std.elf.DT_STRSZ, 0 },
+                .{ std.elf.DT_STRTAB, @intCast(elf.computeNodeVAddr(elf.si.dynstr.node(elf))) },
+                .{ std.elf.DT_STRSZ, elf.targetLoad(
+                    &@field(elf.shdrPtr(elf.si.dynstr.shndx(elf)), @tagName(ct_class)).size,
+                ) },
                 .{ std.elf.DT_NULL, 0 },
             };
-            dynamic_index += 5;
+            dynamic_index += 12;
             assert(dynamic_index == dynamic_len);
             if (elf.targetEndian() != native_endian) for (dynamic_entries) |*dynamic_entry|
                 std.mem.byteSwapAllFields(@TypeOf(dynamic_entry.*), dynamic_entry);
@@ -2103,26 +2483,41 @@ fn prelinkInner(elf: *Elf) !void {
             const dynamic_sym = elf.si.dynamic.get(elf);
             assert(dynamic_sym.loc_relocs == .none);
             dynamic_sym.loc_relocs = @enumFromInt(elf.relocs.items.len);
-            try elf.addReloc(
+            try elf.ensureUnusedRelocCapacity(elf.si.dynamic, 5);
+            elf.addRelocAssumeCapacity(
                 elf.si.dynamic,
-                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 5) + 1),
-                elf.si.dynsym,
+                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 12) + 1),
+                rela_dyn_si,
                 0,
                 .absAddr(elf),
             );
-            try elf.addReloc(
+            elf.addRelocAssumeCapacity(
                 elf.si.dynamic,
-                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 3) + 1),
-                elf.si.dynstr,
+                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 9) + 1),
+                rela_plt_si,
                 0,
                 .absAddr(elf),
             );
-            try elf.addReloc(
+            elf.addRelocAssumeCapacity(
                 elf.si.dynamic,
-                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 2) + 1),
+                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 7) + 1),
+                elf.si.got_plt,
+                0,
+                .absAddr(elf),
+            );
+            elf.addRelocAssumeCapacity(
+                elf.si.dynamic,
+                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 5) + 1),
+                elf.si.dynsym,
+                0,
+                .absAddr(elf),
+            );
+            elf.addRelocAssumeCapacity(
+                elf.si.dynamic,
+                @sizeOf(ElfN.Addr) * (2 * (dynamic_len - 3) + 1),
                 elf.si.dynstr,
                 0,
-                .sizeAddr(elf),
+                .absAddr(elf),
             );
         },
     };
@@ -2168,6 +2563,7 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct {
     addralign: std.mem.Alignment = .@"1",
     entsize: std.elf.Word = 0,
     node_align: std.mem.Alignment = .@"1",
+    fixed: bool = false,
 }) !Symbol.Index {
     switch (opts.type) {
         .NULL => assert(opts.size == 0),
@@ -2215,8 +2611,9 @@ fn addSection(elf: *Elf, segment_ni: MappedFile.Node.Index, opts: struct {
         .REL => elf.ni.file,
         .EXEC, .DYN => segment_ni,
     }, .{
-        .alignment = opts.addralign.max(opts.node_align),
         .size = opts.size,
+        .alignment = opts.addralign.max(opts.node_align),
+        .fixed = opts.fixed,
         .resized = opts.size > 0,
     });
     const si = elf.addSymbolAssumeCapacity();
@@ -2253,12 +2650,6 @@ fn renameSection(elf: *Elf, si: Symbol.Index, name: []const u8) !void {
     }
 }
 
-fn linkSections(elf: *Elf, si: Symbol.Index, link_si: Symbol.Index) !void {
-    switch (elf.shdrPtr(si.shndx(elf))) {
-        inline else => |shdr| elf.targetStore(&shdr.link, @intFromEnum(link_si.shndx(elf))),
-    }
-}
-
 fn sectionName(elf: *Elf, si: Symbol.Index) [:0]const u8 {
     const name = elf.si.shstrtab.node(elf).slice(&elf.mf)[switch (elf.shdrPtr(si.shndx(elf))) {
         inline else => |shdr| elf.targetLoad(&shdr.name),
@@ -2285,41 +2676,60 @@ pub fn addReloc(
 pub fn ensureUnusedRelocCapacity(elf: *Elf, loc_si: Symbol.Index, len: usize) !void {
     if (len == 0) return;
     const gpa = elf.base.comp.gpa;
-
     try elf.relocs.ensureUnusedCapacity(gpa, len);
-    if (elf.ehdrField(.type) != .REL) return;
-
-    const shndx = loc_si.shndx(elf);
-    const sh = shndx.get(elf);
-    if (sh.rela_si == .null) {
-        var stack = std.heap.stackFallback(32, gpa);
-        const allocator = stack.get();
-
-        const rela_name = try std.fmt.allocPrint(allocator, ".rela{s}", .{elf.sectionName(sh.si)});
-        defer allocator.free(rela_name);
-
-        const class = elf.identClass();
-        sh.rela_si = try elf.addSection(.none, .{
-            .name = rela_name,
-            .type = .RELA,
-            .link = @intFromEnum(elf.si.symtab.shndx(elf)),
-            .info = @intFromEnum(shndx),
-            .addralign = switch (class) {
-                .NONE, _ => unreachable,
-                .@"32" => .@"4",
-                .@"64" => .@"8",
-            },
-            .entsize = switch (class) {
-                .NONE, _ => unreachable,
-                inline else => |ct_class| @sizeOf(ct_class.ElfN().Rela),
-            },
-            .node_align = elf.mf.flags.block_size,
-        });
-    }
-    const rela_ni = sh.rela_si.node(elf);
+    const class = elf.identClass();
+    const rela_si, const rela_len = rela: switch (elf.ehdrField(.type)) {
+        .NONE, .CORE, _ => unreachable,
+        .REL => {
+            const shndx = loc_si.shndx(elf);
+            const sh = shndx.get(elf);
+            if (sh.rela_si == .null) {
+                var stack = std.heap.stackFallback(32, gpa);
+                const allocator = stack.get();
+
+                const rela_name =
+                    try std.fmt.allocPrint(allocator, ".rela{s}", .{elf.sectionName(sh.si)});
+                defer allocator.free(rela_name);
+
+                sh.rela_si = try elf.addSection(.none, .{
+                    .name = rela_name,
+                    .type = .RELA,
+                    .link = @intFromEnum(elf.si.symtab.shndx(elf)),
+                    .info = @intFromEnum(shndx),
+                    .addralign = switch (class) {
+                        .NONE, _ => unreachable,
+                        .@"32" => .@"4",
+                        .@"64" => .@"8",
+                    },
+                    .entsize = switch (class) {
+                        .NONE, _ => unreachable,
+                        inline else => |ct_class| @sizeOf(ct_class.ElfN().Rela),
+                    },
+                    .node_align = elf.mf.flags.block_size,
+                });
+            }
+            break :rela .{ sh.rela_si, len };
+        },
+        .EXEC, .DYN => switch (elf.got.tlsld) {
+            _ => return,
+            .none => if (elf.si.dynamic != .null) {
+                try elf.mf.updates.ensureUnusedCapacity(gpa, 1);
+                const got_ni = elf.si.got.node(elf);
+                _, const got_node_size = got_ni.location(&elf.mf).resolve(&elf.mf);
+                const got_size = switch (class) {
+                    .NONE, _ => unreachable,
+                    inline else => |ct_class| (elf.got.len + 2) * @sizeOf(ct_class.ElfN().Addr),
+                };
+                if (got_size > got_node_size)
+                    try got_ni.resize(&elf.mf, gpa, got_size +| got_size / MappedFile.growth_factor);
+                break :rela .{ elf.si.got.shndx(elf).get(elf).rela_si, 1 };
+            } else return,
+        },
+    };
+    const rela_ni = rela_si.node(elf);
     _, const rela_node_size = rela_ni.location(&elf.mf).resolve(&elf.mf);
-    const rela_size = switch (elf.shdrPtr(sh.rela_si.shndx(elf))) {
-        inline else => |shdr| elf.targetLoad(&shdr.size) + elf.targetLoad(&shdr.entsize) * len,
+    const rela_size = switch (elf.shdrPtr(rela_si.shndx(elf))) {
+        inline else => |shdr| elf.targetLoad(&shdr.size) + elf.targetLoad(&shdr.entsize) * rela_len,
     };
     if (rela_size > rela_node_size)
         try rela_ni.resize(&elf.mf, gpa, rela_size +| rela_size / MappedFile.growth_factor);
@@ -2377,7 +2787,59 @@ pub fn addRelocAssumeCapacity(
                     },
                 }
             },
-            .EXEC, .DYN => .none,
+            .EXEC, .DYN => {
+                switch (elf.ehdrField(.machine)) {
+                    else => |machine| @panic(@tagName(machine)),
+                    .AARCH64, .PPC64, .RISCV => {},
+                    .X86_64 => switch (@"type".X86_64) {
+                        else => {},
+                        .TLSLD => switch (elf.got.tlsld) {
+                            _ => {},
+                            .none => if (elf.si.dynamic != .null) {
+                                const tlsld_index = elf.got.len;
+                                elf.got.tlsld = .wrap(tlsld_index);
+                                elf.got.len = tlsld_index + 2;
+                                const got_addr = got_addr: switch (elf.shdrPtr(elf.si.got.shndx(elf))) {
+                                    inline else => |shdr, class| {
+                                        const addr_size = @sizeOf(class.ElfN().Addr);
+                                        const old_size = addr_size * tlsld_index;
+                                        const new_size = old_size + addr_size * 2;
+                                        @memset(
+                                            elf.si.got.node(elf).slice(&elf.mf)[old_size..new_size],
+                                            0,
+                                        );
+                                        break :got_addr elf.targetLoad(&shdr.addr) + old_size;
+                                    },
+                                };
+                                const rela_dyn_si = elf.si.got.shndx(elf).get(elf).rela_si;
+                                const rela_dyn_ni = rela_dyn_si.node(elf);
+                                switch (elf.shdrPtr(rela_dyn_si.shndx(elf))) {
+                                    inline else => |shdr, class| {
+                                        const Rela = class.ElfN().Rela;
+                                        const old_size = elf.targetLoad(&shdr.size);
+                                        const new_size = old_size + elf.targetLoad(&shdr.entsize);
+                                        elf.targetStore(&shdr.size, new_size);
+                                        const rela: *Rela = @ptrCast(@alignCast(rela_dyn_ni
+                                            .slice(&elf.mf)[@intCast(old_size)..@intCast(new_size)]));
+                                        rela.* = .{
+                                            .offset = @intCast(got_addr),
+                                            .info = .{
+                                                .type = @intFromEnum(std.elf.R_X86_64.DTPMOD64),
+                                                .sym = 0,
+                                            },
+                                            .addend = 0,
+                                        };
+                                        if (elf.targetEndian() != native_endian)
+                                            std.mem.byteSwapAllFields(Rela, rela);
+                                    },
+                                }
+                                rela_dyn_ni.resizedAssumeCapacity(&elf.mf);
+                            },
+                        },
+                    },
+                }
+                break :index .none;
+            },
         },
         .offset = offset,
         .addend = addend,
@@ -2901,7 +3363,7 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void {
                         .NULL, .LOAD => return,
                         .DYNAMIC, .INTERP => {},
                         .PHDR => @field(elf.ehdrPtr(), @tagName(class)).phoff = ph.offset,
-                        .TLS => {},
+                        .TLS, std.elf.PT.GNU_RELRO => {},
                     }
                     elf.targetStore(&ph.vaddr, @intCast(elf.computeNodeVAddr(ni)));
                     ph.paddr = ph.vaddr;
@@ -2911,10 +3373,101 @@ fn flushMoved(elf: *Elf, ni: MappedFile.Node.Index) !void {
         .section => |si| {
             try elf.flushFileOffset(ni);
             const addr = elf.computeNodeVAddr(ni);
-            switch (elf.shdrPtr(si.shndx(elf))) {
+            const shndx = si.shndx(elf);
+            switch (elf.shdrPtr(shndx)) {
                 inline else => |shdr, class| {
                     const flags = elf.targetLoad(&shdr.flags).shf;
                     if (flags.ALLOC) {
+                        if (elf.si.dynamic != .null) {
+                            if (si == elf.si.got) {
+                                const old_addr = elf.targetLoad(&shdr.addr);
+                                const rela_dyn_si = shndx.get(elf).rela_si;
+                                const relas: []class.ElfN().Rela = @ptrCast(@alignCast(
+                                    rela_dyn_si.node(elf).slice(&elf.mf)[0..@intCast(
+                                        elf.targetLoad(&@field(
+                                            elf.shdrPtr(rela_dyn_si.shndx(elf)),
+                                            @tagName(class),
+                                        ).size),
+                                    )],
+                                ));
+                                switch (elf.ehdrField(.machine)) {
+                                    else => |machine| @panic(@tagName(machine)),
+                                    .AARCH64, .PPC64, .RISCV => {},
+                                    .X86_64 => for (relas) |*rela| switch (@as(
+                                        std.elf.R_X86_64,
+                                        @enumFromInt(elf.targetLoad(&rela.info).type),
+                                    )) {
+                                        else => |@"type"| @panic(@tagName(@"type")),
+                                        .RELATIVE => {},
+                                        .GLOB_DAT, .DTPMOD64, .DTPOFF64 => elf.targetStore(
+                                            &rela.offset,
+                                            @intCast(elf.targetLoad(&rela.offset) - old_addr + addr),
+                                        ),
+                                    },
+                                }
+                            } else if (si == elf.si.got_plt) {
+                                const target_endian = elf.targetEndian();
+                                const old_addr = elf.targetLoad(&shdr.addr);
+                                const rela_plt_si = shndx.get(elf).rela_si;
+                                const relas: []class.ElfN().Rela = @ptrCast(@alignCast(
+                                    rela_plt_si.node(elf).slice(&elf.mf)[0..@intCast(
+                                        elf.targetLoad(&@field(
+                                            elf.shdrPtr(rela_plt_si.shndx(elf)),
+                                            @tagName(class),
+                                        ).size),
+                                    )],
+                                ));
+                                const plt_sec_slice = elf.si.plt_sec.node(elf).slice(&elf.mf);
+                                switch (elf.ehdrField(.machine)) {
+                                    else => |machine| @panic(@tagName(machine)),
+                                    .AARCH64, .PPC64, .RISCV => {},
+                                    .X86_64 => {
+                                        for (relas) |*rela| switch (@as(
+                                            std.elf.R_X86_64,
+                                            @enumFromInt(elf.targetLoad(&rela.info).type),
+                                        )) {
+                                            else => |@"type"| @panic(@tagName(@"type")),
+                                            .JUMP_SLOT => elf.targetStore(
+                                                &rela.offset,
+                                                @intCast(elf.targetLoad(&rela.offset) - old_addr + addr),
+                                            ),
+                                        };
+                                        for (0..elf.got.plt.count()) |plt_index| {
+                                            const slice = plt_sec_slice[16 * plt_index + 6 ..][0..4];
+                                            std.mem.writeInt(
+                                                i32,
+                                                slice,
+                                                @intCast(@as(i64, @bitCast(@as(u64, @bitCast(@as(
+                                                    i64,
+                                                    std.mem.readInt(i32, slice, target_endian),
+                                                ))) -% old_addr +% addr))),
+                                                target_endian,
+                                            );
+                                        }
+                                    },
+                                }
+                            } else if (si == elf.si.plt_sec) {
+                                const target_endian = elf.targetEndian();
+                                const old_addr = elf.targetLoad(&shdr.addr);
+                                const plt_sec_slice = ni.slice(&elf.mf);
+                                switch (elf.ehdrField(.machine)) {
+                                    else => |machine| @panic(@tagName(machine)),
+                                    .AARCH64, .PPC64, .RISCV => {},
+                                    .X86_64 => for (0..elf.got.plt.count()) |plt_index| {
+                                        const slice = plt_sec_slice[16 * plt_index + 6 ..][0..4];
+                                        std.mem.writeInt(
+                                            i32,
+                                            slice,
+                                            @intCast(@as(i64, @bitCast(@as(u64, @bitCast(@as(
+                                                i64,
+                                                std.mem.readInt(i32, slice, target_endian),
+                                            ))) -% addr +% old_addr))),
+                                            target_endian,
+                                        );
+                                    },
+                                }
+                            }
+                        }
                         elf.targetStore(&shdr.addr, @intCast(addr));
                         @field(elf.symPtr(si), @tagName(class)).value = shdr.addr;
                     }
@@ -2960,15 +3513,21 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void {
                 const ph = &phdr[phndx];
                 elf.targetStore(&ph.filesz, @intCast(size));
                 if (size > elf.targetLoad(&ph.memsz)) {
-                    const memsz = ni.alignment(&elf.mf).forward(@intCast(size * 4));
-                    elf.targetStore(&ph.memsz, @intCast(memsz));
                     switch (elf.targetLoad(&ph.type)) {
                         else => unreachable,
                         .NULL => if (size > 0) elf.targetStore(&ph.type, .LOAD),
                         .LOAD => if (size == 0) elf.targetStore(&ph.type, .NULL),
-                        .DYNAMIC, .INTERP, .PHDR => return,
-                        .TLS => return ni.childrenMoved(elf.base.comp.gpa, &elf.mf),
+                        .DYNAMIC, .INTERP, .PHDR, std.elf.PT.GNU_RELRO => {
+                            elf.targetStore(&ph.memsz, @intCast(size));
+                            return;
+                        },
+                        .TLS => {
+                            elf.targetStore(&ph.memsz, @intCast(size));
+                            return ni.childrenMoved(elf.base.comp.gpa, &elf.mf);
+                        },
                     }
+                    const memsz = ni.alignment(&elf.mf).forward(@intCast(size * 4));
+                    elf.targetStore(&ph.memsz, @intCast(memsz));
                     var vaddr = elf.targetLoad(&ph.vaddr);
                     var new_phndx = phndx;
                     for (phdr[phndx + 1 ..], phndx + 1..) |*next_ph, next_phndx| {
@@ -2998,18 +3557,56 @@ fn flushResized(elf: *Elf, ni: MappedFile.Node.Index) !void {
             },
         },
         .section => |si| switch (elf.shdrPtr(si.shndx(elf))) {
-            inline else => |shdr| {
+            inline else => |shdr, class| {
                 switch (elf.targetLoad(&shdr.type)) {
                     else => unreachable,
                     .NULL => if (size > 0) elf.targetStore(&shdr.type, .PROGBITS),
                     .PROGBITS => if (size == 0) elf.targetStore(&shdr.type, .NULL),
-                    .SYMTAB, .STRTAB, .RELA, .DYNAMIC, .REL, .DYNSYM => return,
+                    .SYMTAB, .DYNAMIC, .REL, .DYNSYM => return,
+                    .STRTAB => {
+                        if (elf.si.dynamic != .null) {
+                            if (si == elf.si.dynstr) {
+                                const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast(
+                                    elf.si.dynamic.node(elf).slice(&elf.mf),
+                                ));
+                                for (dynamic_entries) |*dynamic_entry|
+                                    switch (elf.targetLoad(&dynamic_entry[0])) {
+                                        else => {},
+                                        std.elf.DT_STRSZ => dynamic_entry[1] = shdr.size,
+                                    };
+                            }
+                        }
+                        return;
+                    },
+                    .RELA => {
+                        if (elf.si.dynamic != .null) {
+                            if (si == elf.si.got.shndx(elf).get(elf).rela_si) {
+                                const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast(
+                                    elf.si.dynamic.node(elf).slice(&elf.mf),
+                                ));
+                                for (dynamic_entries) |*dynamic_entry|
+                                    switch (elf.targetLoad(&dynamic_entry[0])) {
+                                        else => {},
+                                        std.elf.DT_RELASZ => dynamic_entry[1] = shdr.size,
+                                    };
+                            } else if (si == elf.si.got_plt.shndx(elf).get(elf).rela_si) {
+                                const dynamic_entries: [][2]class.ElfN().Addr = @ptrCast(@alignCast(
+                                    elf.si.dynamic.node(elf).slice(&elf.mf),
+                                ));
+                                for (dynamic_entries) |*dynamic_entry|
+                                    switch (elf.targetLoad(&dynamic_entry[0])) {
+                                        else => {},
+                                        std.elf.DT_PLTRELSZ => dynamic_entry[1] = shdr.size,
+                                    };
+                            }
+                        }
+                        return;
+                    },
                 }
                 elf.targetStore(&shdr.size, @intCast(size));
             },
         },
-        .input_section => {},
-        .nav, .uav, .lazy_code, .lazy_const_data => {},
+        .input_section, .nav, .uav, .lazy_code, .lazy_const_data => {},
     }
 }
 
@@ -3069,7 +3666,11 @@ fn updateExportsInner(
     while (try elf.idle(pt.tid)) {}
     const exported_ni = exported_si.node(elf);
     const value, const size, const shndx = switch (elf.symPtr(exported_si)) {
-        inline else => |exported_sym| .{ exported_sym.value, exported_sym.size, exported_sym.shndx },
+        inline else => |exported_sym| .{
+            elf.targetLoad(&exported_sym.value),
+            exported_sym.size,
+            exported_sym.shndx,
+        },
     };
     for (export_indices) |export_index| {
         const @"export" = export_index.ptr(zcu);
@@ -3092,18 +3693,11 @@ fn updateExportsInner(
         export_si.get(elf).ni = exported_ni;
         switch (elf.symPtr(export_si)) {
             inline else => |export_sym| {
-                export_sym.value = @intCast(value);
                 export_sym.size = @intCast(size);
                 export_sym.shndx = shndx;
             },
         }
-        export_si.applyTargetRelocs(elf);
-        if (std.mem.eql(u8, name, "_start")) {
-            elf.entry_hack = exported_si;
-            switch (elf.ehdrPtr()) {
-                inline else => |ehdr| ehdr.entry = @intCast(value),
-            }
-        }
+        export_si.flushMoved(elf, value);
     }
 }
 
src/link/MappedFile.zig
@@ -213,7 +213,7 @@ pub const Node = extern struct {
             defer node_moved.* = false;
             return node_moved.*;
         }
-        fn movedAssumeCapacity(ni: Node.Index, mf: *MappedFile) void {
+        pub fn movedAssumeCapacity(ni: Node.Index, mf: *MappedFile) void {
             if (ni.hasMoved(mf)) return;
             const node = ni.get(mf);
             node.flags.moved = true;
@@ -234,7 +234,7 @@ pub const Node = extern struct {
             defer node_resized.* = false;
             return node_resized.*;
         }
-        fn resizedAssumeCapacity(ni: Node.Index, mf: *MappedFile) void {
+        pub fn resizedAssumeCapacity(ni: Node.Index, mf: *MappedFile) void {
             const node = ni.get(mf);
             if (node.flags.resized) return;
             node.flags.resized = true;
src/link.zig
@@ -1069,7 +1069,7 @@ pub const File = struct {
             errdefer archive.file.close();
             loadInput(base, .{ .archive = archive }) catch |err| switch (err) {
                 error.BadMagic, error.UnexpectedEndOfFile => {
-                    if (base.tag != .elf) return err;
+                    if (base.tag != .elf and base.tag != .elf2) return err;
                     try loadGnuLdScript(base, path, query, archive.file);
                     archive.file.close();
                     return;
@@ -1091,7 +1091,7 @@ pub const File = struct {
         errdefer dso.file.close();
         loadInput(base, .{ .dso = dso }) catch |err| switch (err) {
             error.BadMagic, error.UnexpectedEndOfFile => {
-                if (base.tag != .elf) return err;
+                if (base.tag != .elf and base.tag != .elf2) return err;
                 try loadGnuLdScript(base, path, query, dso.file);
                 dso.file.close();
                 return;
@@ -1101,8 +1101,9 @@ pub const File = struct {
     }
 
     fn loadGnuLdScript(base: *File, path: Path, parent_query: UnresolvedInput.Query, file: fs.File) anyerror!void {
-        const diags = &base.comp.link_diags;
-        const gpa = base.comp.gpa;
+        const comp = base.comp;
+        const diags = &comp.link_diags;
+        const gpa = comp.gpa;
         const stat = try file.stat();
         const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig;
         const buf = try gpa.alloc(u8, size);
@@ -1124,7 +1125,11 @@ pub const File = struct {
                 @panic("TODO");
             } else {
                 if (fs.path.isAbsolute(arg.path)) {
-                    const new_path = Path.initCwd(try gpa.dupe(u8, arg.path));
+                    const new_path = Path.initCwd(path: {
+                        comp.mutex.lock();
+                        defer comp.mutex.unlock();
+                        break :path try comp.arena.dupe(u8, arg.path);
+                    });
                     switch (Compilation.classifyFileExt(arg.path)) {
                         .shared_library => try openLoadDso(base, new_path, query),
                         .object => try openLoadObject(base, new_path),