This lecture is the first hands-on build in the bootloader chapter of this free embedded linux course. Instead of requiring a specific development board, we’ll build U-Boot for QEMU’s ARM “virt” machine, so every reader can follow along on a laptop and get a real U-Boot prompt in under ten minutes.
defconfig
cross-compilation
QEMU
free embedded linux course
What You Will Learn
- How U-Boot’s source tree and defconfig system are organized
- How to cross-compile U-Boot for an ARM target from an x86_64 host
- What each build output file (u-boot.bin, u-boot.map, u-boot.elf) is actually for
- How to boot your freshly built U-Boot under QEMU and reach a live prompt
Prerequisites
- A Linux host with git, make, and a C toolchain installed
- An ARM cross-compiler (gcc-arm-linux-gnueabihf on Debian/Ubuntu)
- QEMU’s ARM system emulator (qemu-system-arm)
- Read the previous lecture on choosing a bootloader — this one assumes U-Boot is the target
Step 1: Install the Toolchain and QEMU
On a Debian- or Ubuntu-based host, the cross-compiler and emulator come straight from the package manager, no manual toolchain building required for this course:
$ sudo apt update
$ sudo apt install -y gcc-arm-linux-gnueabihf build-essential \
bison flex libssl-dev bc python3 qemu-system-arm
$ arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (Ubuntu 13.2.0-4ubuntu3) 13.2.0
Step 2: Fetch the U-Boot Source
Always build from a tagged release rather than a random commit on the development branch — it keeps your build reproducible and matches what’s documented upstream:
$ git clone https://source.denx.de/u-boot/u-boot.git
$ cd u-boot
$ git checkout v2024.10
$ git log -1 --oneline
a1b2c3d Tag v2024.10 release
Step 3: Understand the defconfig System
U-Boot ships hundreds of board-specific configuration files under configs/. Each one is a starting point generated with make savedefconfig on a real board, and building for a target is a two-command process: select the defconfig, then build.
|
v
make qemu_arm_defconfig (generates .config)
|
v
make CROSS_COMPILE=… (compiles source tree)
|
v
u-boot.bin + u-boot.elf + u-boot.map
|
v
qemu-system-arm -bios u-boot.bin
Step 4: Configure and Build
$ export CROSS_COMPILE=arm-linux-gnueabihf-
$ export ARCH=arm
$ make qemu_arm_defconfig
$ make -j$(nproc)
...
LD u-boot
OBJCOPY u-boot.bin
$ ls -l u-boot.bin u-boot.elf u-boot.map
-rw-r--r-- 1 ravi ravi 612348 u-boot.bin
-rwxr-xr-x 1 ravi ravi 1893552 u-boot.elf
-rw-r--r-- 1 ravi ravi 498221 u-boot.map
What you just produced:
| File | Format | Purpose |
|---|---|---|
| u-boot.elf | ELF object | Debugging with gdb; not directly bootable on most SoCs |
| u-boot.bin | Raw binary | What the boot ROM or QEMU’s -bios flag actually loads and runs |
| u-boot.map | Text | Symbol table — invaluable when a crash dump gives you only an address |
Step 5: Boot It
$ qemu-system-arm -M virt -nographic -bios u-boot.bin
U-Boot 2024.10 (Nov 01 2024 - 09:14:02 +0000)
DRAM: 128 MiB
Core: 56 devices, 13 uclasses, devicetree: board
Flash: 64 MiB
In: pl011@9000000
Out: pl011@9000000
Err: pl011@9000000
Net: eth0: virtio-net#32
Hit any key to stop autoboot: 0
=>
That => prompt is a real, working U-Boot shell — the same interface you’ll use on physical hardware later in this course. Press Ctrl+A then X to exit QEMU.
Real-World Use Case: Reproducible CI Builds
Teams shipping products on top of U-Boot typically pin an exact git tag (like we did here) and build inside a container image with a fixed toolchain version. That combination — tagged source plus pinned toolchain — is what makes a bootloader build reproducible across machines and across years, which matters enormously when you need to rebuild a five-year-old product for a security patch.
Common Mistakes
- Forgetting to export ARCH and CROSS_COMPILE before running
make— the build will silently target your host architecture instead of ARM. - Building on the development branch instead of a tagged release, which makes bugs harder to reproduce and report.
- Confusing u-boot.elf with u-boot.bin when flashing — most boot ROMs expect the raw binary, not the ELF.
Best Practices
- Always build from a tagged release for anything beyond experimentation.
- Keep the toolchain version pinned in your build scripts, not just “whatever apt installs today.”
- Save your
.configchanges back into a custom defconfig withmake savedefconfigso board customizations survive a clean checkout.
Performance Considerations
A full U-Boot build with a wide feature set can take a couple of minutes even on modern hardware, mostly due to the number of enabled drivers. Trimming unused drivers from your defconfig (network protocols, filesystem support you don’t need) both shrinks SPL size and cuts build time meaningfully on repeated CI builds.
Summary
Building U-Boot comes down to three things: pick the right defconfig for your target, cross-compile with the correct toolchain prefix, and know what each output file is for. Doing this on QEMU first, before touching real hardware, removes an entire category of “is it my board or my build” debugging headaches — a genuinely useful habit for anyone working through a free embedded linux course.
Frequently Asked Questions
Why build for QEMU instead of a real board first?
It isolates build problems from hardware problems. If QEMU boots cleanly, you know your toolchain and source tree are correct before you ever touch flash memory on real silicon.
What does CROSS_COMPILE actually do?
It’s a prefix U-Boot’s Makefiles prepend to every toolchain binary name (gcc, ld, objcopy), letting the same Makefile target any architecture by swapping the prefix.
Can I use clang instead of gcc?
Yes, recent U-Boot releases support building with clang/LLVM, though gcc remains the more commonly tested path.
Why does the build produce both u-boot and u-boot.bin?
u-boot (or u-boot.elf) retains debug symbols and is used with a debugger; u-boot.bin is the stripped raw binary that actually gets loaded onto hardware or into QEMU.
How long should a clean U-Boot build take?
On a modern multi-core machine, a few minutes for a typical defconfig; trimming unused drivers can bring that down noticeably.
Is v2024.10 the version I should always use?
Use whatever the current stable tag is when you read this — always check the U-Boot release page rather than hardcoding a version from a tutorial.
Got U-Boot booting on QEMU?
Next, we’ll walk through what “installing” a bootloader actually means on real hardware.
