U-Boot 빌드 시스템 분석 — Kconfig·Makefile·defconfig 동작 추적
#한 줄 요약
“U-Boot은 Linux Kbuild를 그대로 가져다 씁니다.” —
make <board>_defconfig로 옵션 한 묶음을 한 번에 적용하고,menuconfig로 세부 조정하고,make로 빌드합니다. 흐름은 커널과 똑같습니다.
U-Boot 소스 트리를 처음 열면 수만 개의 파일에 압도됩니다. arch/ 아래만 해도 수십 개의 아키텍처, drivers/ 아래는 수백 개의 driver, configs/ 아래는 천 개가 넘는 defconfig. 다행히 빌드 시스템은 단순합니다. Linux 커널과 같은 Kbuild입니다. 한 번 익히면 어떤 보드든 같은 흐름으로 빌드합니다.
#빌드의 세 단계
U-Boot 빌드는 세 단계입니다.
가장 자주 쓰는 흐름은 다음과 같습니다.
# 1. U-Boot 소스 받기git clone https://source.denx.de/u-boot/u-boot.gitcd u-boot
# 2. cross-compiler 환경 변수export ARCH=armexport CROSS_COMPILE=aarch64-linux-gnu-
# 3. defconfig 적용make qemu_arm64_defconfig
# 4. 빌드make -j$(nproc)빌드 결과는 소스 트리 루트에 생깁니다.
u-boot.bin ← raw binary (TF-A의 BL33으로 사용)u-boot ← ELF binary (디버깅용)u-boot.dtb ← control DTB (CONFIG_OF_SEPARATE인 경우)u-boot-dtb.bin ← u-boot.bin + u-boot.dtb (concat)u-boot-spl.bin ← SPL (CONFIG_SPL=y인 경우)u-boot-spl-dtb.bin ← SPL + SPL용 DTBspl/u-boot-spl ← SPL ELFu-boot.itb ← FIT image (CONFIG_FIT=y인 경우)#디렉터리 구조
U-Boot 소스 트리는 영역별로 디렉터리가 나뉩니다.
u-boot/├── arch/ ← 아키텍처별 코드│ ├── arm/│ │ ├── cpu/ ← cortex-a53, armv8 등│ │ ├── mach-imx/ ← SoC 패밀리 코드│ │ ├── mach-rockchip/│ │ ├── dts/ ← Device Tree source│ │ └── lib/ ← 아키텍처 헬퍼│ ├── riscv/│ └── x86/│├── board/ ← 보드별 코드│ ├── freescale/imx8mp_evk/│ ├── ti/am335x/│ ├── rockchip/evb_rk3399/│ └── beagle/beagleboneblack/│├── configs/ ← defconfig 모음│ ├── qemu_arm64_defconfig│ ├── am335x_evm_defconfig│ ├── imx8mp_evk_defconfig│ └── ...│├── include/ ← 헤더│ ├── configs/ ← 보드별 헤더 (CONFIG_*)│ ├── dt-bindings/│ └── ...│├── common/ ← 공통 코드│ ├── board_f.c ← board_init_f│ ├── board_r.c ← board_init_r│ ├── main.c ← 명령 인터프리터│ ├── cli.c│ └── spl/│ └── spl.c ← SPL 메인│├── drivers/ ← 드라이버 (DM 기반)│ ├── mmc/│ ├── net/│ ├── serial/│ ├── usb/│ └── ...│├── fs/ ← 파일 시스템│ ├── ext4/│ ├── fat/│ └── ubifs/│├── lib/ ← 라이브러리│ ├── libfdt/ ← DT 처리│ ├── crypto/│ └── ...│├── cmd/ ← 명령 구현│ ├── mmc.c│ ├── tftp.c│ ├── boot.c│ └── ...│├── tools/ ← 호스트 도구│ ├── mkimage.c│ └── ...│├── Kconfig ← 최상위 Kconfig├── Makefile├── scripts/│ ├── Kbuild.include│ └── ...└── doc/이 중 보드 포팅에 가장 자주 손대는 곳은 board/, configs/, arch/<arch>/dts/, include/configs/입니다. BSP Ch 6에서 다룬 적이 있습니다.
#Kconfig — 옵션 정의
Kconfig 파일이 모든 옵션을 정의합니다. 옵션은 bool, tristate, string, int, hex가 가능하고, 의존 관계를 표현할 수 있습니다.
# drivers/mmc/Kconfig (발췌)
menu "MMC Host controller Support"
config MMC bool "MMC/SD/SDIO card support" default ARM || PPC || SANDBOX help MMC/SD card support.
config DM_MMC bool "Enable MMC controllers using Driver Model" depends on MMC && DM help Use Driver Model for MMC.
config FSL_USDHC bool "Freescale/NXP i.MX uSDHC controller" depends on DM_MMC && BLK select MMC_SDHCI help Driver for i.MX MMC controller.
endmenudepends on은 활성화 조건, select는 자동 활성화. CONFIG_FSL_USDHC=y를 켜면 MMC_SDHCI가 자동으로 켜집니다.
#menuconfig
make menuconfig는 대화식 TUI로 .config를 편집합니다.
U-Boot Configuration 최상위 메뉴:
- General setup --->
- ARM architecture --->
- Boot media --->
- Boot images --->
- Boot timing --->
- Boot count support --->
- Console --->
- Device Tree Control --->
- Environment --->
- Network --->
- Library routines --->
옵션을 검색하려면 /를 누르고 검색어를 입력합니다.
Search Results──────────────Symbol: SPL [=y]Type : boolPrompt: Enable SPL Location: -> Boot images Defined at common/spl/Kconfig:33 Selected by: - ARCH_OMAP2PLUS && ... Selects: SUPPORT_OF_CONTROL && ...옵션 하나를 찾는 가장 빠른 방법입니다.
#defconfig — 옵션 묶음
configs/<board>_defconfig는 해당 보드에서 활성화된 옵션만 모은 압축 .config입니다.
# configs/qemu_arm64_defconfig (발췌)CONFIG_ARM=yCONFIG_ARCH_QEMU=yCONFIG_SYS_TEXT_BASE=0x00000000CONFIG_NR_DRAM_BANKS=1CONFIG_DEFAULT_DEVICE_TREE="qemu-arm64"CONFIG_OF_BOARD=yCONFIG_DISTRO_DEFAULTS=yCONFIG_FIT=yCONFIG_FIT_SIGNATURE=yCONFIG_BOOTDELAY=0CONFIG_SYS_PROMPT="=> "CONFIG_CMD_BOOTEFI_HELLO=yCONFIG_CMD_BOOTEFI_SELFTEST=yCONFIG_CMD_PCI=yCONFIG_CMD_DHCP=yCONFIG_CMD_TFTPBOOT=yCONFIG_CMD_EXT2=yCONFIG_CMD_EXT4=yCONFIG_CMD_FAT=yCONFIG_OF_CONTROL=yCONFIG_NET=yCONFIG_DM=yCONFIG_DM_USB=yCONFIG_USB=yCONFIG_USB_XHCI_HCD=yCONFIG_USB_XHCI_PCI=yCONFIG_VIRTIO_PCI=yCONFIG_VIRTIO_NET=yCONFIG_VIRTIO_BLK=yCONFIG_EFI_LOADER=ymake <board>_defconfig는 이 파일을 .config로 풀어, Kconfig의 default 값을 채워, 전체 .config를 만듭니다.
#새 defconfig 만들기
menuconfig로 옵션을 수정한 뒤 defconfig를 저장하려면 make savedefconfig를 씁니다.
# 1. 적용make qemu_arm64_defconfig
# 2. 수정make menuconfig# (옵션 추가/삭제)
# 3. 새 defconfig 저장make savedefconfig
# 4. configs/ 안으로cp defconfig configs/my_board_defconfigsavedefconfig는 기본값과 다른 옵션만 저장합니다. 그래서 defconfig 파일이 짧고 가독성 있게 유지됩니다.
#Makefile — Kbuild
U-Boot의 Makefile은 Linux Kbuild를 거의 그대로 씁니다. 모듈 단위 Makefile은 obj-y로 해당 디렉터리에서 빌드할 파일을 명시합니다.
# drivers/mmc/Makefile (발췌)obj-$(CONFIG_$(SPL_TPL_)MMC) += mmc.o mmc-uclass.oobj-$(CONFIG_DM_MMC) += mmc-uclass.oobj-$(CONFIG_FSL_USDHC) += fsl_esdhc_imx.oobj-$(CONFIG_MMC_SDHCI) += sdhci.oobj-$(CONFIG_MMC_BCM2835) += bcm2835_sdhost.oobj-$(CONFIG_FSL_USDHC) += fsl_esdhc_imx.o는 CONFIG_FSL_USDHC=y일 때만 fsl_esdhc_imx.o를 빌드 대상에 추가합니다.
#SPL vs U-Boot Proper 빌드
$(SPL_TPL_) 접두사가 어느 단계에서 활성화할지를 분리합니다.
obj-$(CONFIG_$(SPL_TPL_)MMC) += mmc.o빌드되는 경우:
- U-Boot Proper 빌드 시:
CONFIG_MMC=y이면 컴파일 - SPL 빌드 시:
CONFIG_SPL_MMC=y이면 컴파일 - TPL 빌드 시:
CONFIG_TPL_MMC=y이면 컴파일
같은 소스를 다른 .config로 세 번 컴파일하는 구조입니다. SPL은 공간이 작으므로 CONFIG_SPL_MMC=y지만 CONFIG_SPL_MMC_WRITE는 꺼서 코드를 줄이는 식입니다.
#out-of-tree 빌드
소스 트리를 건드리지 않고 빌드 산출물을 별도 디렉터리에 두려면 O=를 씁니다.
# 소스는 ~/u-boot, 빌드는 ~/build/u-bootmkdir -p ~/build/u-bootmake -C ~/u-boot O=~/build/u-boot qemu_arm64_defconfigmake -C ~/u-boot O=~/build/u-boot -j$(nproc)여러 보드를 동시에 개발할 때 유용합니다. 같은 소스로 여러 빌드 디렉터리를 만들 수 있습니다.
~/build/qemu_arm64/~/build/beagle/~/build/imx8mp_evk/#cross-compile 환경
ARM 보드를 빌드하려면 aarch64 cross-compiler가 필요합니다. Ubuntu/Debian:
sudo apt install gcc-aarch64-linux-gnu환경 변수:
export ARCH=armexport CROSS_COMPILE=aarch64-linux-gnu-ARCH=arm은 ARMv7과 ARMv8 공통으로 씁니다. CROSS_COMPILE이 aarch64- 또는 *arm-*인지로 32/64 비트를 구분합니다.
| 타깃 | ARCH | CROSS_COMPILE |
|---|---|---|
| ARMv7-A 32bit | arm | arm-linux-gnueabihf- |
| ARMv8-A 64bit | arm | aarch64-linux-gnu- |
| RISC-V 64bit | riscv | riscv64-linux-gnu- |
| MIPS | mips | mips-linux-gnu- |
CROSS_COMPILE의 끝 dash가 중요합니다. aarch64-linux-gnu-gcc가 컴파일러 이름인데, Makefile은 $(CROSS_COMPILE)gcc로 조립합니다.
#빌드 출력 읽기
make의 출력을 읽을 줄 알아야 어디서 실패했는지 압니다.
$ make -j8 HOSTCC scripts/basic/fixdep HOSTCC scripts/kconfig/conf.o ... GEN include/autoconf.mk CHK include/config/uboot.release CHK include/generated/version_autogenerated.h ... CC arch/arm/cpu/armv8/cache.o CC arch/arm/cpu/armv8/cpu.o ... CC common/board_f.o CC common/board_r.o ... LD u-boot OBJCOPY u-boot-nodtb.bin CAT u-boot-dtb.bin COPY u-boot.bin SYM u-boot.symHOSTCC는 host 컴파일러(scripts/, tools/), CC는 cross 컴파일러. LD는 링크, OBJCOPY는 binary 변환.
빌드 실패 시 줄의 맨 앞 단어가 어느 도구가 실패했는지 알려 줍니다.
arch/arm/mach-imx/spl.c:42:5: error: 'CONFIG_FOO' undeclared이런 메시지는 Kconfig에서 옵션 누락입니다. defconfig를 점검합니다.
#binary 크기 확인
산출물 크기를 보려면:
$ ls -l u-boot*.bin-rw-r--r-- 1 user user 812032 May 19 09:00 u-boot.bin-rw-r--r-- 1 user user 84368 May 19 09:00 u-boot-spl.binSPL은 수십 KB 안에 들어가야 합니다. SoC 내부 SRAM 크기를 넘으면 빌드는 통과해도 부팅이 안 됩니다.
make u-boot.map을 열면 심볼별 크기를 봅니다.
.text 0x40200000 0x6a2c8 ... mmc.o .text 0x402030c0 0x2c84 net.o .text 0x40205d44 0x4a18 ...크기를 줄이려면 불필요한 driver를 끄거나 CONFIG_LTO=y(link-time optimization)를 활성화합니다.
#자주 하는 실수
#make clean 안 하고 defconfig 변경
.config가 덮어쓰기는 되지만 .o 파일이 stale 상태가 됩니다. Kconfig 옵션이 바뀐 파일은 재컴파일되는데, 간접 의존이 빠질 수 있습니다. 큰 변경 후에는:
make distcleanmake <board>_defconfigmake#ARCH 환경 변수 누락
make qemu_arm64_defconfig 시 ARCH가 호스트로 잡혀 빌드 산출물이 x86이 되는 경우. 항상 환경 변수를 셸 프로필이나 export로 고정합니다.
#CROSS_COMPILE을 상대 경로로
# Badexport CROSS_COMPILE=./toolchain/bin/aarch64-linux-gnu-
# Good (절대 경로 또는 PATH)export PATH=$HOME/toolchain/bin:$PATHexport CROSS_COMPILE=aarch64-linux-gnu-빌드 중 sub-make에서 cwd가 바뀌면 상대 경로가 깨집니다.
#savedefconfig로 만든 파일 그대로 커밋
savedefconfig가 정확히 정렬해 주지만, 알파벳 순이 아닌 Kconfig 트리 순입니다. 가독성을 위해 그룹별 빈 줄을 수동으로 넣지 마세요. 다음 savedefconfig에서 사라집니다.
#obj-y 대신 obj-$(CONFIG_X)를 빼먹음
새 driver 파일을 추가할 때 Makefile에 obj-y += my_driver.o라고 쓰면 모든 빌드에 무조건 포함됩니다. 보통은 obj-$(CONFIG_MY_DRIVER) += my_driver.o로 Kconfig와 연결합니다.
#menuconfig가 ncurses-dev 없이 안 됨
*** Unable to find the ncurses libraries.*** make[1]: *** [scripts/kconfig/Makefile:14: menuconfig] Error 1Ubuntu/Debian: sudo apt install libncurses-dev. macOS: brew install ncurses.
#정리
- U-Boot은 Linux Kbuild를 그대로 사용합니다.
make <board>_defconfig→make menuconfig→make. - 디렉터리는 영역별입니다. arch/, board/, configs/, include/configs/, common/, drivers/, cmd/.
- Kconfig가 옵션을 정의하고, defconfig가 옵션 묶음을 저장합니다.
- 같은 소스를 SPL용 .config, U-Boot Proper용 .config로 두 번 빌드해 두 binary를 만듭니다.
make savedefconfig로 menuconfig 변경을 defconfig 형태로 저장합니다.- out-of-tree 빌드는
O=<dir>로 합니다. 여러 보드 개발에 편리합니다. - cross-compile은
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu-환경 변수로 정합니다. - SPL binary 크기는 SoC 내부 SRAM 크기 안에 들어가야 합니다.
u-boot.map으로 점검합니다.
#다음 편
Ch 4: 부트 단계 — BL1 → SPL → TPL → U-Boot Proper에서는 ARMv8-A의 BL1·BL2·BL31·BL33과 U-Boot의 SPL·TPL·U-Boot Proper의 책임을 정리합니다. 각 단계의 메모리 모델과 권한 수준도 함께 봅니다.
#관련 항목
Bootloader Internals · 3 of 37
- 1ROM부터 init까지 — 임베디드 부팅 단계의 빈자리 분석
- 2Das U-Boot vs TF-A vs EDK II — 임베디드 부트로더 생태계 비교
- 3U-Boot 빌드 시스템 분석 — Kconfig·Makefile·defconfig 동작 추적
- 4ARM 임베디드 부트 4단계 분해 — BL1·SPL·TPL·U-Boot Proper의 역할
- 5U-Boot Falcon Mode — SPL이 U-Boot Proper 없이 커널 직접 부팅
- 6Device Tree DTB 부트로더 처리 — 로딩 시점과 fixup 메커니즘 추적
- 7U-Boot Driver Model 내부 — uclass·driver·device 추상화 구조
- 8U-Boot 보드 초기화 시퀀스 — board_init_f와 board_init_r 분리 이유
- 9DDR Controller 프로그래밍과 PHY Training — SPL의 가장 어려운 작업
- 10임베디드 스토리지 부팅 분석 — MMC·SCSI·NAND·SPI Flash 비교
- 11임베디드 네트워크 부팅 — TFTP·PXE·BOOTP 시퀀스 분석
- 12U-Boot USB 부팅 — fastboot·UMS·USB host 메커니즘
- 13U-Boot 환경 변수와 bootcmd — 부팅 시나리오 정의하기
- 14Modern U-Boot bootflow / bootmeth — 새 추상화 레이어 분석
- 15FIT image 구조 분석 — multi-image·hash·configuration 추적
- 16U-Boot Verified Boot — RSA 서명과 public key 임베딩 흐름
- 17임베디드 A/B 부팅 이중화 — OTA 안전성을 위한 부트 슬롯 설계
- 18U-Boot의 EFI 호환 분석 — bootefi 명령과 EFI loader 동작 원리
- 19Linux Boot ABI — ARM/ARM64 커널 진입 규약 추적
- 20임베디드 펌웨어 업데이트 — RAUC vs SWUpdate 비교
- 21새 보드 U-Boot 포팅 실전 — defconfig 작성부터 첫 부팅까지
- 22부트로더 디버깅 기법 — DEBUG·JTAG·serial·post-mortem 분석
- 23SoC BootROM·eFuse·OTP — 부팅의 0단계 분석
- 24SPL·TPL 내부 해부 — 가장 작은 부트 단계의 동작 추적
- 25ARM Trusted Firmware-A 통합 — BL1·BL2·BL31·BL32·BL33 흐름
- 26DDR Training과 PHY Calibration — 보드별 파라미터 튜닝
- 27임베디드 Chain of Trust — 다단계 서명 검증의 전체 흐름
- 28임베디드 Flash Layout 설계 — partition·NAND·eMMC·UBI 비교
- 29U-Boot Distro Boot — extlinux·boot.scr 표준화 분석
- 30부트로더 CI 구축 — build matrix와 자동 부팅 테스트
- 31TF-A BL31 EL3 Runtime 분석 — PSCI·SDEI·RAS dispatcher 추적
- 32PSCI와 SMCCC ABI — ARM 표준 SMC 호출 규약 분석
- 33ARM64 Secondary Core Bring-up — PSCI CPU_ON 호출부터 EL1 진입까지
- 34U-Boot PCIe Enumeration — 부트로더가 디바이스를 찾는 흐름 분석
- 35EFI·UEFI에서 CXL 초기화 — CEDT 생성과 HDM Decoder 사전 설정
- 36부트 시 메모리 토폴로지 결정 — DDR + CXL.mem 통합 인식
- 37UEFI Secure Boot 인증서 만료 — 2011→2023 CA 롤오버와 PQC 대비
관련 글
U-Boot PCIe Enumeration — 부트로더가 디바이스를 찾는 흐름 분석
U-Boot PCIe 열거 과정 — Root Complex 초기화·Config Space scan·BAR sizing·resource 할당, CXL DVSEC 인식까지.
U-Boot Distro Boot — extlinux·boot.scr 표준화 분석
보드별 다른 부트 스크립트를 표준화 — U-Boot Distro Boot, extlinux.conf, boot.scr의 차이와 선택.
SPL·TPL 내부 해부 — 가장 작은 부트 단계의 동작 추적
SPL과 TPL의 정확한 역할, SRAM 안에 들어가는 코드 구조, DDR이 없는 환경에서 어떻게 동작하는가.