Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
ELF Object Files & objdump Analysis
Understand the ELF file format, explore .text/.data/.bss/.rodata sections, and master analysis tools like objdump, readelf, and nm for ARM embedded development.
1. The ELF File Format
ELF stands for Executable and Linkable Format. It is the standard binary format used by GCC for both relocatable object files (.o) and final executable files (.elf). Every ARM GCC output file — whether an intermediate object or the final firmware — is an ELF file.
Why ELF?
- Portable standard — used across Linux, embedded ARM, MIPS, RISC-V, and more.
- Rich metadata — contains not just code, but section information, symbol tables, debug info, and relocation entries.
- Toolchain integration — objdump, readelf, gdb, and OpenOCD all understand ELF natively.
- Flexible — sections can be placed at any memory address via the linker script.
| ELF Header | |
| Program Headers | Describes segments for loader |
| .text | Machine code instructions |
| .rodata | Read-only data (consts, strings) |
| .data | Initialized global variables |
| .bss | Uninitialized globals (size only) |
| .debug_info | DWARF debug information |
| Symbol Table | Function/variable names + addresses |
| Section Headers | Index of all sections |
2. Key ELF Sections
.text — Code Section
Contains all compiled machine code instructions. It is read-only at runtime and stored in FLASH on an MCU. Every function body ends up in .text.
.data — Initialized Data
Contains global and static variables that have an initial value at compile time. These values are stored in FLASH at load time and copied to SRAM at startup.
.bss — Block Started by Symbol
Contains global and static variables that are zero-initialized (or have no explicit initializer). This section consumes no flash space — only its size is stored. At startup, the startup code zeros out this SRAM region.
.rodata — Read-Only Data
Contains constants and string literals that never change. Stored in FLASH and never copied to RAM. Typical examples: string literals used in printf, const arrays, lookup tables.
C — What Goes Where
#include <stdint.h>
/* .text — compiled to machine code */
void my_function(void) { }
/* .rodata — read-only constant, stored in flash */
const uint32_t lookup_table[4] = {10, 20, 30, 40};
/* .data — initialized global, initial value in flash, copied to SRAM */
uint32_t blink_period_ms = 500;
/* .bss — uninitialized global, zero'd at startup, only in SRAM */
uint32_t error_count;
/* .bss — explicitly zero-initialized, same section */
static uint32_t tick_accumulator = 0;
void some_function(void)
{
/* .text (code) */
uint32_t local_var = 42; /* stack — NOT in .data/.bss */
(void) local_var;
}
Flash vs RAM sections:
Stored in FLASH: .text, .rodata, initial values of .data
Stored in SRAM at runtime: .data (copied from flash), .bss (zero-filled), stack, heap
3. How Code and Data Map to Sections
C — Annotated Example
/* Section mapping reference table */
/* Global INITIALIZED variable → .data section */
uint32_t g_initialized = 100;
/* Global UNINITIALIZED variable → .bss section */
uint32_t g_uninitialized;
/* Global STATIC initialized → .data section */
static uint32_t gs_init = 200;
/* Global STATIC uninitialized → .bss section */
static uint32_t gs_uninit;
/* Global CONST → .rodata section */
const uint32_t g_const = 42;
void demo(void)
{
/* local INITIALIZED → stack (at runtime) */
uint32_t local_init = 5;
/* local UNINITIALIZED → stack (at runtime) */
uint32_t local_uninit;
/* local STATIC initialized → .data section */
static uint32_t ls_init = 10;
/* local STATIC uninitialized → .bss section*/
static uint32_t ls_uninit;
(void)(local_init + local_uninit + ls_init + ls_uninit);
}
| Variable Type | Load (Flash) | Run (RAM) | Section |
|---|---|---|---|
| Global initialized | FLASH | SRAM | .data |
| Global uninitialized | — | SRAM | .bss |
| Global static initialized | FLASH | SRAM | .data |
| Global const | FLASH | — | .rodata |
| Local initialized | — | Stack | — |
| Local static initialized | FLASH | SRAM | .data |
| Function code | FLASH | — | .text |
4. Using objdump
arm-none-eabi-objdump is the Swiss Army knife for ELF inspection. Here are the most useful invocations:
Shell — objdump Cheat Sheet
# Disassemble only .text section (code sections)
arm-none-eabi-objdump -d main.o
# Disassemble ALL sections (including data as instructions — for debugging)
arm-none-eabi-objdump -D main.o
# Show section headers (names, sizes, addresses, flags)
arm-none-eabi-objdump -h main.o
# Show full contents (hex dump) of all sections
arm-none-eabi-objdump -s main.o
# Show contents of a specific section (e.g., .comment has compiler version)
arm-none-eabi-objdump -j .comment -s main.o
# Disassemble with source code interleaved (-g required during compilation)
arm-none-eabi-objdump -dS main.o
Reading objdump Section Output
Sample objdump -h Output
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000003c 00000000 00000000 00000034 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000004 00000000 00000000 00000070 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000008 00000000 00000000 00000074 2**2
ALLOC
3 .rodata 00000010 00000000 00000000 00000074 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
Key fields:
- Size — how many bytes this section occupies.
- VMA (Virtual Memory Address) — address at runtime (0 in .o files).
- LMA (Load Memory Address) — address in flash where the data is stored.
- CONTENTS — the section has actual data (not just a reservation).
- ALLOC — memory should be allocated at runtime.
- LOAD — section should be loaded from the file (not .bss, which has no contents).
5. Using readelf and nm
readelf — ELF Header Inspector
Shell — readelf Examples
# Show ELF file header (architecture, entry point, etc.)
arm-none-eabi-readelf -h main.o
# List all section headers
arm-none-eabi-readelf -S main.o
# Show symbol table
arm-none-eabi-readelf -s main.o
# Show program headers (only in final .elf)
arm-none-eabi-readelf -l firmware.elf
# Print string content of .comment section
arm-none-eabi-readelf -p .comment main.o
# Output: [ 0] GCC: (GNU Arm Embedded Toolchain) 13.2.1 20231009
nm — Symbol Table Browser
Shell — nm Examples
# List all symbols with their type and value
arm-none-eabi-nm main.o
# Common symbol types:
# T = .text (function/code) t = local .text
# D = .data (initialized var) d = local .data
# B = .bss (uninitialized var) b = local .bss
# R = .rodata (const) r = local .rodata
# U = undefined (to be resolved by linker)
# Example output:
# 00000000 T led_init ← function in .text
# 00000004 D blink_period_ms ← initialized global in .data
# 00000000 B error_count ← uninitialized global in .bss
# 00000000 U GPIOD ← undefined — defined in CMSIS header
6. Other Object File Formats
While ELF is standard for GCC-based toolchains, you will encounter other formats in embedded development:
| COFF | Common Object File Format — introduced by Unix System V, used by older Windows compilers |
| AIF | ARM Image Format — proprietary ARM format used with the KEIL/armcc toolchain |
| S-Record (.srec) | Motorola SRECORD — ASCII hex format used for flashing, similar role to Intel HEX (.hex) |
| Intel HEX (.hex) | ASCII text format with address, data, and checksum records — widely supported by programmers |
| Binary (.bin) | Raw binary dump of the code — no address metadata, simplest for direct flash writing |
7. Programming Exercises
Exercise 1 — Section Size Prediction
Before compiling, predict which section each variable below will land in. Then compile and verify with arm-none-eabi-nm.
C — Prediction Exercise
uint32_t a = 10; /* predict: ? */
uint32_t b; /* predict: ? */
const uint32_t c = 20; /* predict: ? */
static uint32_t d = 30; /* predict: ? */
static uint32_t e; /* predict: ? */
const char *msg = "Hello ARM"; /* predict: pointer = .data, string = .rodata */
void test(void) {
static uint32_t local_s = 50; /* predict: ? */
}
Exercise 2 — objdump Treasure Hunt
Compile the scheduler project and run the following queries. Record your findings:
- What is the total size of the
.textsection? - What is the address of
SysTick_Handler? - Are there any
U(undefined) symbols? What are they? - What compiler version was used? (hint: use
-p .comment)
Exercise 3 — Force a Variable into Custom Section
Place a variable in a custom section called .mydata using GCC’s __attribute__((section(".mydata"))). Verify it appears in the right place with objdump.
/* Force variable into custom section */
uint32_t __attribute__((section(".mydata"))) special_value = 0xDEADBEEF;
/* Check with: arm-none-eabi-objdump -h prog.o | grep mydata */
8. Frequently Asked Questions
Quick Knowledge Check
1. Where is a const uint32_t global array stored at runtime?
- Stack
- SRAM (via .data copy)
- FLASH (stays in .rodata)
- .bss region
Check Answer
const globals go to .rodata, which stays in FLASH permanently — no copy to SRAM.
2. What does the ‘B’ type in nm output indicate?
- Binary executable symbol
- Global symbol in .bss (uninitialized)
- Built-in compiler symbol
- Symbol in bootloader section
Check Answer
B = global .bss symbol (uninitialized/zero-initialized data). b = local .bss symbol.
3. Which objdump flag shows section headers (names, sizes, flags)?
- -d
- -D
- -h
- -s
Check Answer
-h shows section headers. -d disassembles code, -D disassembles all, -s shows hex dump.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments