Map, relocate, and link ELF images at runtime.
From file paths and in-memory buffers to shared objects, executables, and relocatable objects.
ET_DYN · ET_EXEC · ET_REL · no_std · Typed symbols · Hybrid linking
Relink is a high-performance, no_std-friendly ELF loader and runtime linker for Rust. It is built for plugin systems, JITs, runtimes, kernels, embedded loaders, hot-reload workflows, and other environments where dlopen-style loading is too rigid and hand-rolled relocation logic is too painful to maintain.
- Shared objects / dynamic libraries (
ET_DYN) - Executables and PIE-style images (
ET_EXEC, plus executable-styleET_DYN) - Relocatable object files (
ET_REL) when theobjectfeature is enabled - File-backed or in-memory inputs via
&str,String,&[u8],&Vec<u8],ElfFile, andElfBinary
If you want automatic detection, use Loader::load(). If you want strict type checks, use load_dylib(), load_exec(), or load_object().
| If you need... | Relink gives you... |
|---|---|
| Runtime loading from files or memory | Loader::load* accepts paths, ElfFile, ElfBinary, &[u8], and &Vec<u8] |
| Safer symbol handling | Typed get::<T>() lookups tied to the loaded image lifetime |
| Host-controlled linking | pre_find_fn(), post_find_fn(), pre_handler(), and post_handler() |
| Hybrid linking at runtime | Mix .so and .o inputs with scope() and add_scope() |
| Low-level deployment targets | A no_std core plus custom Mmap backends |
| Capability | Relink | dlopen-style loading |
Hand-rolled ELF loader |
|---|---|---|---|
| Load directly from memory | Yes | Usually awkward or unavailable | Yes, if you build it |
Load relocatable objects (ET_REL) |
Yes, feature-gated | No | Yes, if you build it |
| Typed symbol lifetime safety | Yes | No | Depends on your design |
| Custom relocation interception | Yes | Usually no | Yes, if you build it |
no_std-friendly core |
Yes | No | Depends on your implementation |
Typed symbols borrow the loaded image, so they cannot outlive the library that produced them.
let symbol = unsafe {
lib.get::<fn()>("plugin_fn")
.expect("symbol `plugin_fn` not found")
};
drop(lib);
// symbol(); // does not compile: the symbol cannot outlive the libraryAdd the crate with the default feature set:
[dependencies]
elf_loader = "0.14"Or enable the common advanced feature bundle:
[dependencies]
elf_loader = { version = "0.14", features = ["full"] }use elf_loader::{Loader, Result};
extern "C" fn host_double(value: i32) -> i32 {
value * 2
}
fn main() -> Result<()> {
let lib = Loader::new()
.load_dylib("path/to/plugin.so")?
.relocator()
.pre_find_fn(|name| {
if name == "host_double" {
Some(host_double as *const ())
} else {
None
}
})
.relocate()?;
let run = unsafe {
lib.get::<extern "C" fn(i32) -> i32>("run")
.expect("symbol `run` not found")
};
assert_eq!(run(21), 42);
Ok(())
}path / bytes / ElfFile / ElfBinary
|
Loader
|
+------------+-------------+
| | |
RawDylib RawExec RawObject*
|
Relocator
pre_find / scope / handlers / binding
|
+------------+-------------+
| | |
LoadedDylib LoadedExec LoadedObject*
|
get() / deps() / TLS / metadata
* requires the `object` feature
use elf_loader::{Loader, Result, input::ElfBinary};
fn main() -> Result<()> {
let bytes = std::fs::read("path/to/plugin.so").unwrap();
let lib = Loader::new()
.load_dylib(ElfBinary::new("plugin.so", &bytes))?
.relocator()
.relocate()?;
println!("loaded {} at 0x{:x}", lib.name(), lib.base());
Ok(())
}load_dylib(&bytes) and load_exec(&bytes) also work if a synthetic name such as "<memory>" is acceptable.
This requires the object feature.
# use elf_loader::{Loader, Result};
# fn main() -> Result<()> {
let mut loader = Loader::new();
let base = loader
.load_object("path/to/base.o")?
.relocator()
.pre_find_fn(|_| None)
.relocate()?;
let plugin = loader
.load_dylib("path/to/plugin.so")?
.relocator()
.scope([&base])
.relocate()?;
# let _ = plugin;
# Ok(())
# }use elf_loader::{Loader, Result};
fn main() -> Result<()> {
let mut loader = Loader::new();
let exec = loader.load_exec("path/to/program")?;
println!("name = {}", exec.name());
println!("entry = 0x{:x}", exec.entry());
println!("base = 0x{:x}", exec.base());
Ok(())
}- Plugin and extension systems that need host-provided symbols or custom symbol search order
- JITs and runtimes that want to load ELF content from memory instead of only from disk
- Kernels, embedded environments, and low-level runtimes that need more control than an OS-native loader exposes
- Hot-reload or instrumentation workflows that benefit from relocation hooks and lifecycle control
- ELF-focused tooling and research projects where visibility into relocation behavior matters
- Applications that only need plain OS-native dynamic loading with no custom symbol policy
- Projects that want a module/plugin boundary but do not want to think about ELF details at all
- Heavy
ET_RELworkflows on non-x86_64targets that have not been validated in your environment yet
| Feature | Default | Purpose |
|---|---|---|
tls |
Yes | Enables TLS relocation handling and APIs such as Loader::with_default_tls_resolver() |
lazy-binding |
No | Enables PLT/GOT lazy binding and methods like Relocator::lazy() / lazy_scope() |
object |
No | Enables relocatable object (ET_REL) loading via Loader::load_object() |
version |
No | Enables version-aware symbol lookup such as get_version() |
log |
No | Enables log integration for loader and relocation diagnostics |
portable-atomic |
No | Adds support for targets without native pointer-sized atomics |
use-syscall |
No | Uses the Linux syscall backend instead of libc where applicable |
full |
No | Convenience bundle for tls, lazy-binding, and object |
Notes:
- Compiling with
tlsis not enough by itself for TLS-using modules. Start fromLoader::new().with_default_tls_resolver()or provide your own TLS resolver when loading ELF objects that require TLS relocations. load_object()is feature-gated.cargo run --example load_objectwill fail under the default feature set unless you add--features object.
The examples/ directory covers the main extension points:
| Example | What it demonstrates | Command |
|---|---|---|
load_dylib |
Load shared objects and resolve host symbols | cargo run --example load_dylib |
from_memory |
Load ELF data from a byte buffer | cargo run --example from_memory |
load_exec |
Inspect executable metadata such as entry/base | cargo run --example load_exec |
load_hook |
Observe segment loading with with_hook() |
cargo run --example load_hook |
lifecycle |
Custom .init / .fini handling |
cargo run --example lifecycle |
user_data |
Attach per-image metadata with with_context_loader() |
cargo run --example user_data |
relocation_handler |
Intercept relocations with a custom handler | cargo run --example relocation_handler |
load_object |
Load relocatable object files | cargo run --example load_object --features object |
- The crate currently targets
x86_64,x86,aarch64,arm,riscv64,riscv32, andloongarch64. - Dynamic library and executable loading are the primary supported paths across those architectures.
- Relocatable object (
.o) support is currently centered onx86_64relocation handling. Treat non-x86_64object loading as experimental unless you have validated it for your own target. - Symbol lookup is name-based and does not perform Rust name mangling for you. Export C ABI symbols when you want stable runtime lookup names.
Issues and pull requests are welcome, especially around relocation coverage, platform support, and documentation.
- Open an issue if you hit a loader or relocation edge case.
- Send a PR if you want to improve architecture support, examples, or diagnostics.
- Star the project if it is useful in your work.
This project is dual-licensed under either of the following:
