Relink ELF Loader and Runtime Linker

repository·main·Indexed 40 days ago

https://github.com/weizhiao/relink

A high-performance, no_std-friendly ELF loader and runtime linker for Rust, provided via the elf_loader crate (v0.17.0). It supports loading shared objects (ET_DYN), executables/PIE (ET_EXEC), and relocatable object files (ET_REL) across x86_64, aarch64, and riscv64 architectures. Relink offers advanced features beyond standard dlopen, including in-memory loading, custom mapping policies, observer hooks for symbol binding, and context isolation.

Tokens
25K
Snippets
63
Records
103
Agent score
99%

What's inside Relink

  1. Overview of gen-elf

    main

    gen-elf is a utility for generating ELF files (Shared Objects and Relocatable Objects) specifically designed for testing ELF loaders. It allows developers to create binaries with specific symbol structures, relocation entries, and memory layouts to verify the correctness of elf_loader implementations.

    Key Features

    • Multi-architecture Support: x86_64, x86, Aarch64, Riscv64, Riscv32, Arm, and Loongarch64.
    • Dynamic Library Generation: Creates Shared Objects (.so) with .dynamic sections, relocation tables (RELA/REL), symbol tables, and GOT/PLT.
    • TLS and IFUNC Support: Supports Thread-Local Storage (TLS) symbols and Indirect Functions (IFUNC) with automatic resolver generation.
    • Relocatable Object Generation: Creates standard relocatable object files (.o).
    • Customizable Layout: Configure base address and page size for memory mapping tests.
    • Metadata Export: Exports detailed relocation information and section addresses alongside the ELF data for easy verification in tests.
  2. Overview of `gen-elf` tool

    main

    gen-elf is a utility designed to generate test ELF files (both shared objects and relocatable objects) specifically for verifying ELF loaders like elf_loader.

    Key Features:

    • Multi-architecture support: x86_64, x86, Aarch64, Riscv64, Riscv32, Arm, and Loongarch64.
    • Dynamic Library Generation: Supports .dynamic sections, relocation tables (RELA/REL), symbol tables, and GOT/PLT.
    • Advanced Features: Supports TLS (Thread Local Storage) and IFUNC (Indirect Functions) with automatic resolver generation.
    • Customizable Layout: Allows configuring base addresses and page sizes for memory mapping tests.
    • Metadata Export: Exports detailed relocation information and segment addresses for test verification.
  3. Overview of Relink

    main

    Relink is a high-performance, highly customizable Rust library for loading, linking, and rewriting ELF files. It is designed to work in no_std environments, kernels, embedded systems, or non-standard runtimes.

    Key capabilities include:

    • Loading .so (shared objects), executables, and relocatable object files (.o / .ko / ET_REL) from files or memory.
    • Dependency resolution, relocation, and symbol lookup.
    • An observer mechanism to hook into loading, relocation, symbol binding, and lifecycle events.
    • Custom mapping strategies (replacing mmap, controlling page size/permissions).
    • Custom dependency (DT_NEEDED) and symbol scope management.
    • Section reordering (on x86_64) for hot-path clustering or custom layouts.
  4. Linking workflows in Relink

    main

    The linking API allows for more complex dependency resolution and planning:

    • linker_load: Resolves and loads a shared library along with all its dependencies.
    • linker_scan_first: Performs a scan to plan section placement before the images are actually mapped.
  5. Loading workflows in Relink

    main

    Relink provides several ways to load ELF files depending on your use case:

    • load_dylib: Loads and relocates dependent shared libraries.
    • load_exec: Loads an ELF executable and allows inspection of its entry point.
    • load_object: Loads relocatable object files. Note: This requires the object feature to be enabled.
    • load_from_memory: Loads a shared library directly from an in-memory byte buffer.
  6. Map ELF runtime addresses to memory regions using `ElfSegments`

    main

    While MappedRegion manages a contiguous block of memory, an ELF image may have a sparse virtual address layout. ElfSegments bridges this gap by mapping ELF runtime addresses to specific offsets within one or more MappedRegions.

    The Address Translation Flow:

    1. An upper layer provides a runtime address.
    2. ElfSegments checks if the address falls within its known mapped ranges.
    3. If valid, ElfSegments converts the runtime address into a region offset.
    4. MappedRegion / RegionAccess performs the actual read/write at that offset.

    This allows Relink to support sparse ELF images while using efficient, contiguous backing memory.

  7. Understand the Relink Memory Model layers

    main

    The memory abstraction in src/os/memory is organized into three distinct layers to decouple memory allocation from memory access. This allows the loader to handle various input types (file mappings, raw memory buffers, or existing kernel mappings) through a unified interface.

    1. Platform Mmap Backend: Responsible for creating and populating the virtual address space (e.g., via Linux mmap or Windows VirtualAlloc).
    2. MappedRegion: A shared handle that stores a mapped region and manages its lifetime.
    3. RegionAccess: The interface used to read, write, zero, borrow, or protect the memory within a region.

    This separation ensures that upper layers (like the loader or relocator) do not need to know whether they are interacting with host memory, a file mapping, or a custom guest VM memory backend.

  8. What can Relink load?

    main

    Relink is designed to load various ELF types, making it suitable for different runtime scenarios:

    • Shared objects / dynamic libraries (ET_DYN)
    • Executables and PIE-style images (ET_EXEC, plus executable-style ET_DYN)
    • Relocatable object files (ET_REL, such as .o or .ko kernel modules)
  9. Relink vs dlopen

    main

    Relink provides several capabilities that standard dlopen does not:

    • In-memory loading: Load from byte buffers or parsed ELF inputs instead of just file paths.
    • ET_REL loading: Ability to load and relocate .o or .ko files.
    • Pre-link planning: Resolve dependencies and sections before deciding how to map them.
    • Custom Mapping/Policy: Replace mmap, page size, permissions, and memory-access backends.
    • Observer Hooks: Hook into load, symbol binding, and relocation stages.
    • Context Isolation: Use multiple LinkContext instances to keep dependency graphs and symbol scopes independent.
  10. Distinguish between `VmAddr` and `VmOffset`

    main

    Relink uses two distinct types to prevent errors when performing address arithmetic. While both are wrappers around usize, they represent different semantic spaces:

    • VmAddr: Represents a runtime virtual address in the process or runtime address space (e.g., 0x7f00_1234_0000).
    • VmOffset: Represents an offset relative to the start of an ELF image's virtual address space (e.g., a section offset or a symbol value).

    Relationship: To calculate a runtime address from an image-relative offset, use the formula: runtime address = load base + vm offset.

    In code, this is expressed as:

    let addr = base + offset;

    When you need the underlying numeric value, you must explicitly call .get() on the wrapper.

  11. Manage memory lifetimes with `MappedRegion` and `HostRegion`

    main

    A MappedRegion<R> is a thread-safe, shared handle to a memory region (wrapping an Arc<R>). It provides thin wrappers for common operations like read_bytes, write_value, and mprotect.

    When working with HostRegion (the standard implementation for host-accessible memory), you must manage ownership using the unmap_on_drop logic:

    • MappedRegion::local(...): The region owns the memory. It will automatically call munmap (or the backend equivalent) when dropped.
    • MappedRegion::local_alias(...): The region is a borrowed alias. It will not release the memory when dropped.

    Usage Note: Use local for memory created via create_space by the loader. Use local_alias for memory already managed by the kernel or external objects to avoid accidental deallocation.

  12. Guidelines for contributing to Relink

    main

    Relink welcomes contributions ranging from bug reports and documentation improvements to new features and performance optimizations.

    When to open an Issue before a PR

    For small fixes, you can submit a Pull Request directly. However, for large-scale changes, you should open an issue first to discuss the design, especially regarding:

    • Public API changes
    • Loader behavior changes
    • Feature flag changes
    • Large-scale refactoring
    • Cross-platform behavior or no_std related changes

    Coding Best Practices

    • Focus: Keep changes focused; avoid mixing unrelated refactoring or formatting with functional changes.
    • API Stability: Keep public APIs clear and maintain existing behavior unless an intentional change is required.
    • Safety: Since the project involves low-level loader logic and unsafe code, keep changes minimal, use comments to explain key invariants, and explain safety considerations in your PR description.
    • Documentation: Ensure examples and documentation are updated in sync with any code or behavior changes.

    Commit Message Convention

    It is recommended to use the Conventional Commit style (e.g., fix(loader): ..., refactor(elf): ..., docs(readme): ...) to maintain a readable history.