aboutsummaryrefslogtreecommitdiff
path: root/src/link/Elf/SharedObject.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/link/Elf/SharedObject.zig')
-rw-r--r--src/link/Elf/SharedObject.zig363
1 files changed, 363 insertions, 0 deletions
diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig
new file mode 100644
index 0000000000..19e688b58d
--- /dev/null
+++ b/src/link/Elf/SharedObject.zig
@@ -0,0 +1,363 @@
+path: []const u8,
+data: []const u8,
+index: File.Index,
+
+header: ?elf.Elf64_Ehdr = null,
+shdrs: std.ArrayListUnmanaged(ElfShdr) = .{},
+symtab: []align(1) const elf.Elf64_Sym = &[0]elf.Elf64_Sym{},
+strtab: []const u8 = &[0]u8{},
+/// Version symtab contains version strings of the symbols if present.
+versyms: std.ArrayListUnmanaged(elf.Elf64_Versym) = .{},
+verstrings: std.ArrayListUnmanaged(u32) = .{},
+
+dynamic_sect_index: ?u16 = null,
+versym_sect_index: ?u16 = null,
+verdef_sect_index: ?u16 = null,
+
+symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+aliases: ?std.ArrayListUnmanaged(u32) = null,
+
+needed: bool,
+alive: bool,
+
+output_symtab_size: Elf.SymtabSize = .{},
+
+pub fn isSharedObject(file: std.fs.File) bool {
+ const reader = file.reader();
+ const header = reader.readStruct(elf.Elf64_Ehdr) catch return false;
+ defer file.seekTo(0) catch {};
+ if (!mem.eql(u8, header.e_ident[0..4], "\x7fELF")) return false;
+ if (header.e_ident[elf.EI_VERSION] != 1) return false;
+ if (header.e_type != elf.ET.DYN) return false;
+ return true;
+}
+
+pub fn deinit(self: *SharedObject, allocator: Allocator) void {
+ allocator.free(self.data);
+ self.versyms.deinit(allocator);
+ self.verstrings.deinit(allocator);
+ self.symbols.deinit(allocator);
+ if (self.aliases) |*aliases| aliases.deinit(allocator);
+ self.shdrs.deinit(allocator);
+}
+
+pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
+ const gpa = elf_file.base.allocator;
+ var stream = std.io.fixedBufferStream(self.data);
+ const reader = stream.reader();
+
+ self.header = try reader.readStruct(elf.Elf64_Ehdr);
+ const shoff = std.math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
+
+ var dynsym_index: ?u16 = null;
+ const shdrs = @as(
+ [*]align(1) const elf.Elf64_Shdr,
+ @ptrCast(self.data.ptr + shoff),
+ )[0..self.header.?.e_shnum];
+ try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len);
+
+ for (shdrs, 0..) |shdr, i| {
+ self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr));
+ switch (shdr.sh_type) {
+ elf.SHT_DYNSYM => dynsym_index = @as(u16, @intCast(i)),
+ elf.SHT_DYNAMIC => self.dynamic_sect_index = @as(u16, @intCast(i)),
+ elf.SHT_GNU_VERSYM => self.versym_sect_index = @as(u16, @intCast(i)),
+ elf.SHT_GNU_VERDEF => self.verdef_sect_index = @as(u16, @intCast(i)),
+ else => {},
+ }
+ }
+
+ if (dynsym_index) |index| {
+ const shdr = self.shdrs.items[index];
+ const symtab = self.shdrContents(index);
+ const nsyms = @divExact(symtab.len, @sizeOf(elf.Elf64_Sym));
+ self.symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(symtab.ptr))[0..nsyms];
+ self.strtab = self.shdrContents(@as(u16, @intCast(shdr.sh_link)));
+ }
+
+ try self.parseVersions(elf_file);
+ try self.initSymtab(elf_file);
+}
+
+fn parseVersions(self: *SharedObject, elf_file: *Elf) !void {
+ const gpa = elf_file.base.allocator;
+
+ try self.verstrings.resize(gpa, 2);
+ self.verstrings.items[elf.VER_NDX_LOCAL] = 0;
+ self.verstrings.items[elf.VER_NDX_GLOBAL] = 0;
+
+ if (self.verdef_sect_index) |shndx| {
+ const verdefs = self.shdrContents(shndx);
+ const nverdefs = self.verdefNum();
+ try self.verstrings.resize(gpa, self.verstrings.items.len + nverdefs);
+
+ var i: u32 = 0;
+ var offset: u32 = 0;
+ while (i < nverdefs) : (i += 1) {
+ const verdef = @as(*align(1) const elf.Elf64_Verdef, @ptrCast(verdefs.ptr + offset)).*;
+ defer offset += verdef.vd_next;
+ if (verdef.vd_flags == elf.VER_FLG_BASE) continue; // Skip BASE entry
+ const vda_name = if (verdef.vd_cnt > 0)
+ @as(*align(1) const elf.Elf64_Verdaux, @ptrCast(verdefs.ptr + offset + verdef.vd_aux)).vda_name
+ else
+ 0;
+ self.verstrings.items[verdef.vd_ndx] = vda_name;
+ }
+ }
+
+ try self.versyms.ensureTotalCapacityPrecise(gpa, self.symtab.len);
+
+ if (self.versym_sect_index) |shndx| {
+ const versyms_raw = self.shdrContents(shndx);
+ const nversyms = @divExact(versyms_raw.len, @sizeOf(elf.Elf64_Versym));
+ const versyms = @as([*]align(1) const elf.Elf64_Versym, @ptrCast(versyms_raw.ptr))[0..nversyms];
+ for (versyms) |ver| {
+ const normalized_ver = if (ver & elf.VERSYM_VERSION >= self.verstrings.items.len - 1)
+ elf.VER_NDX_GLOBAL
+ else
+ ver;
+ self.versyms.appendAssumeCapacity(normalized_ver);
+ }
+ } else for (0..self.symtab.len) |_| {
+ self.versyms.appendAssumeCapacity(elf.VER_NDX_GLOBAL);
+ }
+}
+
+fn initSymtab(self: *SharedObject, elf_file: *Elf) !void {
+ const gpa = elf_file.base.allocator;
+
+ try self.symbols.ensureTotalCapacityPrecise(gpa, self.symtab.len);
+
+ for (self.symtab, 0..) |sym, i| {
+ const hidden = self.versyms.items[i] & elf.VERSYM_HIDDEN != 0;
+ const name = self.getString(sym.st_name);
+ // We need to garble up the name so that we don't pick this symbol
+ // during symbol resolution. Thank you GNU!
+ const off = if (hidden) blk: {
+ const full_name = try std.fmt.allocPrint(gpa, "{s}@{s}", .{
+ name,
+ self.versionString(self.versyms.items[i]),
+ });
+ defer gpa.free(full_name);
+ break :blk try elf_file.strtab.insert(gpa, full_name);
+ } else try elf_file.strtab.insert(gpa, name);
+ const gop = try elf_file.getOrPutGlobal(off);
+ self.symbols.addOneAssumeCapacity().* = gop.index;
+ }
+}
+
+pub fn resolveSymbols(self: *SharedObject, elf_file: *Elf) void {
+ for (self.globals(), 0..) |index, i| {
+ const esym_index = @as(u32, @intCast(i));
+ const this_sym = self.symtab[esym_index];
+
+ if (this_sym.st_shndx == elf.SHN_UNDEF) continue;
+
+ const global = elf_file.symbol(index);
+ if (self.asFile().symbolRank(this_sym, false) < global.symbolRank(elf_file)) {
+ global.value = this_sym.st_value;
+ global.atom_index = 0;
+ global.esym_index = esym_index;
+ global.version_index = self.versyms.items[esym_index];
+ global.file_index = self.index;
+ }
+ }
+}
+
+pub fn resetGlobals(self: *SharedObject, elf_file: *Elf) void {
+ for (self.globals()) |index| {
+ const global = elf_file.symbol(index);
+ const off = global.name_offset;
+ global.* = .{};
+ global.name_offset = off;
+ }
+}
+
+pub fn markLive(self: *SharedObject, elf_file: *Elf) void {
+ for (self.globals(), 0..) |index, i| {
+ const sym = self.symtab[i];
+ if (sym.st_shndx != elf.SHN_UNDEF) continue;
+
+ const global = elf_file.symbol(index);
+ const file = global.file(elf_file) orelse continue;
+ const should_drop = switch (file) {
+ .shared_object => |sh| !sh.needed and sym.st_bind() == elf.STB_WEAK,
+ else => false,
+ };
+ if (!should_drop and !file.isAlive()) {
+ file.setAlive();
+ file.markLive(elf_file);
+ }
+ }
+}
+
+pub fn updateSymtabSize(self: *SharedObject, elf_file: *Elf) void {
+ for (self.globals()) |global_index| {
+ const global = elf_file.symbol(global_index);
+ if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
+ if (global.isLocal()) continue;
+ global.flags.output_symtab = true;
+ self.output_symtab_size.nglobals += 1;
+ }
+}
+
+pub fn writeSymtab(self: *SharedObject, elf_file: *Elf, ctx: anytype) void {
+ var iglobal = ctx.iglobal;
+ for (self.globals()) |global_index| {
+ const global = elf_file.symbol(global_index);
+ if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
+ if (!global.flags.output_symtab) continue;
+ global.setOutputSym(elf_file, &ctx.symtab[iglobal]);
+ iglobal += 1;
+ }
+}
+
+pub fn globals(self: SharedObject) []const Symbol.Index {
+ return self.symbols.items;
+}
+
+pub fn shdrContents(self: SharedObject, index: u16) []const u8 {
+ const shdr = self.shdrs.items[index];
+ return self.data[shdr.sh_offset..][0..shdr.sh_size];
+}
+
+pub fn getString(self: SharedObject, off: u32) [:0]const u8 {
+ assert(off < self.strtab.len);
+ return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);
+}
+
+pub fn versionString(self: SharedObject, index: elf.Elf64_Versym) [:0]const u8 {
+ const off = self.verstrings.items[index & elf.VERSYM_VERSION];
+ return self.getString(off);
+}
+
+pub fn asFile(self: *SharedObject) File {
+ return .{ .shared_object = self };
+}
+
+fn dynamicTable(self: *SharedObject) []align(1) const elf.Elf64_Dyn {
+ const shndx = self.dynamic_sect_index orelse return &[0]elf.Elf64_Dyn{};
+ const raw = self.shdrContents(shndx);
+ const num = @divExact(raw.len, @sizeOf(elf.Elf64_Dyn));
+ return @as([*]align(1) const elf.Elf64_Dyn, @ptrCast(raw.ptr))[0..num];
+}
+
+fn verdefNum(self: *SharedObject) u32 {
+ const entries = self.dynamicTable();
+ for (entries) |entry| switch (entry.d_tag) {
+ elf.DT_VERDEFNUM => return @as(u32, @intCast(entry.d_val)),
+ else => {},
+ };
+ return 0;
+}
+
+pub fn soname(self: *SharedObject) []const u8 {
+ const entries = self.dynamicTable();
+ for (entries) |entry| switch (entry.d_tag) {
+ elf.DT_SONAME => return self.getString(@as(u32, @intCast(entry.d_val))),
+ else => {},
+ };
+ return std.fs.path.basename(self.path);
+}
+
+pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void {
+ assert(self.aliases == null);
+
+ const SortAlias = struct {
+ pub fn lessThan(ctx: *Elf, lhs: Symbol.Index, rhs: Symbol.Index) bool {
+ const lhs_sym = ctx.symbol(lhs).elfSym(ctx);
+ const rhs_sym = ctx.symbol(rhs).elfSym(ctx);
+ return lhs_sym.st_value < rhs_sym.st_value;
+ }
+ };
+
+ const gpa = elf_file.base.allocator;
+ var aliases = std.ArrayList(Symbol.Index).init(gpa);
+ defer aliases.deinit();
+ try aliases.ensureTotalCapacityPrecise(self.globals().len);
+
+ for (self.globals()) |index| {
+ const global = elf_file.symbol(index);
+ const global_file = global.file(elf_file) orelse continue;
+ if (global_file.index() != self.index) continue;
+ aliases.appendAssumeCapacity(index);
+ }
+
+ std.mem.sort(u32, aliases.items, elf_file, SortAlias.lessThan);
+
+ self.aliases = aliases.moveToUnmanaged();
+}
+
+pub fn symbolAliases(self: *SharedObject, index: u32, elf_file: *Elf) []const u32 {
+ assert(self.aliases != null);
+
+ const symbol = elf_file.symbol(index).elfSym(elf_file);
+ const aliases = self.aliases.?;
+
+ const start = for (aliases.items, 0..) |alias, i| {
+ const alias_sym = elf_file.symbol(alias).elfSym(elf_file);
+ if (symbol.st_value == alias_sym.st_value) break i;
+ } else aliases.items.len;
+
+ const end = for (aliases.items[start..], 0..) |alias, i| {
+ const alias_sym = elf_file.symbol(alias).elfSym(elf_file);
+ if (symbol.st_value < alias_sym.st_value) break i + start;
+ } else aliases.items.len;
+
+ return aliases.items[start..end];
+}
+
+pub fn format(
+ self: SharedObject,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = self;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format shared objects directly");
+}
+
+pub fn fmtSymtab(self: SharedObject, elf_file: *Elf) std.fmt.Formatter(formatSymtab) {
+ return .{ .data = .{
+ .shared = self,
+ .elf_file = elf_file,
+ } };
+}
+
+const FormatContext = struct {
+ shared: SharedObject,
+ elf_file: *Elf,
+};
+
+fn formatSymtab(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const shared = ctx.shared;
+ try writer.writeAll(" globals\n");
+ for (shared.symbols.items) |index| {
+ const global = ctx.elf_file.symbol(index);
+ try writer.print(" {}\n", .{global.fmt(ctx.elf_file)});
+ }
+}
+
+const SharedObject = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const elf = std.elf;
+const log = std.log.scoped(.elf);
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Elf = @import("../Elf.zig");
+const ElfShdr = @import("Object.zig").ElfShdr;
+const File = @import("file.zig").File;
+const Symbol = @import("Symbol.zig");