diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index 66bf5cadf3..d27c8d6d27 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -405,6 +405,17 @@ pub fn checkInDynamicSection(self: *CheckObject) void { self.checkExact(label); } +/// Creates a new check checking specifically symbol table parsed and dumped from the archive +/// file. +pub fn checkInArchiveSymtab(self: *CheckObject) void { + const label = switch (self.obj_format) { + .elf => ElfDumper.archive_symtab_label, + else => @panic("TODO other file formats"), + }; + self.checkStart(); + self.checkExact(label); +} + /// Creates a new standalone, singular check which allows running simple binary operations /// on the extracted variables. It will then compare the reduced program with the value of /// the expected variable. @@ -884,35 +895,177 @@ const ElfDumper = struct { const symtab_label = "symbol table"; const dynamic_symtab_label = "dynamic symbol table"; const dynamic_section_label = "dynamic section"; - - const Symtab = struct { - symbols: []align(1) const elf.Elf64_Sym, - strings: []const u8, - - fn get(st: Symtab, index: usize) ?elf.Elf64_Sym { - if (index >= st.symbols.len) return null; - return st.symbols[index]; - } - - fn getName(st: Symtab, index: usize) ?[]const u8 { - const sym = st.get(index) orelse return null; - return getString(st.strings, sym.st_name); - } - }; - - const Context = struct { - gpa: Allocator, - data: []const u8, - hdr: elf.Elf64_Ehdr, - shdrs: []align(1) const elf.Elf64_Shdr, - phdrs: []align(1) const elf.Elf64_Phdr, - shstrtab: []const u8, - symtab: ?Symtab = null, - dysymtab: ?Symtab = null, - }; + const archive_symtab_label = "archive symbol table"; fn parseAndDump(step: *Step, bytes: []const u8) ![]const u8 { const gpa = step.owner.allocator; + return parseAndDumpArchive(gpa, bytes) catch |err| switch (err) { + error.InvalidArchiveMagicNumber => try parseAndDumpObject(gpa, bytes), + else => |e| return e, + }; + } + + fn parseAndDumpArchive(gpa: Allocator, bytes: []const u8) ![]const u8 { + var stream = std.io.fixedBufferStream(bytes); + const reader = stream.reader(); + + const magic = try reader.readBytesNoEof(elf.ARMAG.len); + if (!mem.eql(u8, &magic, elf.ARMAG)) { + return error.InvalidArchiveMagicNumber; + } + + var ctx = ArchiveContext{ + .gpa = gpa, + .data = bytes, + .strtab = &[0]u8{}, + }; + defer { + for (ctx.objects.items) |*object| { + gpa.free(object.name); + } + ctx.objects.deinit(gpa); + } + + while (true) { + if (stream.pos >= ctx.data.len) break; + if (!mem.isAligned(stream.pos, 2)) stream.pos += 1; + + const hdr = try reader.readStruct(elf.ar_hdr); + + if (!mem.eql(u8, &hdr.ar_fmag, elf.ARFMAG)) return error.InvalidArchiveHeaderMagicNumber; + + const size = try hdr.size(); + defer { + _ = stream.seekBy(size) catch {}; + } + + if (hdr.isSymtab()) { + try ctx.parseSymtab(ctx.data[stream.pos..][0..size], .p32); + continue; + } + if (hdr.isSymtab64()) { + try ctx.parseSymtab(ctx.data[stream.pos..][0..size], .p64); + continue; + } + if (hdr.isStrtab()) { + ctx.strtab = ctx.data[stream.pos..][0..size]; + continue; + } + if (hdr.isSymdef() or hdr.isSymdefSorted()) continue; + + const name = if (hdr.name()) |name| + try gpa.dupe(u8, name) + else if (try hdr.nameOffset()) |off| + try gpa.dupe(u8, ctx.getString(off)) + else + unreachable; + + try ctx.objects.append(gpa, .{ .name = name, .off = stream.pos, .len = size }); + } + + var output = std.ArrayList(u8).init(gpa); + const writer = output.writer(); + + try ctx.dumpSymtab(writer); + try ctx.dumpObjects(writer); + + return output.toOwnedSlice(); + } + + const ArchiveContext = struct { + gpa: Allocator, + data: []const u8, + symtab: std.ArrayListUnmanaged(ArSymtabEntry) = .{}, + strtab: []const u8, + objects: std.ArrayListUnmanaged(struct { name: []const u8, off: usize, len: usize }) = .{}, + + fn parseSymtab(ctx: *ArchiveContext, raw: []const u8, ptr_width: enum { p32, p64 }) !void { + var stream = std.io.fixedBufferStream(raw); + const reader = stream.reader(); + const num = switch (ptr_width) { + .p32 => try reader.readInt(u32, .big), + .p64 => try reader.readInt(u64, .big), + }; + const ptr_size: usize = switch (ptr_width) { + .p32 => @sizeOf(u32), + .p64 => @sizeOf(u64), + }; + const strtab_off = (num + 1) * ptr_size; + const strtab_len = raw.len - strtab_off; + const strtab = raw[strtab_off..][0..strtab_len]; + + try ctx.symtab.ensureTotalCapacityPrecise(ctx.gpa, num); + + var stroff: usize = 0; + for (0..num) |_| { + const off = switch (ptr_width) { + .p32 => try reader.readInt(u32, .big), + .p64 => try reader.readInt(u64, .big), + }; + const name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + stroff)), 0); + stroff += name.len + 1; + ctx.symtab.appendAssumeCapacity(.{ .off = off, .name = name }); + } + } + + fn dumpSymtab(ctx: ArchiveContext, writer: anytype) !void { + if (ctx.symtab.items.len == 0) return; + + var files = std.AutoHashMap(usize, []const u8).init(ctx.gpa); + defer files.deinit(); + try files.ensureUnusedCapacity(@intCast(ctx.objects.items.len)); + + for (ctx.objects.items) |object| { + files.putAssumeCapacityNoClobber(object.off - @sizeOf(elf.ar_hdr), object.name); + } + + var symbols = std.AutoArrayHashMap(usize, std.ArrayList([]const u8)).init(ctx.gpa); + defer { + for (symbols.values()) |*value| { + value.deinit(); + } + symbols.deinit(); + } + + for (ctx.symtab.items) |entry| { + const gop = try symbols.getOrPut(@intCast(entry.off)); + if (!gop.found_existing) { + gop.value_ptr.* = std.ArrayList([]const u8).init(ctx.gpa); + } + try gop.value_ptr.append(entry.name); + } + + try writer.print("{s}\n", .{archive_symtab_label}); + for (symbols.keys(), symbols.values()) |off, values| { + try writer.print("in object {s}\n", .{files.get(off).?}); + for (values.items) |value| { + try writer.print("{s}\n", .{value}); + } + } + } + + fn dumpObjects(ctx: ArchiveContext, writer: anytype) !void { + for (ctx.objects.items) |object| { + try writer.print("object {s}\n", .{object.name}); + const output = try parseAndDumpObject(ctx.gpa, ctx.data[object.off..][0..object.len]); + defer ctx.gpa.free(output); + try writer.print("{s}\n", .{output}); + } + } + + fn getString(ctx: ArchiveContext, off: u32) []const u8 { + assert(off < ctx.strtab.len); + const name = mem.sliceTo(@as([*:'\n']const u8, @ptrCast(ctx.strtab.ptr + off)), 0); + return name[0 .. name.len - 1]; + } + + const ArSymtabEntry = struct { + name: [:0]const u8, + off: u64, + }; + }; + + fn parseAndDumpObject(gpa: Allocator, bytes: []const u8) ![]const u8 { var stream = std.io.fixedBufferStream(bytes); const reader = stream.reader(); @@ -924,7 +1077,7 @@ const ElfDumper = struct { const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(bytes.ptr + hdr.e_shoff))[0..hdr.e_shnum]; const phdrs = @as([*]align(1) const elf.Elf64_Phdr, @ptrCast(bytes.ptr + hdr.e_phoff))[0..hdr.e_phnum]; - var ctx = Context{ + var ctx = ObjectContext{ .gpa = gpa, .data = bytes, .hdr = hdr, @@ -932,14 +1085,14 @@ const ElfDumper = struct { .phdrs = phdrs, .shstrtab = undefined, }; - ctx.shstrtab = getSectionContents(ctx, ctx.hdr.e_shstrndx); + ctx.shstrtab = ctx.getSectionContents(ctx.hdr.e_shstrndx); for (ctx.shdrs, 0..) |shdr, i| switch (shdr.sh_type) { elf.SHT_SYMTAB, elf.SHT_DYNSYM => { - const raw = getSectionContents(ctx, i); + const raw = ctx.getSectionContents(i); const nsyms = @divExact(raw.len, @sizeOf(elf.Elf64_Sym)); const symbols = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw.ptr))[0..nsyms]; - const strings = getSectionContents(ctx, shdr.sh_link); + const strings = ctx.getSectionContents(shdr.sh_link); switch (shdr.sh_type) { elf.SHT_SYMTAB => { @@ -964,201 +1117,348 @@ const ElfDumper = struct { var output = std.ArrayList(u8).init(gpa); const writer = output.writer(); - try dumpHeader(ctx, writer); - try dumpShdrs(ctx, writer); - try dumpPhdrs(ctx, writer); - try dumpDynamicSection(ctx, writer); - try dumpSymtab(ctx, .symtab, writer); - try dumpSymtab(ctx, .dysymtab, writer); + try ctx.dumpHeader(writer); + try ctx.dumpShdrs(writer); + try ctx.dumpPhdrs(writer); + try ctx.dumpDynamicSection(writer); + try ctx.dumpSymtab(.symtab, writer); + try ctx.dumpSymtab(.dysymtab, writer); return output.toOwnedSlice(); } - inline fn getSectionName(ctx: Context, shndx: usize) []const u8 { - const shdr = ctx.shdrs[shndx]; - return getString(ctx.shstrtab, shdr.sh_name); - } + const ObjectContext = struct { + gpa: Allocator, + data: []const u8, + hdr: elf.Elf64_Ehdr, + shdrs: []align(1) const elf.Elf64_Shdr, + phdrs: []align(1) const elf.Elf64_Phdr, + shstrtab: []const u8, + symtab: ?Symtab = null, + dysymtab: ?Symtab = null, - fn getSectionContents(ctx: Context, shndx: usize) []const u8 { - const shdr = ctx.shdrs[shndx]; - assert(shdr.sh_offset < ctx.data.len); - assert(shdr.sh_offset + shdr.sh_size <= ctx.data.len); - return ctx.data[shdr.sh_offset..][0..shdr.sh_size]; - } + fn dumpHeader(ctx: ObjectContext, writer: anytype) !void { + try writer.writeAll("header\n"); + try writer.print("type {s}\n", .{@tagName(ctx.hdr.e_type)}); + try writer.print("entry {x}\n", .{ctx.hdr.e_entry}); + } - fn getSectionByName(ctx: Context, name: []const u8) ?usize { - for (0..ctx.shdrs.len) |shndx| { - if (mem.eql(u8, getSectionName(ctx, shndx), name)) return shndx; - } else return null; - } + fn dumpPhdrs(ctx: ObjectContext, writer: anytype) !void { + if (ctx.phdrs.len == 0) return; + + try writer.writeAll("program headers\n"); + + for (ctx.phdrs, 0..) |phdr, phndx| { + try writer.print("phdr {d}\n", .{phndx}); + try writer.print("type {s}\n", .{fmtPhType(phdr.p_type)}); + try writer.print("vaddr {x}\n", .{phdr.p_vaddr}); + try writer.print("paddr {x}\n", .{phdr.p_paddr}); + try writer.print("offset {x}\n", .{phdr.p_offset}); + try writer.print("memsz {x}\n", .{phdr.p_memsz}); + try writer.print("filesz {x}\n", .{phdr.p_filesz}); + try writer.print("align {x}\n", .{phdr.p_align}); + + { + const flags = phdr.p_flags; + try writer.writeAll("flags"); + if (flags > 0) try writer.writeByte(' '); + if (flags & elf.PF_R != 0) { + try writer.writeByte('R'); + } + if (flags & elf.PF_W != 0) { + try writer.writeByte('W'); + } + if (flags & elf.PF_X != 0) { + try writer.writeByte('E'); + } + if (flags & elf.PF_MASKOS != 0) { + try writer.writeAll("OS"); + } + if (flags & elf.PF_MASKPROC != 0) { + try writer.writeAll("PROC"); + } + try writer.writeByte('\n'); + } + } + } + + fn dumpShdrs(ctx: ObjectContext, writer: anytype) !void { + if (ctx.shdrs.len == 0) return; + + try writer.writeAll("section headers\n"); + + for (ctx.shdrs, 0..) |shdr, shndx| { + try writer.print("shdr {d}\n", .{shndx}); + try writer.print("name {s}\n", .{ctx.getSectionName(shndx)}); + try writer.print("type {s}\n", .{fmtShType(shdr.sh_type)}); + try writer.print("addr {x}\n", .{shdr.sh_addr}); + try writer.print("offset {x}\n", .{shdr.sh_offset}); + try writer.print("size {x}\n", .{shdr.sh_size}); + try writer.print("addralign {x}\n", .{shdr.sh_addralign}); + // TODO dump formatted sh_flags + } + } + + fn dumpDynamicSection(ctx: ObjectContext, writer: anytype) !void { + const shndx = ctx.getSectionByName(".dynamic") orelse return; + const shdr = ctx.shdrs[shndx]; + const strtab = ctx.getSectionContents(shdr.sh_link); + const data = ctx.getSectionContents(shndx); + const nentries = @divExact(data.len, @sizeOf(elf.Elf64_Dyn)); + const entries = @as([*]align(1) const elf.Elf64_Dyn, @ptrCast(data.ptr))[0..nentries]; + + try writer.writeAll(ElfDumper.dynamic_section_label ++ "\n"); + + for (entries) |entry| { + const key = @as(u64, @bitCast(entry.d_tag)); + const value = entry.d_val; + + const key_str = switch (key) { + elf.DT_NEEDED => "NEEDED", + elf.DT_SONAME => "SONAME", + elf.DT_INIT_ARRAY => "INIT_ARRAY", + elf.DT_INIT_ARRAYSZ => "INIT_ARRAYSZ", + elf.DT_FINI_ARRAY => "FINI_ARRAY", + elf.DT_FINI_ARRAYSZ => "FINI_ARRAYSZ", + elf.DT_HASH => "HASH", + elf.DT_GNU_HASH => "GNU_HASH", + elf.DT_STRTAB => "STRTAB", + elf.DT_SYMTAB => "SYMTAB", + elf.DT_STRSZ => "STRSZ", + elf.DT_SYMENT => "SYMENT", + elf.DT_PLTGOT => "PLTGOT", + elf.DT_PLTRELSZ => "PLTRELSZ", + elf.DT_PLTREL => "PLTREL", + elf.DT_JMPREL => "JMPREL", + elf.DT_RELA => "RELA", + elf.DT_RELASZ => "RELASZ", + elf.DT_RELAENT => "RELAENT", + elf.DT_VERDEF => "VERDEF", + elf.DT_VERDEFNUM => "VERDEFNUM", + elf.DT_FLAGS => "FLAGS", + elf.DT_FLAGS_1 => "FLAGS_1", + elf.DT_VERNEED => "VERNEED", + elf.DT_VERNEEDNUM => "VERNEEDNUM", + elf.DT_VERSYM => "VERSYM", + elf.DT_RELACOUNT => "RELACOUNT", + elf.DT_RPATH => "RPATH", + elf.DT_RUNPATH => "RUNPATH", + elf.DT_INIT => "INIT", + elf.DT_FINI => "FINI", + elf.DT_NULL => "NULL", + else => "UNKNOWN", + }; + try writer.print("{s}", .{key_str}); + + switch (key) { + elf.DT_NEEDED, + elf.DT_SONAME, + elf.DT_RPATH, + elf.DT_RUNPATH, + => { + const name = getString(strtab, @intCast(value)); + try writer.print(" {s}", .{name}); + }, + + elf.DT_INIT_ARRAY, + elf.DT_FINI_ARRAY, + elf.DT_HASH, + elf.DT_GNU_HASH, + elf.DT_STRTAB, + elf.DT_SYMTAB, + elf.DT_PLTGOT, + elf.DT_JMPREL, + elf.DT_RELA, + elf.DT_VERDEF, + elf.DT_VERNEED, + elf.DT_VERSYM, + elf.DT_INIT, + elf.DT_FINI, + elf.DT_NULL, + => try writer.print(" {x}", .{value}), + + elf.DT_INIT_ARRAYSZ, + elf.DT_FINI_ARRAYSZ, + elf.DT_STRSZ, + elf.DT_SYMENT, + elf.DT_PLTRELSZ, + elf.DT_RELASZ, + elf.DT_RELAENT, + elf.DT_RELACOUNT, + => try writer.print(" {d}", .{value}), + + elf.DT_PLTREL => try writer.writeAll(switch (value) { + elf.DT_REL => " REL", + elf.DT_RELA => " RELA", + else => " UNKNOWN", + }), + + elf.DT_FLAGS => if (value > 0) { + if (value & elf.DF_ORIGIN != 0) try writer.writeAll(" ORIGIN"); + if (value & elf.DF_SYMBOLIC != 0) try writer.writeAll(" SYMBOLIC"); + if (value & elf.DF_TEXTREL != 0) try writer.writeAll(" TEXTREL"); + if (value & elf.DF_BIND_NOW != 0) try writer.writeAll(" BIND_NOW"); + if (value & elf.DF_STATIC_TLS != 0) try writer.writeAll(" STATIC_TLS"); + }, + + elf.DT_FLAGS_1 => if (value > 0) { + if (value & elf.DF_1_NOW != 0) try writer.writeAll(" NOW"); + if (value & elf.DF_1_GLOBAL != 0) try writer.writeAll(" GLOBAL"); + if (value & elf.DF_1_GROUP != 0) try writer.writeAll(" GROUP"); + if (value & elf.DF_1_NODELETE != 0) try writer.writeAll(" NODELETE"); + if (value & elf.DF_1_LOADFLTR != 0) try writer.writeAll(" LOADFLTR"); + if (value & elf.DF_1_INITFIRST != 0) try writer.writeAll(" INITFIRST"); + if (value & elf.DF_1_NOOPEN != 0) try writer.writeAll(" NOOPEN"); + if (value & elf.DF_1_ORIGIN != 0) try writer.writeAll(" ORIGIN"); + if (value & elf.DF_1_DIRECT != 0) try writer.writeAll(" DIRECT"); + if (value & elf.DF_1_TRANS != 0) try writer.writeAll(" TRANS"); + if (value & elf.DF_1_INTERPOSE != 0) try writer.writeAll(" INTERPOSE"); + if (value & elf.DF_1_NODEFLIB != 0) try writer.writeAll(" NODEFLIB"); + if (value & elf.DF_1_NODUMP != 0) try writer.writeAll(" NODUMP"); + if (value & elf.DF_1_CONFALT != 0) try writer.writeAll(" CONFALT"); + if (value & elf.DF_1_ENDFILTEE != 0) try writer.writeAll(" ENDFILTEE"); + if (value & elf.DF_1_DISPRELDNE != 0) try writer.writeAll(" DISPRELDNE"); + if (value & elf.DF_1_DISPRELPND != 0) try writer.writeAll(" DISPRELPND"); + if (value & elf.DF_1_NODIRECT != 0) try writer.writeAll(" NODIRECT"); + if (value & elf.DF_1_IGNMULDEF != 0) try writer.writeAll(" IGNMULDEF"); + if (value & elf.DF_1_NOKSYMS != 0) try writer.writeAll(" NOKSYMS"); + if (value & elf.DF_1_NOHDR != 0) try writer.writeAll(" NOHDR"); + if (value & elf.DF_1_EDITED != 0) try writer.writeAll(" EDITED"); + if (value & elf.DF_1_NORELOC != 0) try writer.writeAll(" NORELOC"); + if (value & elf.DF_1_SYMINTPOSE != 0) try writer.writeAll(" SYMINTPOSE"); + if (value & elf.DF_1_GLOBAUDIT != 0) try writer.writeAll(" GLOBAUDIT"); + if (value & elf.DF_1_SINGLETON != 0) try writer.writeAll(" SINGLETON"); + if (value & elf.DF_1_STUB != 0) try writer.writeAll(" STUB"); + if (value & elf.DF_1_PIE != 0) try writer.writeAll(" PIE"); + }, + + else => try writer.print(" {x}", .{value}), + } + try writer.writeByte('\n'); + } + } + + fn dumpSymtab(ctx: ObjectContext, comptime @"type": enum { symtab, dysymtab }, writer: anytype) !void { + const symtab = switch (@"type") { + .symtab => ctx.symtab, + .dysymtab => ctx.dysymtab, + } orelse return; + + try writer.writeAll(switch (@"type") { + .symtab => symtab_label, + .dysymtab => dynamic_symtab_label, + } ++ "\n"); + + for (symtab.symbols, 0..) |sym, index| { + try writer.print("{x} {x}", .{ sym.st_value, sym.st_size }); + + { + if (elf.SHN_LORESERVE <= sym.st_shndx and sym.st_shndx < elf.SHN_HIRESERVE) { + if (elf.SHN_LOPROC <= sym.st_shndx and sym.st_shndx < elf.SHN_HIPROC) { + try writer.print(" LO+{d}", .{sym.st_shndx - elf.SHN_LOPROC}); + } else { + const sym_ndx = switch (sym.st_shndx) { + elf.SHN_ABS => "ABS", + elf.SHN_COMMON => "COM", + elf.SHN_LIVEPATCH => "LIV", + else => "UNK", + }; + try writer.print(" {s}", .{sym_ndx}); + } + } else if (sym.st_shndx == elf.SHN_UNDEF) { + try writer.writeAll(" UND"); + } else { + try writer.print(" {x}", .{sym.st_shndx}); + } + } + + blk: { + const tt = sym.st_type(); + const sym_type = switch (tt) { + elf.STT_NOTYPE => "NOTYPE", + elf.STT_OBJECT => "OBJECT", + elf.STT_FUNC => "FUNC", + elf.STT_SECTION => "SECTION", + elf.STT_FILE => "FILE", + elf.STT_COMMON => "COMMON", + elf.STT_TLS => "TLS", + elf.STT_NUM => "NUM", + elf.STT_GNU_IFUNC => "IFUNC", + else => if (elf.STT_LOPROC <= tt and tt < elf.STT_HIPROC) { + break :blk try writer.print(" LOPROC+{d}", .{tt - elf.STT_LOPROC}); + } else if (elf.STT_LOOS <= tt and tt < elf.STT_HIOS) { + break :blk try writer.print(" LOOS+{d}", .{tt - elf.STT_LOOS}); + } else "UNK", + }; + try writer.print(" {s}", .{sym_type}); + } + + blk: { + const bind = sym.st_bind(); + const sym_bind = switch (bind) { + elf.STB_LOCAL => "LOCAL", + elf.STB_GLOBAL => "GLOBAL", + elf.STB_WEAK => "WEAK", + elf.STB_NUM => "NUM", + else => if (elf.STB_LOPROC <= bind and bind < elf.STB_HIPROC) { + break :blk try writer.print(" LOPROC+{d}", .{bind - elf.STB_LOPROC}); + } else if (elf.STB_LOOS <= bind and bind < elf.STB_HIOS) { + break :blk try writer.print(" LOOS+{d}", .{bind - elf.STB_LOOS}); + } else "UNKNOWN", + }; + try writer.print(" {s}", .{sym_bind}); + } + + const sym_vis = @as(elf.STV, @enumFromInt(sym.st_other)); + try writer.print(" {s}", .{@tagName(sym_vis)}); + + const sym_name = switch (sym.st_type()) { + elf.STT_SECTION => ctx.getSectionName(sym.st_shndx), + else => symtab.getName(index).?, + }; + try writer.print(" {s}\n", .{sym_name}); + } + } + + inline fn getSectionName(ctx: ObjectContext, shndx: usize) []const u8 { + const shdr = ctx.shdrs[shndx]; + return getString(ctx.shstrtab, shdr.sh_name); + } + + fn getSectionContents(ctx: ObjectContext, shndx: usize) []const u8 { + const shdr = ctx.shdrs[shndx]; + assert(shdr.sh_offset < ctx.data.len); + assert(shdr.sh_offset + shdr.sh_size <= ctx.data.len); + return ctx.data[shdr.sh_offset..][0..shdr.sh_size]; + } + + fn getSectionByName(ctx: ObjectContext, name: []const u8) ?usize { + for (0..ctx.shdrs.len) |shndx| { + if (mem.eql(u8, ctx.getSectionName(shndx), name)) return shndx; + } else return null; + } + }; + + const Symtab = struct { + symbols: []align(1) const elf.Elf64_Sym, + strings: []const u8, + + fn get(st: Symtab, index: usize) ?elf.Elf64_Sym { + if (index >= st.symbols.len) return null; + return st.symbols[index]; + } + + fn getName(st: Symtab, index: usize) ?[]const u8 { + const sym = st.get(index) orelse return null; + return getString(st.strings, sym.st_name); + } + }; fn getString(strtab: []const u8, off: u32) []const u8 { assert(off < strtab.len); return mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + off)), 0); } - fn dumpHeader(ctx: Context, writer: anytype) !void { - try writer.writeAll("header\n"); - try writer.print("type {s}\n", .{@tagName(ctx.hdr.e_type)}); - try writer.print("entry {x}\n", .{ctx.hdr.e_entry}); - } - - fn dumpShdrs(ctx: Context, writer: anytype) !void { - if (ctx.shdrs.len == 0) return; - - try writer.writeAll("section headers\n"); - - for (ctx.shdrs, 0..) |shdr, shndx| { - try writer.print("shdr {d}\n", .{shndx}); - try writer.print("name {s}\n", .{getSectionName(ctx, shndx)}); - try writer.print("type {s}\n", .{fmtShType(shdr.sh_type)}); - try writer.print("addr {x}\n", .{shdr.sh_addr}); - try writer.print("offset {x}\n", .{shdr.sh_offset}); - try writer.print("size {x}\n", .{shdr.sh_size}); - try writer.print("addralign {x}\n", .{shdr.sh_addralign}); - // TODO dump formatted sh_flags - } - } - - fn dumpDynamicSection(ctx: Context, writer: anytype) !void { - const shndx = getSectionByName(ctx, ".dynamic") orelse return; - const shdr = ctx.shdrs[shndx]; - const strtab = getSectionContents(ctx, shdr.sh_link); - const data = getSectionContents(ctx, shndx); - const nentries = @divExact(data.len, @sizeOf(elf.Elf64_Dyn)); - const entries = @as([*]align(1) const elf.Elf64_Dyn, @ptrCast(data.ptr))[0..nentries]; - - try writer.writeAll(ElfDumper.dynamic_section_label ++ "\n"); - - for (entries) |entry| { - const key = @as(u64, @bitCast(entry.d_tag)); - const value = entry.d_val; - - const key_str = switch (key) { - elf.DT_NEEDED => "NEEDED", - elf.DT_SONAME => "SONAME", - elf.DT_INIT_ARRAY => "INIT_ARRAY", - elf.DT_INIT_ARRAYSZ => "INIT_ARRAYSZ", - elf.DT_FINI_ARRAY => "FINI_ARRAY", - elf.DT_FINI_ARRAYSZ => "FINI_ARRAYSZ", - elf.DT_HASH => "HASH", - elf.DT_GNU_HASH => "GNU_HASH", - elf.DT_STRTAB => "STRTAB", - elf.DT_SYMTAB => "SYMTAB", - elf.DT_STRSZ => "STRSZ", - elf.DT_SYMENT => "SYMENT", - elf.DT_PLTGOT => "PLTGOT", - elf.DT_PLTRELSZ => "PLTRELSZ", - elf.DT_PLTREL => "PLTREL", - elf.DT_JMPREL => "JMPREL", - elf.DT_RELA => "RELA", - elf.DT_RELASZ => "RELASZ", - elf.DT_RELAENT => "RELAENT", - elf.DT_VERDEF => "VERDEF", - elf.DT_VERDEFNUM => "VERDEFNUM", - elf.DT_FLAGS => "FLAGS", - elf.DT_FLAGS_1 => "FLAGS_1", - elf.DT_VERNEED => "VERNEED", - elf.DT_VERNEEDNUM => "VERNEEDNUM", - elf.DT_VERSYM => "VERSYM", - elf.DT_RELACOUNT => "RELACOUNT", - elf.DT_RPATH => "RPATH", - elf.DT_RUNPATH => "RUNPATH", - elf.DT_INIT => "INIT", - elf.DT_FINI => "FINI", - elf.DT_NULL => "NULL", - else => "UNKNOWN", - }; - try writer.print("{s}", .{key_str}); - - switch (key) { - elf.DT_NEEDED, - elf.DT_SONAME, - elf.DT_RPATH, - elf.DT_RUNPATH, - => { - const name = getString(strtab, @intCast(value)); - try writer.print(" {s}", .{name}); - }, - - elf.DT_INIT_ARRAY, - elf.DT_FINI_ARRAY, - elf.DT_HASH, - elf.DT_GNU_HASH, - elf.DT_STRTAB, - elf.DT_SYMTAB, - elf.DT_PLTGOT, - elf.DT_JMPREL, - elf.DT_RELA, - elf.DT_VERDEF, - elf.DT_VERNEED, - elf.DT_VERSYM, - elf.DT_INIT, - elf.DT_FINI, - elf.DT_NULL, - => try writer.print(" {x}", .{value}), - - elf.DT_INIT_ARRAYSZ, - elf.DT_FINI_ARRAYSZ, - elf.DT_STRSZ, - elf.DT_SYMENT, - elf.DT_PLTRELSZ, - elf.DT_RELASZ, - elf.DT_RELAENT, - elf.DT_RELACOUNT, - => try writer.print(" {d}", .{value}), - - elf.DT_PLTREL => try writer.writeAll(switch (value) { - elf.DT_REL => " REL", - elf.DT_RELA => " RELA", - else => " UNKNOWN", - }), - - elf.DT_FLAGS => if (value > 0) { - if (value & elf.DF_ORIGIN != 0) try writer.writeAll(" ORIGIN"); - if (value & elf.DF_SYMBOLIC != 0) try writer.writeAll(" SYMBOLIC"); - if (value & elf.DF_TEXTREL != 0) try writer.writeAll(" TEXTREL"); - if (value & elf.DF_BIND_NOW != 0) try writer.writeAll(" BIND_NOW"); - if (value & elf.DF_STATIC_TLS != 0) try writer.writeAll(" STATIC_TLS"); - }, - - elf.DT_FLAGS_1 => if (value > 0) { - if (value & elf.DF_1_NOW != 0) try writer.writeAll(" NOW"); - if (value & elf.DF_1_GLOBAL != 0) try writer.writeAll(" GLOBAL"); - if (value & elf.DF_1_GROUP != 0) try writer.writeAll(" GROUP"); - if (value & elf.DF_1_NODELETE != 0) try writer.writeAll(" NODELETE"); - if (value & elf.DF_1_LOADFLTR != 0) try writer.writeAll(" LOADFLTR"); - if (value & elf.DF_1_INITFIRST != 0) try writer.writeAll(" INITFIRST"); - if (value & elf.DF_1_NOOPEN != 0) try writer.writeAll(" NOOPEN"); - if (value & elf.DF_1_ORIGIN != 0) try writer.writeAll(" ORIGIN"); - if (value & elf.DF_1_DIRECT != 0) try writer.writeAll(" DIRECT"); - if (value & elf.DF_1_TRANS != 0) try writer.writeAll(" TRANS"); - if (value & elf.DF_1_INTERPOSE != 0) try writer.writeAll(" INTERPOSE"); - if (value & elf.DF_1_NODEFLIB != 0) try writer.writeAll(" NODEFLIB"); - if (value & elf.DF_1_NODUMP != 0) try writer.writeAll(" NODUMP"); - if (value & elf.DF_1_CONFALT != 0) try writer.writeAll(" CONFALT"); - if (value & elf.DF_1_ENDFILTEE != 0) try writer.writeAll(" ENDFILTEE"); - if (value & elf.DF_1_DISPRELDNE != 0) try writer.writeAll(" DISPRELDNE"); - if (value & elf.DF_1_DISPRELPND != 0) try writer.writeAll(" DISPRELPND"); - if (value & elf.DF_1_NODIRECT != 0) try writer.writeAll(" NODIRECT"); - if (value & elf.DF_1_IGNMULDEF != 0) try writer.writeAll(" IGNMULDEF"); - if (value & elf.DF_1_NOKSYMS != 0) try writer.writeAll(" NOKSYMS"); - if (value & elf.DF_1_NOHDR != 0) try writer.writeAll(" NOHDR"); - if (value & elf.DF_1_EDITED != 0) try writer.writeAll(" EDITED"); - if (value & elf.DF_1_NORELOC != 0) try writer.writeAll(" NORELOC"); - if (value & elf.DF_1_SYMINTPOSE != 0) try writer.writeAll(" SYMINTPOSE"); - if (value & elf.DF_1_GLOBAUDIT != 0) try writer.writeAll(" GLOBAUDIT"); - if (value & elf.DF_1_SINGLETON != 0) try writer.writeAll(" SINGLETON"); - if (value & elf.DF_1_STUB != 0) try writer.writeAll(" STUB"); - if (value & elf.DF_1_PIE != 0) try writer.writeAll(" PIE"); - }, - - else => try writer.print(" {x}", .{value}), - } - try writer.writeByte('\n'); - } - } - fn fmtShType(sh_type: u32) std.fmt.Formatter(formatShType) { return .{ .data = sh_type }; } @@ -1206,45 +1506,6 @@ const ElfDumper = struct { try writer.writeAll(name); } - fn dumpPhdrs(ctx: Context, writer: anytype) !void { - if (ctx.phdrs.len == 0) return; - - try writer.writeAll("program headers\n"); - - for (ctx.phdrs, 0..) |phdr, phndx| { - try writer.print("phdr {d}\n", .{phndx}); - try writer.print("type {s}\n", .{fmtPhType(phdr.p_type)}); - try writer.print("vaddr {x}\n", .{phdr.p_vaddr}); - try writer.print("paddr {x}\n", .{phdr.p_paddr}); - try writer.print("offset {x}\n", .{phdr.p_offset}); - try writer.print("memsz {x}\n", .{phdr.p_memsz}); - try writer.print("filesz {x}\n", .{phdr.p_filesz}); - try writer.print("align {x}\n", .{phdr.p_align}); - - { - const flags = phdr.p_flags; - try writer.writeAll("flags"); - if (flags > 0) try writer.writeByte(' '); - if (flags & elf.PF_R != 0) { - try writer.writeByte('R'); - } - if (flags & elf.PF_W != 0) { - try writer.writeByte('W'); - } - if (flags & elf.PF_X != 0) { - try writer.writeByte('E'); - } - if (flags & elf.PF_MASKOS != 0) { - try writer.writeAll("OS"); - } - if (flags & elf.PF_MASKPROC != 0) { - try writer.writeAll("PROC"); - } - try writer.writeByte('\n'); - } - } - } - fn fmtPhType(ph_type: u32) std.fmt.Formatter(formatPhType) { return .{ .data = ph_type }; } @@ -1278,88 +1539,6 @@ const ElfDumper = struct { }; try writer.writeAll(p_type); } - - fn dumpSymtab(ctx: Context, comptime @"type": enum { symtab, dysymtab }, writer: anytype) !void { - const symtab = switch (@"type") { - .symtab => ctx.symtab, - .dysymtab => ctx.dysymtab, - } orelse return; - - try writer.writeAll(switch (@"type") { - .symtab => symtab_label, - .dysymtab => dynamic_symtab_label, - } ++ "\n"); - - for (symtab.symbols, 0..) |sym, index| { - try writer.print("{x} {x}", .{ sym.st_value, sym.st_size }); - - { - if (elf.SHN_LORESERVE <= sym.st_shndx and sym.st_shndx < elf.SHN_HIRESERVE) { - if (elf.SHN_LOPROC <= sym.st_shndx and sym.st_shndx < elf.SHN_HIPROC) { - try writer.print(" LO+{d}", .{sym.st_shndx - elf.SHN_LOPROC}); - } else { - const sym_ndx = &switch (sym.st_shndx) { - elf.SHN_ABS => "ABS", - elf.SHN_COMMON => "COM", - elf.SHN_LIVEPATCH => "LIV", - else => "UNK", - }; - try writer.print(" {s}", .{sym_ndx}); - } - } else if (sym.st_shndx == elf.SHN_UNDEF) { - try writer.writeAll(" UND"); - } else { - try writer.print(" {x}", .{sym.st_shndx}); - } - } - - blk: { - const tt = sym.st_type(); - const sym_type = switch (tt) { - elf.STT_NOTYPE => "NOTYPE", - elf.STT_OBJECT => "OBJECT", - elf.STT_FUNC => "FUNC", - elf.STT_SECTION => "SECTION", - elf.STT_FILE => "FILE", - elf.STT_COMMON => "COMMON", - elf.STT_TLS => "TLS", - elf.STT_NUM => "NUM", - elf.STT_GNU_IFUNC => "IFUNC", - else => if (elf.STT_LOPROC <= tt and tt < elf.STT_HIPROC) { - break :blk try writer.print(" LOPROC+{d}", .{tt - elf.STT_LOPROC}); - } else if (elf.STT_LOOS <= tt and tt < elf.STT_HIOS) { - break :blk try writer.print(" LOOS+{d}", .{tt - elf.STT_LOOS}); - } else "UNK", - }; - try writer.print(" {s}", .{sym_type}); - } - - blk: { - const bind = sym.st_bind(); - const sym_bind = switch (bind) { - elf.STB_LOCAL => "LOCAL", - elf.STB_GLOBAL => "GLOBAL", - elf.STB_WEAK => "WEAK", - elf.STB_NUM => "NUM", - else => if (elf.STB_LOPROC <= bind and bind < elf.STB_HIPROC) { - break :blk try writer.print(" LOPROC+{d}", .{bind - elf.STB_LOPROC}); - } else if (elf.STB_LOOS <= bind and bind < elf.STB_HIOS) { - break :blk try writer.print(" LOOS+{d}", .{bind - elf.STB_LOOS}); - } else "UNKNOWN", - }; - try writer.print(" {s}", .{sym_bind}); - } - - const sym_vis = @as(elf.STV, @enumFromInt(sym.st_other)); - try writer.print(" {s}", .{@tagName(sym_vis)}); - - const sym_name = switch (sym.st_type()) { - elf.STT_SECTION => getSectionName(ctx, sym.st_shndx), - else => symtab.getName(index).?, - }; - try writer.print(" {s}\n", .{sym_name}); - } - } }; const WasmDumper = struct { diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 7cb36a17ee..4c53898df8 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1896,3 +1896,92 @@ pub const STV = enum(u2) { HIDDEN = 2, PROTECTED = 3, }; + +pub const ar_hdr = extern struct { + /// Member file name, sometimes / terminated. + ar_name: [16]u8, + + /// File date, decimal seconds since Epoch. + ar_date: [12]u8, + + /// User ID, in ASCII format. + ar_uid: [6]u8, + + /// Group ID, in ASCII format. + ar_gid: [6]u8, + + /// File mode, in ASCII octal. + ar_mode: [8]u8, + + /// File size, in ASCII decimal. + ar_size: [10]u8, + + /// Always contains ARFMAG. + ar_fmag: [2]u8, + + pub fn date(self: ar_hdr) std.fmt.ParseIntError!u64 { + const value = mem.trimRight(u8, &self.ar_date, &[_]u8{0x20}); + return std.fmt.parseInt(u64, value, 10); + } + + pub fn size(self: ar_hdr) std.fmt.ParseIntError!u32 { + const value = mem.trimRight(u8, &self.ar_size, &[_]u8{0x20}); + return std.fmt.parseInt(u32, value, 10); + } + + pub fn isStrtab(self: ar_hdr) bool { + return mem.eql(u8, &self.ar_name, STRNAME); + } + + pub fn isSymtab(self: ar_hdr) bool { + return mem.eql(u8, &self.ar_name, SYMNAME); + } + + pub fn isSymtab64(self: ar_hdr) bool { + return mem.eql(u8, &self.ar_name, SYM64NAME); + } + + pub fn isSymdef(self: ar_hdr) bool { + return mem.eql(u8, &self.ar_name, SYMDEFNAME); + } + + pub fn isSymdefSorted(self: ar_hdr) bool { + return mem.eql(u8, &self.ar_name, SYMDEFSORTEDNAME); + } + + pub fn name(self: *const ar_hdr) ?[]const u8 { + const value = &self.ar_name; + if (value[0] == '/') return null; + const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len; + return value[0..sentinel]; + } + + pub fn nameOffset(self: ar_hdr) std.fmt.ParseIntError!?u32 { + const value = &self.ar_name; + if (value[0] != '/') return null; + const trimmed = mem.trimRight(u8, value, &[_]u8{0x20}); + return try std.fmt.parseInt(u32, trimmed[1..], 10); + } +}; + +fn genSpecialMemberName(comptime name: []const u8) *const [16]u8 { + assert(name.len <= 16); + const padding = 16 - name.len; + return name ++ &[_]u8{0x20} ** padding; +} + +// Archive files start with the ARMAG identifying string. Then follows a +// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// member indicates, for each member file. +/// String that begins an archive file. +pub const ARMAG = "!\n"; +/// String in ar_fmag at the end of each header. +pub const ARFMAG = "`\n"; +/// 32-bit symtab identifier +pub const SYMNAME = genSpecialMemberName("/"); +/// Strtab identifier +pub const STRNAME = genSpecialMemberName("//"); +/// 64-bit symtab identifier +pub const SYM64NAME = genSpecialMemberName("/SYM64/"); +pub const SYMDEFNAME = genSpecialMemberName("__.SYMDEF"); +pub const SYMDEFSORTEDNAME = genSpecialMemberName("__.SYMDEF SORTED"); diff --git a/src/Compilation.zig b/src/Compilation.zig index c2a48f57ff..c8618154d9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2304,10 +2304,16 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void defer comp.gpa.free(o_sub_path); // Work around windows `AccessDenied` if any files within this directory are open - // by doing the makeExecutable/makeWritable dance. + // by closing and reopening the file handles. const need_writable_dance = builtin.os.tag == .windows and comp.bin_file.file != null; if (need_writable_dance) { - try comp.bin_file.makeExecutable(); + // We cannot just call `makeExecutable` as it makes a false assumption that we have a + // file handle open only when linking an executable file. This used to be true when + // our linkers were incapable of emitting relocatables and static archive. Now that + // they are capable, we need to unconditionally close the file handle and re-open it + // in the follow up call to `makeWritable`. + comp.bin_file.file.?.close(); + comp.bin_file.file = null; } try comp.bin_file.renameTmpIntoCache(comp.local_cache_directory, tmp_dir_sub_path, o_sub_path); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 7311bd38b4..f4b9b5ff33 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -288,7 +288,9 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option const index = @as(File.Index, @intCast(try self.files.addOne(allocator))); self.files.set(index, .{ .zig_object = .{ .index = index, - .path = options.module.?.main_mod.root_src_path, + .path = try std.fmt.allocPrint(self.base.allocator, "{s}.o", .{std.fs.path.stem( + options.module.?.main_mod.root_src_path, + )}), } }); self.zig_object_index = index; try self.zigObjectPtr().?.init(self); @@ -940,14 +942,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node } else null; const gc_sections = self.base.options.gc_sections orelse false; - if (self.isRelocatable() and self.zig_object_index == null) { - if (self.isStaticLib()) { - var err = try self.addErrorWithNotes(0); - try err.addMsg(self, "fatal linker error: emitting static libs unimplemented", .{}); - return; - } + if (self.isObject() and self.zig_object_index == null) { // TODO this will become -r route I guess. For now, just copy the object file. - assert(self.base.file == null); // TODO uncomment once we implement -r const the_object_path = blk: { if (self.base.options.objects.len != 0) { break :blk self.base.options.objects[0].path; @@ -1287,8 +1283,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try positionals.append(.{ .path = ssp.full_object_path }); } - if (self.isStaticLib()) return self.flushStaticLib(comp, positionals.items); - for (positionals.items) |obj| { var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; self.parsePositional(obj.path, obj.must_link, &parse_ctx) catch |err| @@ -1394,6 +1388,16 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try self.handleAndReportParseError(obj.path, err, &parse_ctx); } + if (self.isStaticLib()) return self.flushStaticLib(comp); + + // Init all objects + for (self.objects.items) |index| { + try self.file(index).?.object.init(self); + } + for (self.shared_objects.items) |index| { + try self.file(index).?.shared_object.init(self); + } + // Dedup shared objects { var seen_dsos = std.StringHashMap(void).init(gpa); @@ -1523,18 +1527,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node } } -pub fn flushStaticLib( - self: *Elf, - comp: *Compilation, - positionals: []const Compilation.LinkObject, -) link.File.FlushError!void { +pub fn flushStaticLib(self: *Elf, comp: *Compilation) link.File.FlushError!void { _ = comp; - if (positionals.len > 0) { - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, "fatal linker error: too many input positionals", .{}); - try err.addNote(self, "TODO implement linking objects into an static library", .{}); - return; - } const gpa = self.base.allocator; // First, we flush relocatable object file generated with our backends. @@ -1546,27 +1540,33 @@ pub fn flushStaticLib( try self.initShStrtab(); try self.sortShdrs(); zig_object.updateRelaSectionSizes(self); - try self.updateSymtabSize(); + self.updateSymtabSizeObject(zig_object); self.updateShStrtabSize(); try self.allocateNonAllocSections(); try self.writeShdrTable(); try zig_object.writeRelaSections(self); - try self.writeSymtab(); + try self.writeSymtabObject(zig_object); try self.writeShStrtab(); try self.writeElfHeader(); } - // TODO parse positionals that we want to make part of the archive - - // TODO update ar symtab from parsed positionals + 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); + // Update ar symtab from parsed objects var ar_symtab: Archive.ArSymtab = .{}; defer ar_symtab.deinit(gpa); - if (self.zigObjectPtr()) |zig_object| { - try zig_object.updateArSymtab(&ar_symtab, self); + for (files.items) |index| { + try self.file(index).?.updateArSymtab(&ar_symtab, self); } ar_symtab.sort(); @@ -1575,25 +1575,32 @@ pub fn flushStaticLib( var ar_strtab: Archive.ArStrtab = .{}; defer ar_strtab.deinit(gpa); - if (self.zigObjectPtr()) |zig_object| { - try zig_object.updateArStrtab(gpa, &ar_strtab); - zig_object.updateArSize(self); + for (files.items) |index| { + const file_ptr = self.file(index).?; + try file_ptr.updateArStrtab(gpa, &ar_strtab); + file_ptr.updateArSize(self); } // Update file offsets of contributing objects. const total_size: usize = blk: { - var pos: usize = Archive.SARMAG; - pos += @sizeOf(Archive.ar_hdr) + ar_symtab.size(.p64); + var pos: usize = elf.ARMAG.len; + pos += @sizeOf(elf.ar_hdr) + ar_symtab.size(.p64); if (ar_strtab.size() > 0) { pos = mem.alignForward(usize, pos, 2); - pos += @sizeOf(Archive.ar_hdr) + ar_strtab.size(); + pos += @sizeOf(elf.ar_hdr) + ar_strtab.size(); } - if (self.zigObjectPtr()) |zig_object| { + for (files.items) |index| { + const file_ptr = self.file(index).?; + const state = switch (file_ptr) { + .zig_object => |x| &x.output_ar_state, + .object => |x| &x.output_ar_state, + else => unreachable, + }; pos = mem.alignForward(usize, pos, 2); - zig_object.output_ar_state.file_off = pos; - pos += @sizeOf(Archive.ar_hdr) + (math.cast(usize, zig_object.output_ar_state.size) orelse return error.Overflow); + state.file_off = pos; + pos += @sizeOf(elf.ar_hdr) + (math.cast(usize, state.size) orelse return error.Overflow); } break :blk pos; @@ -1609,7 +1616,7 @@ pub fn flushStaticLib( try buffer.ensureTotalCapacityPrecise(total_size); // Write magic - try buffer.writer().writeAll(Archive.ARMAG); + try buffer.writer().writeAll(elf.ARMAG); // Write symtab try ar_symtab.write(.p64, self, buffer.writer()); @@ -1621,9 +1628,9 @@ pub fn flushStaticLib( } // Write object files - if (self.zigObjectPtr()) |zig_object| { + for (files.items) |index| { if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0); - try zig_object.writeAr(self, buffer.writer()); + try self.file(index).?.writeAr(self, buffer.writer()); } assert(buffer.items.len == total_size); @@ -4054,7 +4061,7 @@ fn updateSectionSizes(self: *Elf) !void { self.shdrs.items[index].sh_size = self.verneed.size(); } - try self.updateSymtabSize(); + self.updateSymtabSize(); self.updateShStrtabSize(); } @@ -4477,7 +4484,7 @@ fn writeAtoms(self: *Elf) !void { try self.reportUndefined(&undefs); } -fn updateSymtabSize(self: *Elf) !void { +fn updateSymtabSize(self: *Elf) void { var sizes = SymtabSize{}; if (self.zigObjectPtr()) |zig_object| { @@ -4538,6 +4545,25 @@ fn updateSymtabSize(self: *Elf) !void { strtab.sh_size = sizes.strsize + 1; } +fn updateSymtabSizeObject(self: *Elf, zig_object: *ZigObject) void { + zig_object.asFile().updateSymtabSize(self); + const sizes = zig_object.output_symtab_size; + + const symtab_shdr = &self.shdrs.items[self.symtab_section_index.?]; + symtab_shdr.sh_info = sizes.nlocals + 1; + symtab_shdr.sh_link = self.strtab_section_index.?; + + const sym_size: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Sym), + .p64 => @sizeOf(elf.Elf64_Sym), + }; + const needed_size = (sizes.nlocals + sizes.nglobals + 1) * sym_size; + symtab_shdr.sh_size = needed_size; + + const strtab = &self.shdrs.items[self.strtab_section_index.?]; + strtab.sh_size = sizes.strsize + 1; +} + fn writeSyntheticSections(self: *Elf) !void { const gpa = self.base.allocator; @@ -4782,6 +4808,54 @@ fn writeSymtab(self: *Elf) !void { try self.base.file.?.pwriteAll(self.strtab.items, strtab_shdr.sh_offset); } +fn writeSymtabObject(self: *Elf, zig_object: *ZigObject) !void { + const gpa = self.base.allocator; + const symtab_shdr = self.shdrs.items[self.symtab_section_index.?]; + const strtab_shdr = self.shdrs.items[self.strtab_section_index.?]; + const sym_size: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Sym), + .p64 => @sizeOf(elf.Elf64_Sym), + }; + const nsyms = math.cast(usize, @divExact(symtab_shdr.sh_size, sym_size)) orelse return error.Overflow; + + log.debug("writing {d} symbols at 0x{x}", .{ nsyms, symtab_shdr.sh_offset }); + + try self.symtab.resize(gpa, nsyms); + const needed_strtab_size = math.cast(usize, strtab_shdr.sh_size - 1) orelse return error.Overflow; + try self.strtab.ensureUnusedCapacity(gpa, needed_strtab_size); + + zig_object.asFile().writeSymtab(self, .{ .ilocal = 1, .iglobal = symtab_shdr.sh_info }); + + const foreign_endian = self.base.options.target.cpu.arch.endian() != builtin.cpu.arch.endian(); + switch (self.ptr_width) { + .p32 => { + const buf = try gpa.alloc(elf.Elf32_Sym, self.symtab.items.len); + defer gpa.free(buf); + + for (buf, self.symtab.items) |*out, sym| { + out.* = .{ + .st_name = sym.st_name, + .st_info = sym.st_info, + .st_other = sym.st_other, + .st_shndx = sym.st_shndx, + .st_value = @as(u32, @intCast(sym.st_value)), + .st_size = @as(u32, @intCast(sym.st_size)), + }; + if (foreign_endian) mem.byteSwapAllFields(elf.Elf32_Sym, out); + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), symtab_shdr.sh_offset); + }, + .p64 => { + if (foreign_endian) { + for (self.symtab.items) |*sym| mem.byteSwapAllFields(elf.Elf64_Sym, sym); + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.symtab.items), symtab_shdr.sh_offset); + }, + } + + try self.base.file.?.pwriteAll(self.strtab.items, strtab_shdr.sh_offset); +} + /// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF. fn ptrWidthBytes(self: Elf) u8 { return switch (self.ptr_width) { diff --git a/src/link/Elf/Archive.zig b/src/link/Elf/Archive.zig index 58a9e541a7..8e405e966c 100644 --- a/src/link/Elf/Archive.zig +++ b/src/link/Elf/Archive.zig @@ -8,8 +8,8 @@ pub fn isArchive(path: []const u8) !bool { const file = try std.fs.cwd().openFile(path, .{}); defer file.close(); const reader = file.reader(); - const magic = reader.readBytesNoEof(SARMAG) catch return false; - if (!mem.eql(u8, &magic, ARMAG)) return false; + const magic = reader.readBytesNoEof(elf.ARMAG.len) catch return false; + if (!mem.eql(u8, &magic, elf.ARMAG)) return false; return true; } @@ -24,21 +24,19 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void { var stream = std.io.fixedBufferStream(self.data); const reader = stream.reader(); - _ = try reader.readBytesNoEof(SARMAG); + _ = try reader.readBytesNoEof(elf.ARMAG.len); while (true) { if (stream.pos >= self.data.len) break; + if (!mem.isAligned(stream.pos, 2)) stream.pos += 1; - if (stream.pos % 2 != 0) { - stream.pos += 1; - } - const hdr = try reader.readStruct(ar_hdr); + const hdr = try reader.readStruct(elf.ar_hdr); - if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { + if (!mem.eql(u8, &hdr.ar_fmag, elf.ARFMAG)) { // TODO convert into an error log.debug( "{s}: invalid header delimiter: expected '{s}', found '{s}'", - .{ self.path, std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag) }, + .{ self.path, std.fmt.fmtSliceEscapeLower(elf.ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag) }, ); return; } @@ -48,28 +46,23 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void { _ = stream.seekBy(size) catch {}; } - if (hdr.isSymtab()) continue; + if (hdr.isSymtab() or hdr.isSymtab64()) continue; if (hdr.isStrtab()) { self.strtab = self.data[stream.pos..][0..size]; continue; } + if (hdr.isSymdef() or hdr.isSymdefSorted()) continue; - const name = ar_hdr.getValue(&hdr.ar_name); - - if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue; - - const object_name = blk: { - if (name[0] == '/') { - const off = try std.fmt.parseInt(u32, name[1..], 10); - const object_name = self.getString(off); - break :blk try gpa.dupe(u8, object_name[0 .. object_name.len - 1]); // To account for trailing '/' - } - break :blk try gpa.dupe(u8, name); - }; + const name = if (hdr.name()) |name| + try gpa.dupe(u8, name) + else if (try hdr.nameOffset()) |off| + try gpa.dupe(u8, self.getString(off)) + else + unreachable; const object = Object{ .archive = try gpa.dupe(u8, self.path), - .path = object_name, + .path = name, .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]), .index = undefined, .alive = false, @@ -83,7 +76,8 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void { fn getString(self: Archive, off: u32) []const u8 { assert(off < self.strtab.len); - return mem.sliceTo(@as([*:strtab_delimiter]const u8, @ptrCast(self.strtab.ptr + off)), 0); + const name = mem.sliceTo(@as([*:'\n']const u8, @ptrCast(self.strtab.ptr + off)), 0); + return name[0 .. name.len - 1]; } pub fn setArHdr(opts: struct { @@ -94,8 +88,8 @@ pub fn setArHdr(opts: struct { name_off: u32, }, size: u32, -}) ar_hdr { - var hdr: ar_hdr = .{ +}) elf.ar_hdr { + var hdr: elf.ar_hdr = .{ .ar_name = undefined, .ar_date = undefined, .ar_uid = undefined, @@ -105,15 +99,15 @@ pub fn setArHdr(opts: struct { .ar_fmag = undefined, }; @memset(mem.asBytes(&hdr), 0x20); - @memcpy(&hdr.ar_fmag, Archive.ARFMAG); + @memcpy(&hdr.ar_fmag, elf.ARFMAG); { var stream = std.io.fixedBufferStream(&hdr.ar_name); const writer = stream.writer(); switch (opts.name) { - .symtab => writer.print("{s}", .{Archive.SYM64NAME}) catch unreachable, + .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable, .strtab => writer.print("//", .{}) catch unreachable, - .name => |x| writer.print("{s}", .{x}) catch unreachable, + .name => |x| writer.print("{s}/", .{x}) catch unreachable, .name_off => |x| writer.print("/{d}", .{x}) catch unreachable, } } @@ -125,72 +119,8 @@ pub fn setArHdr(opts: struct { return hdr; } -// Archive files start with the ARMAG identifying string. Then follows a -// `struct ar_hdr', and as many bytes of member file data as its `ar_size' -// member indicates, for each member file. -/// String that begins an archive file. -pub const ARMAG: *const [SARMAG:0]u8 = "!\n"; -/// Size of that string. -pub const SARMAG = 8; - -/// String in ar_fmag at the end of each header. -const ARFMAG: *const [2:0]u8 = "`\n"; - -/// Strtab identifier -const STRNAME: *const [2:0]u8 = "//"; - -/// 32-bit symtab identifier -const SYMNAME: *const [1:0]u8 = "/"; - -/// 64-bit symtab identifier -const SYM64NAME: *const [7:0]u8 = "/SYM64/"; - const strtab_delimiter = '\n'; - -pub const ar_hdr = extern struct { - /// Member file name, sometimes / terminated. - ar_name: [16]u8, - - /// File date, decimal seconds since Epoch. - ar_date: [12]u8, - - /// User ID, in ASCII format. - ar_uid: [6]u8, - - /// Group ID, in ASCII format. - ar_gid: [6]u8, - - /// File mode, in ASCII octal. - ar_mode: [8]u8, - - /// File size, in ASCII decimal. - ar_size: [10]u8, - - /// Always contains ARFMAG. - ar_fmag: [2]u8, - - fn date(self: ar_hdr) !u64 { - const value = getValue(&self.ar_date); - return std.fmt.parseInt(u64, value, 10); - } - - fn size(self: ar_hdr) !u32 { - const value = getValue(&self.ar_size); - return std.fmt.parseInt(u32, value, 10); - } - - fn getValue(raw: []const u8) []const u8 { - return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); - } - - fn isStrtab(self: ar_hdr) bool { - return mem.eql(u8, getValue(&self.ar_name), STRNAME); - } - - fn isSymtab(self: ar_hdr) bool { - return mem.eql(u8, getValue(&self.ar_name), SYMNAME) or mem.eql(u8, getValue(&self.ar_name), SYM64NAME); - } -}; +pub const max_member_name_len = 15; pub const ArSymtab = struct { symtab: std.ArrayListUnmanaged(Entry) = .{}, @@ -230,6 +160,9 @@ pub const ArSymtab = struct { if (elf_file.zigObjectPtr()) |zig_object| { offsets.putAssumeCapacityNoClobber(zig_object.index, zig_object.output_ar_state.file_off); } + for (elf_file.objects.items) |index| { + offsets.putAssumeCapacityNoClobber(index, elf_file.file(index).?.object.output_ar_state.file_off); + } // Number of symbols try writer.writeInt(u64, @as(u64, @intCast(ar.symtab.items.len)), .big); diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 968ff84853..e710a81ad3 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -20,6 +20,7 @@ alive: bool = true, num_dynrelocs: u32 = 0, output_symtab_size: Elf.SymtabSize = .{}, +output_ar_state: Archive.ArState = .{}, pub fn isObject(path: []const u8) !bool { const file = try std.fs.cwd().openFile(path, .{}); @@ -96,7 +97,9 @@ pub fn parse(self: *Object, elf_file: *Elf) !void { sym.st_name + strtab_bias; } } +} +pub fn init(self: *Object, elf_file: *Elf) !void { try self.initAtoms(elf_file); try self.initSymtab(elf_file); @@ -651,6 +654,36 @@ pub fn allocateAtoms(self: Object, elf_file: *Elf) void { } } +pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + const start = self.first_global orelse self.symtab.items.len; + + try ar_symtab.symtab.ensureUnusedCapacity(gpa, self.symtab.items.len - start); + + for (self.symtab.items[start..]) |sym| { + if (sym.st_shndx == elf.SHN_UNDEF) continue; + const off = try ar_symtab.strtab.insert(gpa, self.getString(sym.st_name)); + ar_symtab.symtab.appendAssumeCapacity(.{ .off = off, .file_index = self.index }); + } +} + +pub fn updateArSize(self: *Object) void { + self.output_ar_state.size = self.data.len; +} + +pub fn writeAr(self: Object, 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(self.data.len), + }); + try writer.writeAll(mem.asBytes(&hdr)); + try writer.writeAll(self.data); +} + pub fn locals(self: Object) []const Symbol.Index { const end = self.first_global orelse self.symbols.items.len; return self.symbols.items[0..end]; @@ -922,6 +955,7 @@ const math = std.math; const mem = std.mem; const Allocator = mem.Allocator; +const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); const Cie = eh_frame.Cie; const Elf = @import("../Elf.zig"); diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index 0924d3c761..1785232625 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -72,7 +72,6 @@ pub fn parse(self: *SharedObject, elf_file: *Elf) !void { } try self.parseVersions(elf_file); - try self.initSymtab(elf_file); } fn parseVersions(self: *SharedObject, elf_file: *Elf) !void { @@ -120,7 +119,7 @@ fn parseVersions(self: *SharedObject, elf_file: *Elf) !void { } } -fn initSymtab(self: *SharedObject, elf_file: *Elf) !void { +pub fn init(self: *SharedObject, elf_file: *Elf) !void { const gpa = elf_file.base.allocator; const symtab = self.getSymtabRaw(); const strtab = self.getStrtabRaw(); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index a6ac6cfc11..dd2126a99e 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -3,7 +3,6 @@ //! and any relocations that may have been emitted. //! Think about this as fake in-memory Object file for the Zig module. -/// Path is owned by Module and lives as long as *Module. path: []const u8, index: File.Index, @@ -78,7 +77,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void { try self.atoms.append(gpa, 0); // null input section try self.strtab.buffer.append(gpa, 0); - const name_off = try self.strtab.insert(gpa, std.fs.path.stem(self.path)); + const name_off = try self.strtab.insert(gpa, self.path); const symbol_index = try elf_file.addSymbol(); try self.local_symbols.append(gpa, symbol_index); const symbol_ptr = elf_file.symbol(symbol_index); @@ -98,6 +97,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void { } pub fn deinit(self: *ZigObject, allocator: Allocator) void { + allocator.free(self.path); self.local_esyms.deinit(allocator); self.global_esyms.deinit(allocator); self.strtab.deinit(allocator); @@ -512,25 +512,13 @@ pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: * 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; + if (global.outputShndx() == null) continue; const off = try ar_symtab.strtab.insert(gpa, global.name(elf_file)); ar_symtab.symtab.appendAssumeCapacity(.{ .off = off, .file_index = self.index }); } } -pub fn updateArStrtab( - self: *ZigObject, - allocator: Allocator, - ar_strtab: *Archive.ArStrtab, -) error{OutOfMemory}!void { - const name = try std.fmt.allocPrint(allocator, "{s}.o", .{std.fs.path.stem(self.path)}); - defer allocator.free(name); - if (name.len <= 15) return; - const name_off = try ar_strtab.insert(allocator, name); - self.output_ar_state.name_off = name_off; -} - pub fn updateArSize(self: *ZigObject, elf_file: *Elf) void { var end_pos: u64 = elf_file.shdr_table_offset.?; for (elf_file.shdrs.items) |shdr| { @@ -549,11 +537,12 @@ pub fn writeAr(self: ZigObject, elf_file: *Elf, writer: anytype) !void { const amt = try elf_file.base.file.?.preadAll(contents, 0); if (amt != self.output_ar_state.size) return error.InputOutput; - const name = try std.fmt.allocPrint(gpa, "{s}.o", .{std.fs.path.stem(self.path)}); - defer gpa.free(name); - + const name = self.path; const hdr = Archive.setArHdr(.{ - .name = if (name.len <= 15) .{ .name = name } else .{ .name_off = self.output_ar_state.name_off }, + .name = if (name.len <= Archive.max_member_name_len) + .{ .name = name } + else + .{ .name_off = self.output_ar_state.name_off }, .size = @intCast(size), }); try writer.writeAll(mem.asBytes(&hdr)); diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index e31170a178..cc0b486692 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -161,7 +161,7 @@ pub const File = union(enum) { } pub fn writeSymtab(file: File, elf_file: *Elf, ctx: anytype) void { - var ilocal = ctx.ilocal; + var ilocal: usize = ctx.ilocal; for (file.locals()) |local_index| { const local = elf_file.symbol(local_index); if (!local.flags.output_symtab) continue; @@ -173,7 +173,7 @@ pub const File = union(enum) { ilocal += 1; } - var iglobal = ctx.iglobal; + var iglobal: usize = ctx.iglobal; for (file.globals()) |global_index| { const global = elf_file.symbol(global_index); const file_ptr = global.file(elf_file) orelse continue; @@ -199,7 +199,38 @@ pub const File = union(enum) { pub fn updateArSymtab(file: File, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) !void { return switch (file) { .zig_object => |x| x.updateArSymtab(ar_symtab, elf_file), - .object => @panic("TODO"), + .object => |x| x.updateArSymtab(ar_symtab, elf_file), + inline else => unreachable, + }; + } + + pub fn updateArStrtab(file: File, allocator: Allocator, ar_strtab: *Archive.ArStrtab) !void { + const path = switch (file) { + .zig_object => |x| x.path, + .object => |x| x.path, + inline else => unreachable, + }; + const state = switch (file) { + .zig_object => |x| &x.output_ar_state, + .object => |x| &x.output_ar_state, + inline else => unreachable, + }; + if (path.len <= Archive.max_member_name_len) return; + state.name_off = try ar_strtab.insert(allocator, path); + } + + pub fn updateArSize(file: File, elf_file: *Elf) void { + return switch (file) { + .zig_object => |x| x.updateArSize(elf_file), + .object => |x| x.updateArSize(), + inline else => unreachable, + }; + } + + pub fn writeAr(file: File, elf_file: *Elf, writer: anytype) !void { + return switch (file) { + .zig_object => |x| x.writeAr(elf_file, writer), + .object => |x| x.writeAr(writer), inline else => unreachable, }; } diff --git a/test/link/elf.zig b/test/link/elf.zig index 0402637348..02eae58a06 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -21,6 +21,9 @@ pub fn build(b: *Build) void { .abi = .gnu, }; + // Exercise linker in ar mode + elf_step.dependOn(testEmitStaticLib(b, .{ .target = musl_target })); + // Exercise linker with self-hosted backend (no LLVM) elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target })); elf_step.dependOn(testLinkingObj(b, .{ .use_llvm = false, .target = default_target })); @@ -626,6 +629,65 @@ fn testDsoUndef(b: *Build, opts: Options) *Step { return test_step; } +fn testEmitStaticLib(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "emit-static-lib", opts); + + const obj1 = addObject(b, "obj1", opts); + addCSourceBytes(obj1, + \\int foo = 0; + \\int bar = 2; + \\int fooBar() { + \\ return foo + bar; + \\} + , &.{}); + + const obj2 = addObject(b, "obj2", opts); + addCSourceBytes(obj2, "int tentative;", &.{"-fcommon"}); + + const obj3 = addObject(b, "a_very_long_file_name_so_that_it_ends_up_in_strtab", opts); + addZigSourceBytes(obj3, + \\fn weakFoo() callconv(.C) usize { + \\ return 42; + \\} + \\export var strongBar: usize = 100; + \\comptime { + \\ @export(weakFoo, .{ .name = "weakFoo", .linkage = .Weak }); + \\ @export(strongBar, .{ .name = "strongBarAlias", .linkage = .Strong }); + \\} + ); + + const lib = addStaticLibrary(b, "lib", opts); + lib.addObject(obj1); + lib.addObject(obj2); + lib.addObject(obj3); + + const check = lib.checkObject(); + check.checkInArchiveSymtab(); + check.checkExactPath("in object", obj1.getEmittedBin()); + check.checkExact("foo"); + check.checkInArchiveSymtab(); + check.checkExactPath("in object", obj1.getEmittedBin()); + check.checkExact("bar"); + check.checkInArchiveSymtab(); + check.checkExactPath("in object", obj1.getEmittedBin()); + check.checkExact("fooBar"); + check.checkInArchiveSymtab(); + check.checkExactPath("in object", obj2.getEmittedBin()); + check.checkExact("tentative"); + check.checkInArchiveSymtab(); + check.checkExactPath("in object", obj3.getEmittedBin()); + check.checkExact("weakFoo"); + check.checkInArchiveSymtab(); + check.checkExactPath("in object", obj3.getEmittedBin()); + check.checkExact("strongBar"); + check.checkInArchiveSymtab(); + check.checkExactPath("in object", obj3.getEmittedBin()); + check.checkExact("strongBarAlias"); + test_step.dependOn(&check.step); + + return test_step; +} + fn testEmptyObject(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "empty-object", opts); @@ -1858,34 +1920,30 @@ fn testLinkingObj(b: *Build, opts: Options) *Step { fn testLinkingStaticLib(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "linking-static-lib", opts); - const lib = b.addStaticLibrary(.{ - .name = "alib", - .target = opts.target, - .optimize = opts.optimize, - .use_llvm = opts.use_llvm, - .use_lld = false, - }); + const obj = addObject(b, "bobj", opts); + addZigSourceBytes(obj, "export var bar: i32 = -42;"); + + const lib = addStaticLibrary(b, "alib", opts); addZigSourceBytes(lib, - \\extern var mod: usize; - \\export fn callMe() usize { - \\ return me * mod; + \\export fn foo() i32 { + \\ return 42; \\} - \\var me: usize = 42; ); + lib.addObject(obj); const exe = addExecutable(b, "testlib", opts); addZigSourceBytes(exe, \\const std = @import("std"); - \\extern fn callMe() usize; - \\export var mod: usize = 2; + \\extern fn foo() i32; + \\extern var bar: i32; \\pub fn main() void { - \\ std.debug.print("{d}\n", .{callMe()}); + \\ std.debug.print("{d}\n", .{foo() + bar}); \\} ); exe.linkLibrary(lib); const run = addRunArtifact(exe); - run.expectStdErrEqual("84\n"); + run.expectStdErrEqual("0\n"); test_step.dependOn(&run.step); return test_step; @@ -3332,7 +3390,7 @@ fn addStaticLibrary(b: *Build, name: []const u8, opts: Options) *Compile { .target = opts.target, .optimize = opts.optimize, .use_llvm = opts.use_llvm, - .use_lld = true, + .use_lld = opts.use_lld, }); }