All executable artifacts produced by the included examples are static executables that load dynamic libraries without using libc's dlopen.
Warning: prototype quality: lots of bugs, lots of TODOs remaining.
Tested on x86_64-linux, with libraries compiled against glibc from 2.23 to 2.43 and musl from 1.2.1 to 1.2.5.
See this thread for further information.
For now, the library always follows the latest "master" x86_64 tarball available on ziglang.org.
zig fetch --save=dll https://github.com/TibboddiT/dyn-loader/archive/refs/heads/main.tar.gz// build.zig
// ...
const dll_dep = b.dependency("dll", .{
.optimize = optimize,
// no `target`, because it is always x86_64 linux baseline
});
const dll_mod = dll_dep.module("dll");
// then add the module to your executable as usualconst std = @import("std");
const dll = @import("dll");
pub const debug = struct {
pub const SelfInfo = dll.CustomSelfInfo;
};
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
const io = init.io;
const args = init.minimal.args;
const environ = init.minimal.environ;
// `dll` is a singleton, it should be initialized early and only once, on the main thread
try dll.init(.{ .allocator = allocator, .io = io, .args = args, .environ = environ, .log_level = .err });
defer dll.deinit();
const lib_c = try dll.loadSystemLibC();
// or load any other dynamic library:
// const lib_x11 = try dll.load("libX11.so.6");
const printf_sym = try lib_c.getSymbol("printf");
const printf_addr = printf_sym.addr;
const printf: *const fn ([*:0]const u8, ...) callconv(.c) c_int = @ptrFromInt(printf_addr);
_ = printf("Hello, %s!\n", "World");
}- Loading libraries should be done before starting any thread.
- Some (rare) relocation types are still missing.
- Dirty tricks are used to accommodate patched libc versions from various distros.
- Some libc functions that need to be implemented in zig are not yet implemented.
Here is a simplified overview of what is done when loading a dynamic library:
- libraries from
LD_PRELOADare loaded first - dependencies are resolved, and for each library to load:
- segments are mmapped
- relative relocations are processed
- for each newly mapped library:
- libc-specific patches are applied if needed
- TLS offsets are computed
- "normal" relocations are processed
- dl, malloc, and thread functions are "redirected" to zig code
- TLS is set up
IRELATIVErelocations are processed
- then for each newly loaded library:
- segment permissions are applied
- information about the extra ELF files is added to
CustomSelfInfoto get nice stack traces - init functions are called
- with specific handling in the case of libc
A copy of musl's libc.so is included, compiled from sources without any modification.
You should load it before loading libraries compiled against musl on a non musl based system (see the musl printf example).
The library is stripped (strip --strip-unneeded lib/libc.so) as is often the case when it is packaged for linux distros.
To demonstrate loading musl based libraries, an original copy of libvulkan.so.1.4.326 from the vulkan-loader package of Chimera Linux
is also included (renamed libvulkan.so.1) to make the vulkan_version_musl example work.
A copy of libraylib.so.5.5.0 from the raylib repository release assets
is included to make the raylib example work. Since this library is compiled against glibc, it will not work on musl based systems.
It is in the resources/raylib directory.
It is recommended that you build these binary artifacts yourself.
zig build run-printf
zig build run-printf_musl
zig build run-vulkan_version
zig build run-vulkan_version_musl
zig build run-vulkan_instance
zig build run-x11_window
zig build run-x11_egl
zig build run-x11_vulkan_triangle
zig build run-wayland_vulkan_triangle
The following example will intentionally trigger a segfault to demonstrate stack traces across loaded libraries:
zig build run-segfault
The following example will only work on glibc-based systems (because it uses libraries compiled against glibc):
zig build run-raylib