# What Happens During the elf2image Conversion Step for ESP32 Bootloader Files

> Understand the elf2image conversion for ESP32 bootloader files. Learn how ELF files transform into flashable binaries through parsing sections, adding ROM headers, and merging segments.

- Repository: [Jason2866/esp_flasher](https://github.com/jason2866/esp_flasher)
- Tags: internals
- Published: 2026-03-04

---

**The `elf2image` command performs a deterministic, multi-stage transformation that converts linker-generated ELF files into flashable binary images by parsing sections, injecting ROM-specific headers, and merging adjacent memory segments.**

The `elf2image` conversion step is the critical bridge between ESP-IDF linker output and bootable firmware for ESP32 microcontrollers. Implemented in the `jason2866/esp_flasher` repository, this process resides primarily in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) and handles everything from secure boot padding to flash parameter encoding. Understanding this transformation allows developers to debug boot failures and optimize bootloader layouts for specific hardware configurations.

## ELF Parsing and Chip-Specific Image Initialization

The conversion begins by loading the source ELF and selecting the appropriate chip-specific image handler that understands the target ROM's expected binary layout.

### Loading the ELF Structure

The tool first instantiates an **`ELFFile`** object to parse the input bootloader ELF. This extracts the entry point address, individual sections, and optionally computes a SHA-256 hash of the entire file for integrity verification.

```python
e = ELFFile(args.input)

```

According to the source in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) (lines 5718-5722), this initialization captures all metadata required for subsequent image generation, including the critical entry point that the ESP32 ROM will jump to after loading the bootloader.

### Selecting the Firmware Image Class

Based on the `--chip` argument, `elf2image` instantiates the appropriate subclass of `BaseFirmwareImage`. For ESP32 bootloaders, this creates an **`ESP32FirmwareImage`** object (defined around line 4518) that understands the specific header layout, secure-pad handling, and MMU page size requirements of the ESP32 ROM loader.

```python
if args.chip == 'esp32':
    image = ESP32FirmwareImage()

```

This chip-specific class ensures that the resulting binary matches exactly what the target ROM expects when reading from flash memory.

## Flash Parameter Injection and Secure Padding

After initialization, the tool populates the image header with flash configuration parameters and optional security features required for secure boot workflows.

### ROM Header Configuration

The function copies critical parameters from the ELF and command-line arguments into the image header structure:

- **Entry point**: `image.entrypoint = e.entrypoint`
- **Flash mode**: Translated from strings (`qio`, `dio`, `dout`, `qout`) into numeric enums that the ROM understands
- **Flash size and frequency**: Combined into a single 32-bit word via `image.flash_size_freq` using helper methods `ROM_LOADER.parse_flash_size_arg` and `parse_flash_freq_arg`
- **MMU page size**: Set via `image.set_mmu_page_size` when `--flash-mmu-page-size` is specified
- **Chip revision bounds**: Stored in `image.min_rev`, `image.max_rev_full`, and related fields for chips supporting revision checks

These assignments occur between lines 5670-5889 in [`own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/own_esptool.py), ensuring the bootloader binary communicates compatible flash settings to the ROM before executing user code.

### Secure Boot Padding Options

If the `--secure-pad` or `--secure-pad-v2` flags are present, the tool configures additional padding blocks required by the ESP32 secure boot ROM. Setting `image.secure_pad` to `'1'` or `'2'` instructs the ROM to expect a 16 KB (v1) or 32 KB (v2) secure padding block after the bootloader binary, protecting against certain classes of physical flash attacks.

```python
if args.secure_pad:
    image.secure_pad = '1'

```

This handling occurs at lines 5727-5731, immediately after chip selection.

## Segment Processing and Image Validation

The final stages involve selecting data sources, optimizing the memory layout, validating integrity, and writing the output file.

### Sections vs. Segments Selection

By default, `elf2image` builds the image from ELF **sections** (`e.sections`). However, when `--use_segments` is passed, it uses raw ELF **segments** (`e.segments`) instead, preserving custom alignments that the linker may have produced for specific memory layouts.

```python
image.segments = e.segments if args.use_segments else e.sections

```

This choice affects how the linker-generated data is packed into the flash image and can impact boot performance on certain ESP32 variants.

### Merging and Padding Strategies

The tool performs two optimization steps to ensure the binary matches ROM expectations:

1. **Optional size padding**: When `--pad-to-size` is specified, the image is padded to a fixed flash size (e.g., the first 0x10000 bytes), ensuring the bootloader occupies a predictable region regardless of actual code size.

2. **Segment merging**: `image.merge_adjacent_segments()` collapses contiguous memory regions into single blocks, reducing flash write operations and ensuring the layout matches what the ESP32 ROM bootloader expects when mapping flash into RAM.

These operations occur at lines 5784-5791, preparing the image for final validation.

### Verification and Output Generation

Before writing, `image.verify()` runs internal consistency checks including header field validation, padding verification, and revision bound checks. If verification fails, a `FatalError` aborts the conversion immediately.

The output filename is determined either by `--output` or automatically via `image.default_output_name(args.input)`, which replaces the `.elf` extension with `.bin`. Finally, `image.save(args.output)` serializes the complete flash image including the ROM loader header, optional secure padding, and any appended SHA-256 digest (when `--elf-sha256-offset` is specified).

```python
image.save(args.output)
print("Successfully created {} image.".format(args.chip))

```

This sequence at lines 5801-5806 produces a binary ready for flashing via `write_flash` or `merge_bin` commands.

## Practical elf2image Usage Examples

### Converting an ESP32-S3 Bootloader via CLI

The following command converts an ESP32-S3 bootloader ELF with specific flash parameters:

```bash
python -m esp_flasher elf2image \
    bootloader/esp32s3/bin/bootloader_qio_80m.elf \
    --chip esp32s3 \
    --flash_mode dio \
    --flash_freq 80m \
    --flash_size 4MB \
    -o bootloader_esp32s3.bin

```

This invocation parses the ELF, creates an `ESP32S3FirmwareImage`, encodes DIO mode at 80 MHz, pads to 4 MiB, and writes the merged binary.

### Programmatic API Usage

You can invoke the same logic programmatically:

```python
from esp_flasher.own_esptool import elf2image, argparse

args = argparse.Namespace(
    input='bootloader/esp32s3/bin/bootloader_qio_80m.elf',
    chip='esp32s3',
    flash_mode='dio',
    flash_freq='80m',
    flash_size='4MB',
    version='1',
    output='bootloader_s3.bin',
    secure_pad=False,
    secure_pad_v2=False,
    use_segments=False,
    pad_to_size=None,
    elf_sha256_offset=None,
    flash_mmu_page_size=None,
    min_rev=0,
    min_rev_full=0,
    max_rev_full=0,
)

elf2image(args)

```

### Verifying the Generated Image

Confirm the conversion succeeded by inspecting the image metadata:

```bash
python -m esp_flasher image_info bootloader_s3.bin --chip esp32s3

```

This displays the entry point, flash parameters, and segment layout, verifying that `elf2image` encoded everything correctly.

## Summary

- The `elf2image` function in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) orchestrates the conversion from ELF to flashable binary through a deterministic 11-step pipeline.
- **Chip-specific image classes** like `ESP32FirmwareImage` handle ROM-specific header layouts and secure boot padding requirements.
- Flash parameters including mode, frequency, size, and MMU page size are encoded into the binary header exactly as the ESP32 ROM expects.
- The tool supports both **sections** (default) and **segments** (`--use_segments`) as data sources, with automatic merging of adjacent regions to optimize flash layout.
- Optional **secure padding** (16 KB or 32 KB) and **SHA-256 insertion** support advanced secure boot workflows.
- Validation via `image.verify()` ensures the output binary will be accepted by the target ROM before writing the final `.bin` file.

## Frequently Asked Questions

### What is the difference between using sections and segments in elf2image?

By default, `elf2image` uses ELF **sections** (`e.sections`) which represent the logical divisions created by the compiler and linker. When you pass `--use_segments`, the tool uses raw ELF **segments** (`e.segments`) instead, preserving the exact memory alignment and permissions set by the linker. Segments are typically larger, page-aligned regions that the OS loader uses, while sections are finer-grained divisions used by the linker for relocation and symbol management.

### How does secure padding affect the bootloader binary size?

When `--secure-pad` or `--secure-pad-v2` is enabled, the tool sets `image.secure_pad` to `'1'` or `'2'`, instructing the ESP32 ROM to reserve 16 KB or 32 KB of secure padding space immediately following the bootloader. This increases the total binary size to accommodate the padding block, which the ROM uses for cryptographic verification during secure boot. The actual bootloader code size remains unchanged, but the flash image occupies more space to satisfy these security requirements.

### Can elf2image generate images for multiple ESP32 variants?

Yes, the tool supports all major ESP32 variants including ESP32, ESP32-S2, ESP32-S3, ESP32-C3, and ESP32-C6 through chip-specific subclasses of `BaseFirmwareImage`. The `--chip` argument determines which class is instantiated (e.g., `ESP32FirmwareImage`, `ESP32S2FirmwareImage`), ensuring the generated binary uses the correct header format, MMU settings, and ROM layout for the specific silicon revision.

### Where does elf2image store flash mode and frequency in the output binary?

The tool combines flash mode (QIO, DIO, etc.) and frequency (40 MHz, 80 MHz, etc.) into a single 32-bit word stored in `image.flash_size_freq`. This word is written into the image header at a specific offset expected by the ESP32 ROM bootloader. The helper functions `ROM_LOADER.parse_flash_size_arg` and `parse_flash_freq_arg` perform the translation from human-readable strings to the numeric enums defined by Espressif's boot ROM specification.