Commit eddf9cc65b

Jakub Konka <kubkon@jakubkonka.com>
2023-11-02 17:13:49
elf: collect exports from ZigObject into AR symtab
1 parent 481ee1b
src/link/Elf/Archive.zig
@@ -13,11 +13,11 @@ pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
 pub const SARMAG: u4 = 8;
 
 /// String in ar_fmag at the end of each header.
-const ARFMAG: *const [2:0]u8 = "`\n";
+pub const ARFMAG: *const [2:0]u8 = "`\n";
 
-const SYM64NAME: *const [7:0]u8 = "/SYM64/";
+pub const SYM64NAME: *const [7:0]u8 = "/SYM64/";
 
-const ar_hdr = extern struct {
+pub const ar_hdr = extern struct {
     /// Member file name, sometimes / terminated.
     ar_name: [16]u8,
 
src/link/Elf/file.zig
@@ -136,7 +136,7 @@ pub const File = union(enum) {
             if (local.atom(elf_file)) |atom| if (!atom.flags.alive) continue;
             const esym = local.elfSym(elf_file);
             switch (esym.st_type()) {
-                elf.STT_SECTION => if (!elf_file.isObject()) continue,
+                elf.STT_SECTION => if (!elf_file.isRelocatable()) continue,
                 elf.STT_NOTYPE => continue,
                 else => {},
             }
@@ -196,6 +196,14 @@ pub const File = union(enum) {
         }
     }
 
+    pub fn updateArSymtab(file: File, elf_file: *Elf) !void {
+        return switch (file) {
+            .zig_object => |x| x.updateArSymtab(elf_file),
+            .object => @panic("TODO"),
+            inline else => unreachable,
+        };
+    }
+
     pub const Index = u32;
 
     pub const Entry = union(enum) {
src/link/Elf/Symbol.zig
@@ -43,7 +43,7 @@ pub fn outputShndx(symbol: Symbol) ?u16 {
 }
 
 pub fn isLocal(symbol: Symbol, elf_file: *Elf) bool {
-    if (elf_file.isObject()) return symbol.elfSym(elf_file).st_bind() == elf.STB_LOCAL;
+    if (elf_file.isRelocatable()) return symbol.elfSym(elf_file).st_bind() == elf.STB_LOCAL;
     return !(symbol.flags.import or symbol.flags.@"export");
 }
 
@@ -168,7 +168,7 @@ const GetOrCreateZigGotEntryResult = struct {
 };
 
 pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, elf_file: *Elf) !GetOrCreateZigGotEntryResult {
-    assert(!elf_file.isObject());
+    assert(!elf_file.isRelocatable());
     assert(symbol.flags.needs_zig_got);
     if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.extra(elf_file).?.zig_got };
     const index = try elf_file.zig_got.addSymbol(symbol_index, elf_file);
@@ -220,7 +220,7 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void {
         if (symbol.flags.has_copy_rel) break :blk elf_file.copy_rel_section_index.?;
         if (file_ptr == .shared_object or esym.st_shndx == elf.SHN_UNDEF) break :blk elf.SHN_UNDEF;
         // TODO I think this is wrong and obsolete
-        if (elf_file.isObject() and st_type == elf.STT_SECTION) break :blk symbol.outputShndx().?;
+        if (elf_file.isRelocatable() and st_type == elf.STT_SECTION) break :blk symbol.outputShndx().?;
         if (symbol.atom(elf_file) == null and file_ptr != .linker_defined)
             break :blk elf.SHN_ABS;
         break :blk symbol.outputShndx() orelse elf.SHN_UNDEF;
src/link/Elf/ZigObject.zig
@@ -502,6 +502,22 @@ fn sortSymbols(self: *ZigObject, elf_file: *Elf) error{OutOfMemory}!void {
     // mem.sort(Entry, sorted_globals, elf_file, Entry.lessThan);
 }
 
+pub fn updateArSymtab(self: ZigObject, elf_file: *Elf) !void {
+    const gpa = elf_file.base.allocator;
+
+    try elf_file.ar_symtab.ensureUnusedCapacity(gpa, self.globals().len);
+
+    for (self.globals()) |global_index| {
+        const global = elf_file.symbol(global_index);
+        const file_ptr = global.file(elf_file).?;
+        assert(file_ptr.index() == self.index);
+        if (global.type(elf_file) == elf.SHN_UNDEF) continue;
+
+        const off = try elf_file.ar_strtab.insert(gpa, global.name(elf_file));
+        elf_file.ar_symtab.appendAssumeCapacity(.{ off, self.index });
+    }
+}
+
 pub fn updateRelaSectionSizes(self: ZigObject, elf_file: *Elf) void {
     _ = self;
 
src/link/Elf.zig
@@ -186,6 +186,12 @@ comdat_groups_table: std.AutoHashMapUnmanaged(u32, ComdatGroupOwner.Index) = .{}
 /// such as `resolver` and `comdat_groups_table`.
 strings: StringTable = .{},
 
+/// Static archive state.
+/// TODO it may be wise to move it somewhere else, but for the time being, it
+/// is far easier to pollute global state.
+ar_symtab: std.ArrayListUnmanaged(struct { u32, File.Index }) = .{},
+ar_strtab: StringTable = .{},
+
 /// When allocating, the ideal_capacity is calculated by
 /// actual_capacity + (actual_capacity / ideal_factor)
 const ideal_factor = 3;
@@ -392,6 +398,9 @@ pub fn deinit(self: *Elf) void {
     self.copy_rel.deinit(gpa);
     self.rela_dyn.deinit(gpa);
     self.rela_plt.deinit(gpa);
+
+    self.ar_symtab.deinit(gpa);
+    self.ar_strtab.deinit(gpa);
 }
 
 pub fn getDeclVAddr(self: *Elf, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo) !u64 {
@@ -1383,15 +1392,15 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     if (csu.crtend) |v| try positionals.append(.{ .path = v });
     if (csu.crtn) |v| try positionals.append(.{ .path = v });
 
+    if (self.zigObjectPtr()) |zig_object| try zig_object.flushModule(self);
+    if (self.isStaticLib()) return self.flushStaticLib(comp);
+
     for (positionals.items) |obj| {
         var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined };
         self.parsePositional(obj.path, obj.must_link, &parse_ctx) catch |err|
             try self.handleAndReportParseError(obj.path, err, &parse_ctx);
     }
 
-    if (self.zigObjectPtr()) |zig_object| try zig_object.flushModule(self);
-    if (self.isStaticLib()) return self.flushStaticLib(comp);
-
     // Dedup shared objects
     {
         var seen_dsos = std.StringHashMap(void).init(gpa);
@@ -1412,7 +1421,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
 
     // If we haven't already, create a linker-generated input file comprising of
     // linker-defined synthetic symbols only such as `_DYNAMIC`, etc.
-    if (self.linker_defined_index == null and !self.isObject()) {
+    if (self.linker_defined_index == null and !self.isRelocatable()) {
         const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
         self.files.set(index, .{ .linker_defined = .{ .index = index } });
         self.linker_defined_index = index;
@@ -1517,14 +1526,55 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     } else {
         log.debug("flushing. no_entry_point_found = false", .{});
         self.error_flags.no_entry_point_found = false;
-        try self.writeHeader();
+        try self.writeElfHeader();
     }
 }
 
 pub fn flushStaticLib(self: *Elf, comp: *Compilation) link.File.FlushError!void {
     _ = comp;
-    var err = try self.addErrorWithNotes(0);
-    try err.addMsg(self, "fatal linker error: emitting static libs unimplemented", .{});
+
+    // First, we flush relocatable object file generated with our backends.
+    if (self.zigObjectPtr()) |zig_object| {
+        zig_object.resolveSymbols(self);
+        zig_object.claimUnresolvedObject(self);
+
+        try self.initSymtab();
+        try self.initShStrtab();
+        try self.sortShdrs();
+        zig_object.updateRelaSectionSizes(self);
+        try self.updateSymtabSize();
+        self.updateShStrtabSize();
+
+        try self.allocateNonAllocSections();
+
+        try self.writeShdrTable();
+        try zig_object.writeRelaSections(self);
+        try self.writeSymtab();
+        try self.writeShStrtab();
+        try self.writeElfHeader();
+
+        // Update ar symbol and string tables.
+        try zig_object.asFile().updateArSymtab(self);
+
+        for (self.ar_symtab.items, 0..) |entry, i| {
+            std.debug.print("{d}: {s} in {}\n", .{
+                i,
+                self.ar_strtab.getAssumeExists(entry[0]),
+                self.file(entry[1]).?.fmtPath(),
+            });
+        }
+    }
+
+    // TODO parse positionals that we want to make part of the archive
+
+    if (build_options.enable_logging) {
+        state_log.debug("{}", .{self.dumpState()});
+    }
+
+    // try self.writeArHdr();
+    // TODO beyond this point I expect writing out objects parsed from the cmdline
+
+    try self.writeArMagic();
 }
 
 pub fn flushObject(self: *Elf, comp: *Compilation) link.File.FlushError!void {
@@ -1543,7 +1593,7 @@ pub fn flushObject(self: *Elf, comp: *Compilation) link.File.FlushError!void {
 
     try self.writeShdrTable();
     try self.writeSyntheticSections();
-    try self.writeHeader();
+    try self.writeElfHeader();
 }
 
 const ParseError = error{
@@ -2797,7 +2847,7 @@ fn writePhdrTable(self: *Elf) !void {
     }
 }
 
-fn writeHeader(self: *Elf) !void {
+fn writeElfHeader(self: *Elf) !void {
     var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
 
     var index: usize = 0;
@@ -2918,6 +2968,15 @@ fn writeHeader(self: *Elf) !void {
     try self.base.file.?.pwriteAll(hdr_buf[0..index], 0);
 }
 
+fn writeArMagic(self: *Elf) !void {
+    // Magic bytes.
+    var buffer: [@as(usize, Archive.SARMAG) + 1]u8 = undefined;
+    var stream = std.io.fixedBufferStream(&buffer);
+    const writer = stream.writer();
+    try writer.print("{s}\x00", .{Archive.ARMAG});
+    try self.base.file.?.pwriteAll(&buffer, 0);
+}
+
 pub fn freeDecl(self: *Elf, decl_index: Module.Decl.Index) void {
     if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
     return self.zigObjectPtr().?.freeDecl(self, decl_index);
@@ -3147,17 +3206,13 @@ fn allocateLinkerDefinedSymbols(self: *Elf) void {
 }
 
 fn initSections(self: *Elf) !void {
-    const small_ptr = switch (self.ptr_width) {
-        .p32 => true,
-        .p64 => false,
-    };
     const ptr_size = self.ptrWidthBytes();
 
-    if (!self.isStaticLib()) for (self.objects.items) |index| {
+    for (self.objects.items) |index| {
         try self.file(index).?.object.initOutputSections(self);
-    };
+    }
 
-    const needs_eh_frame = if (self.isStaticLib()) false else for (self.objects.items) |index| {
+    const needs_eh_frame = for (self.objects.items) |index| {
         if (self.file(index).?.object.cies.items.len > 0) break true;
     } else false;
     if (needs_eh_frame) {
@@ -3340,6 +3395,15 @@ fn initSections(self: *Elf) !void {
         }
     }
 
+    try self.initSymtab();
+    try self.initShStrtab();
+}
+
+fn initSymtab(self: *Elf) !void {
+    const small_ptr = switch (self.ptr_width) {
+        .p32 => true,
+        .p64 => false,
+    };
     if (self.symtab_section_index == null) {
         self.symtab_section_index = try self.addSection(.{
             .name = ".symtab",
@@ -3358,6 +3422,9 @@ fn initSections(self: *Elf) !void {
             .offset = std.math.maxInt(u64),
         });
     }
+}
+
+fn initShStrtab(self: *Elf) !void {
     if (self.shstrtab_section_index == null) {
         self.shstrtab_section_index = try self.addSection(.{
             .name = ".shstrtab",
@@ -3933,10 +4000,11 @@ fn updateSectionSizes(self: *Elf) !void {
         self.shdrs.items[index].sh_size = self.verneed.size();
     }
 
-    if (self.symtab_section_index != null) {
-        try self.updateSymtabSize();
-    }
+    try self.updateSymtabSize();
+    self.updateShStrtabSize();
+}
 
+fn updateShStrtabSize(self: *Elf) void {
     if (self.shstrtab_section_index) |index| {
         self.shdrs.items[index].sh_size = self.shstrtab.items.len;
     }
@@ -4546,14 +4614,15 @@ fn writeSyntheticSections(self: *Elf) !void {
         try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.rela_plt.items), shdr.sh_offset);
     }
 
+    try self.writeSymtab();
+    try self.writeShStrtab();
+}
+
+fn writeShStrtab(self: *Elf) !void {
     if (self.shstrtab_section_index) |index| {
         const shdr = self.shdrs.items[index];
         try self.base.file.?.pwriteAll(self.shstrtab.items, shdr.sh_offset);
     }
-
-    if (self.symtab_section_index) |_| {
-        try self.writeSymtab();
-    }
 }
 
 fn writeSymtab(self: *Elf) !void {