master
  1path: Path,
  2index: File.Index,
  3
  4parsed: Parsed,
  5
  6symbols: std.ArrayList(Symbol),
  7symbols_extra: std.ArrayList(u32),
  8symbols_resolver: std.ArrayList(Elf.SymbolResolver.Index),
  9
 10aliases: ?std.ArrayList(u32),
 11
 12needed: bool,
 13alive: bool,
 14
 15output_symtab_ctx: Elf.SymtabCtx,
 16
 17pub fn deinit(so: *SharedObject, gpa: Allocator) void {
 18    gpa.free(so.path.sub_path);
 19    so.parsed.deinit(gpa);
 20    so.symbols.deinit(gpa);
 21    so.symbols_extra.deinit(gpa);
 22    so.symbols_resolver.deinit(gpa);
 23    if (so.aliases) |*aliases| aliases.deinit(gpa);
 24    so.* = undefined;
 25}
 26
 27pub const Header = struct {
 28    dynamic_table: []const elf.Elf64_Dyn,
 29    soname_index: ?u32,
 30    verdefnum: ?u32,
 31
 32    sections: []const elf.Elf64_Shdr,
 33    dynsym_sect_index: ?u32,
 34    versym_sect_index: ?u32,
 35    verdef_sect_index: ?u32,
 36
 37    stat: Stat,
 38    strtab: std.ArrayList(u8),
 39
 40    pub fn deinit(header: *Header, gpa: Allocator) void {
 41        gpa.free(header.sections);
 42        gpa.free(header.dynamic_table);
 43        header.strtab.deinit(gpa);
 44        header.* = undefined;
 45    }
 46
 47    pub fn soname(header: Header) ?[]const u8 {
 48        const i = header.soname_index orelse return null;
 49        return Elf.stringTableLookup(header.strtab.items, i);
 50    }
 51};
 52
 53pub const Parsed = struct {
 54    stat: Stat,
 55    strtab: []const u8,
 56    soname_index: ?u32,
 57    sections: []const elf.Elf64_Shdr,
 58
 59    /// Nonlocal symbols only.
 60    symtab: []const elf.Elf64_Sym,
 61    /// Version symtab contains version strings of the symbols if present.
 62    /// Nonlocal symbols only.
 63    versyms: []const elf.Versym,
 64    /// Nonlocal symbols only.
 65    symbols: []const Parsed.Symbol,
 66
 67    verstrings: []const u32,
 68
 69    const Symbol = struct {
 70        mangled_name: u32,
 71    };
 72
 73    pub fn deinit(p: *Parsed, gpa: Allocator) void {
 74        gpa.free(p.strtab);
 75        gpa.free(p.sections);
 76        gpa.free(p.symtab);
 77        gpa.free(p.versyms);
 78        gpa.free(p.symbols);
 79        gpa.free(p.verstrings);
 80        p.* = undefined;
 81    }
 82
 83    pub fn versionString(p: Parsed, index: elf.Versym) [:0]const u8 {
 84        return versionStringLookup(p.strtab, p.verstrings, index);
 85    }
 86
 87    pub fn soname(p: Parsed) ?[]const u8 {
 88        const i = p.soname_index orelse return null;
 89        return Elf.stringTableLookup(p.strtab, i);
 90    }
 91};
 92
 93pub fn parseHeader(
 94    gpa: Allocator,
 95    diags: *Diags,
 96    file_path: Path,
 97    fs_file: std.fs.File,
 98    stat: Stat,
 99    target: *const std.Target,
100) !Header {
101    var ehdr: elf.Elf64_Ehdr = undefined;
102    {
103        const buf = mem.asBytes(&ehdr);
104        const amt = try fs_file.preadAll(buf, 0);
105        if (amt != buf.len) return error.UnexpectedEndOfFile;
106    }
107    if (!mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF")) return error.BadMagic;
108    if (ehdr.e_ident[elf.EI.VERSION] != 1) return error.BadElfVersion;
109    if (ehdr.e_type != elf.ET.DYN) return error.NotSharedObject;
110
111    if (target.toElfMachine() != ehdr.e_machine)
112        return diags.failParse(file_path, "invalid ELF machine type: {s}", .{@tagName(ehdr.e_machine)});
113
114    const shoff = std.math.cast(usize, ehdr.e_shoff) orelse return error.Overflow;
115    const shnum = std.math.cast(u32, ehdr.e_shnum) orelse return error.Overflow;
116
117    const sections = try gpa.alloc(elf.Elf64_Shdr, shnum);
118    errdefer gpa.free(sections);
119    {
120        const buf = mem.sliceAsBytes(sections);
121        const amt = try fs_file.preadAll(buf, shoff);
122        if (amt != buf.len) return error.UnexpectedEndOfFile;
123    }
124
125    var dynsym_sect_index: ?u32 = null;
126    var dynamic_sect_index: ?u32 = null;
127    var versym_sect_index: ?u32 = null;
128    var verdef_sect_index: ?u32 = null;
129    for (sections, 0..) |shdr, i_usize| {
130        const i: u32 = @intCast(i_usize);
131        switch (shdr.sh_type) {
132            elf.SHT_DYNSYM => dynsym_sect_index = i,
133            elf.SHT_DYNAMIC => dynamic_sect_index = i,
134            elf.SHT_GNU_VERSYM => versym_sect_index = i,
135            elf.SHT_GNU_VERDEF => verdef_sect_index = i,
136            else => continue,
137        }
138    }
139
140    const dynamic_table: []elf.Elf64_Dyn = if (dynamic_sect_index) |index| dt: {
141        const shdr = sections[index];
142        const n = std.math.cast(usize, shdr.sh_size / @sizeOf(elf.Elf64_Dyn)) orelse return error.Overflow;
143        const dynamic_table = try gpa.alloc(elf.Elf64_Dyn, n);
144        errdefer gpa.free(dynamic_table);
145        const buf = mem.sliceAsBytes(dynamic_table);
146        const amt = try fs_file.preadAll(buf, shdr.sh_offset);
147        if (amt != buf.len) return error.UnexpectedEndOfFile;
148        break :dt dynamic_table;
149    } else &.{};
150    errdefer gpa.free(dynamic_table);
151
152    var strtab: std.ArrayList(u8) = .empty;
153    errdefer strtab.deinit(gpa);
154
155    if (dynsym_sect_index) |index| {
156        const dynsym_shdr = sections[index];
157        if (dynsym_shdr.sh_link >= sections.len) return error.BadStringTableIndex;
158        const strtab_shdr = sections[dynsym_shdr.sh_link];
159        const n = std.math.cast(usize, strtab_shdr.sh_size) orelse return error.Overflow;
160        const buf = try strtab.addManyAsSlice(gpa, n);
161        const amt = try fs_file.preadAll(buf, strtab_shdr.sh_offset);
162        if (amt != buf.len) return error.UnexpectedEndOfFile;
163    }
164
165    var soname_index: ?u32 = null;
166    var verdefnum: ?u32 = null;
167    for (dynamic_table) |entry| switch (entry.d_tag) {
168        elf.DT_SONAME => {
169            if (entry.d_val >= strtab.items.len) return error.BadSonameIndex;
170            soname_index = @intCast(entry.d_val);
171        },
172        elf.DT_VERDEFNUM => {
173            verdefnum = @intCast(entry.d_val);
174        },
175        else => continue,
176    };
177
178    return .{
179        .dynamic_table = dynamic_table,
180        .soname_index = soname_index,
181        .verdefnum = verdefnum,
182        .sections = sections,
183        .dynsym_sect_index = dynsym_sect_index,
184        .versym_sect_index = versym_sect_index,
185        .verdef_sect_index = verdef_sect_index,
186        .strtab = strtab,
187        .stat = stat,
188    };
189}
190
191pub fn parse(
192    gpa: Allocator,
193    /// Moves resources from header. Caller may unconditionally deinit.
194    header: *Header,
195    fs_file: std.fs.File,
196) !Parsed {
197    const symtab = if (header.dynsym_sect_index) |index| st: {
198        const shdr = header.sections[index];
199        const n = std.math.cast(usize, shdr.sh_size / @sizeOf(elf.Elf64_Sym)) orelse return error.Overflow;
200        const symtab = try gpa.alloc(elf.Elf64_Sym, n);
201        errdefer gpa.free(symtab);
202        const buf = mem.sliceAsBytes(symtab);
203        const amt = try fs_file.preadAll(buf, shdr.sh_offset);
204        if (amt != buf.len) return error.UnexpectedEndOfFile;
205        break :st symtab;
206    } else &.{};
207    defer gpa.free(symtab);
208
209    var verstrings: std.ArrayList(u32) = .empty;
210    defer verstrings.deinit(gpa);
211
212    if (header.verdef_sect_index) |shndx| {
213        const shdr = header.sections[shndx];
214        const verdefs = try Elf.preadAllAlloc(gpa, fs_file, shdr.sh_offset, shdr.sh_size);
215        defer gpa.free(verdefs);
216
217        var offset: u32 = 0;
218        while (true) {
219            const verdef = mem.bytesAsValue(elf.Verdef, verdefs[offset..][0..@sizeOf(elf.Verdef)]);
220            if (verdef.ndx == .UNSPECIFIED) return error.VerDefSymbolTooLarge;
221
222            if (verstrings.items.len <= @intFromEnum(verdef.ndx))
223                try verstrings.appendNTimes(gpa, 0, @intFromEnum(verdef.ndx) + 1 - verstrings.items.len);
224
225            const aux = mem.bytesAsValue(elf.Verdaux, verdefs[offset + verdef.aux ..][0..@sizeOf(elf.Verdaux)]);
226            verstrings.items[@intFromEnum(verdef.ndx)] = aux.name;
227
228            if (verdef.next == 0) break;
229            offset += verdef.next;
230        }
231    }
232
233    const versyms = if (header.versym_sect_index) |versym_sect_index| vs: {
234        const shdr = header.sections[versym_sect_index];
235        if (shdr.sh_size != symtab.len * @sizeOf(elf.Versym)) return error.BadVerSymSectionSize;
236
237        const versyms = try gpa.alloc(elf.Versym, symtab.len);
238        errdefer gpa.free(versyms);
239        const buf = mem.sliceAsBytes(versyms);
240        const amt = try fs_file.preadAll(buf, shdr.sh_offset);
241        if (amt != buf.len) return error.UnexpectedEndOfFile;
242        break :vs versyms;
243    } else &.{};
244    defer gpa.free(versyms);
245
246    var nonlocal_esyms: std.ArrayList(elf.Elf64_Sym) = .empty;
247    defer nonlocal_esyms.deinit(gpa);
248
249    var nonlocal_versyms: std.ArrayList(elf.Versym) = .empty;
250    defer nonlocal_versyms.deinit(gpa);
251
252    var nonlocal_symbols: std.ArrayList(Parsed.Symbol) = .empty;
253    defer nonlocal_symbols.deinit(gpa);
254
255    var strtab = header.strtab;
256    header.strtab = .empty;
257    defer strtab.deinit(gpa);
258
259    for (symtab, 0..) |sym, i| {
260        const ver: elf.Versym = if (versyms.len == 0 or sym.st_shndx == elf.SHN_UNDEF)
261            .GLOBAL
262        else
263            .{ .VERSION = versyms[i].VERSION, .HIDDEN = false };
264
265        // https://github.com/ziglang/zig/issues/21678
266        //if (ver == .LOCAL) continue;
267        if (@as(u16, @bitCast(ver)) == 0) continue;
268
269        try nonlocal_esyms.ensureUnusedCapacity(gpa, 1);
270        try nonlocal_versyms.ensureUnusedCapacity(gpa, 1);
271        try nonlocal_symbols.ensureUnusedCapacity(gpa, 1);
272
273        const name = Elf.stringTableLookup(strtab.items, sym.st_name);
274        const is_default = versyms.len == 0 or !versyms[i].HIDDEN;
275        const mangled_name = if (is_default) sym.st_name else mn: {
276            const off: u32 = @intCast(strtab.items.len);
277            const version_string = versionStringLookup(strtab.items, verstrings.items, versyms[i]);
278            try strtab.ensureUnusedCapacity(gpa, name.len + version_string.len + 2);
279            // Reload since the string table might have been resized.
280            const name2 = Elf.stringTableLookup(strtab.items, sym.st_name);
281            const version_string2 = versionStringLookup(strtab.items, verstrings.items, versyms[i]);
282            strtab.appendSliceAssumeCapacity(name2);
283            strtab.appendAssumeCapacity('@');
284            strtab.appendSliceAssumeCapacity(version_string2);
285            strtab.appendAssumeCapacity(0);
286            break :mn off;
287        };
288
289        nonlocal_esyms.appendAssumeCapacity(sym);
290        nonlocal_versyms.appendAssumeCapacity(ver);
291        nonlocal_symbols.appendAssumeCapacity(.{
292            .mangled_name = mangled_name,
293        });
294    }
295
296    const sections = header.sections;
297    header.sections = &.{};
298    errdefer gpa.free(sections);
299
300    return .{
301        .sections = sections,
302        .stat = header.stat,
303        .soname_index = header.soname_index,
304        .strtab = try strtab.toOwnedSlice(gpa),
305        .symtab = try nonlocal_esyms.toOwnedSlice(gpa),
306        .versyms = try nonlocal_versyms.toOwnedSlice(gpa),
307        .symbols = try nonlocal_symbols.toOwnedSlice(gpa),
308        .verstrings = try verstrings.toOwnedSlice(gpa),
309    };
310}
311
312pub fn resolveSymbols(self: *SharedObject, elf_file: *Elf) !void {
313    const gpa = elf_file.base.comp.gpa;
314
315    for (self.parsed.symtab, self.symbols_resolver.items, 0..) |esym, *resolv, i| {
316        const gop = try elf_file.resolver.getOrPut(gpa, .{
317            .index = @intCast(i),
318            .file = self.index,
319        }, elf_file);
320        if (!gop.found_existing) {
321            gop.ref.* = .{ .index = 0, .file = 0 };
322        }
323        resolv.* = gop.index;
324
325        if (esym.st_shndx == elf.SHN_UNDEF) continue;
326        if (elf_file.symbol(gop.ref.*) == null) {
327            gop.ref.* = .{ .index = @intCast(i), .file = self.index };
328            continue;
329        }
330
331        if (self.asFile().symbolRank(esym, false) < elf_file.symbol(gop.ref.*).?.symbolRank(elf_file)) {
332            gop.ref.* = .{ .index = @intCast(i), .file = self.index };
333        }
334    }
335}
336
337pub fn markLive(self: *SharedObject, elf_file: *Elf) void {
338    for (self.parsed.symtab, 0..) |esym, i| {
339        if (esym.st_shndx != elf.SHN_UNDEF) continue;
340
341        const ref = self.resolveSymbol(@intCast(i), elf_file);
342        const sym = elf_file.symbol(ref) orelse continue;
343        const file = sym.file(elf_file).?;
344        const should_drop = switch (file) {
345            .shared_object => |sh| !sh.needed and esym.st_bind() == elf.STB_WEAK,
346            else => false,
347        };
348        if (!should_drop and !file.isAlive()) {
349            file.setAlive();
350            file.markLive(elf_file);
351        }
352    }
353}
354
355pub fn markImportExports(self: *SharedObject, elf_file: *Elf) void {
356    for (0..self.symbols.items.len) |i| {
357        const ref = self.resolveSymbol(@intCast(i), elf_file);
358        const ref_sym = elf_file.symbol(ref) orelse continue;
359        const ref_file = ref_sym.file(elf_file).?;
360        const vis: elf.STV = @enumFromInt(@as(u3, @truncate(ref_sym.elfSym(elf_file).st_other)));
361        if (ref_file != .shared_object and vis != .HIDDEN) ref_sym.flags.@"export" = true;
362    }
363}
364
365pub fn updateSymtabSize(self: *SharedObject, elf_file: *Elf) void {
366    for (self.symbols.items, self.symbols_resolver.items) |*global, resolv| {
367        const ref = elf_file.resolver.get(resolv).?;
368        const ref_sym = elf_file.symbol(ref) orelse continue;
369        if (ref_sym.file(elf_file).?.index() != self.index) continue;
370        if (global.isLocal(elf_file)) continue;
371        global.flags.output_symtab = true;
372        global.addExtra(.{ .symtab = self.output_symtab_ctx.nglobals }, elf_file);
373        self.output_symtab_ctx.nglobals += 1;
374        self.output_symtab_ctx.strsize += @as(u32, @intCast(global.name(elf_file).len)) + 1;
375    }
376}
377
378pub fn writeSymtab(self: *SharedObject, elf_file: *Elf) void {
379    for (self.symbols.items, self.symbols_resolver.items) |global, resolv| {
380        const ref = elf_file.resolver.get(resolv).?;
381        const ref_sym = elf_file.symbol(ref) orelse continue;
382        if (ref_sym.file(elf_file).?.index() != self.index) continue;
383        const idx = global.outputSymtabIndex(elf_file) orelse continue;
384        const st_name = @as(u32, @intCast(elf_file.strtab.items.len));
385        elf_file.strtab.appendSliceAssumeCapacity(global.name(elf_file));
386        elf_file.strtab.appendAssumeCapacity(0);
387        const out_sym = &elf_file.symtab.items[idx];
388        out_sym.st_name = st_name;
389        global.setOutputSym(elf_file, out_sym);
390    }
391}
392
393pub fn versionString(self: SharedObject, index: elf.Versym) [:0]const u8 {
394    return self.parsed.versionString(index);
395}
396
397fn versionStringLookup(strtab: []const u8, verstrings: []const u32, index: elf.Versym) [:0]const u8 {
398    const off = verstrings[index.VERSION];
399    return Elf.stringTableLookup(strtab, off);
400}
401
402pub fn asFile(self: *SharedObject) File {
403    return .{ .shared_object = self };
404}
405
406pub fn soname(self: *SharedObject) []const u8 {
407    return self.parsed.soname() orelse self.path.basename();
408}
409
410pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void {
411    assert(self.aliases == null);
412
413    const SortAlias = struct {
414        so: *SharedObject,
415        ef: *Elf,
416
417        pub fn lessThan(ctx: @This(), lhs: Symbol.Index, rhs: Symbol.Index) bool {
418            const lhs_sym = ctx.so.symbols.items[lhs].elfSym(ctx.ef);
419            const rhs_sym = ctx.so.symbols.items[rhs].elfSym(ctx.ef);
420            return lhs_sym.st_value < rhs_sym.st_value;
421        }
422    };
423
424    const comp = elf_file.base.comp;
425    const gpa = comp.gpa;
426    var aliases = std.array_list.Managed(Symbol.Index).init(gpa);
427    defer aliases.deinit();
428    try aliases.ensureTotalCapacityPrecise(self.symbols.items.len);
429
430    for (self.symbols_resolver.items, 0..) |resolv, index| {
431        const ref = elf_file.resolver.get(resolv).?;
432        const ref_sym = elf_file.symbol(ref) orelse continue;
433        if (ref_sym.file(elf_file).?.index() != self.index) continue;
434        aliases.appendAssumeCapacity(@intCast(index));
435    }
436
437    mem.sort(u32, aliases.items, SortAlias{ .so = self, .ef = elf_file }, SortAlias.lessThan);
438
439    self.aliases = aliases.moveToUnmanaged();
440}
441
442pub fn symbolAliases(self: *SharedObject, index: u32, elf_file: *Elf) []const u32 {
443    assert(self.aliases != null);
444
445    const symbol = self.symbols.items[index].elfSym(elf_file);
446    const aliases = self.aliases.?;
447
448    const start = for (aliases.items, 0..) |alias, i| {
449        const alias_sym = self.symbols.items[alias].elfSym(elf_file);
450        if (symbol.st_value == alias_sym.st_value) break i;
451    } else aliases.items.len;
452
453    const end = for (aliases.items[start..], 0..) |alias, i| {
454        const alias_sym = self.symbols.items[alias].elfSym(elf_file);
455        if (symbol.st_value < alias_sym.st_value) break i + start;
456    } else aliases.items.len;
457
458    return aliases.items[start..end];
459}
460
461pub fn getString(self: SharedObject, off: u32) [:0]const u8 {
462    return Elf.stringTableLookup(self.parsed.strtab, off);
463}
464
465pub fn resolveSymbol(self: SharedObject, index: Symbol.Index, elf_file: *Elf) Elf.Ref {
466    const resolv = self.symbols_resolver.items[index];
467    return elf_file.resolver.get(resolv).?;
468}
469
470pub fn addSymbolAssumeCapacity(self: *SharedObject) Symbol.Index {
471    const index: Symbol.Index = @intCast(self.symbols.items.len);
472    self.symbols.appendAssumeCapacity(.{ .file_index = self.index });
473    return index;
474}
475
476pub fn addSymbolExtraAssumeCapacity(self: *SharedObject, extra: Symbol.Extra) u32 {
477    const index: u32 = @intCast(self.symbols_extra.items.len);
478    const fields = @typeInfo(Symbol.Extra).@"struct".fields;
479    inline for (fields) |field| {
480        self.symbols_extra.appendAssumeCapacity(switch (field.type) {
481            u32 => @field(extra, field.name),
482            else => @compileError("bad field type"),
483        });
484    }
485    return index;
486}
487
488pub fn symbolExtra(self: *SharedObject, index: u32) Symbol.Extra {
489    const fields = @typeInfo(Symbol.Extra).@"struct".fields;
490    var i: usize = index;
491    var result: Symbol.Extra = undefined;
492    inline for (fields) |field| {
493        @field(result, field.name) = switch (field.type) {
494            u32 => self.symbols_extra.items[i],
495            else => @compileError("bad field type"),
496        };
497        i += 1;
498    }
499    return result;
500}
501
502pub fn setSymbolExtra(self: *SharedObject, index: u32, extra: Symbol.Extra) void {
503    const fields = @typeInfo(Symbol.Extra).@"struct".fields;
504    inline for (fields, 0..) |field, i| {
505        self.symbols_extra.items[index + i] = switch (field.type) {
506            u32 => @field(extra, field.name),
507            else => @compileError("bad field type"),
508        };
509    }
510}
511
512pub fn fmtSymtab(self: SharedObject, elf_file: *Elf) std.fmt.Alt(Format, Format.symtab) {
513    return .{ .data = .{
514        .shared = self,
515        .elf_file = elf_file,
516    } };
517}
518
519const Format = struct {
520    shared: SharedObject,
521    elf_file: *Elf,
522
523    fn symtab(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void {
524        const shared = f.shared;
525        const elf_file = f.elf_file;
526        try writer.writeAll("  globals\n");
527        for (shared.symbols.items, 0..) |sym, i| {
528            const ref = shared.resolveSymbol(@intCast(i), elf_file);
529            if (elf_file.symbol(ref)) |ref_sym| {
530                try writer.print("    {f}\n", .{ref_sym.fmt(elf_file)});
531            } else {
532                try writer.print("    {s} : unclaimed\n", .{sym.name(elf_file)});
533            }
534        }
535    }
536};
537
538const SharedObject = @This();
539
540const std = @import("std");
541const assert = std.debug.assert;
542const elf = std.elf;
543const log = std.log.scoped(.elf);
544const mem = std.mem;
545const Path = std.Build.Cache.Path;
546const Stat = std.Build.Cache.File.Stat;
547const Allocator = mem.Allocator;
548
549const Elf = @import("../Elf.zig");
550const File = @import("file.zig").File;
551const Symbol = @import("Symbol.zig");
552const Diags = @import("../../link.zig").Diags;