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-gcc | AArch64 Linux 用户态,glibc |
arm-linux-gnueabihf-gcc | ARMv7 hard-float |
arm-none-eabi-gcc | 裸机,不能编 Linux 用户程序 |
验证安装:
aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-readelf -h /bin/true 2>/dev/null || true1.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 static 下 DNS/NSS/gethostbyname 等可能行为与动态不同;生产常用 musl static 或仍动态链接。
板端运行
/tmp/hello_static
ldd /tmp/hello_static # 通常显示 not a dynamic executable1.5 readelf 必读字段(验收本阶段核心)
1.5.1 ELF Header(readelf -h)
- Class:ELF32 / ELF64
- Machine:
AArch64、ARM等——必须与 CPU 一致 - Type:
EXEC、DYN(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 从哪来
- Buildroot:
output/staging或 SDK 的sysroots/... - Yocto SDK:
environment-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 build1.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 参考
- 嵌入式场景下的交叉编译
- Debian Multiarch 文档(理解 sysroot 思路)
man gcc、man ld、man readelf
勿在教程里用主机 gcc 加 -march 假装交叉编译;请始终使用 独立 cross toolchain。