lib/sys/src/dynamic.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const capabilities = @import("capabilities.zig");
4 const sys_allocator = @import("allocator.zig");
5 const sys_env = @import("env.zig");
6 const sys_memory = @import("memory.zig");
7 const elf = std.elf;
8 const posix = std.posix;
9
10 pub const required_capabilities = capabilities.host(&.{ .allocator, .dynamic_loading, .environment, .filesystem, .memory_mapping });
11
12 const ElfRepairLocalError = error{
13 FileTooBig,
14 InvalidElfImage,
15 UnsupportedElfRelocation,
16 };
17
18 pub const ElfRepairError =
19 std.Io.File.OpenError ||
20 std.Io.File.StatError ||
21 sys_memory.MapError ||
22 ElfRepairLocalError;
23
24 const NativeElfLocalError = error{
25 FileTooBig,
26 InvalidElfImage,
27 NativeElfUnsupported,
28 NativeElfUnsupportedRelocation,
29 NativeElfStringSectionNotFound,
30 NativeElfSymbolSectionNotFound,
31 NativeElfHashTableNotFound,
32 OutOfMemory,
33 };
34
35 pub const NativeElfError =
36 std.Io.File.OpenError ||
37 std.Io.File.StatError ||
38 sys_memory.MapError ||
39 NativeElfLocalError;
40
41 pub const LibraryError = std.DynLib.Error || ElfRepairError || NativeElfError;
42 pub const OpenFirstError = LibraryError || error{NoCandidate};
43
44 pub const Library = struct {
45 raw: LibraryImpl,
46
47 pub fn open(path: []const u8) LibraryError!Library {
48 if (canUseNativeElfPlatform()) {
49 if (openNativeElf(path)) |library| {
50 return library;
51 } else |err| switch (err) {
52 error.NativeElfUnsupported,
53 error.NativeElfUnsupportedRelocation,
54 error.NativeElfHashTableNotFound,
55 error.FileNotFound,
56 => {},
57 else => |native_error| return native_error,
58 }
59 }
60
61 var raw = try std.DynLib.open(path);
62 errdefer raw.close();
63 if (comptime usesStdElfDynLib()) {
64 const require_repair = std.mem.indexOfScalar(u8, path, '/') != null;
65 repairElfImage(path, &raw) catch |err| {
66 if (require_repair) return err;
67 };
68 }
69 return .{ .raw = .{ .std_dynlib = raw } };
70 }
71
72 pub fn openNativeElf(path: []const u8) NativeElfError!Library {
73 const allocator = sys_allocator.processAllocator();
74 const native_path = try resolveNativeElfOpenPath(allocator, path);
75 defer allocator.free(native_path);
76 return .{ .raw = .{ .native_elf = try openNativeElfShared(allocator, native_path) } };
77 }
78
79 pub fn close(self: *Library) void {
80 switch (self.raw) {
81 .std_dynlib => |*raw| raw.close(),
82 .native_elf => |*native| native.close(),
83 }
84 }
85
86 pub fn lookup(self: *Library, comptime T: type, symbol: [:0]const u8) ?T {
87 return switch (self.raw) {
88 .std_dynlib => |*raw| raw.lookup(T, symbol),
89 .native_elf => |*native| native.lookup(T, symbol),
90 };
91 }
92
93 pub fn isNativeElf(self: *const Library) bool {
94 return switch (self.raw) {
95 .native_elf => true,
96 .std_dynlib => false,
97 };
98 }
99 };
100
101 pub fn openRuntimeFfiPlugin(path: []const u8) LibraryError!Library {
102 if (runtimeFfiPluginsRequireNativeElf()) return Library.openNativeElf(path);
103 return Library.open(path);
104 }
105
106 pub fn runtimeFfiPluginsRequireNativeElf() bool {
107 return canUseNativeElfPlatform();
108 }
109
110 const LibraryImpl = union(enum) {
111 std_dynlib: std.DynLib,
112 native_elf: NativeElfHandle,
113 };
114
115 const NativeElfHandle = struct {
116 shared: *NativeElfSharedLibrary,
117
118 fn close(self: *NativeElfHandle) void {
119 releaseNativeElfShared(self.shared);
120 self.* = undefined;
121 }
122
123 fn lookup(self: *const NativeElfHandle, comptime T: type, name: [:0]const u8) ?T {
124 return self.shared.library.lookup(T, name);
125 }
126
127 fn lookupAddressVersioned(self: *const NativeElfHandle, name: []const u8, version: ?[]const u8) ?usize {
128 return self.shared.library.lookupAddressVersioned(name, version);
129 }
130
131 fn lookupTlsSymbolVersioned(self: *const NativeElfHandle, name: []const u8, version: ?[]const u8) ?NativeElfTlsSymbol {
132 return self.shared.library.lookupTlsSymbolVersioned(name, version);
133 }
134 };
135
136 const NativeElfSharedLibrary = struct {
137 path: []u8,
138 ref_count: usize,
139 library: NativeElfLibrary,
140 };
141
142 const NativeElfRegistry = struct {
143 mutex: std.atomic.Mutex = .unlocked,
144 libraries: std.ArrayList(*NativeElfSharedLibrary) = .empty,
145
146 fn lock(self: *NativeElfRegistry) void {
147 while (!self.mutex.tryLock()) std.atomic.spinLoopHint();
148 }
149 };
150
151 var native_elf_registry: NativeElfRegistry = .{};
152
153 const NativeElfIfuncResolver = *const fn () callconv(.c) usize;
154
155 const NativeElfLibrary = struct {
156 allocator: std.mem.Allocator,
157 memory: []align(std.heap.page_size_min) u8,
158 load_bias: usize,
159 strings: [*:0]u8,
160 syms: [*]elf.Sym,
161 symbol_count: usize,
162 versyms: ?[*]elf.Versym,
163 verdef: usize,
164 verdef_count: usize,
165 dependencies: []NativeElfHandle,
166 tls: NativeElfTlsAllocation,
167 fini: usize,
168 fini_array: usize,
169 fini_array_count: usize,
170
171 fn openUncached(path: []const u8) NativeElfError!NativeElfLibrary {
172 if (!canUseNativeElfPlatform() or !hasLibraryPathSeparator(path)) return error.NativeElfUnsupported;
173
174 const io = std.Options.debug_io;
175 var file = if (path.len > 0 and path[0] == '/')
176 try std.Io.Dir.openFileAbsolute(io, path, .{})
177 else
178 try std.Io.Dir.cwd().openFile(io, path, .{});
179 defer file.close(io);
180
181 const stat = try file.stat(io);
182 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
183 if (size < @sizeOf(elf.Ehdr)) return error.InvalidElfImage;
184 const page_size = std.heap.pageSize();
185 const file_bytes = try sys_memory.mapPrivateFile(file.handle, size, .{ .read = true }, 0);
186 defer sys_memory.unmap(file_bytes);
187
188 const eh: *const elf.Ehdr = @ptrCast(file_bytes.ptr);
189 if (!std.mem.eql(u8, eh.e_ident[0..4], elf.MAGIC)) return error.InvalidElfImage;
190 if (eh.e_type != elf.ET.DYN) return error.InvalidElfImage;
191 if (eh.e_phentsize != @sizeOf(elf.Phdr)) return error.InvalidElfImage;
192 if (eh.e_phoff + @as(usize, eh.e_phentsize) * eh.e_phnum > size) return error.InvalidElfImage;
193
194 var load_count: usize = 0;
195 var min_vaddr: usize = std.math.maxInt(usize);
196 var max_vaddr: usize = 0;
197 var dynamic_vaddr: ?usize = null;
198 var relro_vaddr: usize = 0;
199 var relro_memsz: usize = 0;
200 var relro_count: usize = 0;
201 var maybe_tls_image: ?NativeElfTlsImage = null;
202 var i: usize = 0;
203 var ph_addr = @intFromPtr(file_bytes.ptr) + eh.e_phoff;
204 while (i < eh.e_phnum) : ({
205 i += 1;
206 ph_addr += eh.e_phentsize;
207 }) {
208 const ph: *const elf.Phdr = @ptrFromInt(ph_addr);
209 switch (ph.p_type) {
210 elf.PT_LOAD => {
211 const vaddr = std.math.cast(usize, ph.p_vaddr) orelse return error.InvalidElfImage;
212 const memsz = std.math.cast(usize, ph.p_memsz) orelse return error.InvalidElfImage;
213 const filesz = std.math.cast(usize, ph.p_filesz) orelse return error.InvalidElfImage;
214 const offset = std.math.cast(usize, ph.p_offset) orelse return error.InvalidElfImage;
215 if (memsz < filesz) return error.InvalidElfImage;
216 if (offset > size or filesz > size - offset) return error.InvalidElfImage;
217 const segment_end = checkedAdd(vaddr, memsz) orelse return error.InvalidElfImage;
218 min_vaddr = @min(min_vaddr, std.mem.alignBackward(usize, vaddr, page_size));
219 max_vaddr = @max(max_vaddr, std.mem.alignForward(usize, segment_end, page_size));
220 load_count += 1;
221 },
222 elf.PT_DYNAMIC => {
223 dynamic_vaddr = std.math.cast(usize, ph.p_vaddr) orelse return error.InvalidElfImage;
224 },
225 elf.PT_GNU_RELRO => {
226 relro_vaddr = std.math.cast(usize, ph.p_vaddr) orelse return error.InvalidElfImage;
227 relro_memsz = std.math.cast(usize, ph.p_memsz) orelse return error.InvalidElfImage;
228 relro_count += 1;
229 },
230 elf.PT_TLS => {
231 if (maybe_tls_image != null) return error.InvalidElfImage;
232 maybe_tls_image = try NativeElfTlsImage.fromProgramHeader(ph, size);
233 },
234 else => {},
235 }
236 }
237
238 if (load_count == 0) return error.InvalidElfImage;
239 if (relro_count > 1) return error.InvalidElfImage;
240 if (maybe_tls_image != null and !canUseNativeElfTls()) return error.NativeElfUnsupported;
241 if (max_vaddr <= min_vaddr) return error.InvalidElfImage;
242 const memory_size = max_vaddr - min_vaddr;
243 const memory = try sys_memory.mapAnonymous(memory_size, .{});
244 errdefer sys_memory.unmap(memory);
245
246 const allocator = sys_allocator.processAllocator();
247 const load_bias = @intFromPtr(memory.ptr) - min_vaddr;
248 try loadSegments(file.handle, file_bytes, size, load_bias, eh, page_size);
249 var tls = try NativeElfTlsAllocation.init(allocator, maybe_tls_image, load_bias);
250 errdefer tls.deinit(allocator);
251 const dynamic = dynamic_vaddr orelse return error.InvalidElfImage;
252
253 var maybe_strings: ?[*:0]u8 = null;
254 var maybe_syms: ?[*]elf.Sym = null;
255 var maybe_symbol_count: ?usize = null;
256 var maybe_gnu_hash: ?*elf.gnu_hash.Header = null;
257 var maybe_versyms: ?[*]elf.Versym = null;
258 var verdef: usize = 0;
259 var verdef_count: usize = 0;
260 var verneed: usize = 0;
261 var verneed_count: usize = 0;
262 var needed_offsets: std.ArrayList(usize) = .empty;
263 defer needed_offsets.deinit(allocator);
264 var maybe_runpath_offset: ?usize = null;
265 var maybe_rpath_offset: ?usize = null;
266 var init: usize = 0;
267 var init_array: usize = 0;
268 var init_array_size: usize = 0;
269 var fini: usize = 0;
270 var fini_array: usize = 0;
271 var fini_array_size: usize = 0;
272 const dynv: [*]usize = @ptrFromInt(load_bias + dynamic);
273 i = 0;
274 while (dynv[i] != 0) : (i += 2) {
275 const value = dynv[i + 1];
276 switch (dynv[i]) {
277 elf.DT_NEEDED => try needed_offsets.append(allocator, value),
278 elf.DT_STRTAB => maybe_strings = @ptrFromInt(load_bias + value),
279 elf.DT_SYMTAB => maybe_syms = @ptrFromInt(load_bias + value),
280 elf.DT_HASH => {
281 const hash: [*]const u32 = @ptrFromInt(load_bias + value);
282 maybe_symbol_count = hash[1];
283 },
284 elf.DT_GNU_HASH => maybe_gnu_hash = @ptrFromInt(load_bias + value),
285 elf.DT_VERSYM => maybe_versyms = @ptrFromInt(load_bias + value),
286 elf.DT_VERDEF => verdef = value,
287 elf.DT_VERDEFNUM => verdef_count = value,
288 elf.DT_VERNEED => verneed = value,
289 elf.DT_VERNEEDNUM => verneed_count = value,
290 elf.DT_RUNPATH => maybe_runpath_offset = value,
291 elf.DT_RPATH => maybe_rpath_offset = value,
292 elf.DT_INIT => init = value,
293 elf.DT_INIT_ARRAY => init_array = value,
294 elf.DT_INIT_ARRAYSZ => init_array_size = value,
295 elf.DT_FINI => fini = value,
296 elf.DT_FINI_ARRAY => fini_array = value,
297 elf.DT_FINI_ARRAYSZ => fini_array_size = value,
298 else => {},
299 }
300 }
301
302 const strings = maybe_strings orelse return error.NativeElfStringSectionNotFound;
303 const syms = maybe_syms orelse return error.NativeElfSymbolSectionNotFound;
304 const symbol_count = if (maybe_gnu_hash) |gnu_hash|
305 try gnuHashSymbolCount(gnu_hash)
306 else
307 maybe_symbol_count orelse return error.NativeElfHashTableNotFound;
308 const runpath = if (maybe_runpath_offset) |offset|
309 std.mem.sliceTo(strings + offset, 0)
310 else
311 null;
312 const rpath = if (maybe_rpath_offset) |offset|
313 std.mem.sliceTo(strings + offset, 0)
314 else
315 null;
316 const dependencies = try loadNativeElfDependencies(allocator, path, strings, needed_offsets.items, rpath, runpath);
317 errdefer closeNativeElfDependencies(allocator, dependencies);
318 try applyNativeElfRelocations(load_bias, dynamic, syms, strings, symbol_count, maybe_versyms, verneed, verneed_count, &tls, dependencies);
319 try protectNativeElfRelro(load_bias, relro_vaddr, relro_memsz, page_size);
320 try runNativeElfInitializers(load_bias, init, init_array, init_array_size);
321
322 return .{
323 .allocator = allocator,
324 .memory = memory,
325 .load_bias = load_bias,
326 .strings = strings,
327 .syms = syms,
328 .symbol_count = symbol_count,
329 .versyms = maybe_versyms,
330 .verdef = verdef,
331 .verdef_count = verdef_count,
332 .dependencies = dependencies,
333 .tls = tls,
334 .fini = fini,
335 .fini_array = fini_array,
336 .fini_array_count = fini_array_size / @sizeOf(usize),
337 };
338 }
339
340 pub fn close(self: *NativeElfLibrary) void {
341 runNativeElfFinalizers(self.load_bias, self.fini, self.fini_array, self.fini_array_count);
342 self.tls.deinit(self.allocator);
343 closeNativeElfDependencies(self.allocator, self.dependencies);
344 sys_memory.unmap(self.memory);
345 self.* = undefined;
346 }
347
348 pub fn lookup(self: *const NativeElfLibrary, comptime T: type, name: [:0]const u8) ?T {
349 return if (self.lookupAddress(name)) |address| @as(T, @ptrFromInt(address)) else null;
350 }
351
352 fn lookupAddress(self: *const NativeElfLibrary, name: []const u8) ?usize {
353 return self.lookupAddressVersioned(name, null);
354 }
355
356 fn lookupAddressVersioned(self: *const NativeElfLibrary, name: []const u8, version: ?[]const u8) ?usize {
357 return lookupNativeElfAddress(self.load_bias, self.syms, self.strings, self.symbol_count, self.versyms, self.verdef, self.verdef_count, name, version);
358 }
359
360 fn lookupTlsSymbolVersioned(self: *const NativeElfLibrary, name: []const u8, version: ?[]const u8) ?NativeElfTlsSymbol {
361 return lookupNativeElfTlsSymbol(self.load_bias, self.syms, self.strings, self.symbol_count, self.versyms, self.verdef, self.verdef_count, self.tls, name, version);
362 }
363 };
364
365 const NativeElfTlsImage = struct {
366 vaddr: usize,
367 filesz: usize,
368 memsz: usize,
369 offset: usize,
370 alignment: std.mem.Alignment,
371
372 fn fromProgramHeader(ph: *const elf.Phdr, file_len: usize) NativeElfError!NativeElfTlsImage {
373 const vaddr = std.math.cast(usize, ph.p_vaddr) orelse return error.InvalidElfImage;
374 const filesz = std.math.cast(usize, ph.p_filesz) orelse return error.InvalidElfImage;
375 const memsz = std.math.cast(usize, ph.p_memsz) orelse return error.InvalidElfImage;
376 const offset = std.math.cast(usize, ph.p_offset) orelse return error.InvalidElfImage;
377 const alignment_value = std.math.cast(usize, ph.p_align) orelse return error.InvalidElfImage;
378 if (memsz < filesz) return error.InvalidElfImage;
379 if (offset > file_len or filesz > file_len - offset) return error.InvalidElfImage;
380 return .{
381 .vaddr = vaddr,
382 .filesz = filesz,
383 .memsz = memsz,
384 .offset = offset,
385 .alignment = try nativeElfTlsAlignment(alignment_value),
386 };
387 }
388 };
389
390 const NativeElfTlsAllocation = struct {
391 module_id: usize = 0,
392 payload_len: usize = 0,
393
394 fn init(allocator: std.mem.Allocator, maybe_image: ?NativeElfTlsImage, load_bias: usize) NativeElfError!NativeElfTlsAllocation {
395 const image = maybe_image orelse return .{};
396 const template_address = checkedAdd(load_bias, image.vaddr) orelse return error.InvalidElfImage;
397 const module_id = try native_elf_tls_registry.register(allocator, .{
398 .module_id = 0,
399 .template_address = template_address,
400 .filesz = image.filesz,
401 .memsz = image.memsz,
402 .alignment = image.alignment,
403 });
404 return .{
405 .module_id = module_id,
406 .payload_len = image.memsz,
407 };
408 }
409
410 fn deinit(self: *NativeElfTlsAllocation, allocator: std.mem.Allocator) void {
411 if (self.module_id == 0) return;
412 native_elf_tls_registry.unregister(allocator, self.module_id);
413 self.* = .{};
414 }
415
416 fn active(self: *const NativeElfTlsAllocation) bool {
417 return self.module_id != 0;
418 }
419 };
420
421 const NativeElfTlsSymbol = struct {
422 module_id: usize,
423 offset: usize,
424 payload_len: usize,
425 };
426
427 const NativeElfTlsRecord = struct {
428 module_id: usize,
429 template_address: usize,
430 filesz: usize,
431 memsz: usize,
432 alignment: std.mem.Alignment,
433 blocks: std.ArrayList(NativeElfThreadTlsBlock) = .empty,
434
435 fn address(self: *NativeElfTlsRecord, allocator: std.mem.Allocator, thread_id: std.Thread.Id, offset: usize) NativeElfError!usize {
436 for (self.blocks.items) |*block| {
437 if (block.thread_id != thread_id) continue;
438 return block.address(offset) orelse return error.NativeElfUnsupportedRelocation;
439 }
440 var block = try NativeElfThreadTlsBlock.init(allocator, self, thread_id);
441 errdefer block.deinit(allocator);
442 const block_address = block.address(offset) orelse return error.NativeElfUnsupportedRelocation;
443 try self.blocks.append(allocator, block);
444 return block_address;
445 }
446
447 fn deinit(self: *NativeElfTlsRecord, allocator: std.mem.Allocator) void {
448 for (self.blocks.items) |*block| block.deinit(allocator);
449 self.blocks.deinit(allocator);
450 self.* = undefined;
451 }
452 };
453
454 const NativeElfThreadTlsBlock = struct {
455 module_id: usize,
456 thread_id: std.Thread.Id,
457 memory: []u8,
458 payload_len: usize,
459 alignment: std.mem.Alignment,
460
461 fn init(allocator: std.mem.Allocator, module: *const NativeElfTlsRecord, thread_id: std.Thread.Id) NativeElfError!NativeElfThreadTlsBlock {
462 const allocation_len = @max(module.memsz, 1);
463 const ptr = allocator.rawAlloc(allocation_len, module.alignment, @returnAddress()) orelse return error.OutOfMemory;
464 errdefer allocator.rawFree(ptr[0..allocation_len], module.alignment, @returnAddress());
465 const memory = ptr[0..allocation_len];
466 if (module.filesz != 0) {
467 const template: [*]const u8 = @ptrFromInt(module.template_address);
468 @memcpy(memory[0..module.filesz], template[0..module.filesz]);
469 }
470 if (module.memsz > module.filesz) {
471 @memset(memory[module.filesz..module.memsz], 0);
472 }
473 return .{
474 .module_id = module.module_id,
475 .thread_id = thread_id,
476 .memory = memory,
477 .payload_len = module.memsz,
478 .alignment = module.alignment,
479 };
480 }
481
482 fn deinit(self: *NativeElfThreadTlsBlock, allocator: std.mem.Allocator) void {
483 allocator.rawFree(self.memory, self.alignment, @returnAddress());
484 self.* = undefined;
485 }
486
487 fn address(self: *const NativeElfThreadTlsBlock, offset: usize) ?usize {
488 if (offset >= self.payload_len) return null;
489 return checkedAdd(@intFromPtr(self.memory.ptr), offset);
490 }
491 };
492
493 const NativeElfTlsRegistry = struct {
494 mutex: std.atomic.Mutex = .unlocked,
495 next_module_id: usize = 1,
496 records: std.ArrayList(NativeElfTlsRecord) = .empty,
497
498 fn register(self: *NativeElfTlsRegistry, allocator: std.mem.Allocator, record: NativeElfTlsRecord) NativeElfError!usize {
499 self.lock();
500 defer self.mutex.unlock();
501 const module_id = self.next_module_id;
502 const next_module_id = checkedAdd(module_id, 1) orelse return error.NativeElfUnsupported;
503 var module_record = record;
504 module_record.module_id = module_id;
505 try self.records.append(allocator, module_record);
506 self.next_module_id = next_module_id;
507 return module_id;
508 }
509
510 fn unregister(self: *NativeElfTlsRegistry, allocator: std.mem.Allocator, module_id: usize) void {
511 self.lock();
512 defer self.mutex.unlock();
513 var index: usize = 0;
514 while (index < self.records.items.len) : (index += 1) {
515 if (self.records.items[index].module_id == module_id) {
516 var record = self.records.swapRemove(index);
517 record.deinit(allocator);
518 return;
519 }
520 }
521 }
522
523 fn address(self: *NativeElfTlsRegistry, allocator: std.mem.Allocator, module_id: usize, thread_id: std.Thread.Id, offset: usize) NativeElfError!usize {
524 self.lock();
525 defer self.mutex.unlock();
526 for (self.records.items) |*record| {
527 if (record.module_id != module_id) continue;
528 return record.address(allocator, thread_id, offset);
529 }
530 return error.NativeElfUnsupportedRelocation;
531 }
532
533 fn lock(self: *NativeElfTlsRegistry) void {
534 while (!self.mutex.tryLock()) std.atomic.spinLoopHint();
535 }
536 };
537
538 const NativeElfTlsIndex = extern struct {
539 module_id: usize,
540 offset: usize,
541 };
542
543 var native_elf_tls_registry: NativeElfTlsRegistry = .{};
544
545 fn nativeElfTlsGetAddr(index: *const NativeElfTlsIndex) callconv(.c) *anyopaque {
546 const address = native_elf_tls_registry.address(sys_allocator.processAllocator(), index.module_id, std.Thread.getCurrentId(), index.offset) catch @trap();
547 return @ptrFromInt(address);
548 }
549
550 fn nativeElfTlsAlignment(alignment_value: usize) NativeElfError!std.mem.Alignment {
551 if (alignment_value == 0) return .@"1";
552 if (!std.math.isPowerOfTwo(alignment_value)) return error.InvalidElfImage;
553 return .fromByteUnits(alignment_value);
554 }
555
556 fn canUseNativeElfTls() bool {
557 return builtin.cpu.arch == .x86_64;
558 }
559
560 fn lookupNativeElfAddress(
561 load_bias: usize,
562 syms: [*]elf.Sym,
563 strings: [*:0]u8,
564 symbol_count: usize,
565 versyms: ?[*]elf.Versym,
566 verdef: usize,
567 verdef_count: usize,
568 name: []const u8,
569 version: ?[]const u8,
570 ) ?usize {
571 const ok_types = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON | 1 << elf.STT_GNU_IFUNC);
572 const ok_binds = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE);
573
574 var i: usize = 0;
575 while (i < symbol_count) : (i += 1) {
576 const symbol = syms[i];
577 if (symbol.st_shndx == 0) continue;
578 if (0 == (@as(u32, 1) << @as(u5, @intCast(nativeElfSymbolType(symbol))) & ok_types)) continue;
579 if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info >> 4)) & ok_binds)) continue;
580 if (!std.mem.eql(u8, name, std.mem.sliceTo(strings + symbol.st_name, 0))) continue;
581 if (version) |requested| {
582 if (!nativeElfSymbolMatchesVersion(load_bias, strings, versyms, verdef, verdef_count, i, requested)) continue;
583 } else if (!nativeElfSymbolHasDefaultVersion(versyms, i)) continue;
584 return nativeElfSymbolAddress(load_bias, symbol);
585 }
586
587 return null;
588 }
589
590 fn nativeElfSymbolType(symbol: elf.Sym) u8 {
591 return symbol.st_info & 0xf;
592 }
593
594 fn nativeElfSymbolBind(symbol: elf.Sym) u8 {
595 return symbol.st_info >> 4;
596 }
597
598 fn nativeElfSymbolAddress(load_bias: usize, symbol: elf.Sym) usize {
599 const address = load_bias + symbol.st_value;
600 if (nativeElfSymbolType(symbol) == elf.STT_GNU_IFUNC) return @as(NativeElfIfuncResolver, @ptrFromInt(address))();
601 return address;
602 }
603
604 fn nativeElfSymbolHasDefaultVersion(versyms: ?[*]elf.Versym, symbol_index: usize) bool {
605 const table = versyms orelse return true;
606 return !table[symbol_index].HIDDEN;
607 }
608
609 fn nativeElfSymbolMatchesVersion(
610 base: usize,
611 strings: [*:0]u8,
612 versyms: ?[*]elf.Versym,
613 verdef: usize,
614 verdef_count: usize,
615 symbol_index: usize,
616 requested: []const u8,
617 ) bool {
618 const table = versyms orelse return false;
619 const version_name = nativeElfDefinedVersionName(base, strings, table[symbol_index], verdef, verdef_count) orelse return false;
620 return std.mem.eql(u8, requested, version_name);
621 }
622
623 fn nativeElfDefinedVersionName(base: usize, strings: [*:0]u8, versym: elf.Versym, verdef: usize, verdef_count: usize) ?[]const u8 {
624 if (versym.VERSION <= @backingInt(elf.VER_NDX.GLOBAL) or verdef == 0) return null;
625
626 var definition_addr = base + verdef;
627 var index: usize = 0;
628 while (index < verdef_count) : (index += 1) {
629 const definition: *const elf.Verdef = @ptrFromInt(definition_addr);
630 if (@backingInt(definition.ndx) == versym.VERSION) {
631 const aux: *const elf.Verdaux = @ptrFromInt(definition_addr + definition.aux);
632 return std.mem.sliceTo(strings + aux.name, 0);
633 }
634 if (definition.next == 0) break;
635 definition_addr += definition.next;
636 }
637 return null;
638 }
639
640 fn lookupNativeElfTlsSymbol(
641 load_bias: usize,
642 syms: [*]elf.Sym,
643 strings: [*:0]u8,
644 symbol_count: usize,
645 versyms: ?[*]elf.Versym,
646 verdef: usize,
647 verdef_count: usize,
648 tls: NativeElfTlsAllocation,
649 name: []const u8,
650 version: ?[]const u8,
651 ) ?NativeElfTlsSymbol {
652 if (!tls.active()) return null;
653 const ok_binds = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE);
654
655 var i: usize = 0;
656 while (i < symbol_count) : (i += 1) {
657 const symbol = syms[i];
658 if (symbol.st_shndx == 0) continue;
659 if (symbol.st_info & 0xf != elf.STT_TLS) continue;
660 if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info >> 4)) & ok_binds)) continue;
661 if (!std.mem.eql(u8, name, std.mem.sliceTo(strings + symbol.st_name, 0))) continue;
662 if (version) |requested| {
663 if (!nativeElfSymbolMatchesVersion(load_bias, strings, versyms, verdef, verdef_count, i, requested)) continue;
664 } else if (!nativeElfSymbolHasDefaultVersion(versyms, i)) continue;
665 const symbol_offset = std.math.cast(usize, symbol.st_value) orelse return null;
666 if (symbol_offset >= tls.payload_len) return null;
667 return .{
668 .module_id = tls.module_id,
669 .offset = symbol_offset,
670 .payload_len = tls.payload_len,
671 };
672 }
673
674 return null;
675 }
676
677 fn gnuHashSymbolCount(header: *elf.gnu_hash.Header) NativeElfError!usize {
678 const header_offset = @intFromPtr(header);
679 const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header);
680 const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(usize);
681 const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32);
682 const buckets: [*]const u32 = @ptrFromInt(buckets_offset);
683 const chains: [*]const elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset);
684
685 var symbol_count: usize = header.symoffset;
686 for (buckets[0..header.nbuckets]) |bucket| {
687 if (bucket == 0) continue;
688 if (bucket < header.symoffset) return error.InvalidElfImage;
689 var chain_index: usize = bucket - header.symoffset;
690 while (true) : (chain_index += 1) {
691 symbol_count = @max(symbol_count, chain_index + header.symoffset + 1);
692 if (chains[chain_index].end_of_chain) break;
693 }
694 }
695 return symbol_count;
696 }
697
698 fn canUseNativeElfPlatform() bool {
699 return builtin.os.tag == .linux;
700 }
701
702 fn hasLibraryPathSeparator(path: []const u8) bool {
703 return std.mem.indexOfScalar(u8, path, '/') != null;
704 }
705
706 fn loadSegments(
707 file_handle: std.posix.fd_t,
708 file_bytes: []align(std.heap.page_size_min) const u8,
709 file_len: usize,
710 load_bias: usize,
711 eh: *const elf.Ehdr,
712 page_size: usize,
713 ) NativeElfError!void {
714 var i: usize = 0;
715 var ph_addr = @intFromPtr(file_bytes.ptr) + eh.e_phoff;
716 while (i < eh.e_phnum) : ({
717 i += 1;
718 ph_addr += eh.e_phentsize;
719 }) {
720 const ph: *const elf.Phdr = @ptrFromInt(ph_addr);
721 if (ph.p_type != elf.PT_LOAD) continue;
722
723 const vaddr = std.math.cast(usize, ph.p_vaddr) orelse return error.InvalidElfImage;
724 const memsz = std.math.cast(usize, ph.p_memsz) orelse return error.InvalidElfImage;
725 const filesz = std.math.cast(usize, ph.p_filesz) orelse return error.InvalidElfImage;
726 const offset = std.math.cast(usize, ph.p_offset) orelse return error.InvalidElfImage;
727 if (offset > file_len or filesz > file_len - offset) return error.InvalidElfImage;
728 const segment_addr = checkedAdd(load_bias, vaddr) orelse return error.InvalidElfImage;
729 const map_addr = std.mem.alignBackward(usize, segment_addr, page_size);
730 const page_delta = segment_addr - map_addr;
731 const map_len = std.mem.alignForward(usize, checkedAdd(page_delta, memsz) orelse return error.InvalidElfImage, page_size);
732 const ptr: [*]align(std.heap.page_size_min) u8 = @ptrFromInt(map_addr);
733 const writable = (ph.p_flags & elf.PF_W) != 0;
734
735 if (!writable and memsz == filesz) {
736 const file_offset = std.mem.alignBackward(usize, offset, page_size);
737 if (page_delta != offset - file_offset) return error.InvalidElfImage;
738 _ = try sys_memory.mapPrivateFileFixed(
739 file_handle,
740 ptr,
741 map_len,
742 elfToProtection(ph.p_flags),
743 file_offset,
744 );
745 } else {
746 const segment = try sys_memory.mapAnonymousFixed(
747 ptr,
748 map_len,
749 .{ .read = true, .write = true, .execute = writable and (ph.p_flags & elf.PF_X) != 0 },
750 );
751 @memcpy(segment[page_delta..][0..filesz], file_bytes[offset..][0..filesz]);
752 if (memsz > filesz) {
753 @memset(segment[page_delta + filesz .. page_delta + memsz], 0);
754 }
755 if (!writable) {
756 try protectNativeElfMemory(segment, elfToProtection(ph.p_flags));
757 }
758 }
759 }
760 }
761
762 test "native ELF loader zero fills read-only segment tails" {
763 if (builtin.os.tag != .linux) return error.SkipZigTest;
764
765 var image: [std.heap.page_size_max * 2]u8 align(std.heap.page_size_min) = @splat(0);
766 const eh: *elf.Ehdr = @ptrCast(&image);
767 eh.e_phoff = @sizeOf(elf.Ehdr);
768 eh.e_phentsize = @sizeOf(elf.Phdr);
769 eh.e_phnum = 1;
770
771 const ph: *elf.Phdr = @ptrCast(@alignCast(image[@sizeOf(elf.Ehdr)..].ptr));
772 const page_size = std.heap.pageSize();
773 const source_offset = page_size;
774 const source = [_]u8{ 0x10, 0x20, 0x30, 0x40 };
775 @memcpy(image[source_offset..][0..source.len], &source);
776 ph.* = .{
777 .p_type = elf.PT_LOAD,
778 .p_flags = elf.PF_R,
779 .p_offset = source_offset,
780 .p_vaddr = 0,
781 .p_paddr = 0,
782 .p_filesz = source.len,
783 .p_memsz = page_size,
784 .p_align = page_size,
785 };
786
787 const memory = try sys_memory.mapAnonymous(page_size, .{});
788 defer sys_memory.unmap(memory);
789 try loadSegments(-1, &image, image.len, @intFromPtr(memory.ptr), eh, page_size);
790 try std.testing.expectEqualSlices(u8, &source, memory[0..source.len]);
791 try std.testing.expect(std.mem.allEqual(u8, memory[source.len..page_size], 0));
792 }
793
794 fn loadNativeElfDependencies(
795 allocator: std.mem.Allocator,
796 owner_path: []const u8,
797 strings: [*:0]u8,
798 needed_offsets: []const usize,
799 rpath: ?[]const u8,
800 runpath: ?[]const u8,
801 ) NativeElfError![]NativeElfHandle {
802 var dependencies: std.ArrayList(NativeElfHandle) = .empty;
803 errdefer {
804 for (dependencies.items) |*dependency| dependency.close();
805 dependencies.deinit(allocator);
806 }
807
808 for (needed_offsets) |offset| {
809 const needed_name = std.mem.sliceTo(strings + offset, 0);
810 const dependency_path = try resolveNeededLibrary(allocator, owner_path, needed_name, rpath, runpath);
811 defer allocator.free(dependency_path);
812 try dependencies.append(allocator, try openNativeElfShared(allocator, dependency_path));
813 }
814
815 return dependencies.toOwnedSlice(allocator);
816 }
817
818 fn openNativeElfShared(allocator: std.mem.Allocator, path: []const u8) NativeElfError!NativeElfHandle {
819 if (retainNativeElfShared(path)) |handle| return handle;
820
821 var library = try NativeElfLibrary.openUncached(path);
822 errdefer library.close();
823
824 const stored_path = try allocator.dupe(u8, path);
825 errdefer allocator.free(stored_path);
826
827 const shared = try allocator.create(NativeElfSharedLibrary);
828 errdefer allocator.destroy(shared);
829 shared.* = .{
830 .path = stored_path,
831 .ref_count = 1,
832 .library = library,
833 };
834
835 native_elf_registry.lock();
836 defer native_elf_registry.mutex.unlock();
837 for (native_elf_registry.libraries.items) |existing| {
838 if (std.mem.eql(u8, existing.path, path)) {
839 existing.ref_count += 1;
840 library.close();
841 allocator.free(stored_path);
842 allocator.destroy(shared);
843 return .{ .shared = existing };
844 }
845 }
846 try native_elf_registry.libraries.append(allocator, shared);
847 return .{ .shared = shared };
848 }
849
850 fn retainNativeElfShared(path: []const u8) ?NativeElfHandle {
851 native_elf_registry.lock();
852 defer native_elf_registry.mutex.unlock();
853 for (native_elf_registry.libraries.items) |shared| {
854 if (!std.mem.eql(u8, shared.path, path)) continue;
855 shared.ref_count += 1;
856 return .{ .shared = shared };
857 }
858 return null;
859 }
860
861 fn resolveNativeElfOpenPath(allocator: std.mem.Allocator, path: []const u8) NativeElfError![]u8 {
862 if (!canUseNativeElfPlatform()) return error.NativeElfUnsupported;
863 if (hasLibraryPathSeparator(path)) return allocator.dupe(u8, path);
864 if (path.len == 0) return error.FileNotFound;
865 return resolvePathlessLibrary(allocator, path);
866 }
867
868 fn releaseNativeElfShared(shared: *NativeElfSharedLibrary) void {
869 const allocator = sys_allocator.processAllocator();
870 var owned: ?*NativeElfSharedLibrary = null;
871 native_elf_registry.lock();
872 if (shared.ref_count > 1) {
873 shared.ref_count -= 1;
874 } else {
875 const index = for (native_elf_registry.libraries.items, 0..) |candidate, index| {
876 if (candidate == shared) break index;
877 } else native_elf_registry.libraries.items.len;
878 if (index < native_elf_registry.libraries.items.len) {
879 _ = native_elf_registry.libraries.swapRemove(index);
880 }
881 owned = shared;
882 }
883 native_elf_registry.mutex.unlock();
884
885 if (owned) |library| {
886 library.library.close();
887 allocator.free(library.path);
888 allocator.destroy(library);
889 }
890 }
891
892 fn closeNativeElfDependencies(allocator: std.mem.Allocator, dependencies: []NativeElfHandle) void {
893 for (dependencies) |*dependency| dependency.close();
894 allocator.free(dependencies);
895 }
896
897 fn resolveNeededLibrary(
898 allocator: std.mem.Allocator,
899 owner_path: []const u8,
900 needed_name: []const u8,
901 rpath: ?[]const u8,
902 runpath: ?[]const u8,
903 ) NativeElfError![]u8 {
904 if (hasLibraryPathSeparator(needed_name)) return allocator.dupe(u8, needed_name);
905
906 if (rpath != null and runpath == null) {
907 if (try resolveLibraryInSearchPath(allocator, owner_path, needed_name, rpath.?)) |path| return path;
908 }
909
910 if (sys_env.getConstant("LD_LIBRARY_PATH")) |paths| {
911 if (try resolveLibraryInSearchPath(allocator, owner_path, needed_name, paths)) |path| return path;
912 }
913
914 if (runpath) |paths| {
915 if (try resolveLibraryInSearchPath(allocator, owner_path, needed_name, paths)) |path| return path;
916 }
917
918 const origin = std.fs.path.dirname(owner_path) orelse ".";
919 const origin_candidate = try std.fs.path.join(allocator, &.{ origin, needed_name });
920 if (pathExists(origin_candidate)) return origin_candidate;
921 allocator.free(origin_candidate);
922
923 return resolveDefaultLibrary(allocator, needed_name);
924 }
925
926 fn resolvePathlessLibrary(allocator: std.mem.Allocator, name: []const u8) NativeElfError![]u8 {
927 if (sys_env.getConstant("LD_LIBRARY_PATH")) |paths| {
928 if (try resolveLibraryInSearchPath(allocator, ".", name, paths)) |path| return path;
929 }
930 return resolveDefaultLibrary(allocator, name);
931 }
932
933 fn resolveLibraryInSearchPath(
934 allocator: std.mem.Allocator,
935 owner_path: []const u8,
936 name: []const u8,
937 search_path: []const u8,
938 ) NativeElfError!?[]u8 {
939 var iterator = std.mem.splitScalar(u8, search_path, ':');
940 while (iterator.next()) |entry| {
941 const path_entry = if (entry.len == 0) "." else entry;
942 const expanded = try expandLibrarySearchEntry(allocator, owner_path, path_entry);
943 defer allocator.free(expanded);
944 const candidate = try std.fs.path.join(allocator, &.{ expanded, name });
945 if (pathExists(candidate)) return candidate;
946 allocator.free(candidate);
947 }
948 return null;
949 }
950
951 fn resolveDefaultLibrary(allocator: std.mem.Allocator, name: []const u8) NativeElfError![]u8 {
952 for (nativeDefaultLibraryDirectories()) |directory| {
953 const candidate = try std.fs.path.join(allocator, &.{ directory, name });
954 if (pathExists(candidate)) return candidate;
955 allocator.free(candidate);
956 }
957 return error.FileNotFound;
958 }
959
960 const generic_library_directories = [_][]const u8{
961 "/lib",
962 "/usr/lib",
963 "/usr/local/lib",
964 "/lib64",
965 "/usr/lib64",
966 "/usr/local/lib64",
967 };
968
969 const x86_64_linux_library_directories = [_][]const u8{
970 "/lib/x86_64-linux-gnu",
971 "/usr/lib/x86_64-linux-gnu",
972 "/usr/local/lib/x86_64-linux-gnu",
973 "/lib",
974 "/usr/lib",
975 "/usr/local/lib",
976 "/lib64",
977 "/usr/lib64",
978 "/usr/local/lib64",
979 };
980
981 const x86_linux_library_directories = [_][]const u8{
982 "/lib/i386-linux-gnu",
983 "/usr/lib/i386-linux-gnu",
984 "/lib/i686-linux-gnu",
985 "/usr/lib/i686-linux-gnu",
986 "/lib",
987 "/usr/lib",
988 "/usr/local/lib",
989 };
990
991 const aarch64_linux_library_directories = [_][]const u8{
992 "/lib/aarch64-linux-gnu",
993 "/usr/lib/aarch64-linux-gnu",
994 "/usr/local/lib/aarch64-linux-gnu",
995 "/lib",
996 "/usr/lib",
997 "/usr/local/lib",
998 };
999
1000 const arm_linux_library_directories = [_][]const u8{
1001 "/lib/arm-linux-gnueabihf",
1002 "/usr/lib/arm-linux-gnueabihf",
1003 "/lib/arm-linux-gnueabi",
1004 "/usr/lib/arm-linux-gnueabi",
1005 "/lib",
1006 "/usr/lib",
1007 "/usr/local/lib",
1008 };
1009
1010 const riscv64_linux_library_directories = [_][]const u8{
1011 "/lib/riscv64-linux-gnu",
1012 "/usr/lib/riscv64-linux-gnu",
1013 "/usr/local/lib/riscv64-linux-gnu",
1014 "/lib",
1015 "/usr/lib",
1016 "/usr/local/lib",
1017 };
1018
1019 const powerpc64le_linux_library_directories = [_][]const u8{
1020 "/lib/powerpc64le-linux-gnu",
1021 "/usr/lib/powerpc64le-linux-gnu",
1022 "/usr/local/lib/powerpc64le-linux-gnu",
1023 "/lib",
1024 "/usr/lib",
1025 "/usr/local/lib",
1026 };
1027
1028 const s390x_linux_library_directories = [_][]const u8{
1029 "/lib/s390x-linux-gnu",
1030 "/usr/lib/s390x-linux-gnu",
1031 "/usr/local/lib/s390x-linux-gnu",
1032 "/lib",
1033 "/usr/lib",
1034 "/usr/local/lib",
1035 };
1036
1037 const loongarch64_linux_library_directories = [_][]const u8{
1038 "/lib/loongarch64-linux-gnu",
1039 "/usr/lib/loongarch64-linux-gnu",
1040 "/usr/local/lib/loongarch64-linux-gnu",
1041 "/lib",
1042 "/usr/lib",
1043 "/usr/local/lib",
1044 };
1045
1046 fn nativeDefaultLibraryDirectories() []const []const u8 {
1047 if (comptime builtin.os.tag != .linux) return &generic_library_directories;
1048 return switch (builtin.cpu.arch) {
1049 .x86_64 => &x86_64_linux_library_directories,
1050 .x86 => &x86_linux_library_directories,
1051 .aarch64, .aarch64_be => &aarch64_linux_library_directories,
1052 .arm, .armeb, .thumb, .thumbeb => &arm_linux_library_directories,
1053 .riscv64, .riscv64be => &riscv64_linux_library_directories,
1054 .powerpc64le => &powerpc64le_linux_library_directories,
1055 .s390x => &s390x_linux_library_directories,
1056 .loongarch64 => &loongarch64_linux_library_directories,
1057 else => &generic_library_directories,
1058 };
1059 }
1060
1061 fn nativeDefaultLibraryDirectoriesContain(path: []const u8) bool {
1062 for (nativeDefaultLibraryDirectories()) |directory| {
1063 if (std.mem.eql(u8, directory, path)) return true;
1064 }
1065 return false;
1066 }
1067
1068 fn expandLibrarySearchEntry(
1069 allocator: std.mem.Allocator,
1070 owner_path: []const u8,
1071 entry: []const u8,
1072 ) NativeElfError![]u8 {
1073 if (!std.mem.startsWith(u8, entry, "$ORIGIN")) return allocator.dupe(u8, entry);
1074
1075 const origin = std.fs.path.dirname(owner_path) orelse ".";
1076 if (entry.len == "$ORIGIN".len) return allocator.dupe(u8, origin);
1077 if (entry.len > "$ORIGIN".len and entry["$ORIGIN".len] == '/') {
1078 return std.fs.path.join(allocator, &.{ origin, entry["$ORIGIN".len + 1 ..] });
1079 }
1080 return allocator.dupe(u8, entry);
1081 }
1082
1083 fn pathExists(path: []const u8) bool {
1084 const io = std.Options.debug_io;
1085 var file = if (path.len > 0 and path[0] == '/')
1086 std.Io.Dir.openFileAbsolute(io, path, .{}) catch return false
1087 else
1088 std.Io.Dir.cwd().openFile(io, path, .{}) catch return false;
1089 file.close(io);
1090 return true;
1091 }
1092
1093 fn applyNativeElfRelocations(
1094 base: usize,
1095 dynamic_vaddr: usize,
1096 syms: [*]elf.Sym,
1097 strings: [*:0]u8,
1098 symbol_count: usize,
1099 versyms: ?[*]elf.Versym,
1100 verneed: usize,
1101 verneed_count: usize,
1102 tls: *const NativeElfTlsAllocation,
1103 dependencies: []const NativeElfHandle,
1104 ) NativeElfError!void {
1105 var rela: usize = 0;
1106 var rela_size: usize = 0;
1107 var rela_entry_size: usize = @sizeOf(elf.Rela);
1108 var rel: usize = 0;
1109 var rel_size: usize = 0;
1110 var rel_entry_size: usize = @sizeOf(elf.Rel);
1111 var relr: usize = 0;
1112 var relr_size: usize = 0;
1113 var relr_entry_size: usize = @sizeOf(elf.Relr);
1114 var jmprel: usize = 0;
1115 var pltrel_size: usize = 0;
1116 var pltrel: usize = elf.DT_RELA;
1117
1118 const dynv: [*]usize = @ptrFromInt(base + dynamic_vaddr);
1119 var i: usize = 0;
1120 while (dynv[i] != 0) : (i += 2) {
1121 switch (dynv[i]) {
1122 elf.DT_RELA => rela = dynv[i + 1],
1123 elf.DT_RELASZ => rela_size = dynv[i + 1],
1124 elf.DT_RELAENT => rela_entry_size = dynv[i + 1],
1125 elf.DT_REL => rel = dynv[i + 1],
1126 elf.DT_RELSZ => rel_size = dynv[i + 1],
1127 elf.DT_RELENT => rel_entry_size = dynv[i + 1],
1128 elf.DT_RELR => relr = dynv[i + 1],
1129 elf.DT_RELRSZ => relr_size = dynv[i + 1],
1130 elf.DT_RELRENT => relr_entry_size = dynv[i + 1],
1131 elf.DT_JMPREL => jmprel = dynv[i + 1],
1132 elf.DT_PLTRELSZ => pltrel_size = dynv[i + 1],
1133 elf.DT_PLTREL => pltrel = dynv[i + 1],
1134 else => {},
1135 }
1136 }
1137
1138 if (rel != 0) {
1139 if (rel_entry_size != @sizeOf(elf.Rel)) return error.NativeElfUnsupportedRelocation;
1140 const rels: []const elf.Rel = @ptrCast(@alignCast(
1141 @as([*]align(@alignOf(elf.Rel)) const u8, @ptrFromInt(base + rel))[0..rel_size],
1142 ));
1143 for (rels) |r| {
1144 try applyNativeElfRel(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, r);
1145 }
1146 }
1147
1148 if (rela != 0) {
1149 if (rela_entry_size != @sizeOf(elf.Rela)) return error.NativeElfUnsupportedRelocation;
1150 const relas: []const elf.Rela = @ptrCast(@alignCast(
1151 @as([*]align(@alignOf(elf.Rela)) const u8, @ptrFromInt(base + rela))[0..rela_size],
1152 ));
1153 for (relas) |r| {
1154 try applyNativeElfRela(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, r);
1155 }
1156 }
1157
1158 if (jmprel != 0 or pltrel_size != 0) {
1159 switch (pltrel) {
1160 elf.DT_REL => {
1161 if (pltrel_size % @sizeOf(elf.Rel) != 0) return error.NativeElfUnsupportedRelocation;
1162 const rels: []const elf.Rel = @ptrCast(@alignCast(
1163 @as([*]align(@alignOf(elf.Rel)) const u8, @ptrFromInt(base + jmprel))[0..pltrel_size],
1164 ));
1165 for (rels) |r| {
1166 try applyNativeElfRel(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, r);
1167 }
1168 },
1169 elf.DT_RELA => {
1170 if (pltrel_size % @sizeOf(elf.Rela) != 0) return error.NativeElfUnsupportedRelocation;
1171 const relas: []const elf.Rela = @ptrCast(@alignCast(
1172 @as([*]align(@alignOf(elf.Rela)) const u8, @ptrFromInt(base + jmprel))[0..pltrel_size],
1173 ));
1174 for (relas) |r| {
1175 try applyNativeElfRela(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, r);
1176 }
1177 },
1178 else => return error.NativeElfUnsupportedRelocation,
1179 }
1180 }
1181
1182 if (relr != 0) {
1183 applyRelativeRelrRelocations(base, relr, relr_size, relr_entry_size) catch return error.NativeElfUnsupportedRelocation;
1184 }
1185 }
1186
1187 fn applyNativeElfRel(
1188 base: usize,
1189 syms: [*]elf.Sym,
1190 strings: [*:0]u8,
1191 symbol_count: usize,
1192 versyms: ?[*]elf.Versym,
1193 verneed: usize,
1194 verneed_count: usize,
1195 tls: *const NativeElfTlsAllocation,
1196 dependencies: []const NativeElfHandle,
1197 relocation: elf.Rel,
1198 ) NativeElfError!void {
1199 const target: *usize = @ptrFromInt(base + relocation.r_offset);
1200 switch (relocation.r_type()) {
1201 relativeRelocationKind() => target.* += base,
1202 else => try applyNativeElfSymbolRelocation(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, relocation.r_sym(), relocation.r_type(), relocation.r_offset, 0),
1203 }
1204 }
1205
1206 fn applyNativeElfRela(
1207 base: usize,
1208 syms: [*]elf.Sym,
1209 strings: [*:0]u8,
1210 symbol_count: usize,
1211 versyms: ?[*]elf.Versym,
1212 verneed: usize,
1213 verneed_count: usize,
1214 tls: *const NativeElfTlsAllocation,
1215 dependencies: []const NativeElfHandle,
1216 relocation: elf.Rela,
1217 ) NativeElfError!void {
1218 if (irelativeRelocationKind()) |irelative_kind| {
1219 if (relocation.r_type() == irelative_kind) {
1220 if (relocation.r_sym() != 0) return error.NativeElfUnsupportedRelocation;
1221 const resolver_address = try addSigned(base, relocation.r_addend);
1222 @as(*usize, @ptrFromInt(base + relocation.r_offset)).* = @as(NativeElfIfuncResolver, @ptrFromInt(resolver_address))();
1223 return;
1224 }
1225 }
1226 switch (relocation.r_type()) {
1227 relativeRelocationKind() => @as(*usize, @ptrFromInt(base + relocation.r_offset)).* = try addSigned(base, relocation.r_addend),
1228 else => try applyNativeElfSymbolRelocation(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, relocation.r_sym(), relocation.r_type(), relocation.r_offset, relocation.r_addend),
1229 }
1230 }
1231
1232 fn applyNativeElfSymbolRelocation(
1233 base: usize,
1234 syms: [*]elf.Sym,
1235 strings: [*:0]u8,
1236 symbol_count: usize,
1237 versyms: ?[*]elf.Versym,
1238 verneed: usize,
1239 verneed_count: usize,
1240 tls: *const NativeElfTlsAllocation,
1241 dependencies: []const NativeElfHandle,
1242 symbol_index: usize,
1243 relocation_kind: u32,
1244 offset: usize,
1245 addend: isize,
1246 ) NativeElfError!void {
1247 const absolute_kind = absoluteRelocationKind();
1248 const glob_dat_kind = globDatRelocationKind();
1249 const jump_slot_kind = jumpSlotRelocationKind();
1250 if (try applyNativeElfTlsRelocation(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, symbol_index, relocation_kind, offset, addend)) return;
1251 if ((absolute_kind == null or relocation_kind != absolute_kind.?) and
1252 (glob_dat_kind == null or relocation_kind != glob_dat_kind.?) and
1253 (jump_slot_kind == null or relocation_kind != jump_slot_kind.?))
1254 {
1255 return error.NativeElfUnsupportedRelocation;
1256 }
1257 if (symbol_index >= symbol_count) return error.NativeElfUnsupportedRelocation;
1258
1259 const symbol = syms[symbol_index];
1260 const name = std.mem.sliceTo(strings + symbol.st_name, 0);
1261 const address = if (symbol.st_shndx != 0)
1262 nativeElfSymbolAddress(base, symbol)
1263 else blk: {
1264 if (nativeElfBuiltinSymbolAddress(name)) |address| break :blk address;
1265 const version = try nativeElfNeededVersionName(base, strings, versyms, verneed, verneed_count, symbol_index);
1266 if (resolveNativeElfDependencySymbol(dependencies, name, version)) |address| break :blk address;
1267 if (nativeElfSymbolBind(symbol) == elf.STB_WEAK) break :blk 0;
1268 return error.NativeElfUnsupportedRelocation;
1269 };
1270 @as(*usize, @ptrFromInt(base + offset)).* = try addSigned(address, addend);
1271 }
1272
1273 fn applyNativeElfTlsRelocation(
1274 base: usize,
1275 syms: [*]elf.Sym,
1276 strings: [*:0]u8,
1277 symbol_count: usize,
1278 versyms: ?[*]elf.Versym,
1279 verneed: usize,
1280 verneed_count: usize,
1281 tls: *const NativeElfTlsAllocation,
1282 dependencies: []const NativeElfHandle,
1283 symbol_index: usize,
1284 relocation_kind: u32,
1285 offset: usize,
1286 addend: isize,
1287 ) NativeElfError!bool {
1288 if (tlsModuleRelocationKind()) |module_kind| {
1289 if (relocation_kind == module_kind) {
1290 if (symbol_index == 0) {
1291 if (!tls.active()) return error.NativeElfUnsupportedRelocation;
1292 @as(*usize, @ptrFromInt(base + offset)).* = try addSigned(tls.module_id, addend);
1293 return true;
1294 }
1295 const symbol = try nativeElfRelocationTlsSymbol(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, symbol_index);
1296 @as(*usize, @ptrFromInt(base + offset)).* = try addSigned(symbol.module_id, addend);
1297 return true;
1298 }
1299 }
1300 if (tlsOffsetRelocationKind()) |offset_kind| {
1301 if (relocation_kind == offset_kind) {
1302 const symbol = try nativeElfRelocationTlsSymbol(base, syms, strings, symbol_count, versyms, verneed, verneed_count, tls, dependencies, symbol_index);
1303 const tls_offset = try addSigned(symbol.offset, addend);
1304 if (tls_offset >= symbol.payload_len) return error.NativeElfUnsupportedRelocation;
1305 @as(*usize, @ptrFromInt(base + offset)).* = tls_offset;
1306 return true;
1307 }
1308 }
1309 return false;
1310 }
1311
1312 fn nativeElfRelocationTlsSymbol(
1313 base: usize,
1314 syms: [*]elf.Sym,
1315 strings: [*:0]u8,
1316 symbol_count: usize,
1317 versyms: ?[*]elf.Versym,
1318 verneed: usize,
1319 verneed_count: usize,
1320 tls: *const NativeElfTlsAllocation,
1321 dependencies: []const NativeElfHandle,
1322 symbol_index: usize,
1323 ) NativeElfError!NativeElfTlsSymbol {
1324 if (symbol_index >= symbol_count) return error.NativeElfUnsupportedRelocation;
1325 const symbol = syms[symbol_index];
1326 if (symbol.st_shndx != 0) {
1327 if (!tls.active()) return error.NativeElfUnsupportedRelocation;
1328 if (symbol.st_info & 0xf != elf.STT_TLS) return error.NativeElfUnsupportedRelocation;
1329 const symbol_offset = std.math.cast(usize, symbol.st_value) orelse return error.NativeElfUnsupportedRelocation;
1330 if (symbol_offset >= tls.payload_len) return error.NativeElfUnsupportedRelocation;
1331 return .{
1332 .module_id = tls.module_id,
1333 .offset = symbol_offset,
1334 .payload_len = tls.payload_len,
1335 };
1336 }
1337
1338 const name = std.mem.sliceTo(strings + symbol.st_name, 0);
1339 if (symbol.st_info & 0xf != elf.STT_TLS) return error.NativeElfUnsupportedRelocation;
1340 const version = try nativeElfNeededVersionName(base, strings, versyms, verneed, verneed_count, symbol_index);
1341 return resolveNativeElfDependencyTlsSymbol(dependencies, name, version) orelse error.NativeElfUnsupportedRelocation;
1342 }
1343
1344 fn nativeElfBuiltinSymbolAddress(name: []const u8) ?usize {
1345 if (canUseNativeElfTls() and std.mem.eql(u8, name, "__tls_get_addr")) return @intFromPtr(&nativeElfTlsGetAddr);
1346 return null;
1347 }
1348
1349 fn resolveNativeElfDependencySymbol(dependencies: []const NativeElfHandle, name: []const u8, version: ?[]const u8) ?usize {
1350 for (dependencies) |*dependency| {
1351 if (dependency.lookupAddressVersioned(name, version)) |address| return address;
1352 }
1353 for (dependencies) |*dependency| {
1354 if (resolveNativeElfDependencySymbol(dependency.shared.library.dependencies, name, version)) |address| return address;
1355 }
1356 return null;
1357 }
1358
1359 fn resolveNativeElfDependencyTlsSymbol(dependencies: []const NativeElfHandle, name: []const u8, version: ?[]const u8) ?NativeElfTlsSymbol {
1360 for (dependencies) |*dependency| {
1361 if (dependency.lookupTlsSymbolVersioned(name, version)) |symbol| return symbol;
1362 }
1363 for (dependencies) |*dependency| {
1364 if (resolveNativeElfDependencyTlsSymbol(dependency.shared.library.dependencies, name, version)) |symbol| return symbol;
1365 }
1366 return null;
1367 }
1368
1369 const ElfVerneed = if (@sizeOf(usize) == 8) elf.Elf64_Verneed else elf.Elf32_Verneed;
1370
1371 fn nativeElfNeededVersionName(
1372 base: usize,
1373 strings: [*:0]u8,
1374 versyms: ?[*]elf.Versym,
1375 verneed: usize,
1376 verneed_count: usize,
1377 symbol_index: usize,
1378 ) NativeElfError!?[]const u8 {
1379 const table = versyms orelse return null;
1380 const requested = table[symbol_index];
1381 if (requested.VERSION <= @backingInt(elf.VER_NDX.GLOBAL)) return null;
1382 if (verneed == 0) return error.NativeElfUnsupportedRelocation;
1383
1384 var need_addr = base + verneed;
1385 var need_index: usize = 0;
1386 while (need_index < verneed_count) : (need_index += 1) {
1387 const need: *const ElfVerneed = @ptrFromInt(need_addr);
1388 var aux_addr = need_addr + need.vn_aux;
1389 var aux_index: usize = 0;
1390 while (aux_index < need.vn_cnt) : (aux_index += 1) {
1391 const aux: *const elf.Vernaux = @ptrFromInt(aux_addr);
1392 if (aux.other == requested.VERSION) return std.mem.sliceTo(strings + aux.name, 0);
1393 if (aux.next == 0) break;
1394 aux_addr += aux.next;
1395 }
1396 if (need.vn_next == 0) break;
1397 need_addr += need.vn_next;
1398 }
1399 return error.NativeElfUnsupportedRelocation;
1400 }
1401
1402 fn runNativeElfInitializers(base: usize, init: usize, init_array: usize, init_array_size: usize) NativeElfError!void {
1403 if (init != 0) callNativeElfInit(base + init);
1404 if (init_array_size % @sizeOf(usize) != 0) return error.InvalidElfImage;
1405 const count = init_array_size / @sizeOf(usize);
1406 if (count == 0) return;
1407 const functions: [*]const usize = @ptrFromInt(base + init_array);
1408 var index: usize = 0;
1409 while (index < count) : (index += 1) {
1410 if (functions[index] != 0) callNativeElfInit(functions[index]);
1411 }
1412 }
1413
1414 fn runNativeElfFinalizers(base: usize, fini: usize, fini_array: usize, fini_array_count: usize) void {
1415 if (fini_array_count != 0) {
1416 const functions: [*]const usize = @ptrFromInt(base + fini_array);
1417 var index = fini_array_count;
1418 while (index > 0) {
1419 index -= 1;
1420 if (functions[index] != 0) callNativeElfInit(functions[index]);
1421 }
1422 }
1423 if (fini != 0) callNativeElfInit(base + fini);
1424 }
1425
1426 fn protectNativeElfRelro(base: usize, relro_vaddr: usize, relro_memsz: usize, page_size: usize) NativeElfError!void {
1427 if (relro_memsz == 0) return;
1428 const relro_start = checkedAdd(base, relro_vaddr) orelse return error.InvalidElfImage;
1429 const relro_end = checkedAdd(relro_start, relro_memsz) orelse return error.InvalidElfImage;
1430 const page_start = std.mem.alignBackward(usize, relro_start, page_size);
1431 const page_end = std.mem.alignForward(usize, relro_end, page_size);
1432 if (page_end <= page_start) return;
1433 const ptr: [*]align(std.heap.page_size_min) u8 = @ptrFromInt(page_start);
1434 try protectNativeElfMemory(ptr[0 .. page_end - page_start], .{ .read = true });
1435 }
1436
1437 fn protectNativeElfMemory(
1438 memory: []align(std.heap.page_size_min) u8,
1439 protection: sys_memory.Protection,
1440 ) NativeElfError!void {
1441 sys_memory.protect(memory, protection) catch |err| switch (err) {
1442 error.InvalidMapping => return error.InvalidElfImage,
1443 error.OutOfMemory => return error.OutOfMemory,
1444 error.AccessDenied, error.PermissionDenied, error.UnsupportedPlatform, error.ProtectFailed => return error.NativeElfUnsupported,
1445 };
1446 }
1447
1448 fn callNativeElfInit(address: usize) void {
1449 const InitFn = *const fn () callconv(.c) void;
1450 @as(InitFn, @ptrFromInt(address))();
1451 }
1452
1453 fn elfToProtection(elf_prot: u64) sys_memory.Protection {
1454 return .{
1455 .read = (elf_prot & elf.PF_R) != 0,
1456 .write = (elf_prot & elf.PF_W) != 0,
1457 .execute = (elf_prot & elf.PF_X) != 0,
1458 };
1459 }
1460
1461 fn checkedAdd(left: usize, right: usize) ?usize {
1462 const result, const overflow = @addWithOverflow(left, right);
1463 if (overflow != 0) return null;
1464 return result;
1465 }
1466
1467 fn addSigned(base: usize, addend: isize) NativeElfError!usize {
1468 if (addend >= 0) return checkedAdd(base, @intCast(addend)) orelse error.InvalidElfImage;
1469 if (addend == std.math.minInt(isize)) return error.InvalidElfImage;
1470 const magnitude: usize = @intCast(-addend);
1471 if (base < magnitude) return error.InvalidElfImage;
1472 return base - magnitude;
1473 }
1474
1475 pub const LibraryCandidate = struct {
1476 os: ?std.Target.Os.Tag = null,
1477 name: []const u8,
1478
1479 pub fn matchesHost(self: LibraryCandidate) bool {
1480 return self.os == null or self.os.? == builtin.os.tag;
1481 }
1482 };
1483
1484 const cuda_driver_candidates = [_]LibraryCandidate{
1485 .{ .os = .linux, .name = "libcuda.so.1" },
1486 .{ .os = .linux, .name = "libcuda.so" },
1487 };
1488
1489 const vulkan_loader_candidates = [_]LibraryCandidate{
1490 .{ .os = .linux, .name = "libvulkan.so.1" },
1491 .{ .os = .linux, .name = "libvulkan.so" },
1492 .{ .os = .macos, .name = "libvulkan.1.dylib" },
1493 .{ .os = .windows, .name = "vulkan-1.dll" },
1494 };
1495
1496 const x11_library_candidates = [_]LibraryCandidate{
1497 .{ .os = .linux, .name = "libX11.so.6" },
1498 .{ .os = .linux, .name = "libX11.so" },
1499 };
1500
1501 pub fn cudaDriverCandidates() []const LibraryCandidate {
1502 return &cuda_driver_candidates;
1503 }
1504
1505 pub fn vulkanLoaderCandidates() []const LibraryCandidate {
1506 return &vulkan_loader_candidates;
1507 }
1508
1509 pub fn x11LibraryCandidates() []const LibraryCandidate {
1510 return &x11_library_candidates;
1511 }
1512
1513 pub fn hasHostCandidate(candidates: []const LibraryCandidate) bool {
1514 for (candidates) |candidate| {
1515 if (candidate.matchesHost()) return true;
1516 }
1517 return false;
1518 }
1519
1520 pub fn openFirst(candidates: []const LibraryCandidate) OpenFirstError!Library {
1521 var matched = false;
1522 var last_error: ?LibraryError = null;
1523
1524 for (candidates) |candidate| {
1525 if (!candidate.matchesHost()) continue;
1526 matched = true;
1527 return Library.open(candidate.name) catch |err| {
1528 last_error = err;
1529 continue;
1530 };
1531 }
1532
1533 if (!matched) return error.NoCandidate;
1534 return last_error.?;
1535 }
1536
1537 pub fn openFirstNativeElfOnLinux(candidates: []const LibraryCandidate) OpenFirstError!Library {
1538 var matched = false;
1539 var last_error: ?LibraryError = null;
1540
1541 for (candidates) |candidate| {
1542 if (!candidate.matchesHost()) continue;
1543 matched = true;
1544 if (comptime builtin.os.tag == .linux) {
1545 return Library.openNativeElf(candidate.name) catch |err| {
1546 last_error = err;
1547 continue;
1548 };
1549 }
1550 return Library.open(candidate.name) catch |err| {
1551 last_error = err;
1552 continue;
1553 };
1554 }
1555
1556 if (!matched) return error.NoCandidate;
1557 return last_error.?;
1558 }
1559
1560 pub fn isLoadable(candidates: []const LibraryCandidate) bool {
1561 var library = openFirst(candidates) catch return false;
1562 library.close();
1563 return true;
1564 }
1565
1566 pub fn openSystemLibrary(candidates: []const LibraryCandidate) OpenFirstError!Library {
1567 if (comptime usesStdElfDynLib()) return openFirstNativeElfOnLinux(candidates);
1568
1569 var matched = false;
1570 var last_error: ?LibraryError = null;
1571 for (candidates) |candidate| {
1572 if (!candidate.matchesHost()) continue;
1573 matched = true;
1574 const raw = openHostSystemLibrary(candidate.name) catch |err| {
1575 last_error = err;
1576 continue;
1577 };
1578 return .{ .raw = .{ .std_dynlib = raw } };
1579 }
1580
1581 if (!matched) return error.NoCandidate;
1582 return last_error.?;
1583 }
1584
1585 fn openHostSystemLibrary(name: []const u8) LibraryError!std.DynLib {
1586 return std.DynLib.open(name) catch |err| {
1587 if (comptime builtin.os.tag != .linux) return err;
1588 if (std.mem.indexOfScalar(u8, name, '/') != null) return err;
1589
1590 const allocator = sys_allocator.processAllocator();
1591 const path = try resolvePathlessLibrary(allocator, name);
1592 defer allocator.free(path);
1593 return std.DynLib.open(path);
1594 };
1595 }
1596
1597 pub fn openCudaDriver() OpenFirstError!Library {
1598 return openSystemLibrary(cudaDriverCandidates());
1599 }
1600
1601 pub fn openVulkanLoader() OpenFirstError!Library {
1602 return openSystemLibrary(vulkanLoaderCandidates());
1603 }
1604
1605 pub fn openX11Library() OpenFirstError!Library {
1606 return openSystemLibrary(x11LibraryCandidates());
1607 }
1608
1609 pub fn isCudaDriverLoadable() bool {
1610 var library = openCudaDriver() catch return false;
1611 library.close();
1612 return true;
1613 }
1614
1615 pub fn isVulkanLoaderLoadable() bool {
1616 var library = openVulkanLoader() catch return false;
1617 library.close();
1618 return true;
1619 }
1620
1621 test "library candidates are host-filtered" {
1622 const other_os: std.Target.Os.Tag = switch (builtin.os.tag) {
1623 .linux => .macos,
1624 else => .linux,
1625 };
1626
1627 try std.testing.expect(hasHostCandidate(&.{
1628 .{ .os = other_os, .name = "sys_missing_library" },
1629 .{ .os = builtin.os.tag, .name = "sys_missing_library" },
1630 }));
1631 try std.testing.expect(!hasHostCandidate(&.{
1632 .{ .os = other_os, .name = "sys_missing_library" },
1633 }));
1634 }
1635
1636 test "openFirst reports missing host candidates" {
1637 const other_os: std.Target.Os.Tag = switch (builtin.os.tag) {
1638 .linux => .macos,
1639 else => .linux,
1640 };
1641
1642 try std.testing.expectError(error.NoCandidate, openFirst(&.{
1643 .{ .os = other_os, .name = "sys_missing_library" },
1644 }));
1645 }
1646
1647 test "openSystemLibrary reports missing host candidates" {
1648 const other_os: std.Target.Os.Tag = switch (builtin.os.tag) {
1649 .linux => .macos,
1650 else => .linux,
1651 };
1652
1653 try std.testing.expectError(error.NoCandidate, openSystemLibrary(&.{
1654 .{ .os = other_os, .name = "sys_missing_library" },
1655 }));
1656 }
1657
1658 test "native-on-Linux openFirst reports missing host candidates" {
1659 const other_os: std.Target.Os.Tag = switch (builtin.os.tag) {
1660 .linux => .macos,
1661 else => .linux,
1662 };
1663
1664 try std.testing.expectError(error.NoCandidate, openFirstNativeElfOnLinux(&.{
1665 .{ .os = other_os, .name = "sys_missing_library" },
1666 }));
1667 }
1668
1669 test "native default library directories include linux multiarch paths" {
1670 if (builtin.os.tag != .linux) return;
1671
1672 switch (builtin.cpu.arch) {
1673 .x86_64 => {
1674 try std.testing.expect(nativeDefaultLibraryDirectoriesContain("/lib/x86_64-linux-gnu"));
1675 try std.testing.expect(nativeDefaultLibraryDirectoriesContain("/usr/lib/x86_64-linux-gnu"));
1676 },
1677 .aarch64, .aarch64_be => {
1678 try std.testing.expect(nativeDefaultLibraryDirectoriesContain("/lib/aarch64-linux-gnu"));
1679 try std.testing.expect(nativeDefaultLibraryDirectoriesContain("/usr/lib/aarch64-linux-gnu"));
1680 },
1681 .riscv64, .riscv64be => {
1682 try std.testing.expect(nativeDefaultLibraryDirectoriesContain("/lib/riscv64-linux-gnu"));
1683 try std.testing.expect(nativeDefaultLibraryDirectoriesContain("/usr/lib/riscv64-linux-gnu"));
1684 },
1685 else => {},
1686 }
1687 }
1688
1689 fn usesStdElfDynLib() bool {
1690 return builtin.os.tag == .linux and (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static);
1691 }
1692
1693 fn repairElfImage(path: []const u8, raw: *std.DynLib) ElfRepairError!void {
1694 if (comptime !usesStdElfDynLib()) {
1695 return;
1696 }
1697
1698 const io = std.Options.debug_io;
1699 var file = if (path.len > 0 and path[0] == '/')
1700 try std.Io.Dir.openFileAbsolute(io, path, .{})
1701 else
1702 try std.Io.Dir.cwd().openFile(io, path, .{});
1703 defer file.close(io);
1704
1705 const stat = try file.stat(io);
1706 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
1707 if (size < @sizeOf(elf.Ehdr)) return error.InvalidElfImage;
1708 const file_bytes = try sys_memory.mapPrivateFile(file.handle, size, .{ .read = true }, 0);
1709 defer sys_memory.unmap(file_bytes);
1710
1711 const eh: *const elf.Ehdr = @ptrCast(file_bytes.ptr);
1712 if (!std.mem.eql(u8, eh.e_ident[0..4], elf.MAGIC)) return error.InvalidElfImage;
1713 if (eh.e_type != elf.ET.DYN) return error.InvalidElfImage;
1714 if (eh.e_phoff + @as(usize, eh.e_phentsize) * eh.e_phnum > size) return error.InvalidElfImage;
1715
1716 const base = @intFromPtr(raw.inner.memory.ptr);
1717 const file_addr = @intFromPtr(file_bytes.ptr);
1718 var dynamic_vaddr: ?usize = null;
1719 var i: usize = 0;
1720 var ph_addr = file_addr + eh.e_phoff;
1721 while (i < eh.e_phnum) : ({
1722 i += 1;
1723 ph_addr += eh.e_phentsize;
1724 }) {
1725 const ph: *const elf.Phdr = @ptrFromInt(ph_addr);
1726 switch (ph.p_type) {
1727 elf.PT_LOAD => if ((ph.p_flags & elf.PF_W) != 0) {
1728 if (ph.p_memsz < ph.p_filesz) return error.InvalidElfImage;
1729 if (ph.p_offset + ph.p_filesz > size) return error.InvalidElfImage;
1730 const segment: [*]u8 = @ptrFromInt(base + ph.p_vaddr);
1731 const source = file_bytes[ph.p_offset..][0..ph.p_filesz];
1732 @memcpy(segment[0..ph.p_filesz], source);
1733 if (ph.p_memsz > ph.p_filesz) {
1734 @memset(segment[ph.p_filesz..ph.p_memsz], 0);
1735 }
1736 },
1737 elf.PT_DYNAMIC => dynamic_vaddr = ph.p_vaddr,
1738 else => {},
1739 }
1740 }
1741
1742 try applyElfRelativeRelocations(base, dynamic_vaddr orelse return error.InvalidElfImage);
1743 }
1744
1745 fn applyElfRelativeRelocations(base: usize, dynamic_vaddr: usize) ElfRepairError!void {
1746 var rela: usize = 0;
1747 var rela_size: usize = 0;
1748 var rela_entry_size: usize = @sizeOf(elf.Rela);
1749 var rel: usize = 0;
1750 var rel_size: usize = 0;
1751 var rel_entry_size: usize = @sizeOf(elf.Rel);
1752 var relr: usize = 0;
1753 var relr_size: usize = 0;
1754 var relr_entry_size: usize = @sizeOf(elf.Relr);
1755
1756 const dynv: [*]usize = @ptrFromInt(base + dynamic_vaddr);
1757 var i: usize = 0;
1758 while (dynv[i] != 0) : (i += 2) {
1759 switch (dynv[i]) {
1760 elf.DT_RELA => rela = dynv[i + 1],
1761 elf.DT_RELASZ => rela_size = dynv[i + 1],
1762 elf.DT_RELAENT => rela_entry_size = dynv[i + 1],
1763 elf.DT_REL => rel = dynv[i + 1],
1764 elf.DT_RELSZ => rel_size = dynv[i + 1],
1765 elf.DT_RELENT => rel_entry_size = dynv[i + 1],
1766 elf.DT_RELR => relr = dynv[i + 1],
1767 elf.DT_RELRSZ => relr_size = dynv[i + 1],
1768 elf.DT_RELRENT => relr_entry_size = dynv[i + 1],
1769 else => {},
1770 }
1771 }
1772
1773 if (rel != 0) {
1774 if (rel_entry_size != @sizeOf(elf.Rel)) return error.UnsupportedElfRelocation;
1775 const rels: []const elf.Rel = @ptrCast(@alignCast(
1776 @as([*]align(@alignOf(elf.Rel)) const u8, @ptrFromInt(base + rel))[0..rel_size],
1777 ));
1778 for (rels) |r| {
1779 if (r.r_type() != relativeRelocationKind()) continue;
1780 @as(*usize, @ptrFromInt(base + r.r_offset)).* += base;
1781 }
1782 }
1783
1784 if (rela != 0) {
1785 if (rela_entry_size != @sizeOf(elf.Rela)) return error.UnsupportedElfRelocation;
1786 const relas: []const elf.Rela = @ptrCast(@alignCast(
1787 @as([*]align(@alignOf(elf.Rela)) const u8, @ptrFromInt(base + rela))[0..rela_size],
1788 ));
1789 for (relas) |r| {
1790 if (r.r_type() != relativeRelocationKind()) continue;
1791 @as(*usize, @ptrFromInt(base + r.r_offset)).* = base + @as(usize, @bitCast(r.r_addend));
1792 }
1793 }
1794
1795 if (relr != 0) {
1796 applyRelativeRelrRelocations(base, relr, relr_size, relr_entry_size) catch return error.UnsupportedElfRelocation;
1797 }
1798 }
1799
1800 fn applyRelativeRelrRelocations(base: usize, relr: usize, relr_size: usize, relr_entry_size: usize) error{UnsupportedRelrRelocation}!void {
1801 if (relr_entry_size != @sizeOf(elf.Relr)) return error.UnsupportedRelrRelocation;
1802 const relrs: []const elf.Relr = @ptrCast(
1803 @as([*]align(@alignOf(elf.Relr)) const u8, @ptrFromInt(base + relr))[0..relr_size],
1804 );
1805 var current: [*]usize = undefined;
1806 for (relrs) |r| {
1807 if ((r & 1) == 0) {
1808 current = @ptrFromInt(base + r);
1809 current[0] += base;
1810 current += 1;
1811 } else {
1812 var bit: usize = 1;
1813 while (bit < @bitSizeOf(elf.Relr)) : (bit += 1) {
1814 const shift: std.math.Log2Int(elf.Relr) = @intCast(bit);
1815 if (((r >> shift) & 1) != 0) current[bit - 1] += base;
1816 }
1817 current += @bitSizeOf(elf.Relr) - 1;
1818 }
1819 }
1820 }
1821
1822 fn relativeRelocationKind() u32 {
1823 return switch (builtin.cpu.arch) {
1824 .x86 => 8,
1825 .x86_64 => 8,
1826 .arc, .arceb => 56,
1827 .arm, .armeb, .thumb, .thumbeb => 23,
1828 .aarch64, .aarch64_be => 1027,
1829 .alpha => 27,
1830 .csky => 9,
1831 .hexagon => 35,
1832 .kvx => 39,
1833 .loongarch32, .loongarch64 => 3,
1834 .m68k => 22,
1835 .microblaze, .microblazeel => 16,
1836 .mips, .mipsel, .mips64, .mips64el => 128,
1837 .or1k => 21,
1838 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 22,
1839 .riscv32, .riscv32be, .riscv64, .riscv64be => 3,
1840 .s390x => 12,
1841 .sh, .sheb => 165,
1842 .sparc, .sparc64 => 22,
1843 else => @compileError("missing relative relocation kind"),
1844 };
1845 }
1846
1847 fn absoluteRelocationKind() ?u32 {
1848 return switch (builtin.cpu.arch) {
1849 .x86, .x86_64 => 1,
1850 .aarch64, .aarch64_be => 257,
1851 else => null,
1852 };
1853 }
1854
1855 fn globDatRelocationKind() ?u32 {
1856 return switch (builtin.cpu.arch) {
1857 .x86, .x86_64 => 6,
1858 .aarch64, .aarch64_be => 1025,
1859 else => null,
1860 };
1861 }
1862
1863 fn jumpSlotRelocationKind() ?u32 {
1864 return switch (builtin.cpu.arch) {
1865 .x86, .x86_64 => 7,
1866 .aarch64, .aarch64_be => 1026,
1867 else => null,
1868 };
1869 }
1870
1871 fn irelativeRelocationKind() ?u32 {
1872 return switch (builtin.cpu.arch) {
1873 .x86_64 => @backingInt(elf.R_X86_64.IRELATIVE),
1874 else => null,
1875 };
1876 }
1877
1878 fn tlsModuleRelocationKind() ?u32 {
1879 return switch (builtin.cpu.arch) {
1880 .x86_64 => @backingInt(elf.R_X86_64.DTPMOD64),
1881 else => null,
1882 };
1883 }
1884
1885 fn tlsOffsetRelocationKind() ?u32 {
1886 return switch (builtin.cpu.arch) {
1887 .x86_64 => @backingInt(elf.R_X86_64.DTPOFF64),
1888 else => null,
1889 };
1890 }