← Back to Learning

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:

  1. Preprocessing — expands includes, macros, and conditional directives
  2. Compilation — translates C into target-specific assembly
  3. Assembling — converts assembly into relocatable object files
  4. Linking — resolves symbols and combines objects and libraries
  5. 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 host
  • arm-none-eabi-gcc — ARM bare-metal toolchain
  • arm-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 sizes
  • nm — inspect symbols
  • objdump — disassemble and inspect binaries
  • objcopy — convert formats, for example ELF to HEX
  • readelf — inspect ELF headers and sections
  • ar — create static libraries
  • gdb — 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/statics
  • stack — automatic variables and call frames
  • heap — 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.