| 1 | const builtin = @import("builtin"); |
| 2 | const native_os = builtin.os.tag; |
| 3 | |
| 4 | const std = @import("std.zig"); |
| 5 | const Io = std.Io; |
| 6 | const mem = std.mem; |
| 7 | const testing = std.testing; |
| 8 | const elf = std.elf; |
| 9 | const windows = std.os.windows; |
| 10 | const posix = std.posix; |
| 11 | |
| 12 | /// Cross-platform dynamic library loading and symbol lookup. |
| 13 | /// Platform-specific functionality is available through the `inner` field. |
| 14 | pub const DynLib = struct { |
| 15 | const InnerType = switch (native_os) { |
| 16 | .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static) |
| 17 | ElfDynLib |
| 18 | else |
| 19 | DlDynLib, |
| 20 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .openbsd, .dragonfly, .illumos => DlDynLib, |
| 21 | else => struct { |
| 22 | const open = @compileError("unsupported platform"); |
| 23 | const openZ = @compileError("unsupported platform"); |
| 24 | }, |
| 25 | }; |
| 26 | |
| 27 | inner: InnerType, |
| 28 | |
| 29 | pub const Error = ElfDynLibError || DlDynLibError; |
| 30 | |
| 31 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 32 | pub fn open(path: []const u8) Error!DynLib { |
| 33 | if (InnerType == ElfDynLib) { |
| 34 | return .{ .inner = try InnerType.open(path, null) }; |
| 35 | } else { |
| 36 | return .{ .inner = try InnerType.open(path) }; |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 41 | pub fn openZ(path_c: [*:0]const u8) Error!DynLib { |
| 42 | if (InnerType == ElfDynLib) { |
| 43 | return .{ .inner = try InnerType.openZ(path_c, null) }; |
| 44 | } else { |
| 45 | return .{ .inner = try InnerType.openZ(path_c) }; |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | /// Trusts the file. |
| 50 | pub fn close(self: *DynLib) void { |
| 51 | return self.inner.close(); |
| 52 | } |
| 53 | |
| 54 | pub fn lookup(self: *DynLib, comptime T: type, name: [:0]const u8) ?T { |
| 55 | return self.inner.lookup(T, name); |
| 56 | } |
| 57 | }; |
| 58 | |
| 59 | // The link_map structure is not completely specified beside the fields |
| 60 | // reported below, any libc is free to store additional data in the remaining |
| 61 | // space. |
| 62 | // An iterator is provided in order to traverse the linked list in a idiomatic |
| 63 | // fashion. |
| 64 | const LinkMap = extern struct { |
| 65 | addr: usize, |
| 66 | name: [*:0]const u8, |
| 67 | ld: ?*elf.Dyn, |
| 68 | next: ?*LinkMap, |
| 69 | prev: ?*LinkMap, |
| 70 | |
| 71 | pub const Iterator = struct { |
| 72 | current: ?*LinkMap, |
| 73 | |
| 74 | pub fn end(self: *Iterator) bool { |
| 75 | return self.current == null; |
| 76 | } |
| 77 | |
| 78 | pub fn next(self: *Iterator) ?*LinkMap { |
| 79 | if (self.current) |it| { |
| 80 | self.current = it.next; |
| 81 | return it; |
| 82 | } |
| 83 | return null; |
| 84 | } |
| 85 | }; |
| 86 | }; |
| 87 | |
| 88 | const RDebug = extern struct { |
| 89 | version: i32, |
| 90 | map: ?*LinkMap, |
| 91 | brk: usize, |
| 92 | ldbase: usize, |
| 93 | }; |
| 94 | |
| 95 | /// TODO fix comparisons of extern symbol pointers so we don't need this helper function. |
| 96 | pub fn get_DYNAMIC() ?[*]const elf.Dyn { |
| 97 | return @extern([*]const elf.Dyn, .{ |
| 98 | .name = "_DYNAMIC", |
| 99 | .linkage = .weak, |
| 100 | .visibility = .hidden, |
| 101 | }); |
| 102 | } |
| 103 | |
| 104 | pub fn linkmap_iterator() error{InvalidExe}!LinkMap.Iterator { |
| 105 | const _DYNAMIC = get_DYNAMIC() orelse { |
| 106 | // No PT.DYNAMIC means this is a statically-linked non-PIE program. |
| 107 | return .{ .current = null }; |
| 108 | }; |
| 109 | |
| 110 | const link_map_ptr = init: { |
| 111 | var i: usize = 0; |
| 112 | while (_DYNAMIC[i].d_tag != elf.DT_NULL) : (i += 1) { |
| 113 | switch (_DYNAMIC[i].d_tag) { |
| 114 | elf.DT_DEBUG => { |
| 115 | const ptr = @as(?*RDebug, @ptrFromInt(_DYNAMIC[i].d_val)); |
| 116 | if (ptr) |r_debug| { |
| 117 | if (r_debug.version != 1) return error.InvalidExe; |
| 118 | break :init r_debug.map; |
| 119 | } |
| 120 | }, |
| 121 | elf.DT_PLTGOT => { |
| 122 | const ptr = @as(?[*]usize, @ptrFromInt(_DYNAMIC[i].d_val)); |
| 123 | if (ptr) |got_table| { |
| 124 | // The address to the link_map structure is stored in |
| 125 | // the second slot |
| 126 | break :init @as(?*LinkMap, @ptrFromInt(got_table[1])); |
| 127 | } |
| 128 | }, |
| 129 | else => {}, |
| 130 | } |
| 131 | } |
| 132 | return .{ .current = null }; |
| 133 | }; |
| 134 | |
| 135 | return .{ .current = link_map_ptr }; |
| 136 | } |
| 137 | |
| 138 | /// Separated to avoid referencing `ElfDynLib`, because its field types may not |
| 139 | /// be valid on other targets. |
| 140 | const ElfDynLibError = error{ |
| 141 | FileTooBig, |
| 142 | NotElfFile, |
| 143 | NotDynamicLibrary, |
| 144 | MissingDynamicLinkingInformation, |
| 145 | ElfStringSectionNotFound, |
| 146 | ElfSymSectionNotFound, |
| 147 | ElfHashTableNotFound, |
| 148 | Canceled, |
| 149 | Streaming, |
| 150 | } || Io.File.OpenError || posix.MMapError; |
| 151 | |
| 152 | pub const ElfDynLib = struct { |
| 153 | strings: [*:0]u8, |
| 154 | syms: [*]elf.Sym, |
| 155 | hash_table: HashTable, |
| 156 | versym: ?[*]elf.Versym, |
| 157 | verdef: ?*elf.Verdef, |
| 158 | memory: []align(std.heap.page_size_min) u8, |
| 159 | |
| 160 | pub const Error = ElfDynLibError; |
| 161 | |
| 162 | const HashTable = union(enum) { |
| 163 | dt_hash: [*]posix.Elf_Symndx, |
| 164 | dt_gnu_hash: *elf.gnu_hash.Header, |
| 165 | }; |
| 166 | |
| 167 | fn openPath(io: Io, path: []const u8) !Io.Dir { |
| 168 | if (path.len == 0) return error.NotDir; |
| 169 | var parts = std.mem.tokenizeScalar(u8, path, '/'); |
| 170 | var parent = if (path[0] == '/') try Io.Dir.cwd().openDir(io, "/", .{}) else Io.Dir.cwd(); |
| 171 | while (parts.next()) |part| { |
| 172 | const child = try parent.openDir(io, part, .{}); |
| 173 | parent.close(io); |
| 174 | parent = child; |
| 175 | } |
| 176 | return parent; |
| 177 | } |
| 178 | |
| 179 | fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?Io.File { |
| 180 | var paths = std.mem.tokenizeScalar(u8, search_path, delim); |
| 181 | while (paths.next()) |p| { |
| 182 | var dir = openPath(io, p) catch continue; |
| 183 | defer dir.close(io); |
| 184 | return dir.openFile(io, file_name, .{}) catch continue; |
| 185 | } |
| 186 | return null; |
| 187 | } |
| 188 | |
| 189 | fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?Io.File { |
| 190 | var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null; |
| 191 | defer dir.close(io); |
| 192 | return dir.openFile(io, file_name, .{}) catch null; |
| 193 | } |
| 194 | |
| 195 | // This implements enough to be able to load system libraries in general |
| 196 | // Places where it differs from dlopen: |
| 197 | // - DT_RPATH of the calling binary is not used as a search path |
| 198 | // - DT_RUNPATH of the calling binary is not used as a search path |
| 199 | // - /etc/ld.so.cache is not read |
| 200 | fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !Io.File { |
| 201 | if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| { |
| 202 | return Io.Dir.cwd().openFile(io, path_or_name, .{}); |
| 203 | } |
| 204 | |
| 205 | // Only read LD_LIBRARY_PATH if the binary is not setuid/setgid |
| 206 | if (std.os.linux.geteuid() == std.os.linux.getuid() and |
| 207 | std.os.linux.getegid() == std.os.linux.getgid()) |
| 208 | { |
| 209 | if (LD_LIBRARY_PATH) |ld_library_path| { |
| 210 | if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |file| { |
| 211 | return file; |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | if (resolveFromParent(io, "/lib", path_or_name)) |file| return file; |
| 217 | if (resolveFromParent(io, "/lib64", path_or_name)) |file| return file; |
| 218 | if (resolveFromParent(io, "/usr/lib", path_or_name)) |file| return file; |
| 219 | if (resolveFromParent(io, "/usr/lib64", path_or_name)) |file| return file; |
| 220 | return error.FileNotFound; |
| 221 | } |
| 222 | |
| 223 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 224 | pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib { |
| 225 | const io = std.Options.debug_io; |
| 226 | |
| 227 | const file = try resolveFromName(io, path, LD_LIBRARY_PATH); |
| 228 | defer file.close(io); |
| 229 | |
| 230 | const stat = try file.stat(io); |
| 231 | const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; |
| 232 | |
| 233 | const page_size = std.heap.pageSize(); |
| 234 | |
| 235 | // This one is to read the ELF info. We do more mmapping later |
| 236 | // corresponding to the actual LOAD sections. |
| 237 | const file_bytes = try posix.mmap( |
| 238 | null, |
| 239 | mem.alignForward(usize, size, page_size), |
| 240 | .{ .READ = true }, |
| 241 | .{ .TYPE = .PRIVATE }, |
| 242 | file.handle, |
| 243 | 0, |
| 244 | ); |
| 245 | defer posix.munmap(file_bytes); |
| 246 | |
| 247 | const eh = @as(*elf.Ehdr, @ptrCast(file_bytes.ptr)); |
| 248 | if (!mem.eql(u8, eh.e_ident[0..4], elf.MAGIC)) return error.NotElfFile; |
| 249 | if (eh.e_type != elf.ET.DYN) return error.NotDynamicLibrary; |
| 250 | |
| 251 | const elf_addr = @intFromPtr(file_bytes.ptr); |
| 252 | |
| 253 | // Iterate over the program header entries to find out the |
| 254 | // dynamic vector as well as the total size of the virtual memory. |
| 255 | var maybe_dynv: ?[*]usize = null; |
| 256 | var virt_addr_end: usize = 0; |
| 257 | { |
| 258 | var i: usize = 0; |
| 259 | var ph_addr: usize = elf_addr + eh.e_phoff; |
| 260 | while (i < eh.e_phnum) : ({ |
| 261 | i += 1; |
| 262 | ph_addr += eh.e_phentsize; |
| 263 | }) { |
| 264 | const ph = @as(*elf.ElfN.Phdr, @ptrFromInt(ph_addr)); |
| 265 | switch (ph.type) { |
| 266 | .LOAD => virt_addr_end = @max(virt_addr_end, ph.vaddr + ph.memsz), |
| 267 | .DYNAMIC => maybe_dynv = @as([*]usize, @ptrFromInt(elf_addr + ph.offset)), |
| 268 | else => {}, |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | const dynv = maybe_dynv orelse return error.MissingDynamicLinkingInformation; |
| 273 | |
| 274 | // Reserve the entire range (with no permissions) so that we can do MAP.FIXED below. |
| 275 | const all_loaded_mem = try posix.mmap( |
| 276 | null, |
| 277 | virt_addr_end, |
| 278 | .{}, |
| 279 | .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, |
| 280 | -1, |
| 281 | 0, |
| 282 | ); |
| 283 | errdefer posix.munmap(all_loaded_mem); |
| 284 | |
| 285 | const base = @intFromPtr(all_loaded_mem.ptr); |
| 286 | |
| 287 | // Now iterate again and actually load all the program sections. |
| 288 | { |
| 289 | var i: usize = 0; |
| 290 | var ph_addr: usize = elf_addr + eh.e_phoff; |
| 291 | while (i < eh.e_phnum) : ({ |
| 292 | i += 1; |
| 293 | ph_addr += eh.e_phentsize; |
| 294 | }) { |
| 295 | const ph = @as(*elf.ElfN.Phdr, @ptrFromInt(ph_addr)); |
| 296 | switch (ph.type) { |
| 297 | .LOAD => { |
| 298 | // The VirtAddr may not be page-aligned; in such case there will be |
| 299 | // extra nonsense mapped before/after the VirtAddr,MemSiz |
| 300 | const aligned_addr = (base + ph.vaddr) & ~(@as(usize, page_size) - 1); |
| 301 | const extra_bytes = (base + ph.vaddr) - aligned_addr; |
| 302 | const extended_memsz = mem.alignForward(usize, ph.memsz + extra_bytes, page_size); |
| 303 | const ptr = @as([*]align(std.heap.page_size_min) u8, @ptrFromInt(aligned_addr)); |
| 304 | const prot = elfToProt(ph.flags); |
| 305 | _ = try posix.mmap( |
| 306 | ptr, |
| 307 | extended_memsz, |
| 308 | prot, |
| 309 | .{ .TYPE = .PRIVATE, .FIXED = true }, |
| 310 | file.handle, |
| 311 | ph.offset - extra_bytes, |
| 312 | ); |
| 313 | }, |
| 314 | else => {}, |
| 315 | } |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | var maybe_strings: ?[*:0]u8 = null; |
| 320 | var maybe_syms: ?[*]elf.Sym = null; |
| 321 | var maybe_hashtab: ?[*]posix.Elf_Symndx = null; |
| 322 | var maybe_gnu_hash: ?*elf.gnu_hash.Header = null; |
| 323 | var maybe_versym: ?[*]elf.Versym = null; |
| 324 | var maybe_verdef: ?*elf.Verdef = null; |
| 325 | |
| 326 | { |
| 327 | var i: usize = 0; |
| 328 | while (dynv[i] != 0) : (i += 2) { |
| 329 | const p = base + dynv[i + 1]; |
| 330 | switch (dynv[i]) { |
| 331 | elf.DT_STRTAB => maybe_strings = @ptrFromInt(p), |
| 332 | elf.DT_SYMTAB => maybe_syms = @ptrFromInt(p), |
| 333 | elf.DT_HASH => maybe_hashtab = @ptrFromInt(p), |
| 334 | elf.DT_GNU_HASH => maybe_gnu_hash = @ptrFromInt(p), |
| 335 | elf.DT_VERSYM => maybe_versym = @ptrFromInt(p), |
| 336 | elf.DT_VERDEF => maybe_verdef = @ptrFromInt(p), |
| 337 | else => {}, |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | const hash_table: HashTable = if (maybe_gnu_hash) |gnu_hash| |
| 343 | .{ .dt_gnu_hash = gnu_hash } |
| 344 | else if (maybe_hashtab) |hashtab| |
| 345 | .{ .dt_hash = hashtab } |
| 346 | else |
| 347 | return error.ElfHashTableNotFound; |
| 348 | |
| 349 | return .{ |
| 350 | .memory = all_loaded_mem, |
| 351 | .strings = maybe_strings orelse return error.ElfStringSectionNotFound, |
| 352 | .syms = maybe_syms orelse return error.ElfSymSectionNotFound, |
| 353 | .hash_table = hash_table, |
| 354 | .versym = maybe_versym, |
| 355 | .verdef = maybe_verdef, |
| 356 | }; |
| 357 | } |
| 358 | |
| 359 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 360 | pub fn openZ(path_c: [*:0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib { |
| 361 | return open(mem.sliceTo(path_c, 0), LD_LIBRARY_PATH); |
| 362 | } |
| 363 | |
| 364 | /// Trusts the file |
| 365 | pub fn close(self: *ElfDynLib) void { |
| 366 | posix.munmap(self.memory); |
| 367 | self.* = undefined; |
| 368 | } |
| 369 | |
| 370 | pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [:0]const u8) ?T { |
| 371 | if (self.lookupAddress("", name)) |symbol| { |
| 372 | return @as(T, @ptrFromInt(symbol)); |
| 373 | } else { |
| 374 | return null; |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | pub const GnuHashSection32 = struct { |
| 379 | symoffset: u32, |
| 380 | bloom_shift: u32, |
| 381 | bloom: []u32, |
| 382 | buckets: []u32, |
| 383 | chain: [*]elf.gnu_hash.ChainEntry, |
| 384 | |
| 385 | pub fn fromPtr(header: *elf.gnu_hash.Header) @This() { |
| 386 | const header_offset = @intFromPtr(header); |
| 387 | const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header); |
| 388 | const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u32); |
| 389 | const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32); |
| 390 | |
| 391 | const bloom_ptr: [*]u32 = @ptrFromInt(bloom_offset); |
| 392 | const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset); |
| 393 | const chain_ptr: [*]elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset); |
| 394 | |
| 395 | return .{ |
| 396 | .symoffset = header.symoffset, |
| 397 | .bloom_shift = header.bloom_shift, |
| 398 | .bloom = bloom_ptr[0..header.bloom_size], |
| 399 | .buckets = buckets_ptr[0..header.nbuckets], |
| 400 | .chain = chain_ptr, |
| 401 | }; |
| 402 | } |
| 403 | }; |
| 404 | |
| 405 | pub const GnuHashSection64 = struct { |
| 406 | symoffset: u32, |
| 407 | bloom_shift: u32, |
| 408 | bloom: []u64, |
| 409 | buckets: []u32, |
| 410 | chain: [*]elf.gnu_hash.ChainEntry, |
| 411 | |
| 412 | pub fn fromPtr(header: *elf.gnu_hash.Header) @This() { |
| 413 | const header_offset = @intFromPtr(header); |
| 414 | const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header); |
| 415 | const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u64); |
| 416 | const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32); |
| 417 | |
| 418 | const bloom_ptr: [*]u64 = @ptrFromInt(bloom_offset); |
| 419 | const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset); |
| 420 | const chain_ptr: [*]elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset); |
| 421 | |
| 422 | return .{ |
| 423 | .symoffset = header.symoffset, |
| 424 | .bloom_shift = header.bloom_shift, |
| 425 | .bloom = bloom_ptr[0..header.bloom_size], |
| 426 | .buckets = buckets_ptr[0..header.nbuckets], |
| 427 | .chain = chain_ptr, |
| 428 | }; |
| 429 | } |
| 430 | }; |
| 431 | |
| 432 | /// ElfDynLib specific |
| 433 | /// Returns the address of the symbol |
| 434 | pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize { |
| 435 | const maybe_versym = if (self.verdef == null) null else self.versym; |
| 436 | |
| 437 | const OK_TYPES = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON); |
| 438 | const OK_BINDS = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE); |
| 439 | |
| 440 | switch (self.hash_table) { |
| 441 | .dt_hash => |hashtab| { |
| 442 | var i: usize = 0; |
| 443 | while (i < hashtab[1]) : (i += 1) { |
| 444 | if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info & 0xf)) & OK_TYPES)) continue; |
| 445 | if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info >> 4)) & OK_BINDS)) continue; |
| 446 | if (0 == self.syms[i].st_shndx) continue; |
| 447 | if (!mem.eql(u8, name, mem.sliceTo(self.strings + self.syms[i].st_name, 0))) continue; |
| 448 | if (maybe_versym) |versym| { |
| 449 | if (!checkver(self.verdef.?, versym[i], vername, self.strings)) |
| 450 | continue; |
| 451 | } |
| 452 | return @intFromPtr(self.memory.ptr) + self.syms[i].st_value; |
| 453 | } |
| 454 | }, |
| 455 | .dt_gnu_hash => |gnu_hash_header| { |
| 456 | const GnuHashSection = switch (@bitSizeOf(usize)) { |
| 457 | 32 => GnuHashSection32, |
| 458 | 64 => GnuHashSection64, |
| 459 | else => |bit_size| @compileError("Unsupported bit size " ++ bit_size), |
| 460 | }; |
| 461 | |
| 462 | const gnu_hash_section: GnuHashSection = .fromPtr(gnu_hash_header); |
| 463 | const hash = elf.gnu_hash.calculate(name); |
| 464 | |
| 465 | const bloom_index = (hash / @bitSizeOf(usize)) % gnu_hash_header.bloom_size; |
| 466 | const bloom_val = gnu_hash_section.bloom[bloom_index]; |
| 467 | |
| 468 | const bit_index_0 = hash % @bitSizeOf(usize); |
| 469 | const bit_index_1 = (hash >> @intCast(gnu_hash_header.bloom_shift)) % @bitSizeOf(usize); |
| 470 | |
| 471 | const one: usize = 1; |
| 472 | const bit_mask: usize = (one << @intCast(bit_index_0)) | (one << @intCast(bit_index_1)); |
| 473 | |
| 474 | if (bloom_val & bit_mask != bit_mask) { |
| 475 | // Symbol is not in bloom filter, so it definitely isn't here. |
| 476 | return null; |
| 477 | } |
| 478 | |
| 479 | const bucket_index = hash % gnu_hash_header.nbuckets; |
| 480 | const chain_index = gnu_hash_section.buckets[bucket_index] - gnu_hash_header.symoffset; |
| 481 | |
| 482 | const chains = gnu_hash_section.chain; |
| 483 | const hash_as_entry: elf.gnu_hash.ChainEntry = @bitCast(hash); |
| 484 | |
| 485 | var current_index = chain_index; |
| 486 | var at_end_of_chain = false; |
| 487 | while (!at_end_of_chain) : (current_index += 1) { |
| 488 | const current_entry = chains[current_index]; |
| 489 | at_end_of_chain = current_entry.end_of_chain; |
| 490 | |
| 491 | if (current_entry.hash != hash_as_entry.hash) continue; |
| 492 | |
| 493 | // check that symbol matches |
| 494 | const symbol_index = current_index + gnu_hash_header.symoffset; |
| 495 | const symbol = self.syms[symbol_index]; |
| 496 | |
| 497 | if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info & 0xf)) & OK_TYPES)) continue; |
| 498 | if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info >> 4)) & OK_BINDS)) continue; |
| 499 | if (0 == symbol.st_shndx) continue; |
| 500 | |
| 501 | const symbol_name = mem.sliceTo(self.strings + symbol.st_name, 0); |
| 502 | if (!mem.eql(u8, name, symbol_name)) { |
| 503 | continue; |
| 504 | } |
| 505 | |
| 506 | if (maybe_versym) |versym| { |
| 507 | if (!checkver(self.verdef.?, versym[symbol_index], vername, self.strings)) { |
| 508 | continue; |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | return @intFromPtr(self.memory.ptr) + symbol.st_value; |
| 513 | } |
| 514 | }, |
| 515 | } |
| 516 | |
| 517 | return null; |
| 518 | } |
| 519 | |
| 520 | fn elfToProt(elf_prot: elf.PF) posix.PROT { |
| 521 | return .{ |
| 522 | .READ = elf_prot.R, |
| 523 | .WRITE = elf_prot.W, |
| 524 | .EXEC = elf_prot.X, |
| 525 | }; |
| 526 | } |
| 527 | }; |
| 528 | |
| 529 | fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, strings: [*:0]u8) bool { |
| 530 | var def = def_arg; |
| 531 | const vsym_index = vsym_arg.VERSION; |
| 532 | while (true) { |
| 533 | if (0 == (def.flags & elf.VER_FLG_BASE) and @backingInt(def.ndx) == vsym_index) break; |
| 534 | if (def.next == 0) return false; |
| 535 | def = @ptrFromInt(@intFromPtr(def) + def.next); |
| 536 | } |
| 537 | const aux: *elf.Verdaux = @ptrFromInt(@intFromPtr(def) + def.aux); |
| 538 | return mem.eql(u8, vername, mem.sliceTo(strings + aux.name, 0)); |
| 539 | } |
| 540 | |
| 541 | test "ElfDynLib" { |
| 542 | if (native_os != .linux) return error.SkipZigTest; |
| 543 | try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so", null)); |
| 544 | try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so", null)); |
| 545 | } |
| 546 | |
| 547 | /// Separated to avoid referencing `DlDynLib`, because its field types may not |
| 548 | /// be valid on other targets. |
| 549 | const DlDynLibError = error{ FileNotFound, NameTooLong }; |
| 550 | |
| 551 | pub const DlDynLib = struct { |
| 552 | pub const Error = DlDynLibError; |
| 553 | |
| 554 | handle: *anyopaque, |
| 555 | |
| 556 | pub fn open(path: []const u8) Error!DlDynLib { |
| 557 | const path_c = try posix.toPosixPath(path); |
| 558 | return openZ(&path_c); |
| 559 | } |
| 560 | |
| 561 | pub fn openZ(path_c: [*:0]const u8) Error!DlDynLib { |
| 562 | return .{ |
| 563 | .handle = std.c.dlopen(path_c, .{ .LAZY = true }) orelse { |
| 564 | return error.FileNotFound; |
| 565 | }, |
| 566 | }; |
| 567 | } |
| 568 | |
| 569 | pub fn close(self: *DlDynLib) void { |
| 570 | switch (posix.errno(std.c.dlclose(self.handle))) { |
| 571 | .SUCCESS => return, |
| 572 | else => unreachable, |
| 573 | } |
| 574 | self.* = undefined; |
| 575 | } |
| 576 | |
| 577 | pub fn lookup(self: *DlDynLib, comptime T: type, name: [:0]const u8) ?T { |
| 578 | // dlsym (and other dl-functions) secretly take shadow parameter - return address on stack |
| 579 | // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66826 |
| 580 | if (@call(.never_tail, std.c.dlsym, .{ self.handle, name.ptr })) |symbol| { |
| 581 | return @as(T, @ptrCast(@alignCast(symbol))); |
| 582 | } else { |
| 583 | return null; |
| 584 | } |
| 585 | } |
| 586 | |
| 587 | /// DlDynLib specific |
| 588 | /// Returns human readable string describing most recent error than occurred from `lookup` |
| 589 | /// or `null` if no error has occurred since initialization or when `getError` was last called. |
| 590 | pub fn getError() ?[:0]const u8 { |
| 591 | return mem.span(std.c.dlerror()); |
| 592 | } |
| 593 | }; |
| 594 | |
| 595 | test "dynamic_library" { |
| 596 | const libname = switch (native_os) { |
| 597 | .linux, .freebsd, .openbsd, .illumos => "invalid_so.so", |
| 598 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => "invalid_dylib.dylib", |
| 599 | else => return error.SkipZigTest, |
| 600 | }; |
| 601 | |
| 602 | try testing.expectError(error.FileNotFound, DynLib.open(libname)); |
| 603 | try testing.expectError(error.FileNotFound, DynLib.openZ(libname.ptr)); |
| 604 | } |