Loading header...

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

flowchart LR A[".c / .h source"] --> B["Preprocessor\nexpands #include and #define"] B --> C["Compiler\nparses C and generates assembly or object code"] C --> D["Assembler\nturns assembly into object files (.o)"] D --> E["Linker\ncombines objects and libraries into ELF"] E --> F["Objcopy / post-build tools\nHEX, BIN, listing, size report"] F --> G["Programmer / flasher\nwrites image to device"]

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

flowchart TD A["Compiler error"] --> A1["Syntax, types, missing declarations"] B["Linker error"] --> B1["Undefined symbol, multiple definition, memory overflow"] C["Flash/program error"] --> C1["Wrong target, bad cable, bootloader or permissions issue"] D["Runtime bug after successful build"] --> D1["Logic bug, startup issue, optimization assumption, hardware problem"]

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 .hex is 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.

Further Reading

Mind Map

mindmap root((Build Artifacts)) Core flow Source and headers Preprocess macros Compile C Assemble objects Link ELF Convert HEX BIN Key files Object per unit ELF with symbols MAP shows memory HEX for flashing LST for disassembly Calculations Flash equals text plus rodata RAM equals data plus bss Stack margin needed Size trend per build Design rules Enable warnings Keep map files Match MCU flag Version build flags Inspect linker script Practical checks Read avr-size Search map symbols Compare disassembly Confirm flashed image Track memory growth Mistakes Treating HEX as debug file Ignoring link errors Wrong target device Warning blindness No artifact review