ARM交叉编译实战:以ARM Cortex-A系列为例,编写完整的工具链文件
说实话,交叉编译这件事,我当年刚入行时也踩过不少坑。那时候我天真地以为,只要装个交叉编译器就能搞定一切。结果呢?链接器报错、头文件找不到、运行时库版本不匹配……折腾了好几天。后来我才明白,工具链文件(toolchain.cmake)才是整个交叉编译的基石。
今天我们就以ARM Cortex-A系列为例,手把手写一个完整的工具链文件。Cortex-A系列常用于高性能嵌入式场景,比如树莓派、各种工业控制板、甚至一些入门级服务器。它跑的是Linux系统,所以我们的工具链要支持glibc或uclibc。
1. 先搞清楚你的目标平台
写工具链文件之前,你得先回答几个问题:
- 目标架构:ARMv7-A?ARMv8-A(AArch64)?
- 浮点单元:硬浮点(hardfp)还是软浮点(softfp)?
- 交叉编译器:用Linaro GCC?还是自己用crosstool-NG编译的?
- 系统类型:Linux?裸机(bare-metal)?
我个人习惯用Linaro的GCC工具链,因为它对Cortex-A系列支持最好,而且更新及时。比如针对ARMv7-A,我会选 arm-linux-gnueabihf- 这个前缀,其中 hf 表示硬浮点。
① 用什么编译器
② 目标系统是什么
③ 编译参数怎么传
2. 工具链文件的结构骨架
一个标准的工具链文件,其实就几个关键部分。我把它拆解成下面这张图,你看一眼就明白了:
3. 手写一个完整的工具链文件
好,我们直接上代码。下面这个文件是我在实际项目中用过的,针对 ARM Cortex-A7(比如全志H3、NXP i.MX6ULL)的完整工具链:
# ============================================================
# toolchain_arm_cortex_a7.cmake
# 目标:ARM Cortex-A7, Linux, 硬浮点
# 编译器:arm-linux-gnueabihf-gcc (Linaro 7.5.0)
# ============================================================
# ---------- 1. 目标系统 ----------
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# ---------- 2. 编译器前缀与路径 ----------
set(TOOLCHAIN_PREFIX arm-linux-gnueabihf)
# 我习惯把工具链路径单独设一个变量,方便切换不同版本
set(TOOLCHAIN_PATH /opt/linaro/gcc-arm-linux-gnueabihf-7.5.0)
# 指定编译器
set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX}-g++)
set(CMAKE_AR ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX}-ar)
set(CMAKE_RANLIB ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX}-ranlib)
set(CMAKE_STRIP ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX}-strip)
# ---------- 3. 查找系统根目录(sysroot) ----------
# 注意:sysroot里要有目标板的libc、头文件等
set(CMAKE_SYSROOT ${TOOLCHAIN_PATH}/${TOOLCHAIN_PREFIX}/sysroot)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
# 查找策略:只在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)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# ---------- 4. 编译参数 ----------
# Cortex-A7 支持ARMv7-A架构,带NEON和VFPv4
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mtune=cortex-a7")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpu=neon-vfpv4 -mfloat-abi=hard")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthumb-interwork")
# 优化选项(我一般调试用-Og,发布用-O2)
set(CMAKE_C_FLAGS_DEBUG "-Og -g -DDEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG")
# 链接参数
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
# ---------- 5. 其他工具 ----------
set(CMAKE_OBJCOPY ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX}-objcopy)
set(CMAKE_SIZE ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_PREFIX}-size)
# ---------- 6. 测试编译 ----------
# 这里我加了个小检查,防止路径配错
if(NOT EXISTS ${CMAKE_C_COMPILER})
message(FATAL_ERROR "交叉编译器未找到:${CMAKE_C_COMPILER}")
endif()
cmake --system-information | grep CMAKE_C_COMPILER 来验证编译器是否被正确识别。别问我为什么,问就是被坑过。
4. 关键参数详解
上面代码里有些参数,我得单独拎出来说说。你想想看,如果这些参数设错了,编译出来的程序根本跑不起来。
| 参数 | 含义 | 我的建议 |
|---|---|---|
CMAKE_SYSTEM_NAME |
目标操作系统 | Cortex-A跑Linux就写 Linux,裸机写 Generic |
CMAKE_SYSROOT |
系统根目录 | 一定要指向包含 usr/lib、usr/include 的目录 |
CMAKE_FIND_ROOT_PATH_MODE_* |
查找策略 | 库和头文件设 ONLY,防止混入宿主机的库 |
-mfloat-abi=hard |
硬浮点ABI | 如果目标内核支持VFP,就用hard,性能更好 |
-mfpu=neon-vfpv4 |
浮点单元类型 | Cortex-A7支持NEON,做多媒体处理时很有用 |
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY 设成了 NEVER,结果CMake跑去宿主机的 /usr/lib 找库,链接了一堆x86的.a文件。最后编译倒是通过了,但一运行就段错误。排查了整整一天……从那以后,我每次都会检查这个参数。
5. 如何使用这个工具链文件
写好了工具链文件,怎么用呢?很简单,在项目根目录执行:
mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain_arm_cortex_a7.cmake
make -j4
嗯,就这么一行 -DCMAKE_TOOLCHAIN_FILE,CMake就会自动切换成交叉编译模式。你可以在CMakeLists.txt里正常写 add_executable、target_link_libraries,完全不用关心底层编译器差异。
我个人习惯把工具链文件放在项目根目录的 cmake/ 文件夹下,这样结构清晰:
my_project/
├── cmake/
│ └── toolchain_arm_cortex_a7.cmake
├── CMakeLists.txt
├── src/
└── build/
6. 针对不同Cortex-A型号的调整
不同Cortex-A型号,编译参数略有不同。我整理了一个对照表,方便你直接抄作业:
| Cortex-A型号 | 架构 | 推荐 -march |
浮点参数 |
|---|---|---|---|
| Cortex-A5 | ARMv7-A | armv7-a |
-mfpu=vfpv4 -mfloat-abi=hard |
| Cortex-A7 | ARMv7-A | armv7-a |
-mfpu=neon-vfpv4 -mfloat-abi=hard |
| Cortex-A8 | ARMv7-A | armv7-a |
-mfpu=neon -mfloat-abi=softfp |
| Cortex-A9 | ARMv7-A | armv7-a |
-mfpu=neon -mfloat-abi=hard |
| Cortex-A53 | ARMv8-A (64位) | armv8-a |
默认支持浮点,无需额外参数 |
| Cortex-A72 | ARMv8-A (64位) | armv8-a |
默认支持浮点,无需额外参数 |
aarch64-linux-gnu- 前缀的编译器,而不是 arm-linux-gnueabihf-。工具链文件里 CMAKE_SYSTEM_PROCESSOR 也要改成 aarch64。
7. 调试工具链的小技巧
写工具链文件难免遇到问题。我分享几个我常用的调试方法:
- 打印变量:在工具链文件里加
message(STATUS "C compiler: ${CMAKE_C_COMPILER}"),看看路径对不对。 - 检查sysroot:运行
${TOOLCHAIN_PREFIX}-gcc --print-sysroot,确认sysroot路径。 - 测试编译:写一个简单的hello.c,手动用交叉编译器编译,排除CMake的问题。
- 查看CMake缓存:
cmake -LA可以列出所有缓存变量,看看有没有被意外覆盖。
说实话,工具链文件写一次,后面就能复用很久。我自己的做法是维护一个 cmake-toolchains/ 仓库,把各种平台的工具链文件都放进去,新项目直接复制一份改改参数就行。
好了,关于ARM Cortex-A的交叉编译工具链文件,核心内容就这些。你只要照着上面的模板,改改架构参数和编译器路径,基本就能搞定大部分Cortex-A平台的交叉编译。
公众号:蓝海资料掘金营,微信deep3321