ELF Object Files & objdump Analysis-Embedded Systems Course for Freshers in Hyderabad

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

ARM Cortex-M Programming← PREV_LECNEXT_LEC →

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.

Pages 291–300ELF FormatobjdumpreadelfSections Analysis

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 File Structure
ELF Header
Program HeadersDescribes segments for loader
.textMachine code instructions
.rodataRead-only data (consts, strings)
.dataInitialized global variables
.bssUninitialized globals (size only)
.debug_infoDWARF debug information
Symbol TableFunction/variable names + addresses
Section HeadersIndex 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 Section Placement Summary
Variable TypeLoad (Flash)Run (RAM)Section
Global initializedFLASHSRAM.data
Global uninitialized—SRAM.bss
Global static initializedFLASHSRAM.data
Global constFLASH—.rodata
Local initialized—Stack—
Local static initializedFLASHSRAM.data
Function codeFLASH—.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).
📷
Suggested images
📷 Suggested image: Terminal screenshot showing objdump -d output with ARM Thumb-2 disassembly of a simple function, with annotations identifying instruction types.

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:

COFFCommon Object File Format — introduced by Unix System V, used by older Windows compilers
AIFARM 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:

  1. What is the total size of the .text section?
  2. What is the address of SysTick_Handler?
  3. Are there any U (undefined) symbols? What are they?
  4. 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

Why does .bss not appear as CONTENTS in the section flags?

.bss variables have no meaningful initial values (they are all zero), so there is no point storing those zeros in the ELF file. Instead, only the size of .bss is recorded. The startup code then zeroes that amount of SRAM at boot. This saves flash space — a 4 KB .bss array adds nothing to the flash usage.

What is the difference between VMA and LMA?

LMA (Load Memory Address) is where the data lives at load time — typically in FLASH. VMA (Virtual Memory Address) is where the data is accessed at run time — typically SRAM for .data and .bss. For .text and .rodata, LMA and VMA are the same (both in flash). For .data, LMA is in flash but VMA is in SRAM, because the startup code copies from LMA to VMA.

What does it mean when nm shows a ‘U’ (undefined) symbol?

An undefined symbol in a .o file means the symbol is referenced in that file but defined elsewhere. The linker resolves these by searching other .o files and libraries. If the linker cannot find a definition, you get an “undefined reference” error. Common examples: printf (defined in libc), memcpy (defined in libc), or functions from another .c file in your project.

When should I use .bin vs .hex format for flashing?

Use .hex (Intel HEX) when your flash programmer needs to know the exact target addresses — which is almost always for MCUs. The .hex file embeds address information. Use .bin only when you know the exact offset the programmer will use, or when writing to a specific memory region via a bootloader. OpenOCD works with both, but .elf is preferred because it has the most information.

Quick Knowledge Check

1. Where is a const uint32_t global array stored at runtime?

  1. Stack
  2. SRAM (via .data copy)
  3. FLASH (stays in .rodata)
  4. .bss region
Check Answer
Correct answer: C) FLASH (stays in .rodata)
const globals go to .rodata, which stays in FLASH permanently — no copy to SRAM.

2. What does the ‘B’ type in nm output indicate?

  1. Binary executable symbol
  2. Global symbol in .bss (uninitialized)
  3. Built-in compiler symbol
  4. Symbol in bootloader section
Check Answer
Correct answer: B) Global symbol in .bss (uninitialized)
B = global .bss symbol (uninitialized/zero-initialized data). b = local .bss symbol.

3. Which objdump flag shows section headers (names, sizes, flags)?

  1. -d
  2. -D
  3. -h
  4. -s
Check Answer
Correct answer: C) -h
-h shows section headers. -d disassembles code, -D disassembles all, -s shows hex dump.

EmbeddedPathashala — Embedded Systems Programming on ARM Cortex-M3/M4

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *