Toolchain and Build Artifacts
When you press Build, several distinct tools run in sequence. If you do not understand that pipeline, debugging build failures, memory overflows, and optimization issues becomes much harder than it needs to be.
Learning Objectives
By the end of this lesson, you should be able to:
- name the main stages of a C firmware build;
- explain what source, object, ELF, map, HEX, and disassembly files are for;
- inspect build artifacts to answer size, symbol, and code-generation questions;
- distinguish compile-time, link-time, and post-link failures.
The Build Pipeline
Some compilers combine several internal stages, but conceptually this is still the model.
Stage 1: Preprocessing
The preprocessor handles:
#include#define#if,#ifdef, and conditional compilation
It produces a translation unit with headers expanded and macros substituted.
Useful when:
- a macro behaves unexpectedly;
- a register definition comes from a device header;
- build configuration changes by target or board revision.
Example:
#define LED_PIN (1u << 5)
PORTB |= LED_PIN;
After preprocessing, the compiler sees the expanded token stream, not the original macro name.
Stage 2: Compilation
The compiler:
- parses C syntax;
- applies type rules;
- performs optimization;
- emits assembly or machine-oriented intermediate output.
Compile errors happen here:
- missing semicolon;
- incompatible types;
- undeclared identifier;
- warning about conversion or unused result.
Stage 3: Assembly
The assembler converts instruction mnemonics and sections into object files (.o).
An object file usually contains:
- machine code for one translation unit;
- symbol information;
- relocation records;
- separate sections such as
.text,.data,.bss, and.rodata.
Object files are not yet final firmware images because addresses are not fully resolved.
Stage 4: Linking
The linker combines object files, libraries, and the linker script.
It decides:
- where code and data live in Flash and RAM;
- which symbol definition satisfies each reference;
- startup layout and interrupt vector placement;
- final addresses for functions and variables.
Link errors happen here:
- undefined reference to a function;
- multiple definition of a global symbol;
- region overflow such as Flash or RAM exhaustion.
Common Build Artifacts
| Artifact | Purpose |
|---|---|
.o |
object file for one source file |
.a |
static library archive |
.elf |
final linked image with symbols and sections |
.map |
memory map report from the linker |
.hex |
Intel HEX image for programming many MCUs |
.bin |
raw binary image |
.lst or disassembly |
mixed source/assembly inspection |
Why the ELF File Matters
The ELF file is usually the richest artifact for debugging because it contains:
- executable sections;
- symbol names;
- section sizes;
- debug information when enabled.
A debugger generally uses the ELF file, not the HEX file, because the HEX file usually lacks symbol and debug metadata.
Why the Map File Matters
The map file is how you answer questions like:
- What is using so much Flash?
- Which variable is consuming RAM?
- Did an unexpected library get linked in?
- Where is the vector table?
If firmware suddenly grows by 20 KB, the map file is often the fastest place to look.
Example AVR Toolchain Commands
avr-gcc -mmcu=atmega328p -DF_CPU=16000000UL -Os -Wall -Wextra -c main.c -o main.o
avr-gcc -mmcu=atmega328p -Wl,-Map=firmware.map main.o -o firmware.elf
avr-objcopy -O ihex -R .eeprom firmware.elf firmware.hex
avr-objdump -d -S firmware.elf > firmware.lst
avr-size --format=avr --mcu=atmega328p firmware.elf
These commands correspond to compile, link, image conversion, disassembly, and size inspection.
Build Failures by Stage
Knowing the stage narrows the search immediately.
Practical Inspection Habits
After every meaningful build, get comfortable checking:
- size output;
- warnings;
- disassembly for critical routines;
- map file when memory use changes;
- the final image name and target device.
Warnings deserve real attention in embedded work. A "harmless" integer-conversion warning can become a field failure.
Common Mistakes
- Thinking
.hexis the only important output. - Ignoring linker scripts and startup objects.
- Treating warnings as noise.
- Not preserving a map file for size regression checks.
- Debugging runtime behavior without verifying which binary was actually flashed.
Summary
An embedded build is a pipeline, not a single action. Source files become object files, object files are linked into an ELF, and post-build tools create flashable images and reports. When you understand the artifacts, you can debug build problems, memory usage, and code generation with far less guesswork.