English | 简体中文 | Contributing | 贡献指南
Load, link, and rewrite ELF in no_std environments.
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.
- 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.
- 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)
| 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 | ❌ |
| Pre-load layout optimization | ✅ 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 | ❌ |
The default feature set is full, which is suitable for loading dynamic libraries, executables,
relocatable objects, TLS, and lazy binding:
[dependencies]
elf_loader = "0.16.0"For a smaller build, disable default features and opt in only to what you need:
[dependencies]
elf_loader = { version = "0.16.0", default-features = false, features = ["libc"] }use elf_loader::{
LinkContext, Linker, Result,
input::PathBuf,
linker::SearchPathResolver,
runtime::DomainId,
};
const LINKER: Linker<'static, PathBuf> = Linker::new();
fn main() -> Result<()> {
let root = PathBuf::from("path/to/plugin.so");
let mut context: LinkContext<PathBuf, ()> = LinkContext::new(DomainId::PROCESS);
let loaded = LINKER
.resolver(SearchPathResolver::new())
.load(&mut context, root)?;
let run = unsafe {
loaded
.get::<extern "C" fn() -> i32>("run")
.expect("symbol `run` not found")
};
let _ = run();
Ok(())
}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 libc -> libb -> liba test chain, not the system C library.
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 | Default | Purpose |
|---|---|---|
libc |
Yes | Use the libc backend on Unix-like platforms |
lazy-binding |
Yes | Enable PLT/GOT lazy binding and lazy-fixup lookup configuration |
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. - 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.
| Instruction set | Dynamic libraries / executables | Pre-load layout optimization | .o / ET_REL |
|---|---|---|---|
x86_64 |
✅ | ✅ | ✅ |
x86 |
✅ | 🔧 | 🚧 |
aarch64 |
✅ | 🔧 | 🚧 |
arm |
✅ | 🔧 | 🚧 |
riscv64 |
✅ | 🔧 | ✅ |
riscv32 |
✅ | 🔧 | 🚧 |
loongarch64 |
✅ | 🔧 | 🚧 |
Legend: ✅ supported, 🔧 basic support, 🚧 pending. Complex section-reorder repair and .o / ET_REL support are currently centered on x86_64 and riscv64 relocation handling; contributions for the other architectures are welcome.
Issues and pull requests are welcome. Star the project if it is useful in your work.
This project is dual-licensed under either of the following: