feature. See also
. The project being documented here (as the example) is the Zig library itself.
dynamic_library.ElfDynLib
pub const ElfDynLib = struct
File
Code
pub const ElfDynLib = struct {
strings: [*:0]u8,
syms: [*]elf.Sym,
hash_table: HashTable,
versym: ?[*]elf.Versym,
verdef: ?*elf.Verdef,
memory: []align(std.heap.page_size_min) u8,
pub const Error = ElfDynLibError;
const HashTable = union(enum) {
dt_hash: [*]posix.Elf_Symndx,
dt_gnu_hash: *elf.gnu_hash.Header,
};
fn openPath(io: Io, path: []const u8) !Io.Dir {
if (path.len == 0) return error.NotDir;
var parts = std.mem.tokenizeScalar(u8, path, '/');
var parent = if (path[0] == '/') try Io.Dir.cwd().openDir(io, "/", .{}) else Io.Dir.cwd();
while (parts.next()) |part| {
const child = try parent.openDir(io, part, .{});
parent.close(io);
parent = child;
}
return parent;
}
fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?Io.File {
var paths = std.mem.tokenizeScalar(u8, search_path, delim);
while (paths.next()) |p| {
var dir = openPath(io, p) catch continue;
defer dir.close(io);
return dir.openFile(io, file_name, .{}) catch continue;
}
return null;
}
fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?Io.File {
var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null;
defer dir.close(io);
return dir.openFile(io, file_name, .{}) catch null;
}
// Places where it differs from dlopen:
// - DT_RPATH of the calling binary is not used as a search path
// - DT_RUNPATH of the calling binary is not used as a search path
// - /etc/ld.so.cache is not read
fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !Io.File {
if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {
return Io.Dir.cwd().openFile(io, path_or_name, .{});
}
if (std.os.linux.geteuid() == std.os.linux.getuid() and
std.os.linux.getegid() == std.os.linux.getgid())
{
if (LD_LIBRARY_PATH) |ld_library_path| {
if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |file| {
return file;
}
}
}
if (resolveFromParent(io, "/lib", path_or_name)) |file| return file;
if (resolveFromParent(io, "/usr/lib", path_or_name)) |file| return file;
return error.FileNotFound;
}
pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
const io = std.Options.debug_io;
const file = try resolveFromName(io, path, LD_LIBRARY_PATH);
defer file.close(io);
const stat = try file.stat(io);
const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
const page_size = std.heap.pageSize();
// corresponding to the actual LOAD sections.
const file_bytes = try posix.mmap(
null,
mem.alignForward(usize, size, page_size),
.{ .READ = true },
.{ .TYPE = .PRIVATE },
file.handle,
0,
);
defer posix.munmap(file_bytes);
const eh = @as(*elf.Ehdr, @ptrCast(file_bytes.ptr));
if (!mem.eql(u8, eh.e_ident[0..4], elf.MAGIC)) return error.NotElfFile;
if (eh.e_type != elf.ET.DYN) return error.NotDynamicLibrary;
const elf_addr = @intFromPtr(file_bytes.ptr);
// dynamic vector as well as the total size of the virtual memory.
var maybe_dynv: ?[*]usize = null;
var virt_addr_end: usize = 0;
{
var i: usize = 0;
var ph_addr: usize = elf_addr + eh.e_phoff;
while (i < eh.e_phnum) : ({
i += 1;
ph_addr += eh.e_phentsize;
}) {
const ph = @as(*elf.Phdr, @ptrFromInt(ph_addr));
switch (ph.p_type) {
elf.PT_LOAD => virt_addr_end = @max(virt_addr_end, ph.p_vaddr + ph.p_memsz),
elf.PT_DYNAMIC => maybe_dynv = @as([*]usize, @ptrFromInt(elf_addr + ph.p_offset)),
else => {},
}
}
}
const dynv = maybe_dynv orelse return error.MissingDynamicLinkingInformation;
const all_loaded_mem = try posix.mmap(
null,
virt_addr_end,
.{},
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
);
errdefer posix.munmap(all_loaded_mem);
const base = @intFromPtr(all_loaded_mem.ptr);
{
var i: usize = 0;
var ph_addr: usize = elf_addr + eh.e_phoff;
while (i < eh.e_phnum) : ({
i += 1;
ph_addr += eh.e_phentsize;
}) {
const ph = @as(*elf.Phdr, @ptrFromInt(ph_addr));
switch (ph.p_type) {
elf.PT_LOAD => {
// extra nonsense mapped before/after the VirtAddr,MemSiz
const aligned_addr = (base + ph.p_vaddr) & ~(@as(usize, page_size) - 1);
const extra_bytes = (base + ph.p_vaddr) - aligned_addr;
const extended_memsz = mem.alignForward(usize, ph.p_memsz + extra_bytes, page_size);
const ptr = @as([*]align(std.heap.page_size_min) u8, @ptrFromInt(aligned_addr));
const prot = elfToProt(ph.p_flags);
_ = try posix.mmap(
ptr,
extended_memsz,
prot,
.{ .TYPE = .PRIVATE, .FIXED = true },
file.handle,
ph.p_offset - extra_bytes,
);
},
else => {},
}
}
}
var maybe_strings: ?[*:0]u8 = null;
var maybe_syms: ?[*]elf.Sym = null;
var maybe_hashtab: ?[*]posix.Elf_Symndx = null;
var maybe_gnu_hash: ?*elf.gnu_hash.Header = null;
var maybe_versym: ?[*]elf.Versym = null;
var maybe_verdef: ?*elf.Verdef = null;
{
var i: usize = 0;
while (dynv[i] != 0) : (i += 2) {
const p = base + dynv[i + 1];
switch (dynv[i]) {
elf.DT_STRTAB => maybe_strings = @ptrFromInt(p),
elf.DT_SYMTAB => maybe_syms = @ptrFromInt(p),
elf.DT_HASH => maybe_hashtab = @ptrFromInt(p),
elf.DT_GNU_HASH => maybe_gnu_hash = @ptrFromInt(p),
elf.DT_VERSYM => maybe_versym = @ptrFromInt(p),
elf.DT_VERDEF => maybe_verdef = @ptrFromInt(p),
else => {},
}
}
}
const hash_table: HashTable = if (maybe_gnu_hash) |gnu_hash|
.{ .dt_gnu_hash = gnu_hash }
else if (maybe_hashtab) |hashtab|
.{ .dt_hash = hashtab }
else
return error.ElfHashTableNotFound;
return .{
.memory = all_loaded_mem,
.strings = maybe_strings orelse return error.ElfStringSectionNotFound,
.syms = maybe_syms orelse return error.ElfSymSectionNotFound,
.hash_table = hash_table,
.versym = maybe_versym,
.verdef = maybe_verdef,
};
}
pub fn openZ(path_c: [*:0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
return open(mem.sliceTo(path_c, 0), LD_LIBRARY_PATH);
}
pub fn close(self: *ElfDynLib) void {
posix.munmap(self.memory);
self.* = undefined;
}
pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [:0]const u8) ?T {
if (self.lookupAddress("", name)) |symbol| {
return @as(T, @ptrFromInt(symbol));
} else {
return null;
}
}
pub const GnuHashSection32 = struct {
symoffset: u32,
bloom_shift: u32,
bloom: []u32,
buckets: []u32,
chain: [*]elf.gnu_hash.ChainEntry,
pub fn fromPtr(header: *elf.gnu_hash.Header) @This() {
const header_offset = @intFromPtr(header);
const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header);
const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u32);
const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32);
const bloom_ptr: [*]u32 = @ptrFromInt(bloom_offset);
const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset);
const chain_ptr: [*]elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset);
return .{
.symoffset = header.symoffset,
.bloom_shift = header.bloom_shift,
.bloom = bloom_ptr[0..header.bloom_size],
.buckets = buckets_ptr[0..header.nbuckets],
.chain = chain_ptr,
};
}
};
pub const GnuHashSection64 = struct {
symoffset: u32,
bloom_shift: u32,
bloom: []u64,
buckets: []u32,
chain: [*]elf.gnu_hash.ChainEntry,
pub fn fromPtr(header: *elf.gnu_hash.Header) @This() {
const header_offset = @intFromPtr(header);
const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header);
const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u64);
const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32);
const bloom_ptr: [*]u64 = @ptrFromInt(bloom_offset);
const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset);
const chain_ptr: [*]elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset);
return .{
.symoffset = header.symoffset,
.bloom_shift = header.bloom_shift,
.bloom = bloom_ptr[0..header.bloom_size],
.buckets = buckets_ptr[0..header.nbuckets],
.chain = chain_ptr,
};
}
};
pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize {
const maybe_versym = if (self.verdef == null) null else self.versym;
const OK_TYPES = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON);
const OK_BINDS = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE);
switch (self.hash_table) {
.dt_hash => |hashtab| {
var i: usize = 0;
while (i < hashtab[1]) : (i += 1) {
if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info & 0xf)) & OK_TYPES)) continue;
if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info >> 4)) & OK_BINDS)) continue;
if (0 == self.syms[i].st_shndx) continue;
if (!mem.eql(u8, name, mem.sliceTo(self.strings + self.syms[i].st_name, 0))) continue;
if (maybe_versym) |versym| {
if (!checkver(self.verdef.?, versym[i], vername, self.strings))
continue;
}
return @intFromPtr(self.memory.ptr) + self.syms[i].st_value;
}
},
.dt_gnu_hash => |gnu_hash_header| {
const GnuHashSection = switch (@bitSizeOf(usize)) {
32 => GnuHashSection32,
64 => GnuHashSection64,
else => |bit_size| @compileError("Unsupported bit size " ++ bit_size),
};
const gnu_hash_section: GnuHashSection = .fromPtr(gnu_hash_header);
const hash = elf.gnu_hash.calculate(name);
const bloom_index = (hash / @bitSizeOf(usize)) % gnu_hash_header.bloom_size;
const bloom_val = gnu_hash_section.bloom[bloom_index];
const bit_index_0 = hash % @bitSizeOf(usize);
const bit_index_1 = (hash >> @intCast(gnu_hash_header.bloom_shift)) % @bitSizeOf(usize);
const one: usize = 1;
const bit_mask: usize = (one << @intCast(bit_index_0)) | (one << @intCast(bit_index_1));
if (bloom_val & bit_mask != bit_mask) {
return null;
}
const bucket_index = hash % gnu_hash_header.nbuckets;
const chain_index = gnu_hash_section.buckets[bucket_index] - gnu_hash_header.symoffset;
const chains = gnu_hash_section.chain;
const hash_as_entry: elf.gnu_hash.ChainEntry = @bitCast(hash);
var current_index = chain_index;
var at_end_of_chain = false;
while (!at_end_of_chain) : (current_index += 1) {
const current_entry = chains[current_index];
at_end_of_chain = current_entry.end_of_chain;
if (current_entry.hash != hash_as_entry.hash) continue;
const symbol_index = current_index + gnu_hash_header.symoffset;
const symbol = self.syms[symbol_index];
if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info & 0xf)) & OK_TYPES)) continue;
if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info >> 4)) & OK_BINDS)) continue;
if (0 == symbol.st_shndx) continue;
const symbol_name = mem.sliceTo(self.strings + symbol.st_name, 0);
if (!mem.eql(u8, name, symbol_name)) {
continue;
}
if (maybe_versym) |versym| {
if (!checkver(self.verdef.?, versym[symbol_index], vername, self.strings)) {
continue;
}
}
return @intFromPtr(self.memory.ptr) + symbol.st_value;
}
},
}
return null;
}
fn elfToProt(elf_prot: u64) posix.PROT {
return .{
.READ = (elf_prot & elf.PF_R) != 0,
.WRITE = (elf_prot & elf.PF_W) != 0,
.EXEC = (elf_prot & elf.PF_X) != 0,
};
}
}