Embedded Build Process and Development Environment
A structured introduction to embedded development platforms, cross-compilation, build phases, GNU tools, and memory layout.
Level: beginner
Tags: embedded, gcc, build-process, cross-compilation, make, linker
Lesson
At a glance
This lesson introduces the basic environment used in embedded software development and explains how source code becomes an executable image for a target board.
1. Embedded platform overview
An embedded system is designed to perform a specific function under constraints such as memory, power, peripherals, cost, timing behavior, and software update limitations.
In embedded development, the machine used to write and compile the code is often different from the machine that will actually run it.
- Host machine — the computer used to write, compile, test, and version the code
- Target — the board or microcontroller that runs the firmware
- Toolchain — compiler, assembler, linker, debugger, and binary utilities
- Development kit — hardware used for prototyping and testing
- Repository — source code, build files, linker scripts, and reproducible configuration
2. Native build vs cross-build
In a native build, the compiler generates code for the same machine on which it runs.
In a cross-build, the compiler runs on the host but generates code for a different architecture, such as an ARM Cortex-M microcontroller.
This is one of the most important concepts in embedded software: the build environment and the runtime environment are often different.
3. Build process: from source to executable
The build process is not a single action. In a classic GCC-based embedded workflow, the main phases are:
- Preprocessing — expands includes, macros, and conditional directives
- Compilation — translates C into target-specific assembly
- Assembling — converts assembly into relocatable object files
- Linking — resolves symbols and combines objects and libraries
- Locating — maps sections into the physical memory regions of the target
A key point is that an object file (.o) is still relocatable: final physical memory
placement happens only after linking and locating.
4. GCC and cross-compilation
GCC means GNU Compiler Collection, but in embedded work the compiler executable often includes target information in its name.
gcc— native compiler for the hostarm-none-eabi-gcc— ARM bare-metal toolchainarm-linux-gnueabi-gcc— ARM Linux target toolchain
Typical embedded flags include:
-mcpu=…— select the target core-mthumb— generate Thumb instructions-mfloat-abi=…and-mfpu=…— control floating-point ABI and FPU settings-std=c99/-std=c11— select the C standard-Wall -Wextra -Werror— enable warnings-O0,-Og,-O2,-Os— control optimization-g— include debug symbols
5. Preprocessing and conditional compilation
The preprocessor runs before compilation. It handles:
#include#define/#undef#ifdef/#ifndef#if/#elif/#else/#endif#warning/#error#pragma
Conditional directives are especially useful when one codebase must support multiple platforms or configurations.
Function-like macros should always use parentheses carefully to avoid precedence bugs and side effects.
6. Headers, modules, and libraries
In C, a module usually separates:
- Header file (
.h) — interface, types, constants, macros, function prototypes - Source file (
.c) — implementation details and function definitions
This improves readability, reuse, modularity, and testability.
Common library forms:
- Static library (
.a) - Shared library (
.so) - Source-distributed module (
.h/.c)
In bare-metal systems, static libraries are much more common than shared ones.
7. Linker, linker scripts, and output formats
The linker resolves symbol references across object files and libraries, but in embedded systems it also has to place sections into real memory regions such as Flash and SRAM.
This placement is controlled through a linker script.
Typical concerns in this stage:
- symbol resolution
- section placement
- entry point definition
- generation of a map file
- control over standard runtime libraries
Typical output formats include:
- ELF — rich debugging-oriented executable format
- Intel HEX
- SREC
- COFF
8. GNU Make and build automation
GNU Make automates the build process by defining targets, prerequisites, and recipes.
It helps:
- avoid repetitive manual commands
- reduce build errors
- rebuild only what changed
Useful concepts include:
- target
- prerequisites
- recipe
$@,$^,$<- pattern rules such as
%.o: %.c =vs:=
A minimal Makefile is often enough to compile sources, link the final executable, and generate a map file.
9. Useful GNU tools
Beyond gcc and make, embedded developers often use:
size— inspect section sizesnm— inspect symbolsobjdump— disassemble and inspect binariesobjcopy— convert formats, for example ELF to HEXreadelf— inspect ELF headers and sectionsar— create static librariesgdb— symbolic debugging
10. Git workflow in embedded projects
The repository should contain everything needed to rebuild the project:
- source files
- headers
- Makefiles
- linker scripts
- README and reproducibility notes
Generated artifacts such as .o, .elf, .hex, and build folders
are usually ignored unless they are part of a formal release.
A simple workflow usually includes:
- initial project structure commit
- stubs/prototypes commit
- incremental implementation commits
- release or tag when a stable firmware version is ready
11. Embedded memory and C segments
Embedded systems often use multiple memory regions with different properties.
- Flash / ROM — non-volatile, used for code and constants
- SRAM — volatile, used for runtime data
- Registers — CPU and peripheral control
- External memory — optional, board-dependent
Common C/ELF sections:
.text— instructions.rodata— constants.data— initialized writable globals/statics.bss— zero-initialized or uninitialized globals/staticsstack— automatic variables and call framesheap— dynamic allocation
12. Storage classes, allocation, and pointers
The memory location of an object depends not only on its type, but also on:
- scope
- lifetime
- linkage
- storage duration
Typical cases:
- global uninitialized variables often go to
.bss - initialized static/global variables often go to
.data - functions live in
.text - string literals usually live in
.rodata - local automatic variables live on the stack
malloc()allocates on the heap
Good practice: initialize pointers, check allocation results, and set pointers to
NULL after free().
13. Key takeaway
Embedded software development is not only about writing C code.
You need to understand the complete pipeline:
- development platform
- toolchain
- cross-compilation
- build automation
- linking and memory placement
- debugging tools
- version control discipline
That full picture is what turns source code into reliable firmware.
Map
Interactive map
Embedded Build Process and Development Environment
Click a node to inspect its meaning.