Commit 44e84af874

Jakub Konka <kubkon@jakubkonka.com>
2023-09-12 16:32:55
elf: add simplistic reloc scanning mechanism
1 parent c654f3b
src/arch/x86_64/CodeGen.zig
@@ -8156,6 +8156,7 @@ 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);
+                sym.flags.needs_got = true;
                 _ = try sym.getOrCreateGotEntry(elf_file);
                 const got_addr = sym.gotAddress(elf_file);
                 try self.asmMemory(.{ ._, .call }, Memory.sib(.qword, .{
@@ -10234,6 +10235,7 @@ 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);
+        sym.flags.needs_got = true;
         _ = try sym.getOrCreateGotEntry(elf_file);
         const got_addr = sym.gotAddress(elf_file);
         const got_mem =
src/link/Elf/Atom.zig
@@ -311,6 +311,57 @@ pub fn freeRelocs(self: Atom, elf_file: *Elf) void {
     zig_module.relocs.items[self.relocs_section_index].clearRetainingCapacity();
 }
 
+pub fn scanRelocs(self: Atom, elf_file: *Elf) !void {
+    const file_ptr = elf_file.file(self.file_index).?;
+    const rels = self.relocs(elf_file);
+    var i: usize = 0;
+    while (i < rels.len) : (i += 1) {
+        const rel = rels[i];
+
+        if (rel.r_type() == elf.R_X86_64_NONE) continue;
+
+        const symbol = switch (file_ptr) {
+            .zig_module => elf_file.symbol(rel.r_sym()),
+            .object => |x| elf_file.symbol(x.symbols.items[rel.r_sym()]),
+            else => unreachable,
+        };
+
+        // While traversing relocations, mark symbols that require special handling such as
+        // pointer indirection via GOT, or a stub trampoline via PLT.
+        switch (rel.r_type()) {
+            elf.R_X86_64_64 => {},
+
+            elf.R_X86_64_32,
+            elf.R_X86_64_32S,
+            => {},
+
+            elf.R_X86_64_GOT32,
+            elf.R_X86_64_GOT64,
+            elf.R_X86_64_GOTPC32,
+            elf.R_X86_64_GOTPC64,
+            elf.R_X86_64_GOTPCREL,
+            elf.R_X86_64_GOTPCREL64,
+            elf.R_X86_64_GOTPCRELX,
+            elf.R_X86_64_REX_GOTPCRELX,
+            => {
+                symbol.flags.needs_got = true;
+            },
+
+            elf.R_X86_64_PLT32,
+            elf.R_X86_64_PLTOFF64,
+            => {
+                if (symbol.flags.import) {
+                    symbol.flags.needs_plt = true;
+                }
+            },
+
+            elf.R_X86_64_PC32 => {},
+
+            else => @panic("TODO"),
+        }
+    }
+}
+
 /// TODO mark relocs dirty
 pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
     relocs_log.debug("0x{x}: {s}", .{ self.value, self.name(elf_file) });
@@ -484,6 +535,9 @@ fn format2(
     }
 }
 
+// TODO this has to be u32 but for now, to avoid redesigning elfSym machinery for
+// ZigModule, keep it at u16 with the intention of bumping it to u32 in the near
+// future.
 pub const Index = u16;
 
 const std = @import("std");
src/link/Elf/Object.zig
@@ -367,15 +367,16 @@ pub fn scanRelocs(self: *Object, elf_file: *Elf) !void {
     }
 
     for (self.cies.items) |cie| {
-        for (cie.getRelocs(elf_file)) |rel| {
+        for (cie.relocs(elf_file)) |rel| {
             const sym = elf_file.symbol(self.symbols.items[rel.r_sym()]);
             if (sym.flags.import) {
-                if (sym.getType(elf_file) != elf.STT_FUNC)
-                    elf_file.base.fatal("{s}: {s}: CIE referencing external data reference", .{
+                if (sym.type(elf_file) != elf.STT_FUNC)
+                    // TODO convert into an error
+                    log.debug("{s}: {s}: CIE referencing external data reference", .{
                         self.fmtPath(),
-                        sym.getName(elf_file),
+                        sym.name(elf_file),
                     });
-                sym.flags.plt = true;
+                sym.flags.needs_plt = true;
             }
         }
     }
src/link/Elf/Symbol.zig
@@ -111,19 +111,23 @@ pub fn address(symbol: Symbol, opts: struct {
 }
 
 pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 {
-    if (!symbol.flags.got) return 0;
+    if (!symbol.flags.has_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;
+const GetOrCreateGotEntryResult = struct {
+    found_existing: bool,
+    index: GotSection.Index,
+};
+
+pub fn getOrCreateGotEntry(symbol: *Symbol, elf_file: *Elf) !GetOrCreateGotEntryResult {
+    assert(symbol.flags.needs_got);
+    if (symbol.flags.has_got) return .{ .found_existing = true, .index = symbol.extra(elf_file).?.got };
+    const index = try elf_file.got.addGotSymbol(symbol.index, elf_file);
+    symbol.flags.has_got = true;
+    return .{ .found_existing = false, .index = index };
 }
 
 // pub fn tlsGdAddress(symbol: Symbol, elf_file: *Elf) u64 {
@@ -310,9 +314,11 @@ pub const Flags = packed struct {
     output_symtab: bool = false,
 
     /// Whether the symbol contains GOT indirection.
-    got: bool = false,
+    needs_got: bool = false,
+    has_got: bool = false,
 
     /// Whether the symbol contains PLT indirection.
+    needs_plt: bool = false,
     plt: bool = false,
     /// Whether the PLT entry is canonical.
     is_canonical: bool = false,
src/link/Elf/ZigModule.zig
@@ -130,6 +130,14 @@ pub fn claimUnresolved(self: *ZigModule, elf_file: *Elf) void {
     }
 }
 
+pub fn scanRelocs(self: *ZigModule, elf_file: *Elf) !void {
+    for (self.atoms.keys()) |atom_index| {
+        const atom = elf_file.atom(atom_index) orelse continue;
+        if (!atom.alive) continue;
+        try atom.scanRelocs(elf_file);
+    }
+}
+
 pub fn updateSymtabSize(self: *ZigModule, elf_file: *Elf) void {
     for (self.locals()) |local_index| {
         const local = elf_file.symbol(local_index);
src/link/Elf.zig
@@ -1049,8 +1049,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     self.resolveSymbols();
     self.markImportsExports();
     self.claimUnresolved();
-
-    if (self.unresolved.keys().len > 0) try self.reportUndefined();
+    try self.scanRelocs();
 
     self.allocateLinkerDefinedSymbols();
 
@@ -1381,6 +1380,28 @@ fn claimUnresolved(self: *Elf) void {
     }
 }
 
+fn scanRelocs(self: *Elf) !void {
+    if (self.zig_module_index) |index| {
+        const zig_module = self.file(index).?.zig_module;
+        try zig_module.scanRelocs(self);
+    }
+    for (self.objects.items) |index| {
+        const object = self.file(index).?.object;
+        try object.scanRelocs(self);
+    }
+
+    // try self.reportUndefined();
+
+    for (self.symbols.items) |*sym| {
+        if (sym.flags.needs_got) {
+            log.debug("'{s}' needs GOT", .{sym.name(self)});
+            // TODO how can we tell we need to write it again, aka the entry is dirty?
+            const gop = try sym.getOrCreateGotEntry(self);
+            try self.got.writeEntry(self, gop.index);
+        }
+    }
+}
+
 fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !void {
     const tracy = trace(@src());
     defer tracy.end();
@@ -2375,8 +2396,9 @@ fn updateDeclCode(
         sym.value = atom_ptr.value;
         esym.st_value = atom_ptr.value;
 
-        const got_index = try sym.getOrCreateGotEntry(self);
-        try self.got.writeEntry(self, got_index);
+        sym.flags.needs_got = true;
+        const gop = try sym.getOrCreateGotEntry(self);
+        try self.got.writeEntry(self, gop.index);
     }
 
     const phdr_index = self.phdr_to_shdr_table.get(shdr_index).?;
@@ -2609,8 +2631,9 @@ 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_index = try local_sym.getOrCreateGotEntry(self);
-    try self.got.writeEntry(self, got_index);
+    local_sym.flags.needs_got = true;
+    const gop = try local_sym.getOrCreateGotEntry(self);
+    try self.got.writeEntry(self, gop.index);
 
     const section_offset = atom_ptr.value - self.phdrs.items[phdr_index].p_vaddr;
     const file_offset = self.shdrs.items[local_sym.output_section_index].sh_offset + section_offset;
src/codegen.zig
@@ -856,6 +856,7 @@ 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);
+        sym.flags.needs_got = true;
         _ = try sym.getOrCreateGotEntry(elf_file);
         return GenResult.mcv(.{ .memory = sym.gotAddress(elf_file) });
     } else if (bin_file.cast(link.File.MachO)) |macho_file| {