elf_loader 0.16.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation

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_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

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

Quick Start

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 Linker to Load Dependencies

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. 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 Flags

Feature Default Purpose
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

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.

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:

Contributors