In this lesson:
- the programs in the toolchain: compiler β assembler β linker β loader
- what the loader means on embedded (flasher + startup code)
- cross-compilation and the
arm-none-eabi-*naming - the GNU pieces: gcc, binutils, newlib, and
objcopy/objdump/size - the gotchas: native vs cross, ABI/float mismatch, library choice
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:
- A flasher/programmer writes your image into the chip's flash (over JTAG/SWD or a bootloader).
- The startup code (the boot/reset handler from the Embedded Fundamentals module) is the on-chip "loader", it copies
.datato RAM, zeroes.bss, and jumps tomain.
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- arm, target CPU family.
- none, no operating system (bare metal). (Contrast
arm-linux-gnueabihffor embedded Linux.) - eabi, the Embedded Application Binary Interface (calling conventions, etc.).
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:
- gcc, the driver: one command (
arm-none-eabi-gcc) that invokes the preprocessor, compiler, assembler, and linker for you. - binutils, the supporting tools:
as(assembler),ld(linker), and the inspection tools below. - newlib (or newlib-nano), a small C standard library implementation for embedded.
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 tablesobjcopy (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
- Cross, not native. Build target firmware with
arm-none-eabi-gcc, never the hostgcc. Host gcc emits x86 code that can't run on the MCU, a frequent first mistake. - ABI / float-ABI must match across all objects. Mixing soft-float and hard-float objects (
-mfloat-abi=softvshard), or different-mcpu/-mfpu, causes link errors or runtime corruption. Use one consistent flag set (and the matching library) everywhere. - The "loader" is the flasher + startup code. There's no OS to load you; you flash once, and the reset handler initializes RAM each boot. A missing/wrong startup or linker script means it won't run even if it links.
- Library choice affects size.
newlib-nano(and-specs=nano.specs) is much smaller than full newlib; fullprintfwith floats pulls in a lot. Pick the lib/specs that fit your flash. - Toolchain version matters. Different GCC versions optimize and warn differently; pin the toolchain version in CI for reproducible builds (a subtle source of "works on my machine").
TL;DR
- A toolchain turns source into a running image through compiler β assembler β linker β loader;
gccis the driver that runs the first stages,ldlinks, and the inspection tools come from binutils. - On embedded the loader is a flasher (writes flash once) plus the startup code (sets up RAM at each reset), there's no OS loader.
- You cross-compile: build on the host for the target with a prefixed toolchain like
arm-none-eabi-gcc(CPU = arm, OS = none/bare-metal, ABI = eabi), never the hostgcc. - Daily tools:
objcopy(ELF β.bin/.hexto flash),size(does it fit flash/RAM?), andobjdump/nm/readelffor disassembly, symbols, and ELF forensics; the C library is newlib/newlib-nano. - Keep the ABI/float/-mcpu flags consistent across all objects and the library, choose a lib that fits your flash, and pin the toolchain version for reproducible builds.