
简介这份下载资源是特定版本的GCC与GLIBC多库支持发行包适用于Linux系统管理员、嵌入式开发者和移动平台工程师。它把GNU编译器套件和GNU C运行库整合在一起并带有Linaro针对多种处理器架构的优化支持ARM、MIPS等平台且通过multilib能力让系统同时运行32位和64位程序有效规避跨平台编译中的兼容性问题。压缩包为gz格式总体积为269.46MB内含编译器可执行文件、配置脚本、头文件、库文件及文档解压后即可加入现有工具链使用。目前已有759人学习下载。借助Linaro优化嵌入式设备能够获得更好的性能表现和更低的功耗多库支持则帮助开发者维护混合架构项目。对于需要固定版本工具链来做软件移植、兼容性测试或系统构建的技术人员这份资源提供了可直接部署的完整环境省去自己下载源码并交叉编译的大量时间与精力。 前阵子清理公司一台闲置的编译服务器在/opt下面翻出一个压缩包名字是gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12.tar.gz日期戳停在2011年12月。我愣了一下才反应过来这是当年不少ARM嵌入式项目的主力交叉编译工具链。如果你现在还在维护2012年前后的BSP或者从某个老款i.MX、OMAP、高通的SDK里翻出这个包别急着当古董扔了这东西在特定场景下还真的能用而且比你现在手头的新版GCC更合适。这篇文章我就从这串文件名入手把它的来历、安装方式、multilib用法以及在现代Linux主机上运行时容易踩的坑一次说清楚。1. 文件名拆解从 gcc-4.6.2 到 linaro-multilib-2011.12 分别代表什么1.1 五个字段对应的实际含义这个文件名很长但它不是随便起的几乎每个字段都对应一个明确的工程决策。gcc-4.6.2指的是工具链里GCC编译器的版本号。GCC 4.6是2011年发布的功能版本发布于2011年10月26日。它支持C11的实验性特性但对嵌入式和内核开发来说4.6系列最大的意义是稳定。它包含了ARM后端的大量改进尤其是对Cortex-A系列的代码生成优化所以在当年Linaro选择以4.6.2为基线打上自己的补丁是很自然的事。glibc-2.13是目标平台上C库的版本。glibc 2.13于2011年1月发布当时对ARM架构做了一些针对性优化包括系统调用和字符串函数的改进。注意交叉编译工具链里的glibc不是用来在宿主机上运行的它属于sysroot也就是目标板根文件系统的一部分。你编译出来的程序运行在目标板上时动态链接的就是这套glibc。linaro是这套工具链的维护方。Linaro是2010年成立的ARM生态联合组织成员包括ARM、飞思卡尔、IBM、三星、ST-Ericsson、TI等。它的目标就是把ARM生态里分散的编译器、内核、用户空间工具统一起来集中优化。所以在2011到2013年间市面上大量ARM开发板的SDK里出现的都是Linaro的工具链。multilib是最值得关注的一个字段。它表示这套工具链内置了多种库配置变体同一个编译器可以通过不同的命令行参数产出适配不同ARM CPU变体和浮点ABI的二进制。这样做的好处是一套工具链覆盖多个产品线不用为每块板子单独下载一个编译器。后面我会专门讲它的原理和使用方法。2011.12是发布批次。Linaro当时按月发布工具链2011.12就是2011年12月的版本。选这个时间节点还有个历史原因2011年底到2012年初大量Cortex-A8、Cortex-A9的开发板集中上市BeagleBoard、Pandaboard都在用这一类工具链。1.2 为什么还要重新理解2011年的技术背景了解这个背景不是考古而是为了搞清楚一件事这套工具链到底面向什么环境设计的。2011年的ARM嵌入式世界主流处理器是ARMv7-A架构Cortex-A8单核和Cortex-A9多核已经起来但老的ARM9、ARM11设备还有大量存量。这些CPU在浮点能力上差别巨大有的根本没有硬件浮点单元有的有VFPv3但ABI选择各不相同。Linaro推出multilib版本的直接动因就是让同一个工具链既能编出在老ARM9上跑的软浮点程序也能在Cortex-A9 NEON上跑出硬浮点性能。当年的Linaro工具链还根据浮点ABI分成了两个前缀常见的是arm-linux-gnueabi-软浮点/softfp和arm-linux-gnueabihf-硬浮点。multilib版本更灵活它可能同时打包了这两类运行的库和配置文件。所以你在解压后如果看到bin目录里有一堆看起来很像但前缀不同的二进制不是打包脚本出错这就是multilib设计的一部分。2. 为什么现在还会翻到这个2011年的工具链2.1 老BSP和历史SDK的指定编译器真正让人重新去找这个老包的原因往往是BSP文档里的一行字“使用Linaro GCC 4.6.2工具链构建”。这不是厂商偷懒而是有实际工程根源的。老的板级支持包BSP通常在某个时间点冻结了工具链版本。底层代码里可能隐式依赖了旧编译器的某些行为比如内联函数处理方式、结构体布局规则、内嵌汇编的语法兼容等。拿到一块2012年的板子厂商给的SDK里U-Boot、内核、文件系统都是经过验证的换一个新版GCC重新编经常会出现一些莫名其妙的链接错误甚至编出来的内核在启动时随机崩溃。你会花大量时间排查最后发现是编译器版本的问题。这种情况下老老实实把2011.12这套工具链找出来反而能一步到位。我自己遇到过最典型的案例一个老掉牙的2.6.35内核用GCC 8去编译编译过程报了一堆未声明变量的错误——这些错误在当年GCC 4.6下全都只是warning。改代码风险大改编译器版本最稳妥。所以这个老工具链存在的最大价值就是“和那个时代保持完全一致”。2.2 新GCC编不过旧代码时的兜底方案新编译器对代码的检查越来越严格这是好事但对旧项目就是灾难。典型的报错包括未声明的函数从warning提升为error指针强转被拒绝老式KR风格的函数声明不再支持隐式int规则取消后导致大量类型错误。如果一套老代码当年是在GCC 4.6下写的那么最适合它的仍然是GCC 4.6。不是说所有老代码都不能在新编译器上编而是你没有时间去逐行改那些几年没人碰过的代码。Linaro 2011.12这个版本还带了一整套经过验证的标准库头文件可以避免头文件版本不匹配引发的连锁问题。2.3 老工具链不是万能的先确认边界任何时候我都建议先给这个工具链划清楚能力边界省得白折腾。它能做的事编译2012年前后Linux内核、U-Boot、BusyBox、老版本e2fsprogs等支持ARMv5到ARMv7软浮点和硬浮点都能处理。它做不了的事编译需要C11/14/17新特性的现代代码编译要求GCC 5.1以上的新版Linux内核源码主线内核从4.19前后开始就明确要求GCC 5.1以及为ARMv8-A或Cortex-A72这类新架构生成深度优化代码。硬要拿它编这些不是不行但你会碰到工具链根本不认识某个编译选项、某个内建函数缺失、汇编语法不兼容等一系列问题性价比极低。3. 安装与验证从解压 tar.gz 到跑通第一个 ARM 可执行文件3.1 解压与目录规划解压这个包本身没什么难度但目录规划值得说两句。mkdir -p /opt/linaro-2011.12 tar -xzf gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12.tar.gz -C /opt/linaro-2011.12解压后先不要急着配环境进去看一眼目录结构。ls /opt/linaro-2011.12不同渠道拿到的Linaro包顶层目录名可能不一样有的叫gcc-linaro有的直接就是arm-linux-gnueabihf这样的前缀名。这不影响使用你只要找到那个bin目录就行。另外一定要确认bin目录里实际有哪些前缀的编译器比如ls /opt/linaro-2011.12/bin | grep gcc$常见的前缀有arm-linux-gnueabihf-和arm-linux-gnueabi-。我建议把工具链放在/opt下而不是/usr/local这样不会污染系统目录也方便后面对多个工具链做隔离。路径里不要有中文和空格这个老编译器对路径里的空格处理非常差。3.2 环境变量配置与版本验证为了不让这套工具链的路径污染当前shell我习惯写一个专门的env.sh每次要用的时候source一下export PATH/opt/linaro-2011.12/bin:$PATH export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf-注意ARCHarm和CROSS_COMPILEarm-linux-gnueabihf-这两行对编译Linux内核是必须的对纯用户态程序不是必须但建议一起设了免得后面切来切去犯迷糊。然后验证一下arm-linux-gnueabihf-gcc -v正常能看到类似这样的输出gcc version 4.6.2 (Linaro GCC 2011.12)如果你解压后发现没有arm-linux-gnueabihf-前缀就用arm-linux-gnueabi-gcc -v验证以实际存在的文件名为准。multilib版的前缀结构在不同月份发布包里确实有过变化别死记。3.3 编译并检查第一个二进制写一个最小的hello程序但带点信息输出#include stdio.h int main(void) { printf(gcc: %s\n, __VERSION__); printf(glibc: %d.%d\n, __GLIBC__, __GLIBC_MINOR__); return 0; }编译arm-linux-gnueabihf-gcc -o hello hello.c然后最重要的一步是检查产物file hello你应该看到类似这样的输出hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3看到ARM和EABI5说明这确实是一个ARM目标平台的可执行文件。动态链接器是/lib/ld-linux-armhf.so.3说明它是hard-float ABI后面读ELF标签的时候还会用到这个信息。如果只想快速验证它能运行又不想下板子可以用QEMU用户态模拟sudo apt install qemu-user qemu-arm -L /opt/linaro-2011.12/sysroot ./hello-L指定sysroot路径也就是工具链自带的根文件系统目录这样QEMU才能找到ARM版本的glibc动态库。跑通之后会看到工具链自报的GCC版本和glibc版本说明整条链路没问题。3.4 在目标板上运行时要注意什么QEMU能跑不代表硬件板子就能直接跑。实际部署到嵌入式板子时要确认目标板的根文件系统里有这套glibc对应的动态库。如果板子上跑的rootfs版本太老动态链接器找不到匹配的so文件程序会启动失败。简单的判断方式是先查一下动态链接器readelf -l hello | grep interpreter然后确认目标板的/lib下存在对应的ld-linux*.so文件。如果板子是软件浮点ABI那你编译时就要换arm-linux-gnueabi-gcc并加上对应的浮点参数否则也会链接或运行失败。这块内容正好引出我下面要说的multilib。4. multilib 的实际用法一套编译链路覆盖多款 ARM 变体4.1 为什么需要multilib而不是多装几份工具链multilib从字面上理解就是“多版本库”。GCC在编译libgcc、标准C库等库的时候会按照不同的参数组合编译出多份然后在编译器内部记录一套匹配规则。当用户用不同的-march、-mfloat-abi、-mfpu参数编译时编译器自动挑选对应的库进行链接。为什么不直接装多份工具链假设你维护三条产品线一条用ARM926EJ-SARMv5TE软浮点一条用Cortex-A8ARMv7-Asoftfp NEON一条用Cortex-A9ARMv7-Ahard NEON。如果每一条线都装一个独立的交叉工具链环境变量、系统头文件、库文件路径会膨胀到难以维护。multilib用一套GCC、一套头文件的编译版本通过选项切换库既减少磁盘占用也减少了配置失误的可能。4.2 查看当前工具链支持的multilib配置拿到工具链后第一条命令就是查看它到底支持哪些变体arm-linux-gnueabi-gcc -print-multi-lib输出类似.; arm;marcharmv7-a thumb;mthumb不同Linaro版本的输出格式不一样但核心意思相同每个条目对应一种库组合编译器在链接时会根据你传入的架构参数决定选择哪一个。你也可以进sysroot看一下实际的多库目录结构ls /opt/linaro-2011.12/sysroot/usr/lib如果看到多个子目录比如arm-linux-gnueabi、arm-linux-gnueabihf之类那正是多套目标ABI的库文件所在。4.3 实际编译时怎么选择浮点与架构参数这是multilib用法的核心。我整理了一个常用参数对照表基本覆盖2011年那批主流ARM板子目标平台典型架构推荐编译参数老式ARM9AT91SAM9、i.MX28等ARMv5TE-marcharmv5te -mfloat-abisoftCortex-A8BeagleBoard等ARMv7-A-marcharmv7-a -mfloat-abisoftfp -mfpuneonCortex-A9硬浮点i.MX6Q等ARMv7-A-marcharmv7-a -mfloat-abihard -mfpuneon兼容性优先的ARMv7软浮点ARMv7-A-marcharmv7-a -mfloat-abisoft需要理解这三个浮点ABI选项的区别soft表示完全不用硬件浮点单元浮点运算由软浮点库模拟函数参数用通用寄存器传递。它的好处是兼容所有ARM CPU缺点是浮点性能很差。softfp表示代码里可以用FPU指令执行浮点运算但函数调用时的参数传递仍然用通用寄存器。这种模式兼容旧库又比纯软浮点快适合库文件还是软ABI、但目标CPU带FPU的场景。hard表示既用FPU指令又用FPU寄存器传参。性能最好但要求你链接的所有库都必须以hard ABI编译。这也是为什么链接的时候如果工具链和库的ABI不匹配会报出类似uses VFP register arguments这种错误。4.4 确认产物到底用了哪套ABI编译完成后如何确认你这次compile确实用对了ABI用readelf -A查看ARM属性标签readelf -A hello | grep Tag_ABI_VFP_args如果输出包含Tag_ABI_VFP_args: VFP registers说明这个二进制使用的是硬浮点ABI如果没有这一行通常就是soft或softfp。我当年有一次编出来的程序在板子上反复段错误最后发现rootfs里的库版本是softfp我却用hard参数编译了应用函数参数传递方式不一致导致栈被写坏。这个问题用readelf -A一查就能定位属于排查效率最高的一步。建议每次出表后都跑一下这个命令养成习惯。5. 现代主机运行老工具链五类高频问题的排查路径5.1 打开终端先做健康检查在64位现代Linux主机上跑这个2011年的工具链最常见的问题不是编译出错而是工具链本身根本跑不起来。所以第一个动作就是健康检查which arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc -v如果which都找不到说明PATH没配好如果找得到但执行报错那就看下面几个典型情况。5.2 “No such file or directory”不一定是文件缺失这是最迷惑人的一个坑。你明明看到/opt/linaro-2011.12/bin/arm-linux-gnueabihf-gcc这个文件存在ls -l也正常但一执行就提示No such file or directory。出现这个错误绝大多数情况不是文件不存在而是这个二进制本身的动态链接器不存在。2011年的Linaro工具链有一部分是在32位宿主机上编译的要在一个纯64位系统上运行对应的32位动态链接器/lib/ld-linux.so.2没有安装。排查链路是file /opt/linaro-2011.12/bin/arm-linux-gnueabihf-gcc readelf -l /opt/linaro-2011.12/bin/arm-linux-gnueabihf-gcc | grep interpreter如果file输出显示是ELF 32-bit LSB executable, Intel 80386readelf显示interpreter是/lib/ld-linux.so.2那问题就清楚了需要一个32位运行库环境。5.3 解决32位动态加载器缺失的问题在Debian/Ubuntu系列系统上标准做法是开启i386架构并安装32位基础库sudo dpkg --add-architecture i386 sudo apt update sudo apt install libc6:i386 libstdc6:i386 zlib1g:i386在较新的Ubuntu 22.04上libc6:i386本身没问题但一些老的辅助库可能找不到。如果遇到libncurses.so.5这类依赖缺失而软件源里已经没有了我建议别在一个新系统上死磕直接开一个容器跑老发行版更省事。用Docker拉一个debian:10或ubuntu:16.04镜像把工具链目录挂载进去docker run -it --rm \ -v /opt/linaro-2011.12:/opt/linaro-2011.12 \ -v $(pwd):/work \ debian:10 bash在容器里先安装32位库再执行arm-linux-gnueabihf-gcc -v大概率一次通过。这种隔离方式也避免了对宿主机的污染。5.4 PATH污染和“GCC升级后还是旧版本”的幻觉有一个很常见的现象你明明把新版GCC装好了一执行gcc -v显示的版本还是旧的。这通常不是没装好而是shell里PATH的查找顺序问题。which -a gcc这个命令能列出PATH里所有能找到的gcc。如果有多个而排在最前面的不是你要的那就修改PATH顺序或者直接调用完整路径。还有一个隐蔽的坑是shell的hash缓存bash会把你执行过的命令路径缓存起来即使PATH变了也用旧路径。执行一下hash -r刷新缓存。如果你发现工具链版本没变但已经source了新的环境脚本十有八九是hash在捣鬼。这个问题在编译内核时尤其烦人因为Makefile里调用的编译器路径一旦不对你会在日志末尾看到一串莫名其妙的格式错误却不知道编译器已经选错了。5.5 编译器选项和新代码不匹配即便工具链本体能跑了还有一个高频问题代码或构建系统里出现了老GCC不认识的编译选项。比如新版Linux内核源码的Makefile里用了-fstack-protector-strong这是GCC 4.9才引入的选项GCC 4.6.2根本无法识别。处理办法是退回代码版本对应的老源码或者修改Makefile去掉该选项。另一个典型是某些代码里用了GCC 4.6没有内建函数编译时会提示implicit declaration of function或undefined reference to这也是版本代差造成的。在这一类情况下我不建议强行修补。版本对齐、工具链对齐比临时改代码靠谱得多。这也是我为什么一直坚持在项目里记录工具链版本和完整参数避免后人拿到代码后用新工具链编译时踩坑。6. 这种老工具链的长期维护心得最后聊几条我自己踩过坑之后沉淀下来的维护经验。第一工具链按项目隔离。不要把Linaro 2011.12和2020年的新工具链混装在同一个PATH下。每个项目独立一个目录写清楚env.sh里面包含完整的环境变量、编译器前缀、常用的架构参数注释。这样哪怕过了半年再捡起旧项目也能快速恢复环境。第二版本记录比命令本身重要。在一个README里写下“工具链包名、解压路径、编译器前缀、sysroot路径、用于哪个产品、对应BSP版本号”。别高估自己的记性也别高估后来接手的人的耐心。我见过太多项目因为工具链版本不确定在根因分析上浪费了几天时间。第三容器是这类老工具链最好的归宿。把老工具链塞进一个固定镜像里用容器去做所有编译宿主机的系统和库随便升级也不影响。这对于维护旧产品但又不得不升级开发机的情况非常有效。第四编译内核时一定要确认是GCC 4.6.2还是其他小版本。4.6.2和4.6.3在个别内联汇编的处理上是有差异的而Linaro又在这基础上打了自己的补丁所以最稳妥的方式是保持和BSP发布时的工具链版本完全一致不要自作主张“顺手升级”。说实话2011年我还在用这套工具链调一块Cortex-A8的板子时间过得确实快。如果你现在翻到同一个包希望这篇能让你少踩几个我当年踩过的坑。工具链这东西有时候真的不是越新越好合适才是硬道理。本文还有配套的精品资源点击获取