크로스 컴파일 환경
x86_64 호스트에서 ARM, ARM64, RISC-V 등 다른 아키텍처용 리눅스 커널을 빌드하는 크로스 컴파일 환경의 원리, 구축, 검증, 배포까지 다루는 종합 가이드입니다.
핵심 요약
- 크로스 컴파일(Cross Compilation) — 호스트(Host) 아키텍처와 다른 타겟(Target) 아키텍처용 바이너리를 생성하는 빌드 방식입니다
- 크로스 툴체인(Cross Toolchain) — 컴파일러(GCC/Clang), 어셈블러(as), 링커(Linker)인
ld, binutils를 포함하는 도구 모음입니다 - ARCH / CROSS_COMPILE — 커널 빌드 시스템(Kbuild)에 타겟 아키텍처와 툴체인 접두사를 지정하는 두 핵심 변수입니다
- 타겟 트리플렛(Target Triplet) —
aarch64-linux-gnu처럼 아키텍처-OS-ABI를 명시하는 명명 규칙입니다 - QEMU 검증 — 실 하드웨어 없이 크로스 빌드된 커널을 에뮬레이터에서 부팅·테스트할 수 있습니다
단계별 이해
- 툴체인 설치 — 배포판 패키지 매니저로 크로스 컴파일러를 설치합니다 (
apt install gcc-aarch64-linux-gnu) - 환경 변수 설정 —
ARCH=arm64와CROSS_COMPILE=aarch64-linux-gnu-를 지정합니다 - defconfig 생성 —
make defconfig로 타겟 아키텍처의 기본 설정을 생성합니다 - 커널 빌드 —
make -j$(nproc)으로 크로스 컴파일을 실행합니다 - 바이너리 검증 —
file,readelf로 생성된 이미지의 아키텍처를 확인합니다 - QEMU 부팅 —
qemu-system-aarch64로 커널을 부팅하여 동작을 검증합니다
개요
크로스 컴파일이란
크로스 컴파일은 프로그램을 실행할 시스템(타겟)과 빌드하는 시스템(호스트)의 CPU 아키텍처가 다를 때 사용하는 빌드 방식입니다. 리눅스 커널 개발에서는 x86_64 워크스테이션에서 ARM, ARM64, RISC-V 등의 임베디드 보드용 커널을 빌드할 때 필수적입니다.
네이티브 빌드 vs 크로스 빌드
| 구분 | 네이티브 빌드 | 크로스 빌드 |
|---|---|---|
| 호스트-타겟 관계 | 동일 아키텍처 | 다른 아키텍처 |
| 컴파일러 | gcc (시스템 기본) | aarch64-linux-gnu-gcc 등 |
| 환경 변수 | 불필요 | ARCH, CROSS_COMPILE 필수 |
| 빌드 산출물 실행 | 호스트에서 직접 실행 | 타겟 보드 또는 QEMU 필요 |
| 빌드 속도 | 타겟 성능에 의존 | 고성능 호스트 활용 가능 |
| 대표 사용처 | 서버·데스크톱 커널 | 임베디드·IoT·모바일 커널 |
크로스 컴파일이 필요한 경우
- 타겟 장치의 컴퓨팅 자원이 부족한 경우 — Raspberry Pi, BeagleBone 등 임베디드 보드에서 직접 커널을 빌드하면 수 시간이 걸릴 수 있습니다
- 타겟 장치에 OS가 없는 경우 — 새 보드에 처음으로 리눅스를 포팅할 때는 호스트에서 빌드할 수밖에 없습니다
- 다중 아키텍처 동시 지원 — 하나의 워크스테이션에서 ARM, ARM64, RISC-V 커널을 모두 빌드할 수 있습니다
- CI/CD 파이프라인(Pipeline) — 서버 팜에서 자동화된 멀티 아키텍처 빌드·테스트를 수행합니다
- 재현 가능한 빌드 환경 — Docker 컨테이너(Container)에서 특정 버전의 크로스 툴체인으로 일관된 빌드를 보장합니다
크로스 컴파일 내부 동작 원리
컴파일 4단계
C 소스가 타겟 아키텍처용 바이너리로 변환되는 과정은 네이티브 빌드와 동일한 4단계를 거칩니다. 차이점은 각 단계에서 사용하는 도구가 크로스 툴체인이라는 점입니다.
| 단계 | 도구 | 입력 | 출력 | 설명 |
|---|---|---|---|---|
| 1. 전처리(Preprocessing) | cpp | .c | .i | 매크로(Macro) 확장, #include 삽입, 조건부 컴파일 처리 |
| 2. 컴파일(Compilation) | cc1 | .i | .s | C 코드를 타겟 아키텍처의 어셈블리(Assembly)로 변환 |
| 3. 어셈블(Assembly) | as | .s | .o | 어셈블리를 타겟 아키텍처의 기계어(Machine Code)(오브젝트 파일)로 변환 |
| 4. 링크(Linking) | ld | .o | ELF | 오브젝트 파일들을 연결하여 최종 실행 파일(vmlinux) 생성 |
# 각 단계를 수동으로 실행하는 예 (ARM64)
# 1) 전처리
aarch64-linux-gnu-gcc -E kernel/fork.c -o fork.i
# 2) 컴파일 (어셈블리 생성)
aarch64-linux-gnu-gcc -S fork.i -o fork.s
# 3) 어셈블 (오브젝트 파일 생성)
aarch64-linux-gnu-as fork.s -o fork.o
# 4) 링크 (실행 파일 생성)
aarch64-linux-gnu-ld fork.o -o fork
코드 설명
ARM64용 크로스 컴파일의 4단계를 개별 명령으로 분리한 예입니다. 실제 커널 빌드에서는 make가 이 과정을 자동으로 수행합니다.
- -E전처리만 수행합니다.
#include,#define매크로가 전개된.i파일을 생성합니다. - -S컴파일만 수행하여 AArch64 어셈블리(
.s)를 출력합니다.adrp,ldr,str등 ARM64 명령어가 포함됩니다. - as크로스 어셈블러가 ARM64 기계어를 담은 ELF 오브젝트 파일(
.o)을 생성합니다. - ld크로스 링커가 오브젝트 파일들을 연결합니다. 커널에서는
vmlinux.lds링커 스크립트를 사용합니다.
Sysroot 개념과 경로 탐색
Sysroot는 크로스 컴파일러가 타겟 아키텍처용 헤더 파일과 라이브러리를 찾는 루트 디렉토리입니다. 커널 빌드에서는 자체 헤더를 사용하므로 sysroot가 필수는 아니지만, 유저스페이스 도구를 크로스 빌드할 때는 중요합니다.
# 크로스 컴파일러의 sysroot 위치 확인
aarch64-linux-gnu-gcc -print-sysroot
# 출력 예: /usr/aarch64-linux-gnu
# sysroot 내부 구조
ls /usr/aarch64-linux-gnu/
# include/ lib/ — 타겟 아키텍처용 헤더와 라이브러리
# 명시적 sysroot 지정 (유저스페이스 빌드 시)
aarch64-linux-gnu-gcc --sysroot=/path/to/sysroot -o hello hello.c
ABI와 호출 규약(Calling Convention)
크로스 컴파일에서 ABI(Application Binary Interface) 일치는 핵심 요소입니다. 특히 ARM 32비트는 여러 ABI 변종이 있어 주의가 필요합니다.
| ABI | 툴체인 접미사 | 부동소수점 처리 | 대표 사용처 |
|---|---|---|---|
| EABI soft-float | arm-linux-gnueabi- | 소프트웨어 에뮬레이션 | FPU 없는 임베디드 장치 |
| EABI hard-float | arm-linux-gnueabihf- | 하드웨어 FPU 사용 | Raspberry Pi, BeagleBone |
| AArch64 | aarch64-linux-gnu- | 하드웨어 FPU (항상) | ARM64 서버, 모바일 SoC |
| RISC-V LP64D | riscv64-linux-gnu- | Double-precision FPU | RISC-V 64비트 보드 |
gnueabi(soft-float)와 gnueabihf(hard-float) 툴체인을 혼용하면 링크 오류 또는 런타임 크래시가 발생합니다. 타겟 보드의 FPU 지원 여부를 먼저 확인하세요.
엔디언(Endianness) 고려사항
대부분의 ARM64와 RISC-V 시스템은 리틀 엔디언(Little-Endian)을 사용하지만, 일부 네트워크 장비나 레거시 시스템에서는 빅 엔디언(Big-Endian)이 필요합니다.
| 아키텍처 | 기본 엔디언 | 빅 엔디언 지원 | 설정 방법 |
|---|---|---|---|
| ARM | 리틀 엔디언 | 예 (armeb) | CONFIG_CPU_BIG_ENDIAN=y |
| ARM64 | 리틀 엔디언 | 예 (aarch64_be) | CONFIG_CPU_BIG_ENDIAN=y |
| RISC-V | 리틀 엔디언 | 아니요 | 리틀 엔디언만 지원 |
| MIPS | 빅 엔디언 | 기본값 | mipsel- 접두사로 리틀 엔디언 선택 |
| PowerPC | 빅 엔디언 | 기본값 | powerpc64le- 접두사로 리틀 엔디언 선택 |
툴체인 해부
툴체인 구성 요소
| 도구 | GNU 이름 | LLVM 이름 | 역할 |
|---|---|---|---|
| C 컴파일러 | aarch64-linux-gnu-gcc | clang --target=aarch64-linux-gnu | C 소스를 어셈블리로 변환 |
| 어셈블러 | aarch64-linux-gnu-as | clang (통합) / llvm-as | 어셈블리를 오브젝트 코드로 변환 |
| 링커 | aarch64-linux-gnu-ld | ld.lld | 오브젝트 파일을 연결하여 ELF 생성 |
| 아카이버 | aarch64-linux-gnu-ar | llvm-ar | 정적 라이브러리(.a) 생성 |
| 오브젝트 복사 | aarch64-linux-gnu-objcopy | llvm-objcopy | ELF → 바이너리 이미지 변환 (vmlinux → Image) |
| 오브젝트 덤프(Dump) | aarch64-linux-gnu-objdump | llvm-objdump | 디스어셈블, 섹션 분석 |
| 심볼 목록 | aarch64-linux-gnu-nm | llvm-nm | 오브젝트 파일의 심볼 테이블(Symbol Table) 출력 |
| ELF 분석 | aarch64-linux-gnu-readelf | llvm-readelf | ELF 헤더, 섹션, 세그먼트 분석 |
| 스트립 | aarch64-linux-gnu-strip | llvm-strip | 디버그 심볼 제거 (바이너리 크기 축소) |
타겟 트리플렛 규칙
-dumpmachine 옵션으로 실제 타겟 트리플렛을 확인할 수 있습니다: aarch64-linux-gnu-gcc -dumpmachine → aarch64-linux-gnu
Multilib와 ABI 변종
Multilib는 하나의 툴체인이 여러 ABI 변종을 지원하는 기능입니다. 예를 들어 ARM 툴체인이 Thumb, ARM, Thumb-2 명령어 세트를 모두 지원할 수 있습니다.
# 툴체인이 지원하는 multilib 구성 확인
aarch64-linux-gnu-gcc -print-multi-lib
# 출력 예: .; (단일 ABI — ARM64는 보통 multilib 불필요)
arm-linux-gnueabihf-gcc -print-multi-lib
# 출력 예:
# .;
# thumb;@mthumb
# 지원되는 CPU 목록 확인
aarch64-linux-gnu-gcc -mcpu=help 2>&1 | head -20
캐나디안 크로스 빌드
캐나디안 크로스(Canadian Cross)는 세 가지 다른 아키텍처가 관여하는 빌드 방식입니다. 빌드 머신(A)에서 호스트 머신(B)용 컴파일러를 만들고, 그 컴파일러가 타겟 머신(C)용 코드를 생성합니다.
| 머신 | 역할 | 예시 |
|---|---|---|
| 빌드(Build) | 컴파일러 자체를 빌드하는 머신 | x86_64 Linux 서버 |
| 호스트(Host) | 컴파일러가 실행되는 머신 | Windows x86_64 PC |
| 타겟(Target) | 컴파일러가 생성하는 코드의 실행 대상 | ARM64 보드 |
일반적인 커널 크로스 컴파일에서는 빌드=호스트(x86_64)이고 타겟만 다르므로, 캐나디안 크로스가 아닌 단순 크로스 빌드입니다.
GNU 크로스 툴체인
배포판별 패키지 설치
# Ubuntu/Debian — 가장 간편한 설치 방법
sudo apt install -y \
gcc-arm-linux-gnueabi \
gcc-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu \
gcc-riscv64-linux-gnu \
gcc-powerpc64le-linux-gnu \
gcc-mips-linux-gnu
# binutils도 함께 설치 (별도 설치가 필요한 경우)
sudo apt install -y \
binutils-aarch64-linux-gnu \
binutils-riscv64-linux-gnu
# Fedora/RHEL
sudo dnf install -y \
gcc-arm-linux-gnu \
gcc-aarch64-linux-gnu \
gcc-riscv64-linux-gnu \
gcc-powerpc64le-linux-gnu
# Arch Linux
sudo pacman -S \
arm-none-eabi-gcc \
aarch64-linux-gnu-gcc \
riscv64-linux-gnu-gcc
Linaro ARM 툴체인
Linaro는 ARM 생태계에 최적화된 크로스 툴체인을 제공합니다. 배포판 패키지보다 최신 버전이 필요하거나 특정 최적화 옵션이 필요할 때 유용합니다.
# Linaro ARM64 툴체인 다운로드 및 설치
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-*-x86_64_aarch64-linux-gnu.tar.xz
tar xf gcc-linaro-*-x86_64_aarch64-linux-gnu.tar.xz -C /opt/
export PATH=/opt/gcc-linaro-*/bin:$PATH
# 설치 확인
aarch64-linux-gnu-gcc --version
Bootlin 사전 빌드 툴체인
Bootlin(구 Free Electrons)은 다양한 아키텍처와 libc 조합의 사전 빌드 툴체인을 제공합니다. musl, uClibc-ng 등 경량 libc 기반 툴체인이 필요할 때 유용합니다.
| 프로젝트 | 지원 아키텍처 | libc 옵션 | 장점 | 단점 |
|---|---|---|---|---|
| 배포판 패키지 | 주요 5~6개 | glibc | 설치 간편, 자동 업데이트 | 버전 선택 제한 |
| Linaro | ARM, ARM64 | glibc | ARM 최적화, 안정성 검증 | ARM 전용 |
| Bootlin | 20+ 아키텍처 | glibc, musl, uClibc-ng | 다양한 조합, 최신 버전 | 수동 설치 필요 |
| crosstool-NG | 모든 아키텍처 | 모든 libc | 완전 커스텀 빌드 | 빌드 시간 소요 (30분~2시간) |
crosstool-NG로 커스텀 툴체인 빌드
crosstool-NG는 GCC 크로스 툴체인을 처음부터 빌드하는 도구입니다. 특정 GCC 버전, libc, binutils 조합이 필요할 때 사용합니다.
# crosstool-NG 설치
git clone https://github.com/crosstool-ng/crosstool-ng.git
cd crosstool-ng
./bootstrap && ./configure --prefix=/opt/crosstool-ng
make && make install
export PATH=/opt/crosstool-ng/bin:$PATH
# 사전 정의된 샘플 목록 확인
ct-ng list-samples | grep aarch64
# 출력 예: aarch64-unknown-linux-gnu
# ARM64 타겟 선택 및 빌드
ct-ng aarch64-unknown-linux-gnu
ct-ng menuconfig # 필요 시 GCC 버전, libc 등 커스텀
ct-ng build # 빌드 시작 (30분~2시간 소요)
툴체인 버전과 커널 버전 호환성
| 커널 버전 | 최소 GCC | 최소 Clang | 최소 binutils | 비고 |
|---|---|---|---|---|
| 5.4 LTS | 4.9 | 10.0 | 2.23 | LLVM 공식 지원 시작 |
| 5.10 LTS | 4.9 | 10.0 | 2.25 | RISC-V Clang 지원 개선 |
| 5.15 LTS | 5.1 | 11.0 | 2.25 | 최소 GCC 버전 상향 |
| 6.1 LTS | 5.1 | 13.0 | 2.25 | Clang CFI 지원 확대 |
| 6.6 LTS | 5.1 | 13.0 | 2.25 | Rust for Linux 초기 지원 |
| 6.12+ | 5.1 | 13.0 | 2.25 | LLVM_IAS=1 기본 활성화 추세 |
Documentation/process/changes.rst 파일에서 정확한 최소 버전 요구사항을 확인할 수 있습니다. scripts/min-tool-version.sh로 프로그래밍적으로 조회할 수도 있습니다.
LLVM/Clang 크로스 컴파일
Clang 크로스 빌드 기본
LLVM/Clang은 단일 바이너리로 여러 타겟 아키텍처를 지원하므로, 아키텍처별로 별도의 컴파일러를 설치할 필요가 없습니다.
# Clang으로 ARM64 커널 크로스 빌드
make ARCH=arm64 LLVM=1 defconfig
make ARCH=arm64 LLVM=1 -j$(nproc)
# RISC-V 커널 크로스 빌드
make ARCH=riscv LLVM=1 defconfig
make ARCH=riscv LLVM=1 -j$(nproc)
# 특정 LLVM 도구 경로 지정 (버전별 설치 시)
make ARCH=arm64 LLVM=-18 defconfig
make ARCH=arm64 LLVM=-18 -j$(nproc)
코드 설명
LLVM 기반 크로스 빌드의 핵심은 LLVM=1 변수 하나입니다.
- LLVM=1CC, LD, AR, NM, OBJCOPY 등 모든 도구를 LLVM 도구체인(clang, ld.lld, llvm-ar 등)으로 전환합니다.
CROSS_COMPILE지정이 불필요합니다. - LLVM=-18버전 접미사를 지정하여
clang-18,ld.lld-18등 특정 버전의 LLVM 도구를 사용합니다. - ARCH=arm64Clang이
--target=aarch64-linux-gnu를 자동으로 추가합니다. GNU 크로스 빌드와 달리CROSS_COMPILE이 불필요합니다.
--target 플래그와 --sysroot
# Clang의 타겟 지정 방식 (수동)
clang --target=aarch64-linux-gnu -c test.c -o test.o
clang --target=riscv64-linux-gnu -c test.c -o test.o
# sysroot와 함께 사용 (유저스페이스 빌드 시)
clang --target=aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu -o hello hello.c
# GNU binutils와 혼합 사용 (LLVM linker 대신 GNU ld 사용)
make ARCH=arm64 CC=clang CROSS_COMPILE=aarch64-linux-gnu- defconfig
통합 어셈블러 (LLVM_IAS=1)
LLVM에는 통합 어셈블러(Integrated Assembler)가 내장되어 있어, GNU as 없이도 어셈블리 파일을 처리할 수 있습니다.
# LLVM 통합 어셈블러 사용 (기본값)
make ARCH=arm64 LLVM=1 LLVM_IAS=1 -j$(nproc)
# GNU 어셈블러 강제 사용 (호환성 문제 시)
make ARCH=arm64 LLVM=1 LLVM_IAS=0 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
GCC vs Clang 크로스 컴파일 비교
| 항목 | GCC 크로스 빌드 | Clang 크로스 빌드 (LLVM=1) |
|---|---|---|
| 설치 | 아키텍처별 별도 패키지 | 단일 clang 패키지 |
| 타겟 지정 | CROSS_COMPILE=aarch64-linux-gnu- | ARCH=arm64만으로 충분 |
| 링커 | GNU ld (bfd/gold) | ld.lld (더 빠른 링크 속도) |
| 어셈블러 | GNU as | 통합 어셈블러 (LLVM_IAS) |
| 정적 분석 | -Wextra 수준 | 더 엄격한 경고, CFI 지원 |
| 빌드 속도 | 보통 | LTO/ThinLTO로 더 빠를 수 있음 |
| 호환성 | 모든 커널 버전 지원 | 5.4+ 공식 지원, 일부 아키텍처 제한 |
| 멀티 아키텍처 | 아키텍처별 별도 설치 | 하나의 바이너리로 모든 타겟 |
툴체인 검증과 진단
사전 점검 루틴
크로스 빌드 실패의 대부분은 소스 문제가 아니라 툴체인 설치 누락이나 환경 변수 불일치에서 발생합니다. 빌드 전 아래 점검을 수행하면 실패 원인을 빠르게 분리할 수 있습니다.
# 1) 툴체인 존재 확인
which aarch64-linux-gnu-gcc || echo "ERROR: 크로스 컴파일러 미설치"
# 2) 타겟 머신 확인
aarch64-linux-gnu-gcc -dumpmachine
# 기대 출력: aarch64-linux-gnu
# 3) 컴파일러 버전 확인
aarch64-linux-gnu-gcc --version | head -1
# 기대 출력: aarch64-linux-gnu-gcc (Ubuntu ...) 12.3.0
# 4) 환경 변수 확인
echo "ARCH=$ARCH CROSS_COMPILE=$CROSS_COMPILE"
# 5) 커널 최소 버전 요구사항 확인
cat scripts/min-tool-version.sh | grep -A2 gcc
테스트 컴파일로 툴체인 검증
# 최소 테스트 프로그램으로 툴체인 동작 검증
echo 'int main() { return 0; }' > /tmp/test_cross.c
aarch64-linux-gnu-gcc -static /tmp/test_cross.c -o /tmp/test_cross
# 생성된 바이너리의 아키텍처 확인
file /tmp/test_cross
# 기대: ELF 64-bit LSB executable, ARM aarch64, ...
# QEMU 유저모드로 실행 검증 (선택)
qemu-aarch64-static /tmp/test_cross; echo "exit code: $?"
# 기대: exit code: 0
# 정리
rm -f /tmp/test_cross /tmp/test_cross.c
Sysroot 디렉토리 탐색
# 크로스 컴파일러의 검색 경로 확인
aarch64-linux-gnu-gcc -print-search-dirs | head -3
# sysroot 위치
aarch64-linux-gnu-gcc -print-sysroot
# 시스템 포함 경로 (include path) 확인
echo | aarch64-linux-gnu-gcc -E -v - 2>&1 | sed -n '/include.*search/,/End/p'
# 라이브러리 경로 확인
aarch64-linux-gnu-gcc -print-file-name=libgcc.a
환경 변수 관리 전략
아키텍처별 환경 스크립트
# env-arm64.sh — ARM64 크로스 빌드 환경 설정
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
export INSTALL_MOD_PATH=$PWD/out/modules
export INSTALL_PATH=$PWD/out/boot
export INSTALL_DTBS_PATH=$PWD/out/dtbs
# 사용 방법
source env-arm64.sh
make defconfig
make -j$(nproc)
# env-riscv.sh — RISC-V 크로스 빌드 환경 설정
export ARCH=riscv
export CROSS_COMPILE=riscv64-linux-gnu-
export INSTALL_MOD_PATH=$PWD/out/modules
export INSTALL_PATH=$PWD/out/boot
ARCH, CROSS_COMPILE 값이 달라지면 재현이 어렵습니다. 프로젝트별 환경 스크립트로 고정하고, source env-arm64.sh로 일관되게 사용하세요.
Makefile 기본값 설정
# 매번 ARCH/CROSS_COMPILE을 입력하는 대신
# 커널 소스 디렉토리에 GNUmakefile을 만들어 기본값 설정
cat > GNUmakefile <<'EOF'
# GNUmakefile — 크로스 빌드 기본값 (Makefile보다 우선 로드)
ARCH ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-
include Makefile
EOF
# 이제 make만 실행하면 ARM64 크로스 빌드 수행
make defconfig
make -j$(nproc)
direnv를 활용한 자동 환경 전환
# direnv 설치
sudo apt install -y direnv
# 커널 소스 디렉토리에 .envrc 생성
cat > .envrc <<'EOF'
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
EOF
# direnv 허용
direnv allow .
# 이후 해당 디렉토리 진입 시 자동으로 환경 변수 설정
cd /path/to/linux
echo $ARCH # → arm64
Kbuild 크로스 컴파일 로직 분석
최상위 Makefile의 ARCH/CROSS_COMPILE 처리
커널 최상위 Makefile에서 크로스 컴파일 관련 변수가 어떻게 처리되는지 핵심 부분을 분석합니다.
# Makefile (top-level) — ARCH/SUBARCH 해석
SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
-e s/sun4u/sparc64/ -e s/aarch64.*/arm64/ \
-e s/riscv.*/riscv/ ...)
# ARCH가 지정되지 않으면 호스트 아키텍처(SUBARCH)를 사용
ARCH ?= $(SUBARCH)
# SRCARCH는 실제 arch/ 디렉토리 이름으로 매핑(Mapping)
SRCARCH := $(ARCH)
# 크로스 컴파일러 도구 변수 조합
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
AR = $(CROSS_COMPILE)ar
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
READELF = $(CROSS_COMPILE)readelf
코드 설명
커널 최상위 Makefile의 크로스 컴파일 변수 해석 로직입니다.
- SUBARCH
uname -m출력을 커널 아키텍처 이름으로 정규화합니다.x86_64→x86,aarch64→arm64등으로 변환합니다. - ARCH ?= $(SUBARCH)
ARCH가 명시적으로 지정되지 않으면 호스트 아키텍처를 기본값으로 사용합니다. 크로스 빌드 시 반드시ARCH=arm64처럼 지정해야 합니다. - SRCARCH
arch/$(SRCARCH)/디렉토리를 결정합니다. 대부분 ARCH와 동일하지만, 일부 매핑(Mapping)이 있습니다 (예:ARCH=arm64→SRCARCH=arm64). - CC = $(CROSS_COMPILE)gcc
CROSS_COMPILE=aarch64-linux-gnu-가 설정되면CC=aarch64-linux-gnu-gcc가 됩니다. 접두사 방식으로 모든 GNU 도구에 일괄 적용됩니다.
LLVM=1 도구 변수 전환
# Makefile — LLVM=1 설정 시 도구 변수 전환
ifdef LLVM
CC = clang
LD = ld.lld
AR = llvm-ar
NM = llvm-nm
OBJCOPY = llvm-objcopy
OBJDUMP = llvm-objdump
READELF = llvm-readelf
STRIP = llvm-strip
endif
# LLVM 버전 접미사 지원 (예: LLVM=-18)
# LLVM=1이면 접미사 없음, LLVM=-18이면 clang-18, ld.lld-18 등 사용
코드 설명
LLVM=1 설정 시 모든 빌드 도구가 GNU에서 LLVM 도구체인으로 전환되는 로직입니다.
- CC = clangGCC 대신 Clang을 사용합니다. Clang은
--target플래그로 타겟 아키텍처를 지정하므로CROSS_COMPILE이 불필요합니다. - LD = ld.lldGNU ld 대신 LLVM 링커(lld)를 사용합니다. 대규모 프로젝트에서 링크 속도가 더 빠릅니다.
- LLVM=-18시스템에 여러 LLVM 버전이 설치된 경우, 접미사로 특정 버전을 지정할 수 있습니다.
ARCH → SRCARCH → 하위 Makefile 변수 전파
아키텍처별 Makefile 분석
아래 예시는 Linux master 기준 핵심 변수만 발췌하거나, 버전과 설정에 따라 달라지는 부분은 실무적으로 확인할 항목만 요약한 것입니다. 실제 값은 사용하는 커널 버전과 .config에 따라 달라질 수 있습니다.
arch/arm64/Makefile 핵심 변수
# arch/arm64/Makefile — Linux master 기준 핵심 발췌
LDFLAGS_vmlinux := --no-undefined -X --pic-veneer
KBUILD_CFLAGS += -mgeneral-regs-only # FP/SIMD 레지스터(Register) 미사용
KBUILD_CFLAGS += $(call cc-option,-mabi=lp64)
boot := arch/arm64/boot
ifeq ($(CONFIG_EFI_ZBOOT),)
KBUILD_IMAGE := $(boot)/Image.gz
else
KBUILD_IMAGE := $(boot)/vmlinuz.efi
endif
코드 설명
arch/arm64/Makefile에서 ARM64 크로스 빌드에 영향을 미치는 핵심 변수입니다.
- LDFLAGS_vmlinuxARM64 전용 vmlinux 링크 옵션입니다. 문서 작성 시점의 상류 커널은 단순
-z norelro만 쓰지 않고 추가 플래그를 함께 사용합니다. - -mgeneral-regs-only커널 코드에서 부동소수점/SIMD 레지스터(Register)를 사용하지 않게 만듭니다. 커널은 인터럽트(Interrupt) 컨텍스트에서 FP 레지스터를 보존하지 않으므로 안전을 위해 필요합니다.
- KBUILD_IMAGE기본 설치 대상 이미지는
CONFIG_EFI_ZBOOT설정에 따라 달라집니다. 일반적으로는Image.gz가 기본이지만,arch/arm64/boot/Image자체도 별도 타겟으로 계속 빌드할 수 있습니다.
arch/arm/Makefile 핵심 변수
# arch/arm/Makefile — ARM 32비트 크로스 빌드 핵심
KBUILD_CFLAGS += -msoft-float # 또는 -mfloat-abi=hard (하드웨어 FPU)
KBUILD_CFLAGS += $(call cc-option,-mno-thumb-interwork)
# CPU 튜닝 (defconfig에 따라 자동 설정)
KBUILD_CFLAGS += $(call cc-option,-mcpu=cortex-a7)
# 부팅 이미지
KBUILD_IMAGE := arch/arm/boot/zImage
# zImage: 압축 + 자체 해제 코드 포함
# uImage: U-Boot 헤더 추가 (make uImage)
arch/riscv/Makefile 핵심 변수
# arch/riscv/Makefile — Linux master 기준 핵심 발췌
KBUILD_CFLAGS += -mabi=lp64
KBUILD_CFLAGS += -mno-save-restore
boot := arch/riscv/boot
boot-image-y := Image
boot-image-$(CONFIG_KERNEL_GZIP) := Image.gz
KBUILD_IMAGE := $(boot)/$(boot-image-y)
아키텍처별 커널 이미지 형식
| 아키텍처 | 이미지 이름 | 경로 | 형식 | 부트로더(Bootloader) |
|---|---|---|---|---|
| ARM | zImage | arch/arm/boot/zImage | 압축 + 자체 해제 코드 | U-Boot (bootz) |
| ARM | uImage | arch/arm/boot/uImage | zImage + U-Boot 헤더 | U-Boot (bootm) |
| ARM64 | Image | arch/arm64/boot/Image | 비압축 Raw Binary | U-Boot (booti) |
| ARM64 | Image.gz | arch/arm64/boot/Image.gz | gzip 압축 | U-Boot (booti) |
| RISC-V | Image | arch/riscv/boot/Image | 비압축 Raw Binary | OpenSBI + U-Boot |
| MIPS | vmlinux | vmlinux | ELF | YAMON, U-Boot |
| PowerPC | zImage | arch/powerpc/boot/zImage | 압축 + 래퍼 | yaboot, GRUB, U-Boot |
| x86 | bzImage | arch/x86/boot/bzImage | setup + 압축 커널 | GRUB, systemd-boot |
defconfig 구조와 선택 가이드
아키텍처별 주요 defconfig 목록
| 아키텍처 | defconfig | 대상 | 설명 |
|---|---|---|---|
| ARM | multi_v7_defconfig | ARMv7 SoC 범용 | Cortex-A7/A9/A15 다중 SoC 지원 |
| ARM | bcm2835_defconfig | Raspberry Pi 1/Zero | BCM2835 SoC 전용 |
| ARM | omap2plus_defconfig | TI OMAP / BeagleBone | AM335x, AM57xx 등 |
| ARM64 | defconfig | ARM64 범용 | 대부분의 ARM64 플랫폼 지원 |
| RISC-V | defconfig | RISC-V 범용 | 64비트 RISC-V 기본 설정 |
| RISC-V | rv32_defconfig | RISC-V 32비트 | 32비트 RISC-V 전용 |
| MIPS | malta_defconfig | MIPS Malta 보드 | QEMU MIPS 에뮬레이션 호환 |
| PowerPC | pseries_defconfig | IBM Power 서버 | QEMU pSeries 호환 |
# 특정 아키텍처의 사용 가능한 defconfig 목록 확인
make ARCH=arm64 help | grep defconfig
make ARCH=arm help | grep defconfig | head -20
make ARCH=riscv help | grep defconfig
커스텀 defconfig 생성과 관리
# 1) 기존 defconfig에서 시작
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
# 2) menuconfig로 커스텀 설정 (필요한 옵션 추가/제거)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
# 3) 커스텀 defconfig 저장
make ARCH=arm64 savedefconfig
cp defconfig arch/arm64/configs/my_board_defconfig
# 4) 이후 사용
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- my_board_defconfig
설정 프래그먼트 활용
# 크로스 빌드 전용 설정 프래그먼트 생성
cat > cross-debug.config <<'EOF'
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
CONFIG_GDB_SCRIPTS=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
EOF
# defconfig에 프래그먼트 병합
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
scripts/kconfig/merge_config.sh .config cross-debug.config
# 병합 결과 확인 후 빌드
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
아키텍처별 크로스 빌드 가이드
ARM (32-bit) 빌드 상세
# ARM 32비트 크로스 빌드 전체 과정
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# multi_v7_defconfig: 대부분의 ARMv7 SoC 지원
make multi_v7_defconfig
make -j$(nproc)
# 빌드 결과 확인
file arch/arm/boot/zImage
# 출력: Linux kernel ARM boot executable zImage
# DTB 빌드 (Device Tree Blob)
make dtbs
ls arch/arm/boot/dts/*.dtb | head -5
ARM64 (AArch64) 빌드 상세
# ARM64 크로스 빌드 전체 과정
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
make -j$(nproc)
# 빌드 결과 확인
file arch/arm64/boot/Image
# raw kernel image이므로 환경에 따라 단순 data 또는 EFI 관련 형식으로 보일 수 있습니다
# ELF 헤더로 아키텍처 이중 확인
readelf -h vmlinux | grep -E "Machine|Class"
# Machine: AArch64
# Class: ELF64
# DTB 빌드
make dtbs
ls arch/arm64/boot/dts/arm/*.dtb 2>/dev/null
ls arch/arm64/boot/dts/rockchip/*.dtb 2>/dev/null | head -3
RISC-V 빌드 상세
# RISC-V 64비트 크로스 빌드 전체 과정
export ARCH=riscv
export CROSS_COMPILE=riscv64-linux-gnu-
make defconfig
make -j$(nproc)
# 빌드 결과 확인
file arch/riscv/boot/Image
# 출력: RISC-V, ...
readelf -h vmlinux | grep -E "Machine|Class"
# Machine: RISC-V
# Class: ELF64
MIPS 빌드 상세
# MIPS 32비트 크로스 빌드 (Malta 보드 — QEMU 호환)
export ARCH=mips
export CROSS_COMPILE=mips-linux-gnu-
make malta_defconfig
make -j$(nproc)
# MIPS는 vmlinux를 직접 QEMU에 전달
file vmlinux
# 출력: ELF 32-bit MSB executable, MIPS, ...
PowerPC 빌드 상세
# PowerPC 64비트 (리틀 엔디언) 크로스 빌드
export ARCH=powerpc
export CROSS_COMPILE=powerpc64le-linux-gnu-
make pseries_defconfig
make -j$(nproc)
file vmlinux
# 출력: ELF 64-bit LSB executable, 64-bit PowerPC, ...
Device Tree 크로스 컴파일
DTB 빌드 (make dtbs)
# DTB (Device Tree Blob) 빌드
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
# 특정 DTB만 빌드
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip/rk3399-rock-pi-4a.dtb
# DTB 설치
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_DTBS_PATH=./out/dtbs dtbs_install
DTB 검증
# DTB 내용 확인 (바이너리 → 텍스트 역변환)
dtc -I dtb -O dts arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4a.dtb | head -30
# DTB 바인딩 검증 (스키마 기반)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dt_binding_check
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs_check
# DTB 파일 크기 확인
ls -lh arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4a.dtb
커널 모듈(Kernel Module) 크로스 컴파일
인트리 모듈 크로스 빌드
# 커널 빌드 시 모듈 함께 빌드
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) modules
# 모듈 설치 (타겟용 디렉토리에)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
INSTALL_MOD_PATH=./out/rootfs modules_install
# 설치 결과 확인
ls ./out/rootfs/lib/modules/$(make kernelrelease)/
# kernel/ modules.dep modules.alias ...
아웃오브트리 모듈 크로스 빌드
/* hello.c — 크로스 빌드 테스트 모듈 */
#include <linux/module.h>
#include <linux/init.h>
static int __init hello_init(void)
{
pr_info("Hello from cross-compiled module!\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("Goodbye from cross-compiled module!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Cross-compile test module");
# Makefile — 아웃오브트리 모듈 크로스 빌드
obj-m := hello.o
KDIR ?= /path/to/linux-source
ARCH ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-
all:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
코드 설명
아웃오브트리(Out-of-tree) 커널 모듈을 크로스 빌드하는 표준 패턴입니다.
- obj-m := hello.o빌드할 모듈 오브젝트를 지정합니다.
hello.c에서hello.ko모듈이 생성됩니다. - KDIR크로스 빌드된 커널 소스 디렉토리를 가리킵니다.
.config와 빌드 산출물이 있어야 합니다. - -C $(KDIR) M=$(PWD)
-C로 커널 소스로 이동하고,M=으로 현재 모듈 디렉토리를 지정합니다. - ARCH, CROSS_COMPILE커널 빌드와 동일한 값을 전달해야 합니다. 불일치 시 모듈 로드가 실패합니다.
# 모듈 빌드 실행
make KDIR=/path/to/linux-source ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
# 생성된 모듈 아키텍처 확인
file hello.ko
# 출력: ELF 64-bit LSB relocatable, ARM aarch64, ...
# modinfo로 모듈 정보 확인
modinfo hello.ko
INSTALL_MOD_PATH로 모듈 설치
| 시나리오 | 명령 | 설치 경로 |
|---|---|---|
| 기본 (호스트에 설치) | make modules_install | /lib/modules/$(uname -r)/ |
| 타겟 rootfs에 설치 | make INSTALL_MOD_PATH=./rootfs modules_install | ./rootfs/lib/modules/... |
| NFS 루트에 설치 | make INSTALL_MOD_PATH=/srv/nfs/arm64 modules_install | /srv/nfs/arm64/lib/modules/... |
모듈 버전 불일치 문제와 해결
# 모듈 로드 실패 시 흔한 오류
insmod hello.ko
# insmod: ERROR: could not insert module hello.ko: Invalid module format
# 원인 진단: vermagic 문자열 비교
modinfo hello.ko | grep vermagic
# vermagic: 6.1.0-custom SMP preempt mod_unload aarch64
uname -r
# 6.1.0 (← 커널 버전이 다름!)
# 해결: 동일한 커널 소스·설정으로 모듈 재빌드
# 또는 개발 중에는 CONFIG_MODVERSIONS=n으로 버전 검사 비활성화
루트 파일시스템(Filesystem) 생성
BusyBox 기반 initramfs (최소 구성)
# 1) BusyBox 크로스 빌드
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar xf busybox-1.36.1.tar.bz2 && cd busybox-1.36.1
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
# 정적 링크 활성화 (Settings → Build static binary)
sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- CONFIG_PREFIX=../initramfs install
# 2) initramfs 디렉토리 구성
cd ../initramfs
mkdir -p proc sys dev etc tmp
# 3) init 스크립트 생성
cat > init <<'EOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
echo "Boot successful! (cross-compiled ARM64 kernel)"
echo "Minimal initramfs only: SSH server not included"
exec /bin/sh
EOF
chmod +x init
# 4) cpio 아카이브 생성
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
코드 설명
QEMU에서 크로스 빌드된 커널을 테스트하기 위한 최소 initramfs 생성 과정입니다.
- CONFIG_STATIC=yBusyBox를 정적 링크합니다. initramfs에는 공유 라이브러리(Shared Library)가 없으므로 정적 빌드가 필수입니다.
- CONFIG_PREFIXBusyBox를 설치할 디렉토리를 지정합니다.
bin/,sbin/,usr/하위에 심볼릭 링크가 생성됩니다. - mount -t proc/sysfs/devtmpfs커널 가상 파일시스템(VFS)을 마운트(Mount)합니다. 이것이 없으면
/proc,/dev접근이 불가합니다. - exec /bin/shPID 1을 쉘로 교체합니다. 이것이 init 프로세스(Process)가 됩니다.
- cpio -o -H newc | gzip커널이 인식하는 newc 형식의 cpio 아카이브를 생성하고 gzip으로 압축합니다.
cpio 아카이브 직접 생성
# gen_init_cpio를 사용한 세밀한 initramfs 구성
cat > initramfs.list <<'EOF'
dir /dev 0755 0 0
nod /dev/console 0600 0 0 c 5 1
nod /dev/null 0666 0 0 c 1 3
dir /bin 0755 0 0
file /bin/busybox ../busybox/busybox 0755 0 0
slink /bin/sh busybox 0755 0 0
dir /proc 0755 0 0
dir /sys 0755 0 0
file /init ../initramfs/init 0755 0 0
EOF
# gen_init_cpio로 아카이브 생성 (커널 소스에 포함)
usr/gen_init_cpio initramfs.list | gzip > initramfs.cpio.gz
Buildroot로 루트 파일시스템 생성
# Buildroot 다운로드
git clone https://github.com/buildroot/buildroot.git
cd buildroot
# QEMU ARM64 virt 보드 설정
make qemu_aarch64_virt_defconfig
# 빌드 (툴체인 + rootfs 자동 생성)
make -j$(nproc)
# 결과물
ls output/images/
# rootfs.ext4 rootfs.cpio.gz Image ...
빌드 산출물 구조와 검증
바이너리 아키텍처 검증
# 1) file 명령 — 빠른 형식 확인
file arch/arm64/boot/Image
# raw kernel image이므로 설정에 따라 단순 data 또는 EFI 관련 형식으로 보일 수 있습니다
file vmlinux
# ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked
# 2) readelf — 상세 ELF 헤더 분석
readelf -h vmlinux
# Class: ELF64
# Data: 2's complement, little endian
# Machine: AArch64
# 3) objdump — 디스어셈블 샘플 확인
aarch64-linux-gnu-objdump -d vmlinux | head -30
# ARM64 명령어 확인: adrp, ldr, str, bl, ret 등
# 4) strings — 커널 버전 문자열 확인
strings vmlinux | grep "Linux version"
# Linux version 6.1.0 (user@host) (aarch64-linux-gnu-gcc ...)
# 5) 바이너리 크기 확인
size vmlinux
ls -lh arch/arm64/boot/Image
QEMU 크로스 아키텍처 부팅
ARM64 QEMU 부팅 상세
# ARM64 커널 QEMU 부팅 (최소 구성)
qemu-system-aarch64 \
-M virt \
-cpu cortex-a57 \
-kernel arch/arm64/boot/Image \
-append "console=ttyAMA0" \
-nographic \
-m 2G
# initramfs 포함 부팅
qemu-system-aarch64 \
-M virt \
-cpu cortex-a57 \
-kernel arch/arm64/boot/Image \
-initrd initramfs.cpio.gz \
-append "console=ttyAMA0 rdinit=/init" \
-nographic \
-m 2G
코드 설명
QEMU ARM64 부팅 옵션의 상세 설명입니다.
- -M virtQEMU의 가상 머신 타입입니다.
virt는 virtio 디바이스 기반의 범용 ARM64 플랫폼으로, 실제 하드웨어를 모방하지 않고 순수 가상화에 최적화되어 있습니다. - -cpu cortex-a57에뮬레이션할 CPU 모델입니다.
cortex-a57은 ARMv8-A 프로파일의 범용 CPU입니다.max를 지정하면 QEMU가 지원하는 최대 기능을 활성화합니다. - -initrdinitramfs(초기 RAM 디스크)를 커널에 전달합니다. 커널은 이것을 루트 파일시스템으로 마운트하고
/init을 실행합니다. - rdinit=/initinitramfs의 init 프로세스 경로를 지정합니다. 기본값은
/init이지만 명시적으로 지정하는 것이 안전합니다. - -nographic그래픽 창 없이 시리얼 콘솔을 터미널에 직접 연결합니다. 서버 환경이나 CI/CD에서 유용합니다.
ARM QEMU 부팅 상세
# ARM 32비트 QEMU 부팅
qemu-system-arm \
-M virt \
-cpu cortex-a15 \
-kernel arch/arm/boot/zImage \
-append "console=ttyAMA0" \
-nographic \
-m 1G
# DTB 파일 지정이 필요한 경우
qemu-system-arm \
-M vexpress-a15 \
-cpu cortex-a15 \
-kernel arch/arm/boot/zImage \
-dtb arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb \
-append "console=ttyAMA0" \
-nographic \
-m 1G
RISC-V QEMU 부팅 상세
# RISC-V 64비트 QEMU 부팅
qemu-system-riscv64 \
-M virt \
-kernel arch/riscv/boot/Image \
-append "console=ttyS0 earlycon" \
-nographic \
-m 2G
# OpenSBI (BIOS) 명시 지정
qemu-system-riscv64 \
-M virt \
-bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.bin \
-kernel arch/riscv/boot/Image \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 rdinit=/init" \
-nographic \
-m 2G
MIPS QEMU 부팅 상세
# MIPS Malta 보드 QEMU 부팅
qemu-system-mips \
-M malta \
-kernel vmlinux \
-append "console=ttyS0" \
-nographic \
-m 256M
| 아키텍처 | QEMU 바이너리 | -M 옵션 | -cpu 옵션 | 콘솔 장치 |
|---|---|---|---|---|
| ARM | qemu-system-arm | virt / vexpress-a15 | cortex-a15 | ttyAMA0 |
| ARM64 | qemu-system-aarch64 | virt | cortex-a57 / max | ttyAMA0 |
| RISC-V | qemu-system-riscv64 | virt | (기본 rv64) | ttyS0 |
| MIPS | qemu-system-mips | malta | 24Kf | ttyS0 |
| PowerPC | qemu-system-ppc64 | pseries | POWER9 | hvc0 |
QEMU 고급 설정
네트워크 설정
# 유저 모드 네트워크 (기본, NAT) — 호스트 포트 포워딩
qemu-system-aarch64 \
-M virt -cpu cortex-a57 -m 2G -nographic \
-kernel arch/arm64/boot/Image \
-initrd initramfs.cpio.gz \
-append "console=ttyAMA0 rdinit=/init" \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0
# 게스트 내부에서 네트워크 확인
# ip addr show → eth0에 10.0.2.15 자동 할당
# 이 최소 initramfs에는 SSH 서버가 없으므로, 포트 포워딩만으로는 바로 ssh 접속되지 않습니다
블록 디바이스와 rootfs 마운트
# ext4 rootfs 이미지 생성
dd if=/dev/zero of=rootfs.ext4 bs=1M count=512
mkfs.ext4 rootfs.ext4
sudo mount rootfs.ext4 /mnt
sudo cp -a ./out/rootfs/* /mnt/
sudo umount /mnt
# rootfs 이미지로 QEMU 부팅
qemu-system-aarch64 \
-M virt -cpu cortex-a57 -m 2G -nographic \
-kernel arch/arm64/boot/Image \
-append "console=ttyAMA0 root=/dev/vda rw" \
-drive file=rootfs.ext4,format=raw,if=virtio
시리얼 콘솔과 로그 캡처
# 부팅 로그를 파일로 캡처
qemu-system-aarch64 \
-M virt -cpu cortex-a57 -m 2G \
-kernel arch/arm64/boot/Image \
-append "console=ttyAMA0 earlycon" \
-nographic \
-serial file:boot.log
# 타임아웃 자동 종료 (CI/CD용)
timeout 60 qemu-system-aarch64 \
-M virt -cpu cortex-a57 -m 2G \
-kernel arch/arm64/boot/Image \
-append "console=ttyAMA0" \
-nographic \
-no-reboot || true
QEMU 기반 커널 디버깅(Debugging)
QEMU GDB 스텁
# 터미널 1: QEMU 시작 (GDB 대기 모드)
qemu-system-aarch64 \
-M virt -cpu cortex-a57 -m 2G -nographic \
-kernel arch/arm64/boot/Image \
-append "console=ttyAMA0 nokaslr" \
-s -S
# -s: TCP 포트 1234에서 GDB 연결 대기
# -S: CPU를 중단 상태로 시작 (GDB 연결 후 continue)
# 터미널 2: GDB 연결
gdb-multiarch vmlinux
# (gdb) target remote :1234
# (gdb) break start_kernel
# (gdb) continue
# (gdb) bt
nokaslr 커널 파라미터는 KASLR(Kernel Address Space Layout Randomization)을 비활성화하여 심볼 주소가 vmlinux와 일치하도록 합니다. 디버깅 시 필수입니다.
실제 하드웨어 배포
SD 카드 배포
# Raspberry Pi 4 (ARM64) 예시
# SD 카드 장치 확인 (예: /dev/sdb)
lsblk
# 부트 파티션에 커널 이미지 복사
sudo mount /dev/sdb1 /mnt/boot
sudo cp arch/arm64/boot/Image /mnt/boot/kernel8.img
sudo cp arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb /mnt/boot/
# 루트 파티션에 모듈 설치
sudo mount /dev/sdb2 /mnt/rootfs
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
INSTALL_MOD_PATH=/mnt/rootfs modules_install
sudo umount /mnt/boot /mnt/rootfs
TFTP 부팅 (U-Boot + tftpd)
# 호스트: TFTP 서버 설정
sudo apt install -y tftpd-hpa
sudo cp arch/arm64/boot/Image /srv/tftp/
sudo cp arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4a.dtb /srv/tftp/
# 타겟 보드 U-Boot 콘솔에서:
# setenv serverip 192.168.1.100
# setenv ipaddr 192.168.1.200
# tftp 0x40080000 Image
# tftp 0x44000000 rk3399-rock-pi-4a.dtb
# booti 0x40080000 - 0x44000000
NFS 루트 파일시스템 부팅
# 호스트: NFS 서버 설정
sudo apt install -y nfs-kernel-server
sudo mkdir -p /srv/nfs/arm64-root
# rootfs와 모듈 복사
sudo cp -a ./out/rootfs/* /srv/nfs/arm64-root/
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
INSTALL_MOD_PATH=/srv/nfs/arm64-root modules_install
# NFS 내보내기 설정
echo "/srv/nfs/arm64-root 192.168.1.0/24(rw,no_root_squash,no_subtree_check)" | \
sudo tee -a /etc/exports
sudo exportfs -ra
# 커널 부트 파라미터 (U-Boot 또는 QEMU)
# root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/arm64-root,v3 ip=dhcp
부트로더 연동
U-Boot에서 커널 로드
| 이미지 형식 | U-Boot 명령 | 아키텍처 | 설명 |
|---|---|---|---|
| Image (raw) | booti | ARM64, RISC-V | 비압축 커널 이미지를 로더(Loader)나 펌웨어(Firmware)가 직접 해석해 부팅 |
| zImage | bootz | ARM | 자체 해제 압축 이미지 부팅 |
| uImage | bootm | ARM, MIPS, PowerPC | U-Boot 헤더 포함 이미지 |
| FIT Image | bootm | 모든 아키텍처 | 커널+DTB+initrd 번들 이미지 |
FIT 이미지 생성
# FIT (Flattened Image Tree) 이미지 — 커널+DTB+initrd 번들
cat > fit-image.its <<'EOF'
/dts-v1/;
/ {
description = "ARM64 Kernel + DTB + initramfs";
images {
kernel {
description = "Linux kernel";
data = /incbin/("arch/arm64/boot/Image.gz");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "gzip";
load = <0x40080000>;
entry = <0x40080000>;
};
fdt {
description = "Device Tree";
data = /incbin/("arch/arm64/boot/dts/rockchip/rk3399-rock-pi-4a.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
};
ramdisk {
description = "initramfs";
data = /incbin/("initramfs.cpio.gz");
type = "ramdisk";
arch = "arm64";
os = "linux";
compression = "none";
};
};
configurations {
default = "config-1";
config-1 {
kernel = "kernel";
fdt = "fdt";
ramdisk = "ramdisk";
};
};
};
EOF
# FIT 이미지 생성
mkimage -f fit-image.its image.itb
# U-Boot에서 부팅
# tftp 0x40000000 image.itb
# bootm 0x40000000
빌드 성능 최적화
ccache 크로스 빌드 설정
# ccache 설치
sudo apt install -y ccache
# 크로스 빌드에 ccache 적용 (방법 1: CC 오버라이드)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
CC="ccache aarch64-linux-gnu-gcc" -j$(nproc)
# 방법 2: PATH에 ccache 심볼릭 링크 디렉토리 추가
export PATH=/usr/lib/ccache:$PATH
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
# ccache 통계 확인
ccache -s
# cache hit rate: 증분 빌드에서 90%+ 가능
# ccache 캐시 크기 설정
ccache -M 10G
병렬 빌드 전략
# CPU 코어 수 기반 병렬 빌드 (가장 일반적)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
# 메모리 제한이 있는 경우 (코어 수보다 작게 설정)
# 경험칙: 코어당 약 2GB RAM 필요
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j4
# 시스템 부하 기반 자동 조절
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) -l$(nproc)
증분 크로스 빌드 팁
# O= 옵션으로 아키텍처별 빌드 디렉토리 분리
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=build/arm64 defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=build/arm64 -j$(nproc)
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- O=build/riscv defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- O=build/riscv -j$(nproc)
# 소스 트리는 깨끗하게 유지, 빌드 산출물은 O= 하위에 분리
ls build/arm64/arch/arm64/boot/Image
ls build/riscv/arch/riscv/boot/Image
CI/CD 크로스 빌드 통합
GitHub Actions 크로스 빌드 파이프라인
# .github/workflows/cross-build.yml
name: Cross-compile Linux Kernel
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
arch: [arm64, riscv, arm]
include:
- arch: arm64
cross: aarch64-linux-gnu-
pkg: gcc-aarch64-linux-gnu
image: arch/arm64/boot/Image
- arch: riscv
cross: riscv64-linux-gnu-
pkg: gcc-riscv64-linux-gnu
image: arch/riscv/boot/Image
- arch: arm
cross: arm-linux-gnueabihf-
pkg: gcc-arm-linux-gnueabihf
image: arch/arm/boot/zImage
steps:
- uses: actions/checkout@v4
- name: Install toolchain
run: sudo apt-get install -y ${{ matrix.pkg }}
- name: Build kernel
run: |
make ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cross }} defconfig
make ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cross }} -j$(nproc)
- name: Verify binary
run: file ${{ matrix.image }}
코드 설명
GitHub Actions 매트릭스 빌드로 ARM64, RISC-V, ARM 커널을 병렬 크로스 빌드하는 워크플로입니다.
- strategy.matrix3개 아키텍처에 대해 병렬 빌드 잡을 생성합니다. 각 잡은 독립적인 VM에서 실행됩니다.
- include아키텍처별 크로스 컴파일러 패키지, 접두사, 이미지 경로를 매핑합니다.
- file ${{ matrix.image }}빌드 후 생성된 이미지의 아키텍처가 올바른지 검증합니다.
Docker 기반 재현 가능한 크로스 빌드
# Dockerfile.cross-arm64
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
build-essential bc kmod flex bison libssl-dev \
gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \
libelf-dev ccache
ENV ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
WORKDIR /linux
CMD ["sh", "-lc", "make -j$(nproc)"]
# 빌드 실행
docker build -t cross-arm64 -f Dockerfile.cross-arm64 .
docker run --rm -v $PWD:/linux cross-arm64 \
sh -c "make defconfig && make -j\$(nproc)"
멀티 아키텍처 동시 빌드
매트릭스 빌드 전략
# 멀티 아키텍처 빌드 스크립트 (build-all.sh)
#!/bin/bash
set -e
ARCHS="arm64:aarch64-linux-gnu- riscv:riscv64-linux-gnu- arm:arm-linux-gnueabihf-"
for entry in $ARCHS; do
arch=${entry%%:*}
cross=${entry##*:}
echo "=== Building $arch ==="
make ARCH=$arch CROSS_COMPILE=$cross O=build/$arch defconfig
make ARCH=$arch CROSS_COMPILE=$cross O=build/$arch -j$(nproc)
done
echo "=== All builds complete ==="
for entry in $ARCHS; do
arch=${entry%%:*}
echo "$arch:" $(file build/$arch/vmlinux | cut -d: -f2)
done
자주 발생하는 오류와 해결
툴체인 관련 오류
| 오류 메시지 | 원인 | 해결 방법 |
|---|---|---|
aarch64-linux-gnu-gcc: command not found |
크로스 컴파일러 미설치 또는 PATH 미설정 | sudo apt install gcc-aarch64-linux-gnu |
cc1: error: unrecognized command-line option '-mgeneral-regs-only' |
호스트 GCC로 빌드 시도 (CROSS_COMPILE 미지정) | make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- 확인 |
gcc: error: unrecognized argument in option '-march=...' |
오래된 GCC 버전이 ISA 확장을 미지원 | GCC 업그레이드 또는 커널 최소 버전 확인 |
링커 오류
| 오류 메시지 | 원인 | 해결 방법 |
|---|---|---|
undefined reference to '__stack_chk_guard' |
스택 보호 심볼 미해결 (libc 미링크) | 커널은 CONFIG_STACKPROTECTOR가 자체 구현 — make clean 후 재빌드 |
ld: incompatible ... when searching for -lgcc |
호스트 libgcc와 크로스 libgcc 혼동 | CROSS_COMPILE 접미사에 - 포함 확인 |
error: arch/arm64/... relocation truncated |
코드가 주소 범위를 초과 | CONFIG_RELOCATABLE 활성화 또는 모듈로 빌드 |
모듈 로드 실패
| 오류 메시지 | 원인 | 해결 방법 |
|---|---|---|
Invalid module format |
vermagic 불일치 (커널 버전/설정 차이) | 동일 커널 소스·설정으로 모듈 재빌드 |
disagrees about version of symbol |
CONFIG_MODVERSIONS CRC 불일치 | Module.symvers 파일 일치 확인, 또는 MODVERSIONS=n |
Unknown symbol in module |
커널에 해당 심볼이 export되지 않음 | 커널 .config에서 관련 기능 활성화 후 재빌드 |
QEMU 부팅 실패 원인 분석
| 증상 | 가능한 원인 | 해결 방법 |
|---|---|---|
| 아무 출력 없이 멈춤 | 콘솔 장치 불일치 또는 이미지 아키텍처 오류 | -append "earlycon" 추가, file Image로 아키텍처 확인 |
| Kernel panic: VFS: unable to mount root fs | initrd 미지정 또는 rootfs 부재 | -initrd 옵션 추가, 또는 root= 파라미터 확인 |
| Kernel panic: not syncing: Attempted to kill init! | init 프로세스가 비정상 종료 | initramfs의 /init 스크립트 확인, 정적 링크 여부 확인 |
| 부팅 중 "Oops" 출력 | 커널 버그 또는 설정 문제 | CONFIG_DEBUG_INFO=y로 재빌드 후 GDB 디버깅 |
트러블슈팅 플레이북
환경 진단 스크립트
#!/bin/bash — cross-diag.sh
# 크로스 빌드 환경 진단 스크립트
echo "=== 크로스 빌드 환경 진단 ==="
echo "[1] 환경 변수"
echo " ARCH=$ARCH"
echo " CROSS_COMPILE=$CROSS_COMPILE"
echo "[2] 툴체인"
if which ${CROSS_COMPILE}gcc >/dev/null 2>&1; then
echo " 컴파일러: $(${CROSS_COMPILE}gcc --version | head -1)"
echo " 타겟: $(${CROSS_COMPILE}gcc -dumpmachine)"
else
echo " ERROR: ${CROSS_COMPILE}gcc 미발견!"
fi
echo "[3] 커널 소스"
if [ -f Makefile ]; then
echo " 버전: $(make kernelversion 2>/dev/null)"
echo " .config: $([ -f .config ] && echo '존재' || echo '없음')"
else
echo " ERROR: 커널 소스 디렉토리가 아닙니다"
fi
echo "[4] QEMU"
for qemu in qemu-system-aarch64 qemu-system-arm qemu-system-riscv64; do
if which $qemu >/dev/null 2>&1; then
echo " $qemu: $($qemu --version | head -1)"
fi
done
빌드 로그 분석 기법
# 빌드 로그 캡처
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) 2>&1 | tee build.log
# 오류만 추출
grep -E "error:|Error:|ERROR" build.log
# 경고 통계
grep -c "warning:" build.log
# 실제 컴파일러 호출 확인 (V=1 상세 모드)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- V=1 kernel/fork.o 2>&1 | grep gcc
# aarch64-linux-gnu-gcc가 호출되는지 확인 (호스트 gcc가 아닌지)
크로스 컴파일 검증 체크리스트
크로스 컴파일된 커널이 제대로 빌드되고 동작하는지 체계적으로 검증하는 것이 중요합니다. 아래 체크리스트는 빌드부터 부팅 검증까지 전체 과정을 안내합니다.
| 아키텍처 | ARCH 변수 | 툴체인 접두사 | QEMU 시스템 | 커널 이미지 경로 | 콘솔 |
|---|---|---|---|---|---|
| ARM (32-bit) | arm |
arm-linux-gnueabihf- |
qemu-system-arm |
arch/arm/boot/zImage |
ttyAMA0 |
| ARM64 (64-bit) | arm64 |
aarch64-linux-gnu- |
qemu-system-aarch64 |
arch/arm64/boot/Image |
ttyAMA0 |
| RISC-V (64-bit) | riscv |
riscv64-linux-gnu- |
qemu-system-riscv64 |
arch/riscv/boot/Image |
ttyS0 |
| PowerPC (64-bit) | powerpc |
powerpc64le-linux-gnu- |
qemu-system-ppc64 |
arch/powerpc/boot/zImage |
hvc0 |
| MIPS (32-bit) | mips |
mips-linux-gnu- |
qemu-system-mips |
vmlinux |
ttyS0 |
참고 자료
공식 커널 문서
Documentation/process/changes.rst— 최소 도구 버전 요구사항Documentation/kbuild/kbuild.rst— Kbuild 시스템 환경 변수Documentation/kbuild/llvm.rst— LLVM/Clang 크로스 빌드 가이드Documentation/admin-guide/README.rst— 커널 빌드 기본 절차
툴체인 프로젝트
- crosstool-NG — 커스텀 크로스 툴체인 빌더
- Linaro — ARM 최적화 크로스 툴체인
- Bootlin Toolchains — 다양한 아키텍처·libc 조합의 사전 빌드 툴체인
- LLVM Project — 멀티 타겟 크로스 컴파일러
커뮤니티 리소스
- KernelCI — 커널 크로스 빌드·부팅 테스트 자동화 프로젝트
- kernel.org — 공식 커널 소스 저장소
- LKML — Linux Kernel Mailing List
관련 문서
- 개발 환경 설정 — 기본 개발 환경 구축
- 커널 빌드 시스템 — Kbuild 시스템 상세
- QEMU 가상화 — QEMU 내부 구조와 고급 설정
- 디바이스 트리 — DTS 문법, 바인딩, 오버레이
- 커널 모듈 — 모듈 빌드, 로드, 디버깅
- GDB 커널 디버깅 — GDB 원격 디버깅 상세
- BusyBox — 경량 유저스페이스 구축
- OpenWrt/Buildroot — 빌드 시스템 기반 rootfs 생성