Home
Softono
Relink

Relink

Open source Apache-2.0 Rust
140
Stars
26
Forks
6
Issues
2
Watchers
1 week
Last Commit

About Relink

A no_std-friendly ELF loader and runtime linker for Rust

Platforms

Web Self-hosted

Languages

Rust

Links

Relink: Rust ELF Loader and Runtime/JIT Linker

Relink logo

Crates.io Crates.io downloads Docs.rs Minimum supported Rust version Build status MIT/Apache-2.0 license

English | 简体中文 | Contributing | 贡献指南

Load, link, and rewrite ELF in Rust and no_std environments.

Relink is a 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.

When dlopen is too rigid, Relink lets you decide how dependencies are found, how symbols are searched, how memory is mapped, and whether to scan and adjust layout before loading.

When To Use It

  • Load plugins, JIT artifacts, or hot-reload modules at runtime.
  • Control DT_NEEDED dependencies, symbol scopes, or relocation handling yourself.
  • 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.
  • 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) when the object feature is enabled
  • File-backed or in-memory inputs via &str, String, &[u8], Vec<u8>, ElfFile, and ElfBinary

Use Loader::load() when you want automatic ELF type detection. Use load_dylib(), load_exec(), or load_object() when you want strict type checks.

Core Capabilities

Capability What Relink provides
In-memory loading Load ELF images from paths, memory buffers, or parsed inputs
Custom linking policy Decide how dependencies resolve, where symbols are searched, and how relocations are intercepted
Isolated link contexts Multiple LinkContexts keep independent loaded-module sets, dependency graphs, and symbol scopes
Scan-first planning Inspect dependencies and sections first, then decide how to map, reorder, or rewrite
Pre-load layout optimization With --emit-relocs, reorder sections, pack hot code, or run custom handling
Replaceable mapping backend Plug in your own mmap, page size, permissions, and memory access model
Type-safe symbol access Symbol handles are tied to the lifetime of their loaded image, reducing dangling-symbol risks
Hybrid loading Combine .so, executable images, and .o / ET_REL inputs in one flow

Compared With dlopen

Capability Relink dlopen-style loading
In-memory loading ✅ Paths / memory buffers / parsed ELF
ET_REL loading ✅ Requires feature
Pre-link planning ✅ Scan dependencies and sections first
Pre-load layout optimization ✅ Section reordering / hot-code packing / custom handling
Mapping policy ✅ Replaceable mmap backend, page size, and permission policy
Dependency and symbol policy ✅ Dependency graph / scope / lookup / interception control
Context isolation ✅ Multiple LinkContexts isolate dependency graphs and symbol scopes
Heterogeneous loading ✅ Different ELF layouts / ABIs / target architectures

Quick Start

The default feature set is suitable for loading dynamic libraries, executables, and handling TLS:

[dependencies]
elf_loader = "0.15.1"

To enable the common advanced features in one bundle:

[dependencies]
elf_loader = { version = "0.15.1", features = ["full"] }

Load a Dynamic Library and Call a Symbol

use elf_loader::{
    image::{SyntheticModule, SyntheticSymbol},
    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()
        .scope([SyntheticModule::new(
            "__host",
            [SyntheticSymbol::function("host_double", host_double as *const ())],
        )])
        .relocate()?;

    let run = unsafe {
        lib.get::<extern "C" fn(i32) -> i32>("run")
            .expect("symbol `run` not found")
    };
    assert_eq!(run(21), 42);

    Ok(())
}

Loading Paths

Path Entry point Best for
Direct loading Loader::load_dylib() / load_exec() / load_object() You already know which ELF to load
Automatic dependency resolution Linker::load() Handle DT_NEEDED, dependency graphs, and symbol scopes
Scan then load Linker::load_scan_first() Inspect dependencies and sections before mapping, then run layout passes
Load .o Loader::load_object() Compose .o and .so inputs; requires the object feature
Custom memory environment Loader::with_mmap(mapper) / with_page_size() Plug in your own mmap backend, page size, or permission policy

Advanced Capability Index

Topic Entry point / example
Load from memory ElfBinary::new(name, bytes), or direct load_dylib(&bytes) / load_exec(&bytes)
Host symbols and scopes SyntheticModule, scope(), extend_scope()
Relocation interception pre_handler(), post_handler(), see cargo run --example relocation_handler
Lazy binding relocator().lazy(), requires the lazy-binding feature
Runtime dependency graphs KeyResolver, LinkContext, Linker::load()
Pre-map layout optimization Linker::load_scan_first(), map_pipeline(), see cargo run --example linker_scan_first
Relocatable objects cargo run --example load_object --features object
Lifecycle callbacks cargo run --example lifecycle

For section reordering or hot-code packing before loading, the target ELF usually needs to keep relocation information, for example by passing -Wl,--emit-relocs to the linker.

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 TLS relocation handling and the built-in TLS resolver
lazy-binding No Enable PLT/GOT lazy binding and lazy-fixup lookup configuration
object No 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 No Convenience bundle: tls, lazy-binding, object, libc

Notes:

  • The default features are tls + libc.
  • Compiling with tls is not enough by itself for TLS-using modules. Start from Loader::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_object will fail under the default feature set unless you add --features object.

Examples

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
linker_load Resolve DT_NEEDED dependencies with Linker::load() cargo run --example linker_load
from_memory Load ELF data from a byte buffer cargo run --example from_memory
load_exec Inspect executable entry and base addresses cargo run --example load_exec
load_hook Observe segment loading with with_observer() cargo run --example load_hook
linker_scan_first Discover DT_NEEDED, run scan-first passes, and configure pre-map layout cargo run --example linker_scan_first
lifecycle Custom .init / .fini handling cargo run --example lifecycle
user_data Initialize dynamic-image metadata 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

Platform Notes

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.

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.

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

Project contributors