Commit 3b9455f005

Jakub Konka <kubkon@jakubkonka.com>
2023-11-02 19:33:10
elf: generate pretty rudimentary archive
1 parent eddf9cc
Changed files (3)
src/link/Elf/Archive.zig
@@ -10,7 +10,7 @@ strtab: []const u8 = &[0]u8{},
 /// String that begins an archive file.
 pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
 /// Size of that string.
-pub const SARMAG: u4 = 8;
+pub const SARMAG = 8;
 
 /// String in ar_fmag at the end of each header.
 pub const ARFMAG: *const [2:0]u8 = "`\n";
@@ -58,7 +58,7 @@ pub const ar_hdr = extern struct {
     }
 
     fn isSymtab(self: ar_hdr) bool {
-        return mem.eql(u8, getValue(&self.ar_name), "/");
+        return mem.eql(u8, getValue(&self.ar_name), "/") or mem.eql(u8, getValue(&self.ar_name), SYM64NAME);
     }
 };
 
src/link/Elf/ZigObject.zig
@@ -514,7 +514,7 @@ pub fn updateArSymtab(self: ZigObject, elf_file: *Elf) !void {
         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 });
+        elf_file.ar_symtab.appendAssumeCapacity(.{ .off = off, .file_index = self.index });
     }
 }
 
src/link/Elf.zig
@@ -189,7 +189,7 @@ 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_symtab: std.ArrayListUnmanaged(ArSymtabEntry) = .{},
 ar_strtab: StringTable = .{},
 
 /// When allocating, the ideal_capacity is calculated by
@@ -1532,6 +1532,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
 
 pub fn flushStaticLib(self: *Elf, comp: *Compilation) link.File.FlushError!void {
     _ = comp;
+    const gpa = self.base.allocator;
 
     // First, we flush relocatable object file generated with our backends.
     if (self.zigObjectPtr()) |zig_object| {
@@ -1553,28 +1554,162 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation) link.File.FlushError!void
         try self.writeShStrtab();
         try self.writeElfHeader();
 
-        // Update ar symbol and string tables.
+        // Update ar symbol table.
         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
 
+    mem.sort(ArSymtabEntry, self.ar_symtab.items, {}, ArSymtabEntry.lessThan);
+
     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
+    // Save object paths in strtab.
+    var files = std.AutoHashMap(File.Index, struct { u32, u64, u64 }).init(gpa);
+    defer files.deinit();
+    try files.ensureUnusedCapacity(@intCast(self.objects.items.len + 1));
+
+    if (self.zigObjectPtr()) |zig_object| {
+        files.putAssumeCapacityNoClobber(zig_object.index, .{ try self.ar_strtab.insert(gpa, zig_object.path), 0, 0 });
+    }
+
+    // Encode ar symtab in 64bit format.
+    var ar_symtab = std.ArrayList(u8).init(gpa);
+    defer ar_symtab.deinit();
+    try ar_symtab.ensureTotalCapacityPrecise(8 * (3 * self.ar_symtab.items.len + 1));
+
+    // Number of symbols
+    ar_symtab.writer().writeInt(u64, @as(u64, @intCast(self.ar_symtab.items.len)), .big) catch unreachable;
+
+    // Offsets which we will relocate later.
+    for (0..self.ar_symtab.items.len) |_| {
+        ar_symtab.writer().writeInt(u64, 0, .big) catch unreachable;
+    }
+
+    // ASCII offsets into the strtab.
+    for (self.ar_symtab.items) |entry| {
+        ar_symtab.writer().print("/{d}", .{entry.off}) catch unreachable;
+    }
+
+    // Align to 8bytes if required
+    {
+        const end = ar_symtab.items.len;
+        const aligned = mem.alignForward(usize, end, 8);
+        ar_symtab.writer().writeByteNTimes(0, aligned - end) catch unreachable;
+    }
+
+    assert(mem.isAligned(ar_symtab.items.len, 8));
 
-    try self.writeArMagic();
+    // Calculate required size for headers before ZigObject pos in file.
+    if (self.zigObjectPtr()) |zig_object| {
+        var file_off: u64 = 0;
+        // Magic
+        file_off += Archive.SARMAG;
+        // Symtab
+        file_off += @sizeOf(Archive.ar_hdr) + @as(u64, @intCast(ar_symtab.items.len));
+        // Strtab
+        file_off += @sizeOf(Archive.ar_hdr) + @as(u64, @intCast(self.ar_strtab.buffer.items.len));
+        // And because we are nice, we will align to 8 bytes.
+        file_off = mem.alignForward(u64, file_off, 8);
+
+        const files_ptr = files.getPtr(zig_object.index).?;
+        files_ptr[1] = file_off;
+
+        // Move ZigObject into place.
+        {
+            var end_pos: u64 = self.shdr_table_offset.?;
+            for (self.shdrs.items) |shdr| {
+                end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size);
+            }
+            const contents = try gpa.alloc(u8, end_pos);
+            defer gpa.free(contents);
+            const amt = try self.base.file.?.preadAll(contents, 0);
+            if (amt != end_pos) return error.InputOutput;
+            try self.base.file.?.pwriteAll(contents, file_off + @sizeOf(Archive.ar_hdr));
+
+            files_ptr[2] = end_pos;
+        }
+    }
+
+    // Fixup file offsets in the symtab.
+    for (self.ar_symtab.items, 1..) |entry, i| {
+        const file_off = files.get(entry.file_index).?[1];
+        mem.writeInt(u64, ar_symtab.items[8 * i ..][0..8], file_off, .big);
+    }
+
+    var pos: usize = Archive.SARMAG;
+
+    // Write symtab.
+    {
+        const hdr = setArHdr(.{ .kind = .symtab, .name_off = 0, .size = @intCast(ar_symtab.items.len) });
+        try self.base.file.?.pwriteAll(mem.asBytes(&hdr), pos);
+        pos += @sizeOf(Archive.ar_hdr);
+        try self.base.file.?.pwriteAll(ar_symtab.items, pos);
+        pos += ar_symtab.items.len;
+    }
+
+    // Write strtab.
+    {
+        const hdr = setArHdr(.{
+            .kind = .strtab,
+            .name_off = 0,
+            .size = @intCast(mem.alignForward(usize, self.ar_strtab.buffer.items.len, 8)),
+        });
+        try self.base.file.?.pwriteAll(mem.asBytes(&hdr), pos);
+        pos += @sizeOf(Archive.ar_hdr);
+        try self.base.file.?.pwriteAll(self.ar_strtab.buffer.items, pos);
+        pos += self.ar_strtab.buffer.items.len;
+    }
+
+    // Zig object if defined
+    if (self.zigObjectPtr()) |zig_object| {
+        const entry = files.get(zig_object.index).?;
+        const hdr = setArHdr(.{ .kind = .object, .name_off = entry[0], .size = @intCast(entry[2]) });
+        try self.base.file.?.pwriteAll(mem.asBytes(&hdr), entry[1]);
+    }
+
+    // TODO parsed positionals
+
+    // Magic bytes.
+    {
+        try self.base.file.?.pwriteAll(Archive.ARMAG, 0);
+    }
+}
+
+fn setArHdr(opts: struct {
+    kind: enum { symtab, strtab, object },
+    name_off: u32,
+    size: u32,
+}) Archive.ar_hdr {
+    var hdr: Archive.ar_hdr = .{
+        .ar_name = undefined,
+        .ar_date = undefined,
+        .ar_uid = undefined,
+        .ar_gid = undefined,
+        .ar_mode = undefined,
+        .ar_size = undefined,
+        .ar_fmag = undefined,
+    };
+    @memset(mem.asBytes(&hdr), 0x20);
+    @memcpy(&hdr.ar_fmag, Archive.ARFMAG);
+
+    {
+        var stream = std.io.fixedBufferStream(&hdr.ar_name);
+        const writer = stream.writer();
+        switch (opts.kind) {
+            .symtab => writer.print("{s}", .{Archive.SYM64NAME}) catch unreachable,
+            .strtab => writer.print("//", .{}) catch unreachable,
+            .object => writer.print("/{d}", .{opts.name_off}) catch unreachable,
+        }
+    }
+    {
+        var stream = std.io.fixedBufferStream(&hdr.ar_size);
+        stream.writer().print("{d}", .{opts.size}) catch unreachable;
+    }
+
+    return hdr;
 }
 
 pub fn flushObject(self: *Elf, comp: *Compilation) link.File.FlushError!void {
@@ -2968,15 +3103,6 @@ fn writeElfHeader(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);
@@ -5683,6 +5809,19 @@ fn fmtDumpState(
     }
     try writer.print("{}\n", .{self.got.fmt(self)});
     try writer.print("{}\n", .{self.zig_got.fmt(self)});
+
+    if (self.isStaticLib()) {
+        try writer.writeAll("ar symtab\n");
+        for (self.ar_symtab.items, 0..) |entry, i| {
+            try writer.print(" {d} : {s} in file({d})\n", .{
+                i,
+                self.ar_strtab.getAssumeExists(entry.off),
+                entry.file_index,
+            });
+        }
+        try writer.writeByte('\n');
+    }
+
     try writer.writeAll("Output shdrs\n");
     for (self.shdrs.items, 0..) |shdr, shndx| {
         try writer.print("shdr({d}) : phdr({?d}) : {}\n", .{
@@ -5815,6 +5954,19 @@ const LastAtomAndFreeList = struct {
 
 const LastAtomAndFreeListTable = std.AutoArrayHashMapUnmanaged(u16, LastAtomAndFreeList);
 
+const ArSymtabEntry = struct {
+    off: u32,
+    file_index: File.Index,
+
+    pub fn lessThan(ctx: void, lhs: ArSymtabEntry, rhs: ArSymtabEntry) bool {
+        _ = ctx;
+        if (lhs.off == rhs.off) {
+            return lhs.file_index < rhs.file_index;
+        }
+        return lhs.off < rhs.off;
+    }
+};
+
 pub const R_X86_64_ZIG_GOT32 = elf.R_X86_64_NUM + 1;
 pub const R_X86_64_ZIG_GOTPCREL = elf.R_X86_64_NUM + 2;