elf_overlay is an ISA-abstracted ELF overlay runtime for embedded systems. It sits next to an ELF loader (such as Espressif's elf_loader) and pages overlay bodies in and out of executable RAM, patching ISA-specific relocations at load time: l32r on Xtensa, auipc on RISC-V.
There are no ESP-IDF, OS, or RTOS assumptions baked in. Everything target-specific goes through a small port layer (port.h); the ESP-IDF port is just one implementation of it.
include/elf_overlay/ Public C API headers
src/isa/ ISA-specific patching (xtensa, riscv, arm...)
src/core/ Blob parsing, loader, frame pool, section map
src/port/ Platform abstraction (posix, esp_idf)
tools/ Host build tools (relink, emit)
tests/ Unit and integration tests
Magic ELFO, version 1, ISA-tagged header. Layout:
+----------------------+
| elf_overlay_header_t | magic, version, ISA tag, table counts
+----------------------+
| overlay entries | id, offset, length (per overlay)
+----------------------+
| stub entries | canonical vaddr, overlay_id, body_offset, stub_off
+----------------------+
| redirect entries | site, stub_off
+----------------------+
| patch entries | overlay_id, blob_offset, insn_offset
+----------------------+
| stub_patch entries | overlay_id, blob_offset, insn_offset
+----------------------+
| host_ref entries | overlay_id, blob_offset, insn_offset, name_off
+----------------------+
| string table | null-terminated host symbol names
+----------------------+
| stub block | enter/leave slots + stub code
+----------------------+
| overlay bodies | verbatim .text images
+----------------------+
ISA-specific code lives behind elf_overlay_isa_ops_t
(include/elf_overlay/isa.h). Two backends are registered:
xtensa-l32rPC-relative literal patching (checked against the Xtensa ISA reference)riscv-auipcPC-relative literal patching
To add one, implement elf_overlay_isa_ops_t and register it with
elf_overlay_isa_register().
Reads an ELF32 executable, scans .text for ISA-specific patch sites
(e.g. l32r on Xtensa), builds a literal pool, classifies relocations, and
writes a .relk intermediate file.
Packs a .relk into an ELFO blob ready for the runtime loader.
/* Load/unload */
int elf_overlay_load(elf_overlay_loader_t **out, const char *path);
void elf_overlay_unload(elf_overlay_loader_t *loader);
/* Enter/leave: called by position-independent stubs */
void *elf_overlay_enter(uint32_t key); /* key = (overlay_id << 16) | body_offset */
void elf_overlay_leave(uint32_t key); /* decrements refcount; 0 = evictable */
/* Section map: canonical vaddr -> resident address */
int elf_overlay_set_section_map(elf_overlay_loader_t *loader,
const elf_overlay_section_map_t *map);
int elf_overlay_resolve(elf_overlay_loader_t *loader,
uint32_t canonical, uint32_t *resident);
/* Symbol resolver for host references */
typedef uintptr_t (*elf_overlay_symbol_resolver_t)(const char *name);
int elf_overlay_set_symbol_resolver(elf_overlay_loader_t *loader,
elf_overlay_symbol_resolver_t);
/* Redirects: patch resident app code to jump through stubs */
int elf_overlay_apply_redirects(elf_overlay_loader_t *loader);
/* Utilities */
void *elf_overlay_get_stub_block(elf_overlay_loader_t *loader);
int elf_overlay_find_stub(elf_overlay_loader_t *loader,
uint32_t canonical, uint32_t *stub_off);
void elf_overlay_set_active(elf_overlay_loader_t *loader);
elf_overlay_loader_t *elf_overlay_get_active(void);A fixed pool of executable-RAM frames holds overlays, paged in on demand.
enter() bumps a frame's refcount, leave() drops it, and a frame stays put
while its refcount is above zero. Literals get patched in place inside the
.text image at page-in, once resident addresses are known; the PC-relative
loads (l32r / auipc) are never touched. All tables are fixed-size, so
page-in does no heap allocation.
elf_loader loads and relocates the resident application ELF; elf_overlay
handles the overlay bodies on top. The integration layer
(include/elf_overlay/esp_elf_integration.h,
src/core/esp_elf_integration.c) hooks in at three points:
- after
esp_elf_relocate()-elf_overlay_esp_elf_set_section_map()copiesesp_elf_t.sec[](canonical vaddr -> resident addr) into the section map - before the entry point runs -
elf_overlay_esp_elf_apply_redirects()patches redirect sites in resident memory so paged functions go through their stubs - at init -
elf_overlay_esp_elf_install_symbol_resolver()wires host-ref resolution toesp_elf_find_symbol()
Putting it together:
esp_elf_t elf;
esp_elf_init(&elf);
esp_elf_relocate(&elf, buf, size);
elf_overlay_loader_t *ov;
elf_overlay_load(&ov, "app.elfo");
elf_overlay_esp_elf_set_section_map(ov, &elf);
elf_overlay_esp_elf_install_symbol_resolver(ov);
elf_overlay_esp_elf_apply_redirects(ov);
/* App can now call into overlays via stubs */The ESP-IDF branch of src/port/port.c (behind ELF_OVERLAY_ESP_IDF)
implements port.h with heap_caps_malloc/MALLOC_CAP_EXEC, ESP_LOG*, the
stdio VFS, and esp_cache_msync for PSRAM overlay regions on ESP32-S3.
MALLOC_CAP_EXEC only exists when the exec heap is enabled
(CONFIG_HEAP_HAS_EXEC_HEAP, on by default when ESP_SYSTEM_MEMPROT is off).
Since the runtime executes out of the memory it allocates, the port #errors at
compile time if the exec heap is missing rather than failing at runtime.
Two build systems are supported.
PlatformIO. library.json registers it for the espidf and arduino
frameworks on espressif32. Depend on it in platformio.ini and add
-DELF_OVERLAY_ESP_IDF=1 to the build flags.
ESP-IDF component. idf_component.yml plus the ESP_PLATFORM branch of the
root CMakeLists.txt register it (REQUIRES elf_loader, PRIV_REQUIRES heap esp_common log esp_mm). The component manager pulls elf_loader from the
registry. Point EXTRA_COMPONENT_DIRS at the repo root, or add it through the
component manager.
examples/ holds runnable consumer examples that CI builds too. Each has its
own README.
host_pipeline- one C function taken from source throughrelink->emit-> load ->enter/leaveon the host.make checkround-trips it against the committed vectors.esp_idf_component- a minimal ESP-IDF project that pulls inelf_overlayas a component; thedevice-buildCI job builds it.
cmake -S . -B build
cmake --build build
ctest --test-dir buildCMake options:
| Option | Default | Effect |
|---|---|---|
ELF_OVERLAY_BUILD_TESTS. |
ON |
Build the unit/host test executables |
ELF_OVERLAY_BUILD_TOOLS |
ON |
Build elf_overlay_relink / elf_overlay_emit |
ELF_OVERLAY_ESP_IDF |
OFF |
Build the ESP-IDF port + esp_elf integration |
ELF_OVERLAY_ESP_ELF_INCLUDE |
"" |
Path to esp_elf.h parent dir (standalone) |
ELF_OVERLAY_WERROR |
OFF |
Promote warnings to errors (CI) |
ELF_OVERLAY_ASAN |
OFF |
AddressSanitizer + UBSan (host only) |
Build it as a component from the minimal project used by CI:
idf.py -C examples/esp_idf_component \
-DELF_LOADER_DIR=/path/to/elf_loader set-target esp32s3 buildDrop ELF_LOADER_DIR to let the component manager fetch elf_loader from the
registry.
Runtime sizes are #ifndef-guarded constants in
include/elf_overlay/config.h, so a consumer can override any of them with a
-D flag instead of editing the header.
| Define | Default | Meaning |
|---|---|---|
ELF_OVERLAY_MAX_FRAMES |
4 |
Executable-RAM frames in the pool |
ELF_OVERLAY_DEFAULT_FRAME_SIZE |
16384 |
Bytes per frame |
ELF_OVERLAY_MAX_SECTIONS |
8 |
Section-map entries |
ctest --test-dir build runs the host test suite. See CONTRIBUTING.md for
the full build, test, and formatting workflow, and CHANGELOG.md for the
release history.
Apache-2.0. See the LICENSE file.