Relink: Rust ELF Loader and Runtime Linker
Relink is a highly customizable, high-performance Rust ELF loading and runtime linking library. It can load .so files, executables, and object files from disk or memory, then resolve dependencies, apply relocations, and look up symbols. It also exposes observer hooks for load, relocation, symbol binding, and lifecycle events.
When To Use It
- Load plugins, JIT artifacts, or hot-reload modules at runtime.
- Control
DT_NEEDEDdependencies, symbol scopes, or relocation handling yourself. - Track or customize load, relocation, symbol binding, and init/fini flow through observer hooks.
- Load ELF from memory, or plug in your own mmap or memory-management backend.
- Scan dependencies and sections first, then reorder layout, pack hot code, use huge pages, or run custom handling.
- Load relocatable ELF files such as
.o/.ko. - Keep ELF loading available in
no_std, kernels, embedded systems, or non-standard runtimes.
What It Loads
- Shared objects / dynamic libraries (
ET_DYN) - Executables and PIE-style images (
ET_EXEC, plus executable-styleET_DYN) - Relocatable object files (
ET_REL, for example.o/.ko)
Compared With dlopen
| Capability | Relink | dlopen-style loading |
|---|---|---|
| In-memory loading | ✅ Load from paths, byte buffers, or already parsed ELF inputs | ❌ |
ET_REL loading |
✅ Load and relocate .o / .ko / ET_REL files |
❌ |
| Pre-link planning | ✅ Resolve dependencies and sections first, then decide how to map | ❌ |
| Section reordering | ✅ On x86_64, adjust section layout before mapping for hot-code packing or custom reordering |
❌ |
| Mapping policy | ✅ Replace mmap, page size, permissions, and memory-access backends | ❌ |
| Dependency and symbol policy | ✅ Customize DT_NEEDED resolution, symbol scopes, and relocation interception |
❌ |
| Observer events | ✅ Hook load, symbol binding, relocation, and related stages | ❌ |
| Context isolation | ✅ Multiple LinkContexts keep independent modules, dependency graphs, and symbol scopes |
❌ |
| Remote / heterogeneous loading | ✅ Use custom memory access to load remote devices or heterogeneous target ELFs locally | ❌ |
Quick Start
The default feature set is full. It includes the libc backend, relocatable-object loading,
and the built-in native lazy binder:
[]
= "0.17.0"
For a smaller build, disable default features and opt in only to what you need:
[]
= { = "0.17.0", = false, = ["libc"] }
Use Linker to Load Dependencies
use ;
const LINKER: Linker = new;
Benchmarks
The table below is a GitHub Actions performance snapshot. Use it only as a reference for the current test suite. Full environment details are in actions/runs/25632675040/job/75239090388. The fixture is the repository's leaf -> middle -> base test chain.
Lower is better for loading. scan_first includes dependency scanning and section planning, so it is not a direct dlopen replacement.
| Benchmark | Time | Relative time |
|---|---|---|
elf_loader/memory |
89.531 µs |
0.78x |
elf_loader/file |
101.01 µs |
0.88x |
linker/runtime |
111.32 µs |
0.97x |
libloading/lazy |
115.34 µs |
1.00x |
libloading/now |
115.77 µs |
1.00x |
linker/scan_first |
288.92 µs |
2.51x |
Symbol lookup was measured after both loaders had already loaded the fixture chain:
| Benchmark | Time | Relative time |
|---|---|---|
symbol/elf_loader/hit |
10.280 ns |
0.13x |
symbol/libloading/hit |
80.154 ns |
1.00x |
symbol/elf_loader/miss |
11.548 ns |
0.03x |
symbol/libloading/miss |
375.49 ns |
1.00x |
Feature Flags
| Feature | Default | Purpose |
|---|---|---|
libc |
Yes | Use the libc backend on Linux and Android |
lazy-binding |
Yes | Enable the built-in native PLT/GOT lazy binder |
object |
Yes | Enable relocatable object (ET_REL) loading and Loader::load_object() |
version |
No | Enable version-aware symbol lookup such as get_version() |
log |
No | Enable log integration for loader and relocation diagnostics |
portable-atomic |
No | Support targets without native pointer-sized atomics |
use-syscall |
No | Use the Linux syscall backend instead of libc |
full |
Yes | Convenience bundle: lazy-binding, object, libc |
Notes:
- TLS relocation and
DefaultTlsResolverare always available; custom runtimes can provide their own resolver. - Call
Loader::with_default_tls_resolver()when native modules use TLS;Loader::new()starts with no TLS runtime. - Custom
LazyBinderimplementations are always available;lazy-bindingonly addsNativeLazyBinder. Relocator::new()uses eager binding until a binder is configured withlazy_binder().- The default feature set is
full, equivalent tolazy-binding+object+libc. load_object()is still controlled by theobjectfeature. Default builds include it; with--no-default-features, enable it explicitly.
Platform Support
| Instruction set | Dynamic libraries / executables | Section reordering | .o / ET_REL |
|---|---|---|---|
x86_64 |
✅ | ✅ | ✅ |
x86 |
✅ | 🚧 | 🚧 |
aarch64 |
✅ | 🚧 | 🚧 |
arm |
✅ | 🚧 | 🚧 |
riscv64 |
✅ | 🚧 | ✅ |
riscv32 |
✅ | 🚧 | 🚧 |
loongarch64 |
✅ | 🚧 | 🚧 |
xtensa |
🔧 | 🚧 | 🚧 |
Legend: ✅ supported, 🔧 basic support, 🚧 pending. Section-reorder repair is currently available on x86_64; .o / ET_REL relocation is available on x86_64 and riscv64. Contributions for the other architectures are welcome.
Xtensa currently supports dynamic relocation, but not lazy binding or TLS relocations.
Contributing
Issues and pull requests are welcome. Star the project if it is useful in your work.
License
This project is dual-licensed under either of the following: