cross-gcc
Cross-GCC(GCC 交叉编译)
用途
引导开发者搭建和使用交叉编译 GCC 工具链:三元组、sysroot、pkg-config、基于 QEMU 的测试及常见失败模式。
触发场景
- "如何在 x86 机器上编译 ARM 程序?"
- "出现 'wrong ELF class' 或 'cannot execute binary file' 怎么办?"
- "如何为交叉编译设置 sysroot?"
- "pkg-config 在我的交叉构建中返回了宿主库路径"
- "如何使用 QEMU + GDB 调试交叉编译的二进制文件?"
工作流程
1. 理解三元组
GNU 三元组的形式为 <arch>-<vendor>-<os>-<abi>(通常为 3 或 4 部分):
| 三元组 | 目标 |
|---|---|
aarch64-linux-gnu |
64 位 ARM Linux(glibc) |
arm-linux-gnueabihf |
32 位 ARM Linux 硬浮点 |
arm-none-eabi |
裸机 ARM(无操作系统) |
riscv64-linux-gnu |
64 位 RISC-V Linux |
x86_64-w64-mingw32 |
从 Linux 构建 Windows(MinGW) |
mipsel-linux-gnu |
小端 MIPS Linux |
2. 安装工具链
# Debian/Ubuntu
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu
# 裸机 ARM(Cortex-M)
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi
# 验证
aarch64-linux-gnu-gcc --version
3. 基础交叉编译
# C
aarch64-linux-gnu-gcc -O2 -o hello hello.c
# C++
aarch64-linux-gnu-g++ -O2 -std=c++17 -o hello hello.cpp
# 裸机(无标准库,无操作系统)
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 \
-ffreestanding -nostdlib -T linker.ld -o firmware.elf startup.s main.c
4. Sysroot(系统根目录)
sysroot 是包含目标平台头文件和库的目录。当代码需要链接目标平台特定的库时必须使用。
# 使用 sysroot
aarch64-linux-gnu-gcc --sysroot=/path/to/aarch64-sysroot -O2 -o prog main.c
# 常见 sysroot 来源:
# - Raspberry Pi:从 raspbian/raspios 下载
# - Debian 多架构:debootstrap --arch arm64 bullseye /tmp/sysroot
# - Yocto/Buildroot:在构建输出中自动生成
验证 sysroot 是否正确:
aarch64-linux-gnu-gcc --sysroot=/path/to/sysroot -v -E - < /dev/null 2>&1 | grep sysroot
5. 交叉构建的 pkg-config
pkg-config 默认会返回宿主库路径。需要覆盖:
export PKG_CONFIG_SYSROOT_DIR=/path/to/sysroot
export PKG_CONFIG_LIBDIR=${PKG_CONFIG_SYSROOT_DIR}/usr/lib/aarch64-linux-gnu/pkgconfig:${PKG_CONFIG_SYSROOT_DIR}/usr/share/pkgconfig
export PKG_CONFIG_PATH= # 清除宿主路径
pkg-config --libs libssl # 现在返回目标路径
6. CMake 交叉编译
创建工具链文件 aarch64.cmake:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_SYSROOT /path/to/aarch64-sysroot)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=aarch64.cmake
cmake --build build
7. 使用 QEMU 测试
# 用户模式仿真(Linux 二进制,无需完整操作系统)
sudo apt install qemu-user-static
qemu-aarch64-static ./hello
# 或设置 binfmt_misc 实现透明执行:
# 然后直接:./hello
# 通过 QEMU 进行 GDB 远程调试
qemu-aarch64-static -g 1234 ./hello &
aarch64-linux-gnu-gdb -ex "target remote :1234" ./hello
8. 常见错误
| 错误 | 原因 | 解决方法 |
|---|---|---|
cannot execute binary file: Exec format error |
在没有 QEMU 的宿主上运行目标二进制 | 使用 qemu-<arch>-static |
wrong ELF class: ELFCLASS64(或 32) |
链接了错误架构的目标文件 | 检查三元组;确保所有目标文件使用同一工具链 |
/usr/bin/ld: cannot find -lfoo |
交叉链接时使用了宿主库路径 | 设置 --sysroot;修复 PKG_CONFIG_LIBDIR |
undefined reference to '__aeabi_*' |
缺少 ARM ABI 运行时 | 链接 -lgcc 或 -lclang_rt.builtins |
relocation R_AARCH64_ADR_PREL_PG_HI21 out of range |
距离过大 | 使用 -mcmodel=large 或重构代码 |
unrecognized opcode |
-mcpu 或 -march 错误 |
为目标设置正确的 CPU 标志 |
9. 环境变量
# 告知构建系统使用交叉编译器
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
export AR=aarch64-linux-gnu-ar
export STRIP=aarch64-linux-gnu-strip
export OBJDUMP=aarch64-linux-gnu-objdump
# 用于 autoconf 项目
./configure --host=aarch64-linux-gnu --prefix=/usr
ARM 特定 GCC 标志参考见 references/arm-flags.md。
相关技能
- GCC 标志详情使用
skills/compilers/gcc - 使用
gdbserver进行远程调试使用skills/debuggers/gdb - AArch64 汇编特性使用
skills/low-level-programming/assembly-arm - 工具链文件设置使用
skills/build-systems/cmake
More from killvxk/low-level-dev-skills-zh
binutils
GNU binutils 二进制操作与分析技能。适用场景:使用 ar 管理静态库、使用 strip 或 objcopy 处理二进制文件、使用 addr2line 将地址转换为源码位置、使用 strings 提取文本、或使用 c++filt 对 C++ 名称进行反混淆。触发条件:涉及 ar、strip、objcopy、addr2line、strings、c++filt、ranlib 或二进制后处理任务的查询。
1ebpf
Linux 可观测性和网络的 eBPF 技能。适用场景:使用 libbpf 或 bpftrace 编写 eBPF 程序、挂载 kprobe/tracepoint/XDP 钩子、调试验证器错误、使用 eBPF map,或实现跨内核版本的 CO-RE 可移植性。触发条件:查询 eBPF、bpftool、bpftrace、XDP 程序、libbpf、验证器错误、eBPF map 或使用 BPF 进行内核追踪相关问题。
1clang
C/C++ 项目的 Clang/LLVM 编译器技能。适用场景:使用 clang 或 clang++ 进行诊断、sanitizer 插桩、优化备注、通过 clang-tidy 进行静态分析、通过 lld 实现 LTO,或从 GCC 迁移到 Clang。触发条件:涉及 clang 标志、clang-tidy、clang-format、更好的错误信息、Apple/FreeBSD 工具链或 LLVM 特定优化的查询。涵盖标志选择、诊断调优及与 LLVM 工具的集成。
1gcc
C/C++ 项目的 GCC 编译器技能。适用场景:选择优化级别、警告标志、调试构建、LTO、sanitizer 插桩,或诊断 GCC 编译错误。涵盖调试与发布构建的标志选择、ABI 问题、预处理器宏、配置引导优化(PGO)及与构建系统的集成。触发条件:涉及 gcc 标志、编译错误、性能调优、警告抑制或跨标准编译的查询。
1cmake
C/C++ 项目的 CMake 构建系统技能。适用场景:编写或重构 CMakeLists.txt、配置源外构建、选择生成器(Ninja、Make、VS)、使用 target_link_libraries 管理目标和依赖、通过 find_package 或 FetchContent 集成外部包、启用 Sanitizer、为交叉编译配置工具链文件,或导出 CMake 包。触发条件:涉及 CMakeLists.txt、cmake 配置错误、目标属性、安装规则、CPack 或 CMake Presets 的查询。
1cpp-modules
现代 C++ 项目的 C++20 模块技能。适用场景:使用命名模块、模块分区、头文件单元、CMake MODULE_SOURCES、Clang -fmodules-ts、BMI 缓存问题,或从头文件迁移到模块。触发条件:涉及 C++20 模块、import 语句、模块接口单元、头文件单元或 BMI 文件的查询。
1