We’re Officially Live β€” Lifetime Founding Membership Available for a Limited Time
Free preview

The Toolchain

The chain of programs that turns source into a flashable image (compiler, assembler, linker, loader), plus cross-compilation (arm-none-eabi-*) and objcopy from ELF to .bin/.hex.

20 min read

In this lesson:

This is the tools view of the C "compilation pipeline" lesson, the actual programs, plus cross-compiling and flashing.


The chain of programs

A toolchain is the set of programs that transform source code into something that runs. The classic chain:

 main.c ─[preprocessor]β†’ main.i ─[compiler]β†’ main.s ─[assembler]β†’ main.o ─[linker]β†’ firmware.elf ─[loader]β†’ running
          cpp                   cc1                  as                   ld                        flasher + startup
Stage Program In β†’ Out
Preprocess cpp .c β†’ expanded .i (macros/includes)
Compile cc1 (via gcc) .i β†’ .s (assembly)
Assemble as .s β†’ .o (object file)
Link ld .o + libraries β†’ executable (ELF)
Load OS loader / flasher + startup ELF/image β†’ memory, then runs

The first four are the compilation pipeline (the C module covered translation units, symbols, and linking). This lesson adds the loader and the cross-compilation reality.


The loader on embedded

On a desktop, the loader is part of the OS: it reads the executable, maps it into RAM, resolves dynamic libraries, and jumps to main. On a bare-metal MCU there's no OS loader. Its job is split:

So "load" on embedded = flash the image once, then startup code sets up RAM at every reset.


Cross-compilation

You build on a powerful host (your x86/ARM laptop) but the code must run on a different target (a Cortex-M). That's cross-compilation: the host toolchain emits machine code for the target architecture, not the host's. You can't run target code on the host, so you flash it.

The toolchain is named by its target triple:

arm-none-eabi-gcc        # ARM architecture, no OS ("none"), embedded ABI
β””armβ”˜ β””noneβ”˜ β””eabiβ”˜
 CPU   OS     ABI

Every tool gets the prefix: arm-none-eabi-gcc, -as, -ld, -objcopy, -gdb, -size. Using the host gcc to build target firmware is a classic beginner mistake, it produces x86 code that won't run on the MCU.


The GNU toolchain pieces

The common embedded toolchain is GNU-based:

Key inspection/conversion tools you'll use constantly:

# Convert the linked ELF into a raw flash image (boot/linker lessons):
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
arm-none-eabi-objcopy -O ihex   firmware.elf firmware.hex
 
arm-none-eabi-size    firmware.elf   # how big are .text/.data/.bss? (fits flash/RAM?)
arm-none-eabi-objdump -d firmware.elf   # disassemble (see the actual instructions)
arm-none-eabi-nm      firmware.elf   # list symbols (find that "undefined reference")
arm-none-eabi-readelf -a firmware.elf   # ELF headers, sections, symbol tables

objcopy (ELF→bin/hex) and size (does it fit?) are daily drivers; objdump/nm/readelf are your forensic tools when something's wrong.


A minimal cross-build

CC=arm-none-eabi-gcc
CFLAGS="-mcpu=cortex-m4 -mthumb -O2 -g -ffunction-sections -fdata-sections"
LDFLAGS="-T stm32f4.ld -nostartfiles -Wl,--gc-sections"
 
$CC $CFLAGS -c main.c startup.c        # β†’ main.o, startup.o
$CC $CFLAGS $LDFLAGS main.o startup.o -o firmware.elf   # link with the chip's script
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
arm-none-eabi-size firmware.elf        # verify it fits
# then flash firmware.bin/.elf to the target (next lessons)

Note the target flags (-mcpu, -mthumb), the chip's linker script (-T), and --gc-sections to drop unused code, all part of producing a correct, small image for this MCU.


Gotchas


TL;DR

This is just the start

Sign up free to track progress on this lesson, get coding problems with the Run button, and unlock the full interview Q&A.

More in Debugging & Toolchain

The Build Process & MakefilesLinker Scripts & Memory Sections
Debugging with GDB
On-Chip Debugging: JTAG & SWD
Breakpoints & Watchpoints
Logic Analyzer & Oscilloscope
The Toolchain | EmbeddedPrep.io