1 应用交叉编译实战指南

本文是 嵌入式 Linux 学习路径 第三阶段:在 PC 上编译、在 ARM/AArch64 板上运行 用户态程序。完成后你应能:各做一次动态与静态链接、用 readelf/file 读懂 Machine 与 ABI、独立解决 链接期与运行期 缺库问题。


1.1 学习目标

  • 配置 CROSS_COMPILE--sysroot(或 CMake toolchain)。
  • 理解 动态链接 依赖 目标 rootfs 上的 ld.so 与 .so
  • 理解 静态链接 的体积、NSS、glibc 等权衡。
  • 会用 readelf -h-d-l 与目标板 ldd 交叉验证。

1.2 工具链与 triplet

triplet 示例含义
aarch64-linux-gnu-gccAArch64 Linux 用户态,glibc
arm-linux-gnueabihf-gccARMv7 hard-float
arm-none-eabi-gcc裸机,不能编 Linux 用户程序

验证安装:

aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-readelf -h /bin/true 2>/dev/null || true

1.3 实验一:动态链接 hello

hello.c

#include <stdio.h>
int main(void) {
	puts("hello, target");
	return 0;
}

编译

export CC=aarch64-linux-gnu-gcc
export SYSROOT=/path/to/target/rootfs   # 来自 BSP/SDK/Buildroot output
 
$CC --sysroot="$SYSROOT" -O2 -g -o hello_dyn hello.c

主机上检查

file hello_dyn
# 期望:ELF 64-bit LSB executable, ARM aarch64, dynamically linked, ...
 
readelf -h hello_dyn | egrep 'Machine|Class|Type'
readelf -d hello_dyn | egrep 'NEEDED|RUNPATH|RPATH'

部署到板子

scp hello_dyn root@board:/tmp/
ssh root@board 'ldd /tmp/hello_dyn; /tmp/hello_dyn'

1.3.1 动态链接失败典型错误

现象原因
No such file or directory(文件存在)解释器 /lib/ld-linux-aarch64.so.1 不在板子上
error while loading shared libraries: libxxx.so缺依赖 .so 或 LD_LIBRARY_PATH 未设
Exec format error架构错(32/64、ARM/x86 混用)

1.4 实验二:静态链接 hello

$CC --sysroot="$SYSROOT" -static -O2 -o hello_static hello.c
ls -lh hello_dyn hello_static
file hello_static

观察

  • 体积显著增大;不依赖 板载 ld.so(仍依赖 内核 syscall ABI 匹配)。
  • glibc staticDNS/NSS/gethostbyname 等可能行为与动态不同;生产常用 musl static 或仍动态链接。

板端运行

/tmp/hello_static
ldd /tmp/hello_static   # 通常显示 not a dynamic executable

1.5 readelf 必读字段(验收本阶段核心)

1.5.1 ELF Header(readelf -h

  • Class:ELF32 / ELF64
  • MachineAArch64ARM 等——必须与 CPU 一致
  • TypeEXECDYN(PIE)、REL(.so)

1.5.2 动态段(readelf -d

  • NEEDED:依赖 .so 列表
  • RUNPATH/RPATH:运行时库搜索路径(链接期写入)

1.5.3 Program Headers(readelf -l

  • INTERP:动态链接器路径
  • LOAD:映射到内存的段

1.5.4 与 ABI 的关系

  • EABI / hard-float (hf):ARM 32 位必核对;混用会导致 非法指令 或 silent 错误。
  • AArch64:Procedure Call Standard(AAPCS64)由工具链默认保证;仍须 rootfs 与内核 同为 64 位用户态。

1.6 sysroot 从哪来

  • Buildrootoutput/staging 或 SDK 的 sysroots/...
  • Yocto SDKenvironment-setup-aarch64-poky-linux 导出变量
  • 厂商 BSP:文档指定路径
  • rsync 板子(权宜):rsync -a root@board:/ /tmp/rootfs/(注意特殊文件系统)

原则编译链接用的 libc 版本 ≤ 或等于 板子上运行的 libc(过新可能依赖新符号)。


1.7 CMake 最小 toolchain(中大型应用)

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_SYSROOT /path/to/rootfs)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
cmake --build build

1.8 pkg-config 陷阱

export PKG_CONFIG_SYSROOT_DIR="$SYSROOT"
export PKG_CONFIG_PATH="$SYSROOT/usr/lib/pkgconfig"
pkg-config --cflags --libs libcurl

未设 SYSROOT_DIR 时,常 链接到主机 x86 库——链接可能报错或更糟 静默链接成功但板端无法运行


1.9 实践练习清单

  • 动态 hello 在板端 ldd 全绿 后运行。
  • 静态 hello 在 /lib/ld-linux-* 的 minimal rootfs 上运行(若你有 initramfs 环境)。
  • 故意用 gnueabi 链编译,在 gnueabihf 板子上运行,记录错误现象。
  • 对带 -lpthread 的程序,比较 链接命令readelf -d 中 NEEDED。

1.10 阶段验收

  • 口述 链接错误 vs 运行错误 排查顺序。
  • readelf -h 读出目标 Machine 并与 uname -m 对照。
  • 说明 为何交叉编译必须 —sysroot(至少两个原因:头文件、libc 链接)。

1.11 下一阶段衔接

  • Buildroot/BSP:sysroot 由构建系统 自动生成;本阶段手搓路径是为读懂 SDK。
  • 内核模块:不是 --sysroot 用户态链接,而是 KERNELDIR + 相同 vermagic 内核

1.12 参考


勿在教程里用主机 gcc 加 -march 假装交叉编译;请始终使用 独立 cross toolchain