authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-17 20:19:51-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-03-17 20:19:51-07:00
logcbeab678a50e48ba55f39b391fcfe98823fe3c8c
treea03afad1fe8ceeaf8b51563ee1cbf15d9f50e330
parent12066042621ca7920d67d339b54c0a2220d40696
parente6f74b78ef48bc1d23ccd98d042534f0bbe3eb80
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #19325 from Vexu/dynlib

std: adjust DynLib API

3 files changed, 90 insertions(+), 36 deletions(-)

lib/std/c.zig+1
...@@ -1881,6 +1881,7 @@ pub const FILE = opaque {};...@@ -1881,6 +1881,7 @@ pub const FILE = opaque {};
1881pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*anyopaque;1881pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*anyopaque;
1882pub extern "c" fn dlclose(handle: *anyopaque) c_int;1882pub extern "c" fn dlclose(handle: *anyopaque) c_int;
1883pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;1883pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;
1884pub extern "c" fn dlerror() ?[*:0]u8;
18841885
1885pub extern "c" fn sync() void;1886pub extern "c" fn sync() void;
1886pub extern "c" fn syncfs(fd: c_int) c_int;1887pub extern "c" fn syncfs(fd: c_int) c_int;
lib/std/dynamic_library.zig+88-35
...@@ -7,14 +7,41 @@ const elf = std.elf;...@@ -7,14 +7,41 @@ const elf = std.elf;
7const windows = std.os.windows;7const windows = std.os.windows;
8const system = std.os.system;8const system = std.os.system;
99
10pub const DynLib = switch (builtin.os.tag) {10/// Cross-platform dynamic library loading and symbol lookup.
11 .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static)11/// Platform-specific functionality is available through the `inner` field.
12 ElfDynLib12pub const DynLib = struct {
13 else13 const InnerType = switch (builtin.os.tag) {
14 DlDynLib,14 .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static)
15 .windows => WindowsDynLib,15 ElfDynLib
16 .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris, .illumos => DlDynLib,16 else
17 else => void,17 DlDynLib,
18 .windows => WindowsDynLib,
19 .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris, .illumos => DlDynLib,
20 else => @compileError("unsupported platform"),
21 };
22
23 inner: InnerType,
24
25 pub const Error = ElfDynLib.Error || DlDynLib.Error || WindowsDynLib.Error;
26
27 /// Trusts the file. Malicious file will be able to execute arbitrary code.
28 pub fn open(path: []const u8) Error!DynLib {
29 return .{ .inner = try InnerType.open(path) };
30 }
31
32 /// Trusts the file. Malicious file will be able to execute arbitrary code.
33 pub fn openZ(path_c: [*:0]const u8) Error!DynLib {
34 return .{ .inner = try InnerType.open(path_c) };
35 }
36
37 /// Trusts the file.
38 pub fn close(self: *DynLib) void {
39 return self.inner.close();
40 }
41
42 pub fn lookup(self: *DynLib, comptime T: type, name: [:0]const u8) ?T {
43 return self.inner.lookup(T, name);
44 }
18};45};
1946
20// The link_map structure is not completely specified beside the fields47// The link_map structure is not completely specified beside the fields
...@@ -59,12 +86,12 @@ pub fn get_DYNAMIC() ?[*]elf.Dyn {...@@ -59,12 +86,12 @@ pub fn get_DYNAMIC() ?[*]elf.Dyn {
59 return @extern([*]elf.Dyn, .{ .name = "_DYNAMIC", .linkage = .weak });86 return @extern([*]elf.Dyn, .{ .name = "_DYNAMIC", .linkage = .weak });
60}87}
6188
62pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator {89pub fn linkmap_iterator(phdrs: []elf.Phdr) error{InvalidExe}!LinkMap.Iterator {
63 _ = phdrs;90 _ = phdrs;
64 const _DYNAMIC = get_DYNAMIC() orelse {91 const _DYNAMIC = get_DYNAMIC() orelse {
65 // No PT_DYNAMIC means this is either a statically-linked program or a92 // No PT_DYNAMIC means this is either a statically-linked program or a
66 // badly corrupted dynamically-linked one.93 // badly corrupted dynamically-linked one.
67 return LinkMap.Iterator{ .current = null };94 return .{ .current = null };
68 };95 };
6996
70 const link_map_ptr = init: {97 const link_map_ptr = init: {
...@@ -89,10 +116,10 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator {...@@ -89,10 +116,10 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator {
89 else => {},116 else => {},
90 }117 }
91 }118 }
92 return LinkMap.Iterator{ .current = null };119 return .{ .current = null };
93 };120 };
94121
95 return LinkMap.Iterator{ .current = link_map_ptr };122 return .{ .current = link_map_ptr };
96}123}
97124
98pub const ElfDynLib = struct {125pub const ElfDynLib = struct {
...@@ -111,10 +138,10 @@ pub const ElfDynLib = struct {...@@ -111,10 +138,10 @@ pub const ElfDynLib = struct {
111 ElfStringSectionNotFound,138 ElfStringSectionNotFound,
112 ElfSymSectionNotFound,139 ElfSymSectionNotFound,
113 ElfHashTableNotFound,140 ElfHashTableNotFound,
114 };141 } || os.OpenError || os.MMapError;
115142
116 /// Trusts the file. Malicious file will be able to execute arbitrary code.143 /// Trusts the file. Malicious file will be able to execute arbitrary code.
117 pub fn open(path: []const u8) !ElfDynLib {144 pub fn open(path: []const u8) Error!ElfDynLib {
118 const fd = try os.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);145 const fd = try os.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
119 defer os.close(fd);146 defer os.close(fd);
120147
...@@ -239,7 +266,7 @@ pub const ElfDynLib = struct {...@@ -239,7 +266,7 @@ pub const ElfDynLib = struct {
239 }266 }
240 }267 }
241268
242 return ElfDynLib{269 return .{
243 .memory = all_loaded_mem,270 .memory = all_loaded_mem,
244 .strings = maybe_strings orelse return error.ElfStringSectionNotFound,271 .strings = maybe_strings orelse return error.ElfStringSectionNotFound,
245 .syms = maybe_syms orelse return error.ElfSymSectionNotFound,272 .syms = maybe_syms orelse return error.ElfSymSectionNotFound,
...@@ -250,7 +277,7 @@ pub const ElfDynLib = struct {...@@ -250,7 +277,7 @@ pub const ElfDynLib = struct {
250 }277 }
251278
252 /// Trusts the file. Malicious file will be able to execute arbitrary code.279 /// Trusts the file. Malicious file will be able to execute arbitrary code.
253 pub fn openZ(path_c: [*:0]const u8) !ElfDynLib {280 pub fn openZ(path_c: [*:0]const u8) Error!ElfDynLib {
254 return open(mem.sliceTo(path_c, 0));281 return open(mem.sliceTo(path_c, 0));
255 }282 }
256283
...@@ -260,7 +287,7 @@ pub const ElfDynLib = struct {...@@ -260,7 +287,7 @@ pub const ElfDynLib = struct {
260 self.* = undefined;287 self.* = undefined;
261 }288 }
262289
263 pub fn lookup(self: *ElfDynLib, comptime T: type, name: [:0]const u8) ?T {290 pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [:0]const u8) ?T {
264 if (self.lookupAddress("", name)) |symbol| {291 if (self.lookupAddress("", name)) |symbol| {
265 return @as(T, @ptrFromInt(symbol));292 return @as(T, @ptrFromInt(symbol));
266 } else {293 } else {
...@@ -268,6 +295,7 @@ pub const ElfDynLib = struct {...@@ -268,6 +295,7 @@ pub const ElfDynLib = struct {
268 }295 }
269 }296 }
270297
298 /// ElfDynLib specific
271 /// Returns the address of the symbol299 /// Returns the address of the symbol
272 pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize {300 pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize {
273 const maybe_versym = if (self.verdef == null) null else self.versym;301 const maybe_versym = if (self.verdef == null) null else self.versym;
...@@ -314,41 +342,59 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: i32, vername: []const u8, strings: [...@@ -314,41 +342,59 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: i32, vername: []const u8, strings: [
314 return mem.eql(u8, vername, mem.sliceTo(strings + aux.vda_name, 0));342 return mem.eql(u8, vername, mem.sliceTo(strings + aux.vda_name, 0));
315}343}
316344
345test "ElfDynLib" {
346 if (builtin.os.tag != .linux) {
347 return error.SkipZigTest;
348 }
349
350 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so"));
351}
352
317pub const WindowsDynLib = struct {353pub const WindowsDynLib = struct {
318 pub const Error = error{FileNotFound};354 pub const Error = error{
355 FileNotFound,
356 InvalidPath,
357 } || windows.LoadLibraryError;
319358
320 dll: windows.HMODULE,359 dll: windows.HMODULE,
321360
322 pub fn open(path: []const u8) !WindowsDynLib {361 pub fn open(path: []const u8) Error!WindowsDynLib {
323 return openEx(path, .none);362 return openEx(path, .none);
324 }363 }
325364
326 pub fn openEx(path: []const u8, flags: windows.LoadLibraryFlags) !WindowsDynLib {365 /// WindowsDynLib specific
327 const path_w = try windows.sliceToPrefixedFileW(null, path);366 /// Opens dynamic library with specified library loading flags.
367 pub fn openEx(path: []const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib {
368 const path_w = windows.sliceToPrefixedFileW(null, path) catch return error.InvalidPath;
328 return openExW(path_w.span().ptr, flags);369 return openExW(path_w.span().ptr, flags);
329 }370 }
330371
331 pub fn openZ(path_c: [*:0]const u8) !WindowsDynLib {372 pub fn openZ(path_c: [*:0]const u8) Error!WindowsDynLib {
332 return openExZ(path_c, .none);373 return openExZ(path_c, .none);
333 }374 }
334375
335 pub fn openExZ(path_c: [*:0]const u8, flags: windows.LoadLibraryFlags) !WindowsDynLib {376 /// WindowsDynLib specific
377 /// Opens dynamic library with specified library loading flags.
378 pub fn openExZ(path_c: [*:0]const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib {
336 const path_w = try windows.cStrToPrefixedFileW(null, path_c);379 const path_w = try windows.cStrToPrefixedFileW(null, path_c);
337 return openExW(path_w.span().ptr, flags);380 return openExW(path_w.span().ptr, flags);
338 }381 }
339382
340 pub fn openW(path_w: [*:0]const u16) !WindowsDynLib {383 /// WindowsDynLib specific
384 pub fn openW(path_w: [*:0]const u16) Error!WindowsDynLib {
341 return openExW(path_w, .none);385 return openExW(path_w, .none);
342 }386 }
343387
344 pub fn openExW(path_w: [*:0]const u16, flags: windows.LoadLibraryFlags) !WindowsDynLib {388 /// WindowsDynLib specific
389 /// Opens dynamic library with specified library loading flags.
390 pub fn openExW(path_w: [*:0]const u16, flags: windows.LoadLibraryFlags) Error!WindowsDynLib {
345 var offset: usize = 0;391 var offset: usize = 0;
346 if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') {392 if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') {
347 // + 4 to skip over the \??\393 // + 4 to skip over the \??\
348 offset = 4;394 offset = 4;
349 }395 }
350396
351 return WindowsDynLib{397 return .{
352 .dll = try windows.LoadLibraryExW(path_w + offset, flags),398 .dll = try windows.LoadLibraryExW(path_w + offset, flags),
353 };399 };
354 }400 }
...@@ -368,17 +414,17 @@ pub const WindowsDynLib = struct {...@@ -368,17 +414,17 @@ pub const WindowsDynLib = struct {
368};414};
369415
370pub const DlDynLib = struct {416pub const DlDynLib = struct {
371 pub const Error = error{FileNotFound};417 pub const Error = error{ FileNotFound, NameTooLong };
372418
373 handle: *anyopaque,419 handle: *anyopaque,
374420
375 pub fn open(path: []const u8) !DlDynLib {421 pub fn open(path: []const u8) Error!DlDynLib {
376 const path_c = try os.toPosixPath(path);422 const path_c = try os.toPosixPath(path);
377 return openZ(&path_c);423 return openZ(&path_c);
378 }424 }
379425
380 pub fn openZ(path_c: [*:0]const u8) !DlDynLib {426 pub fn openZ(path_c: [*:0]const u8) Error!DlDynLib {
381 return DlDynLib{427 return .{
382 .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse {428 .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse {
383 return error.FileNotFound;429 return error.FileNotFound;
384 },430 },
...@@ -386,7 +432,10 @@ pub const DlDynLib = struct {...@@ -386,7 +432,10 @@ pub const DlDynLib = struct {
386 }432 }
387433
388 pub fn close(self: *DlDynLib) void {434 pub fn close(self: *DlDynLib) void {
389 _ = system.dlclose(self.handle);435 switch (std.os.errno(system.dlclose(self.handle))) {
436 .SUCCESS => return,
437 else => unreachable,
438 }
390 self.* = undefined;439 self.* = undefined;
391 }440 }
392441
...@@ -399,6 +448,13 @@ pub const DlDynLib = struct {...@@ -399,6 +448,13 @@ pub const DlDynLib = struct {
399 return null;448 return null;
400 }449 }
401 }450 }
451
452 /// DlDynLib specific
453 /// Returns human readable string describing most recent error than occurred from `lookup`
454 /// or `null` if no error has occurred since initialization or when `getError` was last called.
455 pub fn getError() ?[:0]const u8 {
456 return mem.span(system.dlerror());
457 }
402};458};
403459
404test "dynamic_library" {460test "dynamic_library" {
...@@ -409,8 +465,5 @@ test "dynamic_library" {...@@ -409,8 +465,5 @@ test "dynamic_library" {
409 else => return error.SkipZigTest,465 else => return error.SkipZigTest,
410 };466 };
411467
412 _ = DynLib.open(libname) catch |err| {468 try testing.expectError(error.FileNotFound, DynLib.open(libname));
413 try testing.expect(err == error.FileNotFound);
414 return;
415 };
416}469}
lib/std/os.zig+1-1
...@@ -5979,7 +5979,7 @@ pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {...@@ -5979,7 +5979,7 @@ pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
59795979
5980/// Used to convert a slice to a null terminated slice on the stack.5980/// Used to convert a slice to a null terminated slice on the stack.
5981/// TODO https://github.com/ziglang/zig/issues/2875981/// TODO https://github.com/ziglang/zig/issues/287
5982pub fn toPosixPath(file_path: []const u8) ![MAX_PATH_BYTES - 1:0]u8 {5982pub fn toPosixPath(file_path: []const u8) error{NameTooLong}![MAX_PATH_BYTES - 1:0]u8 {
5983 if (std.debug.runtime_safety) assert(std.mem.indexOfScalar(u8, file_path, 0) == null);5983 if (std.debug.runtime_safety) assert(std.mem.indexOfScalar(u8, file_path, 0) == null);
5984 var path_with_null: [MAX_PATH_BYTES - 1:0]u8 = undefined;5984 var path_with_null: [MAX_PATH_BYTES - 1:0]u8 = undefined;
5985 // >= rather than > to make room for the null byte5985 // >= rather than > to make room for the null byte