GCC Toolchain in Depth
Introduction
You write main.c in English-like C. The microcontroller only flips transistors according to machine code — patterns of bits the CPU decodes as "add", "load", "jump". Somewhere between your editor and the chip, a toolchain must translate, combine, and place that code in flash and RAM.
Compilers and toolchains gave the big picture. Here you see each GCC step, what object files contain, why linking fails, and what names like arm-none-eabi-gcc mean. When you stare at undefined reference to 'sin' or a linker script error, this is the chapter that tells you which stage failed and why.
The translation pipeline — a factory line
Imagine a factory with four stations. Raw material enters; a finished executable leaves.
| Station | Tool | Plain-language job |
|---|---|---|
| 1 | Preprocessor (cpp) |
Paste headers, expand #define, handle #ifdef |
| 2 | Compiler (cc) |
Turn C into assembly — human-readable CPU instructions |
| 3 | Assembler (as) |
Turn assembly into object code — binary with gaps |
| 4 | Linker (ld) |
Stitch objects + libraries, assign final addresses |
When you type gcc main.c -o main, GCC runs all four. Flags let you stop early and inspect intermediate output — essential for learning.
In plain terms
Preprocessor = highlight and expand templates in a document.
Compiler = translate the story from Dutch to machine language.
Assembler = typeset each page as numeric codes.
Linker = bind all chapters into one book with a table of contents and page numbers.
Stage 1: Preprocessor
Before the compiler sees your file, lines starting with # are processed.
1 2 3 4 5 | |
Embedded boards use this heavily:
1 2 3 4 5 | |
One source tree, multiple hardware targets — define BOARD_V2 at compile time (-DBOARD_V2).
See C types and macros for macro pitfalls.
Stage 2: Compiler — C to assembly
1 | |
Open main.s — you see labels, mnemonics, and registers. A simple loop in C becomes compare-and-branch instructions. Optimization level matters:
| Flag | Effect |
|---|---|
-O0 |
Easiest to debug — code maps closely to source |
-Os |
Optimize for size — common on MCUs |
-O2 |
Faster, may reorder code — measure timing on release builds |
Reading assembly connects to Assembly language.
Stage 3: Assembler — object files
1 | |
An object file (.o) is not runnable yet. It contains:
- Machine code for functions in this file
- Data (global variables with initial values)
- BSS info (how much zeroed space to reserve)
- Symbol table — names like
main,counter,HAL_Init - Relocation table — "patch this jump when you know final address"
Symbols: defined vs undefined
| Symbol | Meaning |
|---|---|
| Defined | "I provide this function/variable — here is my local address" |
| Undefined | "I use printf but I do not define it — linker, find it elsewhere" |
main.o might define main and reference undefined printf. libc.a later provides printf.
Stage 4: Linker — the puzzle solver
1 | |
The linker:
- Lays out all code and data in memory (flash at
0x08000000, RAM at0x20000000on typical STM32 — from linker script) - Resolves every undefined symbol to exactly one definition
- Patches relocation entries — branch targets, load addresses
- Discards debug tables or merges them into
.elf
If two files define the same global name, or nothing defines a used name → error.
"Undefined reference" — what it really means
1 | |
Translation: "Some .o called HAL_GPIO_WritePin, but no object or library in the link defined it." Fix: add the right .c file to the project, or link the HAL library.
This is not a syntax error — compile succeeded. Link failed.
Relocation in one sentence
Object files say "call function g" with a placeholder address 0. The linker knows g ended up at 0x08001234 and writes that number into the call instruction.
Stopping GCC at each stage
| Command | Stops after | Inspect |
|---|---|---|
gcc -E file.c |
Preprocessing | Expanded source (huge with big headers) |
gcc -S file.c |
Assembly | file.s |
gcc -c file.c |
Object file | file.o — use nm, objdump |
gcc file.c |
Full program | Runnable binary or .elf |
gcc -v prints every subprocess invoked — useful when the IDE hides commands.
Libraries — static and dynamic
A library is a collection of .o files archived together.
| Type | File | What happens |
|---|---|---|
| Static | .a |
Needed code is copied into your .elf |
| Dynamic | .so |
Executable holds names; OS loads .so at runtime |
Bare-metal firmware almost always statically links libc_nano, libm, and vendor HAL — the chip has no OS to load .so files. Embedded Linux (Pi, gateway) often uses dynamic linking for libc.so to save disk space.
Static trade-off: bigger flash image, but self-contained — flash the one .bin and go.
Library order: sometimes gcc app.o -lm must come after objects that use sin() — linker scans left to right once.
Toolchain names decoded
arm-none-eabi-gcc breaks down as:
| Part | Value | Meaning |
|---|---|---|
| arch | arm |
ARM instruction set |
| vendor | none |
No specific vendor |
| os | eabi |
Embedded ABI (no Linux) |
| tool | gcc |
Compiler driver |
arm-linux-gnueabihf-gcc targets Linux on ARM with hardware float — wrong choice for bare-metal STM32.
Using the wrong toolchain can produce code that does not match your vector table or calling convention.
Build, host, and target
| Term | Your lab setup example |
|---|---|
| Build | Machine that compiled GCC itself |
| Host | Your laptop running arm-none-eabi-gcc |
| Target | STM32 on the bench |
Cross-compilation means host ≠ target. You cannot run the .elf by double-clicking on Windows — you flash it.
Useful inspection commands
1 2 3 4 | |
Your IDE "Build" button runs the same tools with a long flag list and a linker script STM32F411RETx_FLASH.ld.
Relevant topics
Starting points
- Run
gcc -Son a file with aforloop — count branch instructions. - Deliberately omit a source file from the link — read the undefined reference line carefully.
nman object file — list defined (T,D) vs undefined (U) symbols.- Open your project's linker script — find
FLASHandRAMlength.
Focus points
- Compile error ≠ link error — different stage, different fix.
- Linker script is part of the build — wrong RAM size fails link or boot.
-cbuilds objects; you still need a link step for an executable.- Match toolchain to target (
none-eabivslinux-gnueabi).
Key points
- GCC runs preprocess → compile → assemble → link.
- Object files carry code plus symbol and relocation metadata.
- The linker assigns addresses and resolves cross-file references.
- Static linking bundles libraries into firmware; MCUs rarely use dynamic
.so.