Embedded and Cross-Platform Build Workflows

A linker script defines memory regions and section placement for toolchains that support this model.

What this module covers

Cross toolchains and target architecture Firmware build stages Linker scripts and memory maps

Debug/release and board-specific configurations Generated register headers and code generators

Build variants and product configuration Reproducible firmware images

Version metadata and firmware identification Flashing, packaging, and release artifacts

Hardware-independent CI and hardware-in-the-loop boundaries

17.1 Typical firmware pipeline

C/C++ + Device Headers + Generated Config
                 |
                 v
       Cross-Compiler / Assembler
                 |
                 v
          Object Files / Libraries
                 |
                 v
     Linker Script + Startup Objects
                 |
                 v
       ELF + Map File + Debug Symbols
                 |
                 v
       HEX/BIN Conversion / Packaging
                 |
                 v
        Integrity Checks / Signing
                 |
                 v
       Release Artifact / Flash Process

17.2 Linker script and memory map

A linker script defines memory regions and section placement for toolchains that support this model. It can place vectors, code, constants, initialized data, and zero-initialized data into specific memory regions.

Always verify:

Flash and RAM boundaries.

Bootloader/application offsets.

Stack/heap reservations.

Interrupt/startup conventions.

A successful link is not proof that the firmware will boot correctly on hardware.

Measuring sections and enforcing a RAM budget Without a cross toolchain on the build machine, this uses the host GCC — the same commands apply with arm-none-eabi- prefixes and a real linker script:

Terminal session (GCC 11.2, binutils 2.38, Ubuntu 22.04 on WSL2):

The map file attributes each section to the object that contributed it (0x400 = the 1024-byte frame buffer in .bss). A budget script like this turns “the image grew” into a failing CI step instead of a surprise on hardware.

$ cc -O2 -c fw.c -o fw.o && cc fw.o -Wl,-Map=fw.map -o fw.elf
$ size -A fw.elf | grep -E '^(section|\.text|\.rodata|\.data|\.bss) '
section              size    addr
.text                 265    4160
.rodata               288    8192
.data                  20   16384
.bss                 1056   16416
$ grep -E '^ \.(rodata|bss|data)' fw.map | grep fw.o
 .rodata        0x0000000000002020      0x100 fw.o
 .data          0x0000000000004010        0x4 fw.o
 .bss           0x0000000000004040      0x400 fw.o
$ objcopy -O ihex fw.elf fw.hex && head -3 fw.hex && wc -l < fw.hex
:100318002F6C696236342F6C642D6C696E75782D7C
:0C0328007838362D36342E736F2E3200DC
:10033800040000002000000005000000474E5500A2
137
$ ./budget.sh 2048
RAM used: 1076 bytes, budget: 2048 bytes
$ ./budget.sh 1024
RAM used: 1076 bytes, budget: 1024 bytes
[exit status 1]

17.3 Variant management

Represent board, MCU, product, and feature variants in explicit configuration. Avoid scattered preprocessor definitions that create undocumented combinations. Record the selected variant in build metadata and, when appropriate, in firmware version information.

17.4 Build identification

A release may include a semantic version, Git commit ID, build profile, toolchain version, and reproducibility metadata. Be careful: embedding current time or machine-specific paths can make outputs non-reproducible. Define what metadata is essential and how it is normalized.

17.5 Hardware-independent CI

CI can build firmware, run unit tests, perform static analysis, inspect map files, and test host-side logic without physical hardware. Hardware-in-the-loop testing requires controlled device access, flashing safeguards, fixture health checks, and result traceability. Distinguish compile-time validation from target behavior validation.

Quiz

? Which section held the zero-initialised 1024-byte frame buffer?
- `.text` :: Code.
- `.rodata` :: Constant data — the lookup table.
- `.data` :: Initialised variables — `boot_count`.
+ `.bss` :: Zero-initialised data occupies RAM but not image bytes.

? Why avoid embedding the build time in firmware version metadata?
+ It makes otherwise identical builds differ, breaking reproducibility. :: Use the tag and commit ID instead.
- Firmware cannot store dates. :: It can.
- Time zones are unsupported. :: Not the reason.
- It is slower. :: Not the reason.

? What can hardware-independent CI verify? (select all that apply)
+ The firmware builds for each variant :: Compilation and linking.
+ Host-side unit tests pass :: Logic can be tested without hardware.
+ Memory budgets from map files :: As in the budget script.
- That the device boots correctly :: That needs hardware-in-the-loop testing.