Linux 크로스 컴파일

LinuxEmbedded

크로스 컴파일은 호스트(x86)에서 타겟(ARM 등) 바이너리를 빌드하는 것이다. 임베디드 Linux 개발에서 커널, 모듈, 앱 모두 크로스 컴파일이 필요하다.


툴체인

타겟툴체인 접두어설치 (Ubuntu/Debian)
ARM 32-bit (hard-float)arm-linux-gnueabihf-gcc-arm-linux-gnueabihf
ARM 64-bit (AArch64)aarch64-linux-gnu-gcc-aarch64-linux-gnu
ARM bare-metalarm-none-eabi-gcc-arm-none-eabi
# 설치
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

# 확인
arm-linux-gnueabihf-gcc --version

# 테스트
arm-linux-gnueabihf-gcc -o hello hello.c
file hello
# hello: ELF 32-bit LSB executable, ARM, ...

ARM 공식 툴체인을 직접 다운로드할 수도 있다:

wget https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-arm-none-linux-gnueabihf.tar.xz
tar xf arm-gnu-toolchain-*.tar.xz
export PATH=$PATH:$PWD/arm-gnu-toolchain-*/bin

커널 크로스 컴파일

두 환경 변수가 핵심이다: ARCHCROSS_COMPILE.

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# defconfig 선택 (타겟 보드에 맞게)
make vexpress_defconfig         # QEMU Vexpress-A9
# ls arch/arm/configs/          # 사용 가능한 설정 목록

# 커스터마이즈 (선택)
make menuconfig

# 빌드
make -j$(nproc) zImage          # 커널 이미지
make -j$(nproc) dtbs            # Device Tree
make -j$(nproc) modules         # 커널 모듈

# 결과물
# arch/arm/boot/zImage
# arch/arm/boot/dts/*.dtb

AArch64 커널:

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
make -j$(nproc) Image dtbs modules

모듈 크로스 컴파일 (out-of-tree)

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export KDIR=/path/to/kernel/source

make -C $KDIR M=$(pwd) modules

# 타겟 rootfs에 설치
make -C $KDIR M=$(pwd) INSTALL_MOD_PATH=/path/to/rootfs modules_install

Rootfs 구성

BusyBox 최소 rootfs

# BusyBox 빌드
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar xf busybox-*.tar.bz2 && cd busybox-*/

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make defconfig
make menuconfig    # Settings → Build static binary [*]
make -j$(nproc)
make install       # _install/ 에 생성

# rootfs 디렉토리 구성
mkdir -p rootfs/{proc,sys,dev,etc,lib}
cp -a _install/* rootfs/

# init 스크립트
cat > rootfs/init << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
exec /bin/sh
EOF
chmod +x rootfs/init

# initramfs 생성
cd rootfs
find . | cpio -o -H newc | gzip > ../rootfs.cpio.gz

Buildroot (자동화)

git clone https://github.com/buildroot/buildroot.git
cd buildroot
make qemu_arm_vexpress_defconfig
make menuconfig    # 필요한 패키지 추가
make -j$(nproc)

# output/images/ 에 zImage, rootfs.ext2, dtb 생성

Buildroot는 툴체인 + 커널 + rootfs를 한 번에 빌드한다. 패키지 추가도 menuconfig에서 선택하면 된다.


QEMU 테스트

물리 보드 없이 커널과 rootfs를 테스트한다.

# ARM32 (Vexpress-A9)
qemu-system-arm \
    -M vexpress-a9 \
    -m 512M \
    -kernel arch/arm/boot/zImage \
    -dtb arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
    -initrd rootfs.cpio.gz \
    -append "root=/dev/ram rdinit=/init console=ttyAMA0" \
    -nographic

# ARM64
qemu-system-aarch64 \
    -M virt \
    -cpu cortex-a57 \
    -m 1024M \
    -kernel Image \
    -initrd rootfs.cpio.gz \
    -append "root=/dev/ram rdinit=/init console=ttyAMA0" \
    -nographic

# 종료: Ctrl+A → X

GDB 원격 디버깅

# 터미널 1: QEMU에 GDB 서버 활성화
qemu-system-arm -M vexpress-a9 ... -s -S
# -s: gdbserver on :1234
# -S: 시작 시 정지

# 터미널 2: GDB 연결
arm-linux-gnueabihf-gdb vmlinux
(gdb) target remote :1234
(gdb) break start_kernel
(gdb) continue

메모

  • CROSS_COMPILE은 접두어다
    • arm-linux-gnueabihf-처럼 하이픈으로 끝난다. 빌드 시스템이 뒤에 gcc, ld, objcopy 등을 붙인다
  • gnueabihf vs gnueabi
    • hf = hard-float. FPU가 있는 칩에서 사용. Cortex-A 시리즈는 거의 다 hf
    • gnueabi (soft-float)는 FPU가 없는 칩용
  • 커널 소스 버전과 모듈 호환
    • out-of-tree 모듈은 빌드할 때 사용한 커널 소스 버전과 타겟 커널 버전이 정확히 일치해야 한다
    • vermagic 문자열이 다르면 insmod 시 거부된다
  • static vs dynamic BusyBox
    • static 빌드면 공유 라이브러리가 필요 없어서 rootfs가 단순해진다
    • dynamic이면 lib/에 크로스 컴파일된 libc를 복사해야 한다
  • QEMU와 실제 하드웨어의 차이
    • QEMU는 타이밍, DMA, 인터럽트 레이턴시를 정확히 에뮬레이션하지 않는다
    • 기능 테스트에는 충분하지만, 성능 테스트는 실제 보드에서 해야 한다