memprobe is in free beta. Sign up now during beta to get 40% off Pro for life. See pricing

Documentation

Guides for understanding firmware memory maps and ELF files.

Getting started

memprobe is an online analyzer for embedded firmware. Upload a compiled .elf file and it shows you where your Flash and RAM are going: totals, a per-section breakdown, a treemap, the largest symbols, memory warnings, and a function call graph. Nothing to install, and your file is analyzed and not shared.

Analyze a build in three steps

  1. Build with debug info. Compile at least once with -g (any optimization level). The ELF still reports sizes without it, but -g at -O1 or higher is what lets memprobe show source file locations and the call graph.
  2. Upload the file. Open the app and drop in your .elf. Most toolchains write it next to the binary, often as firmware.elf or <project>.elf.
  3. Read the tabs. Start with Overview for the Flash/RAM totals, then Treemap and Symbols to find the largest contributors, and Insights for warnings about things that commonly waste space.

What memprobe accepts

  • ELF files (.elf / .axf) from GCC or Clang, for ARM Cortex-M, Xtensa (ESP32 / ESP32-S3), RISC-V, and x86 targets.

How memprobe measures Flash and RAM

The headline numbers are meant to be exact, and where a number cannot be known from the ELF alone, memprobe says so rather than guessing. Here is precisely what the Flash and RAM totals count.

Flash

Flash is every section that is loaded onto the device and stores bytes in the image. memprobe decides this from the ELF section flags, not from section names, so vendor-specific content sections (for example ESP-IDF's .iram0.vectors or .flash.appdesc) are counted even though their names do not look like .text or .rodata. Two kinds of section are deliberately excluded:

  • Link-time-only metadata such as .symtab, .strtab, and Xtensa's .xt.prop. These live in the ELF but are never written to the device, so they do not count toward Flash.
  • Address-space reservations that store no bytes (NOBITS sections), such as .bss and ESP-IDF's .flash_rodata_dummy. They reserve addresses but contribute zero bytes to the stored image.

Loadable content, not container size. The Flash total is the content your firmware places in flash, not the on-disk size of a packaged image such as an esptool .bin. A packaged .bin adds bootloader headers, a partition table, and alignment padding, so it is usually a little larger than the number here. memprobe reports the content because that is what your code actually occupies.

RAM

RAM is .data plus .bss, plus any heap and stack regions the linker reserves a fixed size for. It does not include runtime heap and stack growth, because how much your program allocates or how deep it recurses is not knowable at link time. The RAM number is the static reservation, not a high-water mark.

What we cannot always show, and why

On the address map, an initialized section like .data has both a flash copy (its load address) and a RAM copy (its runtime address). memprobe places it on the Flash ruler only when the ELF records a distinct load address. On targets where the bootloader performs the copy (ESP32 is the common case), the ELF lists the same address for both, so the flash copy cannot be located from the ELF alone. Rather than draw it at a RAM address and mislead you, memprobe shows it on RAM only and notes the limitation.

How to read your ELF memory map

When you compile firmware for an embedded target, the linker produces an ELF (Executable and Linkable Format) file. An ELF file is not just a flat binary: it contains metadata about every function, variable, and section in your program. memprobe parses that metadata and breaks it down by section and symbol.

What is a memory map?

A memory map shows how your program's code and data are laid out in the target's address space. Each region in the map corresponds to a physical memory area on the chip, most commonly Flash (code and read-only data) and RAM (runtime data and stack).

The linker script for your target defines these regions. For example, an STM32F4 might have:

  • FLASH: 1 MB starting at 0x08000000
  • RAM: 192 KB starting at 0x20000000
  • CCMRAM: 64 KB of tightly-coupled RAM at 0x10000000

Flash usage vs. RAM usage

Flash usage is how much non-volatile storage your firmware occupies. RAM usage is how much working memory your program needs at runtime, including global variables, the heap, and the stack.

These two numbers are independent. A function with large lookup tables may use significant Flash but almost no RAM if those tables are declared const. A large static buffer costs RAM but nothing in Flash if it is zero-initialized.

Reading the memprobe output

The top-level Flash and RAM numbers come from the sections in your ELF matched against the regions in your linker script. Below that:

  • Sections: The raw ELF sections (.text, .rodata, .bss, etc.) sorted by size.
  • Symbols: Individual functions and variables within each section. Sort by size to find the largest contributors.
  • Treemap: A proportional view of section sizes. Larger rectangles use more memory.
  • Regions: Fill percentage for each memory region, so you can see how close you are to the hardware limit.

Tip: Sort the symbol table by size and look at the top 10 entries. A small number of symbols usually account for most of the Flash or RAM usage.

Memory section types: .text, .rodata, .bss, .data, and more

ELF sections are named regions in the binary, each with a specific purpose. Knowing what goes in each one tells you where your memory is going.

Flash sections

SectionContainsMemory type
.text Compiled machine code. Every function in your program ends up here. Usually the largest section in a firmware binary. Flash
.rodata Read-only data: string literals, const arrays, lookup tables, and other compile-time constants. Placed in Flash because it never changes at runtime. Flash
.data (LMA) Initial values for initialized global and static variables. At startup, the C runtime copies this from Flash into RAM. The copy stored in Flash is referred to as the Load Memory Address (LMA) copy. Flash (stored), RAM (at runtime)
.isr_vector / .vectors The interrupt vector table: an array of function pointers the CPU jumps to when an interrupt fires. On most Cortex-M devices this sits at the start of Flash. Flash
.ARM.extab / .ARM.exidx ARM exception-handling tables for stack unwinding (C++ exceptions or debugger backtraces). Often present in C-only projects when using GCC for ARM targets. Flash

RAM sections

SectionContainsNotes
.data Initialized global and static variables with a non-zero initial value, e.g. int x = 5; at file scope. Costs both Flash (initial values) and RAM (runtime copy).
.bss Uninitialized or zero-initialized globals and statics, e.g. int x; or static uint8_t buf[1024];. Zeroed by the C runtime at startup, so no Flash copy is needed. Costs RAM only, not Flash. Often the largest RAM section.
.heap Dynamic memory managed by malloc/free. The linker reserves a fixed block; actual usage depends on runtime behavior. Best avoided on constrained targets. Fragmentation can cause hard-to-reproduce failures.
.stack The call stack: local variables and return addresses for active function calls. The linker reserves a fixed size; overflow causes a hard fault. If you see unexplained hard faults or corrupted variables, stack overflow is a likely cause.
.noinit Variables that must survive a soft reset without being zeroed by startup code. Useful for crash counters or diagnostic state across resets. Requires explicit linker script support.

Common things to check

  • Large .rodata: Usually string tables, font bitmaps, audio samples, or large const arrays. Fine in Flash; check that nothing ended up in RAM by mistake.
  • Large .bss: Often large static buffers or global arrays. Consider whether they can be stack-allocated or replaced with smaller buffers.
  • Large .data: Every byte costs Flash twice (stored initial value + runtime copy). Prefer zero-initialization (.bss) or const data (.rodata) where possible.
  • Many small symbols in .text: May indicate heavy inlining or template expansion. -Os optimizes for size; -O2/-O3 trade code size for speed.

Rule of thumb: Flash = .text + .rodata + .data (LMA). RAM = .data (VMA) + .bss + .heap + .stack.

CLI and CI integration

memprobe has a command-line tool and an HTTP API for running this analysis from your terminal and gating builds in CI. See the dedicated guides:

Analyze your own firmware
Drop in an ELF and see exactly where your flash and RAM go.
Open the analyzer