ARM交叉编译工具链gcc-linaro安装与实战指南 简介面向嵌入式Linux开发的ARM交叉编译工具链版本为Linaro GCC 5.5.0可在x86_64架构的PC主机上编译生成arm-linux-gnueabihf目标代码其中gnueabihf表示基于ARM EABI并采用硬浮点接口适合Cortex-A系列等带有浮点单元的处理器平台适用于基于ARM处理器的各类嵌入式产品开发。适用于嵌入式软件工程师、驱动开发人员与系统集成开发者能有效解决官方下载入口分散、版本陈旧或环境配置繁琐等常见问题是搭建嵌入式交叉编译环境的省心之选也可避免直接在目标板上编译带来的耗时与资源压力。资源包为148.88MB的GZ压缩文件体积适中便于离线备份或复制到不同开发机复用上游目前未提供文件总数与详细文件类型明细暂不作罗列已有819人浏览学习热度表现不错。解压后包含完整的GCC交叉编译工具组覆盖编译器、汇编器、链接器及调试辅助工具并支持C14等标准特性可为嵌入式应用、Linux内核模块、引导程序等开发提供一致的编译环境。在持续集成或团队协作时锁定该版本还能避免工具链差异导致的编译异常提升项目的可复现性与交付效率。1. 为什么ARM板子上的编译任务最后都被我搬回了x86_64台式机最早接触嵌入式Linux开发的时候我也犯过所有新手都会犯的错——直接把代码拷到开发板上插上电源就开始make。结果一块双核A9、主频1GHz出头的板子编译一个稍微像样点的应用就要等十几分钟编译内核更是动辄两三个小时。中途散热跟不上还会过热降频时间直接翻倍。后来换到x86_64的PC上用交叉编译工具链同一个内核源码十几分钟就能编完这种性能差让我彻底倒戈。当然性能只是表面原因。真正的核心逻辑在于交叉编译本身就是嵌入式Linux开发的标准工作流。所谓交叉编译是指在一个架构的机器上编译生成另一个架构的可执行文件。我们的宿主机host是x86_64目标机target是ARM两者指令集完全不同不能用PC上自带的gcc去编ARM程序必须用专门为x86_64编译ARM设计的编译器这就是gcc-linaro这类交叉工具链存在的意义。用生活中的场景来理解你写中文合同但客户要英文版你需要一个懂中英双语的翻译而不是直接让只会说中文的人硬写英文。x86_64的gcc只会说x86指令arm-linux-gnueabihf-gcc则同时懂x86它运行在PC上和ARM它生成ARM指令。gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf.tar.gz这个压缩包就是一套完整的交叉编译工具链里面包含了编译器、汇编器、链接器、C/C标准库头文件和预编译库文件解压即用不需要你手动去拼装各个组件。这套东西最适合谁老实说覆盖面很广做树莓派、BeagleBone这类ARM单板开发的玩家搞全志、瑞芯微、NXP i.MX系列方案的驱动工程师做车机、工控、IoT网关的嵌入式软件开发者只要你的目标平台是32位ARM Linux基本上都绕不开它。2. 拆解gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf.tar.gz这一长串命名很多人在论坛里看到这种长文件名就头大其实命名规则非常规律看懂了一段就全通了。这个压缩包的名称可以拆成六个信息段字段含义gcc工具链基于GCC编译器linaro由Linaro组织编译发布5.5.0GCC的版本号是5.5.02017.10Linaro发布的年月即2017年10月x86_64运行在x86_64架构的宿主机上arm-linux-gnueabihf编译目标平台描述也叫target triplet先说gcc-linaro这个前缀。Linaro是ARM生态里非常活跃的一个工程组织它专门做ARM Linux相关的工具链和内核优化发布的工具链在ARM平台上做过针对性优化和大量回归测试比你自己用crosstool-NG搓出来的工具链要靠谱得多。很多开发板的官方BSP直接捆绑Linaro工具链所以gcc-linaro在嵌入式圈子里几乎成了交叉编译器的代名词。版本号这边GCC 5.5.0属于GCC 5系列2017年10月发布默认支持C11和C11标准对老内核4.x及更早的兼容性很好。这也是我至今仍会在一些老项目的维护场景里翻出这个版本的原因——它编老内核、老应用不会冒出莫名其妙的告警和那些年的BSP配合得严丝合缝。后文我会专门比较版本新老的选择问题。最劝退的其实是最后那段arm-linux-gnueabihf。我把它掰开揉碎来讲这部分读懂了你就能举一反三看懂八成以上的ARM工具链命名。arm目标CPU架构是ARM准确说是32位ARM指令集兼容ARMv7-A及更老的Cortex-A系列处理器。linux目标操作系统是Linux编译器生成的目标文件格式、链接方式都按Linux的规则来。gnu目标系统使用GNU C库也就是glibc。这是Linux发行版最常见的C运行库功能全、兼容性好。eabi嵌入式应用二进制接口规范了函数调用规则、寄存器使用方式等保证编译出的二进制可以正确运行。hfhard float硬浮点。关键就在这个后缀上它表示目标系统使用硬件浮点单元FPU来处理浮点运算对应的还有软浮点soft float。eabi和hf连在一起准确的写法是gnueabihf它代表的是ARM嵌入式Linux使用的硬浮点EABI规范。如果你的目标板子CPU带VFP或NEON浮点单元系统镜像也是用硬浮点工具链构建的那必须用带hf的交叉编译器反过来如果板子没有FPU、系统是软浮点构建的用了hf工具链编出的程序跑起来会直接报Illegal instruction。选错浮点模型是嵌入式开发里最隐蔽也最致命的坑之一。再看倒数第二段x86_64它限制了这套工具链只能跑在64位x86的Linux宿主机上。你把它拿到Windows上解压双击里面的gcc是跑不起来的。这一点很多人下载时没注意后面会讲到对应的检查方法。3. 安装这个工具链解压、路径、环境变量一步到位这套工具链的安装没有什么神秘的本质上就是解压一个tar.gz压缩包然后配置PATH环境变量指向它的bin目录。但这两步里都有一些值得讲究的细节踩过坑的才知道。3.1 下载和解压放置目录有讲究解压前先确认一件事你的宿主机是x86_64架构的LinuxUbuntu、Debian、CentOS、Deepin这些都行。在终端跑一下uname -m输出是x86_64就对了。解压本身不复杂tar -xzf gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf.tar.gz解压出来会得到一个gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf目录里面有bin、lib、libexec、share、aarch64-linux-gnu等子目录。这里我建议的放置路径是/opt或者/usr/local而不是家目录~/toolchain。为什么第一交叉编译工具链是全项目组共用的基础工具放在系统级目录便于多用户共享第二有些老的Makefile或构建脚本会硬编码工具链的绝对路径/opt/gcc-linaro-xxx比/home/username/toolchain/gcc-linaro-xxx通用得多脚本拿到别的机器上也不用改。我自己习惯的做法是解压到/opt后建一个不包含版本号的软链接sudo mv gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf /opt/ sudo ln -s /opt/gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf /opt/gcc-linaro这样每次升级工具链只需要改软链接指向Makefile里写/opt/gcc-linaro/bin永远不会失效。3.2 PATH环境变量配置的两种方式解压完之后工具链还不能直接用因为系统不知道去哪找arm-linux-gnueabihf-gcc。最入门的做法是在当前终端export一下export PATH/opt/gcc-linaro/bin:$PATH arm-linux-gnueabihf-gcc -v这样只在当前终端窗口生效切换了终端又要重新export比较烦。实测下来最省心的方案是写一个独立的脚本放在/etc/profile.d/下面每次登录自动加载适合我这种同时在多个项目里切来切去的人sudo vim /etc/profile.d/arm-linux-gnueabihf.sh写入如下内容export PATH/opt/gcc-linaro/bin:$PATH保存后重新打开终端或者source /etc/profile生效。如果你只给自己用不想影响系统全局那就把同样的export写进~/.bashrc即可。有一个细节要提醒环境变量里PATH追加的顺序有讲究。如果系统里同时装了arm-none-eabi系列等其他ARM工具链它们的部分工具名如gdb、objcopy是重叠的PATH里谁在前面谁就生效。为了避免误用别的工具链我通常把交叉工具链放最前面。3.3 验证不光要看版本号配置完成后先跑arm-linux-gnueabihf-gcc -v能看到版本信息就说明基本正常。但很多教程到这一步就停了我建议再多看两眼arm-linux-gnueabihf-gcc -v输出末尾会显示gcc version 5.5.0 (Linaro GCC 5.5-2017.10)确认是Linaro版本而非发行版自带老gcc。另外看一下bin目录下的完整工具集ls /opt/gcc-linaro/bin/你会看到arm-linux-gnueabihf-gcc、arm-linux-gnueabihf-g、arm-linux-gnueabihf-ld、arm-linux-gnueabihf-objcopy、arm-linux-gnueabihf-readelf、arm-linux-gnueabihf-strip等一系列工具。这些工具各司其职objcopy用来把ELF转成BIN或hex格式刷机readelf用来查看ELF文件的头信息strip用来移除调试符号减小体积。后面调试和发布都会用到别只盯着gcc看。4. 第一次交叉编译验证工具链的完整流程装完就要验证它能真正产出能在ARM板子上跑的程序。这里我走一个完整流程从写代码到板子上运行中间每个验证步骤都说清楚。4.1 hello world从PC到ARM板先写一个最简单的测试程序保存为hello.c#include stdio.h int main(void) { printf(Hello from ARM Linux!\n); return 0; }然后用交叉编译器编译arm-linux-gnueabihf-gcc -o hello hello.c这一步会在当前目录生成hello这个二进制文件。关键验证点来了用file命令查看它的格式file hello输出应该是hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped看到ARM和EABI5说明这确实是一个ARM架构的ELF可执行文件交叉编译工具链工作正常。接下来把这个文件传到ARM板子上运行。传文件的方式很多如果板子有网络scp最直接scp hello user板子IP:/home/user/如果没有网络用U盘或者读卡器都行。在板子终端里加执行权限并运行chmod x hello ./hello能看到Hello from ARM Linux!输出整套工具链就确认可用了。4.2 动态链接和静态编译的选择上面编译出来的是动态链接的ELF运行时会依赖目标板上的动态链接器和共享库。你注意看file输出里的interpreter /lib/ld-linux-armhf.so.3这就是动态链接器的路径它负责在程序启动时加载libc等共享库。如果你的板子是完整跑着某个Linux发行版通常自带了这套运行库程序拷过去直接能跑。但很多嵌入式板子的rootfs是裁剪过的可能没装完整的glibc运行环境这时动态链接的程序就会启动失败具体报错后文会详细讲。临时解决办法是改用静态编译把libc直接编进可执行文件里arm-linux-gnueabihf-gcc -static -o hello_static hello.c对比一下两个文件大小就明白了ls -lh hello hello_statichello可能只有7KB左右hello_static则可能到700KB甚至更大。静态编译的好处是拷过去就能跑、不依赖目标机上任何库坏处是体积膨胀、内存占用偏高而且如果用了getpwnam、NSS这类依赖运行时动态加载的库静态编译会遇到一些奇奇怪怪的链接问题。我的建议是先试动态链接跑不起来再静态不要一上来就无脑-static。4.3 别忘了看工具的帮助信息很多人在这个阶段就开始埋头写大项目了我反而建议你先花十分钟把bin目录里的工具挨个看一眼帮助信息arm-linux-gnueabihf-objcopy --help arm-linux-gnueabihf-readelf --help不必全记住有个印象就行。后面处理bootloader镜像、分析程序崩溃问题、裁剪二进制体积时这些工具都是救命稻草。我遇到过好几回同事拿着Windows下的什么工具在折腾ELF其实readelf一条命令就能看明白的事。5. 实战中绕不开的三个坑连报错信息都帮你整理好了工具链配置好helloworld也跑通了但这只是开始。真正做项目时你会碰到各种各样的问题下面三个是Linaro工具链用户问得最多、也最容易卡住人的我把排查过程完整写出来。5.1 在板子上运行报错No such file or directory程序拷到板上后执行./hello结果提示bash: ./hello: No such file or directory文件明明就在当前目录权限也有x为什么会报找不到文件这个坑我在博客评论区见过无数次了。根因动态链接器路径不对。前文提到的interpreter /lib/ld-linux-armhf.so.3如果板子的rootfs里不存在这个文件内核在加载ELF时就会报这个错。排查链路分三步先在板子上检查动态链接器是否存在ls -l /lib/ld-linux-armhf.so.3如果文件不存在说明rootfs用的是软浮点系统或者没有安装glibc运行库。再回头确认一下板子系统的浮点模型在板子上跑readelf -l hello | grep INTERP看输出里的interpreter路径然后对照板子上的/lib目录。解决办法通常是两种要么升级/修复rootfs补齐glibc要么改用-static静态编译生成不依赖动态链接器的程序。5.2 链接第三方库时找不到头文件交叉编译项目里引用zlib、pthread等第三方库时最常见的报错是fatal error: zlib.h: No such file or directory根因你用的头文件搜索路径还是宿主机默认的/usr/include那里面的头文件是给x86_64架构准备的。排查链路先用arm-linux-gnueabihf-gcc -v打印搜索路径确认交叉工具链的头文件路径确实指向/opt/gcc-linaro/arm-linux-gnueabihf/include。然后检查第三方库是否编译了ARM版本。如果你是在PC上直接apt install zlib1g-dev装的库那是x86_64的静态库交叉链接器根本用不了。正确做法是手动下载zlib源码用交叉编译器重新编一份ARM版本CCarm-linux-gnueabihf-gcc ./configure --prefix/opt/arm-libs make make install然后在你的项目Makefile里显式指定头文件和库路径CFLAGS -I/opt/arm-libs/include LDFLAGS -L/opt/arm-libs/lib这样就不会再撞上拿x86库去做ARM链接这种张冠李戴的错误了。5.3 编译老的Linux内核时报错拿这个2017年的工具链去编近几年的新内核源码你可能会碰到各种语法或代码生成层面的报错比如某些内联汇编不认识某些寄存器、某些编译器内建函数行为变化等。反过来说用太新的工具链编老内核也可能遇到老内核代码对编译器行为的假设不再成立导致的编不过。根因GCC和内核之间存在版本耦合特定时期的内核源码对GCC的最低/最高版本有要求。排查链路先看内核源码根目录的Documentation/process/changes.rst里面明确列出了每个内核版本推荐的GCC版本范围。Linaro 5.5.0这个版本我实测过编Linux 4.4、4.9、4.14这些经典LTS内核很顺手编5.x内核就吃力了。如果你维护的是老项目出现这类报错时不要急着换编译器先想想是不是内核版本和GCC版本不匹配再决定是降内核还是升工具链。5.4 关于版本选择的一点个人经验回到标题里的5.5.0-2017.10这个版本到今天确实不算新了。但对嵌入式项目而言稳定压倒一切功能洁癖要不得。如果项目BSP本来就是基于2017-2018年发布的那就用同时期的Linaro工具链这套组合经过千万次构建验证出问题的概率最低。如果非要升级我再提一句选型方法去Linaro官网的release页面挑跟你目标平台和内核版本匹配的版本。判断标准很简单能把你手上的内核和应用源码完整编过、编出的系统在板子上稳定运行就是好工具链。数字高不代表更好用很多新版本工具链编老代码时反而会多出一堆warning和error。6. 最后再分享一个多版本切换的小技巧实际工作中一个Linux宿主机上同时存在好几套交叉工具链是很常见的事。我电脑的/opt下现在就躺着4个不同版本的Linaro工具链分别服务不同时期的内核和BSP项目。一开始我也往PATH里一次性全加进去结果每次都要用which确认当前用的是哪个版本稍微开小差就编错了目标平台。后来我想了个土办法不做全局PATH改为在需要构建时才source对应的环境脚本。每个工具链目录里放一个env.sh内容大致是这样export PATH/opt/gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf/bin:$PATH export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf-进入项目目录后先source /opt/gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf/env.sh再去执行make或者编译脚本。这样工具链的绑定关系一目了然切项目时不会串味。这个方法我用了好几年简单有效推荐你试试。本文还有配套的精品资源点击获取