# Relink: Rust ELF Loader and Runtime Linker
<p align="center">
<img src="https://raw.githubusercontent.com/weizhiao/elf_loader/main/docs/assets/logo.svg" width="560" alt="Relink logo">
</p>
<p align="center">
<a href="https://crates.io/crates/elf_loader"><img src="https://img.shields.io/crates/v/elf_loader.svg" alt="Crates.io"></a>
<a href="https://crates.io/crates/elf_loader"><img src="https://img.shields.io/crates/d/elf_loader.svg" alt="Crates.io downloads"></a>
<a href="https://docs.rs/elf_loader"><img src="https://docs.rs/elf_loader/badge.svg" alt="Docs.rs"></a>
<img src="https://img.shields.io/badge/rust-1.93.0%2B-blue.svg" alt="Minimum supported Rust version">
<a href="https://github.com/weizhiao/elf_loader/actions/workflows/rust.yml"><img src="https://github.com/weizhiao/elf_loader/actions/workflows/rust.yml/badge.svg" alt="Build status"></a>
<img src="https://img.shields.io/crates/l/elf_loader.svg" alt="MIT/Apache-2.0 license">
</p>
<p align="center">
<a href="README.md">English</a> | <a href="README_zh.md">简体中文</a> |
<a href="CONTRIBUTING.md">Contributing</a> | <a href="CONTRIBUTING_zh.md">贡献指南</a>
</p>
<p align="center">
<strong>Load, link, and rewrite ELF in no_std environments.</strong>
</p>
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_NEEDED` dependencies, 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-style `ET_DYN`)
- Relocatable object files (`ET_REL`, for example `.o` / `.ko`)
## Compared With `dlopen`
| 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 `LinkContext`s 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`, which is suitable for loading dynamic libraries, executables,
relocatable objects, TLS, and lazy binding:
```toml
[dependencies]
elf_loader = "0.16.0"
```
For a smaller build, disable default features and opt in only to what you need:
```toml
[dependencies]
elf_loader = { version = "0.16.0", default-features = false, features = ["libc"] }
```
### Use Linker to Load Dependencies
```rust
use elf_loader::{
LinkContext, Linker, Result,
input::PathBuf,
linker::SearchPathResolver,
};
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();
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(())
}
```
## 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](https://github.com/weizhiao/Relink/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.
| `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:
| `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
| `libc` | Yes | Use the libc backend on Unix-like platforms |
| `tls` | Yes | Enable the built-in same-process TLS resolver |
| `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: `tls`, `lazy-binding`, `object`, `libc` |
Notes:
- The default feature set is `full`, equivalent to `tls` + `lazy-binding` + `object` + `libc`.
- `tls` only provides the default resolver; custom TLS resolvers do not need this feature.
- `load_object()` is still controlled by the `object` feature. Default builds include it; with `--no-default-features`, enable it explicitly.
## Platform Support
| `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.
## 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:
- [MIT License](LICENSE-MIT)
- [Apache License 2.0](LICENSE-APACHE)
## Contributors
<a href="https://github.com/weizhiao/elf_loader/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=weizhiao/elf_loader" alt="Project contributors">
</a>