ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux下ARM交叉编译器安装与配置实战:从工具链选择到环境验证

Linux下ARM交叉编译器安装与配置实战:从工具链选择到环境验证 最近给一块 ARM 开发板搭编译环境又把“linux下安装交叉编译器”这件事从头到尾折腾了一遍。命令行敲了无数遍工具链换了三套最后总算理清了整套流程。这篇文章不打算写成标准教程的复读机而是把我实际安装、配置、验证过程中最常用的一套方法以及那些文档里根本不写、只有踩过坑才知道的细节一次性讲清楚。不管是刚接触嵌入式 Linux 的新手还是需要在不同架构之间切来切去的“老油条”照着这篇文章操作基本能把交叉编译环境一次搞定。先说清楚一点交叉编译器不是什么神秘的东西它就是一个能在你当前这台 x86 电脑上运行、但生成的是 ARM 架构可执行程序的 GCC 工具链。难点不在“安装”这两个字而在选对版本、配好路径、避开系统依赖的坑。下面我按自己习惯的顺序来写。1. 交叉编译到底解决什么问题为什么你写好的代码上不了板很多第一次接触嵌入式的朋友会问既然开发板上跑的是 Linux为什么不能直接把源码拷贝到板子上然后在板上用 gcc 编译理论上当然可以但现实里基本行不通。大多数 ARM 开发板的内存也就几百 MB 到 2GB 左右CPU 性能跟桌面级 x86 差着量级在上面跑一个完整编译任务轻则慢到怀疑人生重则直接把内存吃满。更麻烦的是很多交叉编译目标平台是精简过的嵌入式系统板上根本没有完整的开发工具链连 gcc 都没装你连“在板上编译”这个选项都没有。这时候就需要交叉编译器登场。简单说交叉编译就是让编译动作和运行平台分离我在这台性能强劲的 x86 台式机上用一个面向 ARM 架构的特殊 GCC把源码编译成 ARM 指令集的二进制文件然后把编好的可执行文件丢到开发板上直接跑。整个过程就跟你在 Windows 上打包一个 Android 应用、然后传到手机上安装是一个道理写代码和出包的环境跟最终运行的环境不是同一套。具体到工具链内部交叉编译器不只是“一个 gcc 可执行文件”它是一整套工具的集合。除了 C/C 编译器还包括汇编器 as、链接器 ld、地址转换工具 objcopy、反汇编工具 objdump以及对最终二进制格式起决定性作用的 binutils。另外还有一套目标平台的头文件和相关运行库通常叫 sysroot。程序编出来要能跑依赖的 libc、libstdc、动态链接器 ld-linux-aarch64.so.1都得跟目标板上实际环境对应上否则编译阶段过了运行阶段照样给你报错。所以“安装交叉编译器”这件事本质上是在你 PC 上复刻一套“目标板视角”的完整编译环境这个认知先立住后面再操作就不容易跑偏。2. 选工具链之前先搞清楚这三件事不夸张地说交叉编译环境装不好80% 的问题出在“选错工具链”而不是“装错软件”。动手指敲命令之前我建议先把下面三件事检查一遍省得白折腾一下午。2.1 确定目标架构32 位还是 64 位这是最直接的一个选择题。你的开发板 CPU 是 ARMv7 架构的 32 位处理器还是 ARMv8 以上的 64 位处理器决定了你要装哪一套工具链。32 位板子通常对应 arm-linux-gnueabihf-gcc 这一套64 位板子对应 aarch64-linux-gnu-gcc 这一套。最稳的办法是上板执行uname -m看一眼输出 armv7l 就老老实实用 32 位工具链输出 aarch64 就选 64 位。这里有个容易忽略的细节很多 64 位 ARM 处理器其实也能跑 32 位程序有些新手图省事直接装 32 位工具链编出来的程序在 64 位内核上也能跑但这依赖内核开启了 CONFIG_COMPAT 兼容选项而且系统里得有对应的 32 位运行库。能跑不代表正常架构不一致的程序越到后期越容易出幺蛾子。我自己踩过这个坑某次图省事用 arm-linux-gnueabihf 编了个验证程序前期测试没事后来一接数据库驱动就崩查了半天才发现是架构不匹配。所以别绕先确认架构再选工具链。2.2 浮点运算硬浮点与软浮点的区别这个点在实际安装时特别容易被忽略但又特别致命。ARM 平台的浮点运算有两种方式硬浮点是指 CPU 里有独立的 FPU浮点运算单元编译时直接生成浮点指令交给硬件去算软浮点则是把浮点运算拆成整数运算的库函数调用即使 CPU 没有 FPU 也能跑但性能会差不少。工具链命名上带 hf 的就是硬浮点不带的基本是软浮点或兼容模式比如 arm-linux-gnueabihf 和 arm-linux-gnueabi。判断你的板子支持哪种方式可以在板上执行cat /proc/cpuinfo看 Features 里有没有 neon 或 vfpv3 之类的字段有的话基本可以确定支持硬浮点直接选 hf 版本的工具链。选错的话最常见的现象是编译能过但程序一运行就报 Illegal instruction非法指令因为板子 CPU 不支持编译出来的浮点指令。另外硬浮点工具链编出来的动态库依赖特定的运行库混用软浮点库同样会导致运行时找不到符号所以这块一定要摸清楚再动手。2.3 工具链来源系统仓库、官方包还是自己动手编交叉编译器不是只能从一个地方拿不同来源各有优劣按需选就行。第一种是最省事的直接用系统自带的软件源安装比如 Ubuntu 下apt install gcc-aarch64-linux-gnu一条命令装完缺点是版本通常不会太新而且不一定跟你的目标板系统版本完全匹配。第二种是去 ARM 官方或芯片厂商像瑞芯微、全志、NXP的下载页拿现成的工具链压缩包解压就能用版本可控、兼容性更好这是我个人在生产环境里最常用的一种。第三种是自己用 crosstool-NG 或者 Buildroot 从源码编一套工具链完全定制、彻底可控但耗时动辄一两个小时新手阶段没必要碰。你可以把这三条路理解成系统源是“超市买现成快餐”官方包是“买半成品回来自己加工”自己编是“从种地开始做一顿饭”。日常学习验证、写点小 demo第一条路完全够了真正做嵌入式项目、要对接具体芯片 SDK 的时候第二条路更靠谱。下面我就把这两条路的操作分别展开。3. 最省心的安装法用发行版软件源搞定交叉编译器如果你的目标只是“快速验证交叉编译流程写得对不对”或者公司项目用的板子型号比较主流那系统软件源里的交叉编译器完全够用。这也是我给新手的默认推荐方案。3.1 Ubuntu/Debian 系的一键安装我这边用得最多的是 UbuntuDebian 系的 Kali 也一样命令大同小异。以 64 位 ARM 工具链为例核心安装命令就两条sudo apt update sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu如果你要的是 32 位 ARM 工具链把包名换成 gcc-arm-linux-gnueabihf 即可sudo apt install gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf装完之后不用手动配 PATH因为 apt 管理的工具链默认都在 /usr/bin 下。直接在终端敲aarch64-linux-gnu-gcc --version能正常输出版本号就说明装好了。我用 Kali 和 Ubuntu 都验证过实测下来稳定没问题。有一点要注意安装时最好把 binutils 一起装上。有人只装了 gcc 没装 binutils编译时提示找不到 as 或 ld其实就是汇编器和链接器缺失。GCC 本身是前端的“指挥官”真正的汇编和链接环节还得靠 binutils 这套底层的“干活的工具”两个是配合关系缺一个都不行。3.2 仓库版工具链的适配边界用系统源虽然省心但它有一个天然短板版本比较固定可能比最新版落后几个大版本。如果你只是编个 hello world、做个课后作业这完全不是问题但如果你的目标板系统里带的是新版 glibc或者你在项目里用了比较新的编译特性仓库版可能会让你进退两难。举个例子我前阵子帮朋友调一个老款 ARM 板子那块板上的系统还停留在比较老的内核版本我用新版的 gcc-aarch64-linux-gnu 编出来的程序传上去运行时报 GLIBC_2.33 not found。原因很简单编译机上工具链里的 glibc 版本比板上系统里的高编出来的二进制引用了板上没有的新符号。这种问题在仓库版工具链里尤其容易出现因为 apt 源里的工具链一般跟当前系统版本配套而不是跟你的目标板配套。另外很多交叉编译场景是要编 Linux 内核模块或驱动程序的这种时候仓库版工具链不一定能跟目标板的内核头文件完全对上更容易出现版本衍生的怪问题。所以我的建议是学习验证用 apt做真实的嵌入式项目、拿到厂家 SDK 后用官方工具链。下面这条路径才是生产环境里更常用的。4. 生产环境更常用的安装法手动部署 ARM 官方工具链前面说到的 apt 安装省事但真实嵌入式项目的“正规军”打法基本都是去 ARM 官方的工具链下载页或者芯片厂商 SDK 里拿一个预编译好的工具链压缩包自己解压、自己配环境变量。这种方式最大的优点是完全可控版本你自己选路径你自己定不会污染系统环境而且厂家 SDK 里往往还附带了跟目标板系统精确匹配的库和头文件。4.1 选版本、下载与解压我通常习惯把工具链放在 /opt 或者用户主目录下的 toolchains 文件夹里不建议直接解压到系统乱七八糟的目录中去。假设我们要在 x86_64 的 Ubuntu 上装一套 aarch64 工具链我会这样做mkdir -p ~/toolchains cd ~/toolchains # 从官方下载页拷下对应的 tar.xz 包文件名类似 # gcc-arm-10.3-x86_64-aarch64-none-linux-gnu.tar.xz tar -xf gcc-arm-10.3-x86_64-aarch64-none-linux-gnu.tar.xz下载的时候注意一个细节如果你用的是 64 位主机就选文件名里带 x86_64 的版本如果你还在用 32 位主机就得选 i686 的版本不然会因为架构对不上而解压后直接拒绝执行。这个“编译器和目标架构”的反向匹配很多人会搞反我再强调一遍工具链是在你当前电脑上运行的工具链本身的架构要跟本机 CPU匹配它生成的目标代码才跟开发板 CPU匹配。另外解压这类 .tar.xz 包建议保持在 Linux 环境里操作别把它拿到 Windows 下用解压软件解开再传回来。Windows 解压往往会丢掉可执行文件的执行权限位传到 Linux 下后你还要挨个chmod x非常麻烦。我自己吃过这个亏后来凡是 Linux 下的压缩包一律在 Linux 终端里解压。4.2 环境变量配置当前会话与永久生效下载解压只是第一步更关键的是把工具链的 bin 目录加进 PATH。否则你敲 aarch64-linux-gnu-gcc 的时候shell 根本找不到它。临时验证最直接在当前终端窗口执行export PATH$PATH:$HOME/toolchains/gcc-arm-10.3-x86_64-aarch64-none-linux-gnu/bin然后敲aarch64-linux-gnu-gcc --version验证。但这样配置只在当前终端会话生效一关窗口就完了。要永久生效需要把这一行写进 shell 的启动配置文件里。以最常用的 bash 为例echo export PATH$PATH:$HOME/toolchains/gcc-arm-10.3-x86_64-aarch64-none-linux-gnu/bin ~/.bashrc source ~/.bashrc这里我刻意给 PATH 加了$PATH:前缀意思是在现有 PATH 基础上追加而不是把原来的清空重来。有些教程图省事直接写export PATH/opt/xxx/bin结果配置完以后ls、grep 这些基础命令全都找不到了因为系统路径被你覆盖掉了。这种低错虽然好恢复但在工作环境下会吓出一身汗千万别这么写。如果你用的是 zsh对应文件是 ~/.zshrc在桌面版 Kali 里如果默认 shell 切成了 zsh改 .bashrc 是不生效的先echo $SHELL确认一下自己用的哪种 shell再决定改哪个文件。这是我装了无数回环境之后总结出的“标配动作”。4.3 多套工具链并存与切换实际项目里经常遇到一种情况手里同时有好几块板子一块 32 位 Cortex-A7一块 64 位 Cortex-A53还有一块老架构的产品需要维护多套工具链。这时候很多人的第一反应是装一套“最新最全的”或者反复修改 PATH 来回切其实都麻烦。我自己的做法是把不同工具链放在同一个根目录下保留清晰的文件名然后用软链接来管理“当前默认使用的那套”ln -s ~/toolchains/gcc-arm-10.3-x86_64-aarch64-none-linux-gnu ~/toolchains/aarch64-current export PATH$PATH:$HOME/toolchains/aarch64-current/bin以后要切换工具链只需要把软链接重新指一下确认一遍 PATH 里那个路径没变就能做到“换链不换环境变量”。如果需要更精细的控制可以用 environment-modules 这类工具做模块化加载但对大部分人来说软链接的方案已经够用了。还有一点要注意手动解压工具链后如果执行的时候提示 permission denied别急着怀疑工具链坏了先看一眼文件权限是不是真的带上了 x 标志。刚才提到过 Windows 解压丢权限的问题在 Linux 下解压一般不会有这个问题但如果是从旧压缩包或网盘里拷贝过来的很可能权限位已经丢了。这时候一行chmod x $HOME/toolchains/xxx/bin/*就能搞定。5. 装完怎么验证编译一个“ARM原生”的 Hello World环境配置完了不验证一遍等于白装。我的标准验证流程是三步先编译再看文件类型最后有条件的话直接丢到板上跑。每一步都有能说明问题的输出不是走过场。5.1 三步编译验证法先在任意目录建一个 hello.c#include stdio.h int main(void) { printf(Hello from ARM!\n); return 0; }然后调用交叉编译器编译aarch64-linux-gnu-gcc hello.c -o hello如果这条命令没有任何输出那就是好事——编译成功。接着用 file 命令看产物类型file hello正常情况下你会看到类似这样的输出hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, not stripped看到 “ARM aarch64” 就说明这确实是一份 ARM 架构的可执行文件不是本机 x86 的程序。到这一步交叉编译环境的核心功能已经验证通过了。如果你还想更进一步也可以直接在本机用./hello跑一下试试多半会报Exec format error这不是你的工具链有问题恰恰反过来证明它编出来的东西已经不是本机能直接跑的了相当于反向验证成功。真正要在板上运行把这个可执行文件用 U 盘或者网络传过去在 ARM 开发板终端里chmod x hello ./hello能看到 Hello from ARM! 就说明全链路都是通的。5.2 接入 Makefile 与 CMake实际项目里你不会每次都手动敲 gcc 命令构建系统一般用 Makefile 或 CMake。交叉编译的接入思路其实就一句话把编译器变量指到交叉编译器上其他交给构建系统自己处理。以最简单的 Makefile 为例CROSS aarch64-linux-gnu- CC $(CROSS)gcc hello: hello.c $(CC) hello.c -o hello clean: rm -f hello命令行输入make就能完成编译。如果你用 CMake需要写一个工具链文件比如 toolchain-aarch64.cmakeset(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_FIND_ROOT_PATH /usr/aarch64-linux-gnu) 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 -DCMAKE_TOOLCHAIN_FILEtoolchain-aarch64.cmake .. make这里 CMAKE_FIND_ROOT_PATH 指向的是目标平台的库和头文件搜索根目录。如果你用的是 apt 装的仓库版工具链交叉编译相关的系统库目录通常在 /usr/aarch64-linux-gnu 下如果你用的是官方手动解压的工具链工具链内部自带 sysroot通常把 CMAKE_FIND_ROOT_PATH 指到工具链目录下对应的 aarch64-none-linux-gnu/libc 即可。这一项如果不配CMake 默认会去搜本机 /usr/include 和 /usr/lib很可能引入一堆属于 x86 的东西链接阶段报各种莫名的错误到时候排查起来很头疼。5.3 嵌入式场景扩展内核模块与 Qt 交叉编译如果你不是只写个 hello world而是要做 Linux 驱动开发或者 Qt ARM 应用安装完交叉编译器之后的路径会稍有不同但核心思想一脉相承编译内核模块时必须使用与目标板内核版本一致的内核源码并且把 ARCH 和 CROSS_COMPILE 变量指过来Qt ARM 开发则需要先用交叉编译器把 Qt 库本身编一遍再用 qmake.conf 里指定的交叉工具链去编应用程序。以编译内核模块为例目标板内核源码树在主机上然后执行make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- modules这个过程会用到源码树里构建出来的工具脚本所以不能用系统自带的 gcc 去碰这块代码目录必须让 CROSS_COMPILE 指向交叉工具链前缀。这个前缀概念很好理解编译器全名是 aarch64-linux-gnu-gcc前缀就是 aarch64-linux-gnu-Makefile 会自动在后面补上 gcc、ld、objcopy 这些命令名。很多第一回编内核模块的人老是忘记把 ARCH 和 CROSS_COMPILE 一起赋上就会编出 x86 架构的 .ko加载到板上直接报 invalid module format最常见的错误来源就是这个。6. 安装过程中我踩过的坑常见问题与排查实录最后把这一路折腾中遇到过的典型问题列出来每个都是真实场景里会被反复问到的。建议先存着真遇到问题的时候对着查。6.1 高频报错速查表现象可能原因解决思路bash: aarch64-linux-gnu-gcc: command not found工具链 bin 目录没在 PATH 里或未 source检查 PATH 是否包含工具链 bin 路径执行source ~/.bashrccannot execute binary file: Exec format error工具链本体与主机架构不匹配比如 32 位主机执行 64 位程序用uname -m确认主机架构选对应版本工具链error while loading shared libraries: libstdc.so.6手工解压的老工具链依赖 32 位兼容库Ubuntu 下安装lib32z1 lib32ncurses5等兼容库或换官方最新版本编译通过但板上运行报No such file or directory动态库缺失或二进制架构与板子不符用file检查格式用readelf -d查看依赖库必要时加-static静态编译板上运行报Illegal instruction硬浮点/软浮点选错或编译参数里带了板子不支持的指令扩展确认板子 CPU Features改用匹配的工具链版本ld: cannot find crt1.o之类链接错误sysroot 路径不对找不到目标平台的启动文件和库检查工具链 sysroot 设置命令里--sysroot...或 CMake 里配 CMAKE_FIND_ROOT_PATH编出的 .ko 加载时invalid module format内核模块编译没带对 ARCH 和 CROSS_COMPILE编译命令里赋ARCHarm64 CROSS_COMPILEaarch64-linux-gnu-并确认内核源码版本一致6.2 几个容易被忽略的细节第一个细节是 32 位兼容库。某些老版本的官方工具链即使功能正常在 64 位 Ubuntu 上也会报 shared libraries 缺失因为这些工具链本体是 32 位程序运行它需要 32 位的 libc、libstdc 等。解决方式要么装兼容库要么直接换新版 64 位的工具链。现代 Ubuntu 下旧库包名可能变了搜不到的时候试试apt search lib32找替代包。这个问题在新版工具链上基本绝迹但旧项目里还是会碰到。第二个细节是静态编译这个“万能救急方案”。很多时候你把程序编好传到板上板上缺 libc 缺 libstdc各种运行时错误层出不穷。与其跟它死磕不如在编译时加-static参数把依赖库直接打进可执行文件里。代价是可执行文件会大不少但换来的是“拷过去就能跑”的省心。我做交叉编译的验证程序时经常直接用静态方式能过滤掉一大半环境干扰让问题定位更准。第三个细节是交叉编译器版本和板端 glibc 版本的匹配关系。工具链里的 glibc 版本如果比板上系统的还高编出来的程序必然报 GLIBC_XX not found反过来版本跟不上又会缺失新特性。所以拿厂家 SDK 里的工具链是最稳的因为 SDK 通常跟板端系统打包在一起版本匹配是经过验证的。用 apt 或官网通用工具链时先确认板端ldd --version或apt list --installed | grep libc里的版本心里有个底再决定用哪套工具链。最后再分享一个个人习惯每次装完交叉编译环境我会顺手把一个简单的 hello.c 编译成静态版本再用file查看产物最后把工具链版本、目标架构、sysroot 路径写到项目 README 里。这样做的好处是过两周再回头看这个项目不需要重新踩一遍环境配置的坑。交叉编译环境的“安装”本身并不复杂真正考验人的是版本匹配、架构选择和构建系统的配合。把这些细节提前想清楚你就能少走很多弯路。
返回列表