EmbeddedRelated.com

elf

Category: Tools

ELF (Executable and Linkable Format) is a standard binary file format used to store compiled object files, shared libraries, and executable images. In embedded development, ELF files are the typical output of a toolchain's linker and contain the program code, data, symbol table, debug information, and section layout metadata needed to load or debug the firmware.

In practice

After compilation and linking with toolchains such as arm-none-eabi-gcc or xtensa-esp32-elf-gcc, the linker produces a .elf file. This file is not directly flashed to a target -- it first gets converted to a raw binary or Intel HEX file (via objcopy) that a programmer or bootloader can write to flash. The ELF file itself is what your debugger (GDB, OpenOCD, J-Link GDBServer) loads to resolve addresses, set breakpoints, and display source-level variable names.

ELF files are organized into sections and segments. Sections such as .text (code), .rodata (read-only data), .data (initialized variables), and .bss (zero-initialized variables) are defined by the linker script. The strip and objcopy tools are then used to remove debug/symbol information or to extract and convert loadable content, respectively. Understanding which section consumes flash vs. RAM is essential for memory-constrained targets -- the arm-none-eabi-size utility reports .text + .rodata (flash) and .data + .bss (RAM) breakdowns directly from the ELF.

The ELF symbol table and DWARF debug sections embedded in the file are the primary enablers of source-level debugging, though the debugger must also correctly match the ELF to the loaded image; some workflows supplement this with separate debug info files. When you load a .elf into GDB and step through C source, GDB is reading DWARF records to map machine addresses back to file names and line numbers. Stripping these sections (arm-none-eabi-strip) reduces file size but makes post-mortem debugging from a crash address much harder. It is common practice to archive the full unstripped ELF alongside each firmware release build for exactly this reason.

A subtle pitfall is treating the .elf file size as the flash footprint. The file on disk can be several megabytes due to embedded debug symbols, even though the actual loadable code and data occupy only tens of kilobytes on the target. Always use arm-none-eabi-size or inspect the ELF's PT_LOAD segments to determine the true memory usage.

Frequently asked

What is the difference between a .elf file and a .bin or .hex file?
A .elf file is a rich structured format containing code, data, symbols, relocation info, and debug metadata. A .bin file is a raw memory image with no metadata -- just bytes, with the load address supplied externally by the programmer or bootloader. An Intel HEX (.hex) file encodes the same raw content as ASCII records with address tags. Programmers and bootloaders typically require .bin or .hex; debuggers require .elf. The conversion is done with arm-none-eabi-objcopy (or the equivalent for your toolchain).
Why is my .elf file much larger than the flash size of my MCU?
The .elf file includes DWARF debug sections, symbol tables, and other metadata that are not loaded onto the target. Only the loadable content (typically .text, .rodata, .data, as identified by PT_LOAD segments or tooling-specific rules) is programmed into flash, though the exact content depends on the target image and programmer tooling. Use arm-none-eabi-size to see the true code+data footprint, not the file size reported by the host OS.
Can I debug firmware without the original .elf file?
Meaningful source-level debugging is not possible without the matching .elf. A raw binary gives you only addresses. Without the symbol table and DWARF info from the .elf, a debugger cannot map addresses to function names, variable names, or source lines. This is why retaining the .elf for every released build is important for diagnosing field issues.
What is a linker script's role in shaping the ELF output?
A linker script (typically a .ld file) tells the linker where in the address space each section should be placed -- for example, .text at the start of flash, .data initialized in flash but copied to RAM at startup. The resulting ELF reflects this layout in its section headers and load segments. Incorrect linker scripts are a common source of startup failures on bare-metal targets, often causing .data not to be copied or .bss not to be zeroed before main() runs.
Is ELF specific to ARM or GCC-based toolchains?
No. ELF is a widely adopted format used by GCC, Clang/LLVM, and many other toolchains across multiple architectures including ARM Cortex-M/A/R, RISC-V, MIPS, Xtensa (ESP32), and x86. Some vendor toolchains for older or proprietary architectures may use different formats such as COFF, OMF, or proprietary variants -- though the exact formats vary by vendor and architecture, and ELF is increasingly common even in traditionally non-ELF ecosystems.

Differentiators vs similar concepts

ELF is often confused with the final flashable image. A .elf is a toolchain-side artifact containing full metadata; a .bin or .hex is the stripped, programmer-ready memory image derived from it. ELF is also distinct from COFF (Common Object File Format), an older format still used by some TI and older MIPS toolchains, and from SREC (Motorola S-record), another ASCII hex format sometimes seen in automotive and legacy embedded targets.