From d05db526168351ae9250657725cc126625262fa6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 5 Dec 2023 17:29:26 +0100 Subject: [PATCH] elf: copy out committed ZigObject to a buffer when creating static lib --- src/link/Elf.zig | 16 ++++++++----- src/link/Elf/ZigObject.zig | 46 +++++++++++++++++++++++--------------- src/link/Elf/file.zig | 8 +++---- test/link/elf.zig | 37 ++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 28 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 2ada57e59f..ec29eeb931 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1344,23 +1344,25 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const try zig_object.addAtomsToRelaSections(self); try self.updateSectionSizesObject(); + try self.allocateAllocSectionsObject(); try self.allocateNonAllocSections(); if (build_options.enable_logging) { - state_log.debug("{}", .{self.dumpState()}); + log.debug("{}", .{self.dumpState()}); } try self.writeSyntheticSectionsObject(); try self.writeShdrTable(); try self.writeElfHeader(); + + // TODO we can avoid reading in the file contents we just wrote if we give the linker + // ability to write directly to a buffer. + try zig_object.readFileContents(self); } var files = std.ArrayList(File.Index).init(gpa); defer files.deinit(); try files.ensureTotalCapacityPrecise(self.objects.items.len + 1); - // Note to self: we currently must have ZigObject written out first as we write the object - // file into the same file descriptor and then re-read its contents. - // TODO implement writing ZigObject to a buffer instead of file. if (self.zigObjectPtr()) |zig_object| files.appendAssumeCapacity(zig_object.index); for (self.objects.items) |index| files.appendAssumeCapacity(index); @@ -1381,7 +1383,7 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const for (files.items) |index| { const file_ptr = self.file(index).?; try file_ptr.updateArStrtab(gpa, &ar_strtab); - file_ptr.updateArSize(self); + file_ptr.updateArSize(); } // Update file offsets of contributing objects. @@ -1433,7 +1435,7 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const // Write object files for (files.items) |index| { if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0); - try self.file(index).?.writeAr(self, buffer.writer()); + try self.file(index).?.writeAr(buffer.writer()); } assert(buffer.items.len == total_size); @@ -2970,6 +2972,7 @@ fn writeShdrTable(self: *Elf) !void { defer gpa.free(buf); for (buf, 0..) |*shdr, i| { + assert(self.shdrs.items[i].sh_offset != math.maxInt(u64)); shdr.* = shdrTo32(self.shdrs.items[i]); if (foreign_endian) { mem.byteSwapAllFields(elf.Elf32_Shdr, shdr); @@ -2982,6 +2985,7 @@ fn writeShdrTable(self: *Elf) !void { defer gpa.free(buf); for (buf, 0..) |*shdr, i| { + assert(self.shdrs.items[i].sh_offset != math.maxInt(u64)); shdr.* = self.shdrs.items[i]; if (foreign_endian) { mem.byteSwapAllFields(elf.Elf64_Shdr, shdr); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index ef029c7702..7a1fa21435 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -3,6 +3,7 @@ //! and any relocations that may have been emitted. //! Think about this as fake in-memory Object file for the Zig module. +data: std.ArrayListUnmanaged(u8) = .{}, path: []const u8, index: File.Index, @@ -101,6 +102,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void { } pub fn deinit(self: *ZigObject, allocator: Allocator) void { + self.data.deinit(allocator); allocator.free(self.path); self.local_esyms.deinit(allocator); self.global_esyms.deinit(allocator); @@ -441,6 +443,27 @@ pub fn markLive(self: *ZigObject, elf_file: *Elf) void { } } +/// This is just a temporary helper function that allows us to re-read what we wrote to file into a buffer. +/// We need this so that we can write to an archive. +/// TODO implement writing ZigObject data directly to a buffer instead. +pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + const shsize: u64 = switch (elf_file.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Shdr), + .p64 => @sizeOf(elf.Elf64_Shdr), + }; + var end_pos: u64 = elf_file.shdr_table_offset.? + elf_file.shdrs.items.len * shsize; + for (elf_file.shdrs.items) |shdr| { + if (shdr.sh_type == elf.SHT_NOBITS) continue; + end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size); + } + const size = std.math.cast(usize, end_pos) orelse return error.Overflow; + try self.data.resize(gpa, size); + + const amt = try elf_file.base.file.?.preadAll(self.data.items, 0); + if (amt != size) return error.InputOutput; +} + pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) error{OutOfMemory}!void { const gpa = elf_file.base.allocator; @@ -457,34 +480,21 @@ pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: * } } -pub fn updateArSize(self: *ZigObject, elf_file: *Elf) void { - var end_pos: u64 = elf_file.shdr_table_offset.?; - for (elf_file.shdrs.items) |shdr| { - end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size); - } - self.output_ar_state.size = end_pos; +pub fn updateArSize(self: *ZigObject) void { + self.output_ar_state.size = self.data.items.len; } -pub fn writeAr(self: ZigObject, elf_file: *Elf, writer: anytype) !void { - const gpa = elf_file.base.allocator; - - const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow; - const contents = try gpa.alloc(u8, size); - defer gpa.free(contents); - - const amt = try elf_file.base.file.?.preadAll(contents, 0); - if (amt != self.output_ar_state.size) return error.InputOutput; - +pub fn writeAr(self: ZigObject, writer: anytype) !void { const name = self.path; const hdr = Archive.setArHdr(.{ .name = if (name.len <= Archive.max_member_name_len) .{ .name = name } else .{ .name_off = self.output_ar_state.name_off }, - .size = @intCast(size), + .size = @intCast(self.data.items.len), }); try writer.writeAll(mem.asBytes(&hdr)); - try writer.writeAll(contents); + try writer.writeAll(self.data.items); } pub fn addAtomsToRelaSections(self: ZigObject, elf_file: *Elf) !void { diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index cb83e945d7..30222b148a 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -162,17 +162,17 @@ pub const File = union(enum) { state.name_off = try ar_strtab.insert(allocator, path); } - pub fn updateArSize(file: File, elf_file: *Elf) void { + pub fn updateArSize(file: File) void { return switch (file) { - .zig_object => |x| x.updateArSize(elf_file), + .zig_object => |x| x.updateArSize(), .object => |x| x.updateArSize(), inline else => unreachable, }; } - pub fn writeAr(file: File, elf_file: *Elf, writer: anytype) !void { + pub fn writeAr(file: File, writer: anytype) !void { return switch (file) { - .zig_object => |x| x.writeAr(elf_file, writer), + .zig_object => |x| x.writeAr(writer), .object => |x| x.writeAr(writer), inline else => unreachable, }; diff --git a/test/link/elf.zig b/test/link/elf.zig index c2ed3e08b4..ba33a7eeec 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -29,6 +29,7 @@ pub fn testAll(b: *Build) *Step { // Exercise linker in ar mode elf_step.dependOn(testEmitStaticLib(b, .{ .target = musl_target })); + elf_step.dependOn(testEmitStaticLibZig(b, .{ .use_llvm = false, .target = musl_target })); // Exercise linker with self-hosted backend (no LLVM) elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target })); @@ -743,6 +744,42 @@ fn testEmitStaticLib(b: *Build, opts: Options) *Step { return test_step; } +fn testEmitStaticLibZig(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "emit-static-lib-zig", opts); + + const obj1 = addObject(b, "obj1", opts); + addZigSourceBytes(obj1, + \\export var foo: i32 = 42; + \\export var bar: i32 = 2; + ); + + const lib = addStaticLibrary(b, "lib", opts); + addZigSourceBytes(lib, + \\extern var foo: i32; + \\extern var bar: i32; + \\export fn fooBar() i32 { + \\ return foo + bar; + \\} + ); + lib.addObject(obj1); + + const exe = addExecutable(b, "test", opts); + addZigSourceBytes(exe, + \\const std = @import("std"); + \\extern fn fooBar() i32; + \\pub fn main() void { + \\ std.debug.print("{d}", .{fooBar()}); + \\} + ); + exe.linkLibrary(lib); + + const run = addRunArtifact(exe); + run.expectStdErrEqual("44"); + test_step.dependOn(&run.step); + + return test_step; +} + fn testEmptyObject(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "empty-object", opts);