ARTICLE DETAIL

资讯详情

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

嵌入式Linux交叉编译:aarch64-linux-gnu-gcc安装实战与报错排查

嵌入式Linux交叉编译:aarch64-linux-gnu-gcc安装实战与报错排查 干过嵌入式Linux开发的朋友多半都有过这种经历在PC上把一个程序编译得明明白白、运行得行云流水结果一拷到板子上直接甩你一脸No such file or directory或者Segmentation fault。你第一反应是权限问题chmod x试了没用再看文件明明就在那儿还在。其实根本不是文件不存在而是你手里的可执行文件从架构上就和开发板不是“同一个物种”。这就是交叉编译的日常。而我们要聊的aarch64-linux-gnu-gcc就是解决这个问题的核心工具。简单说它是一套运行在x86_64电脑上、却能生成ARM 64位AArch64架构可执行文件的编译器集合。这篇文章我会把工具链是什么、为什么必须用它、怎么装、装完怎么验证以及我踩过的各种坑一次性讲清楚。新手可以照着一步步操作老手也可以翻翻后面的报错排查说不定能省你半天时间。1. 交叉编译工具链先弄懂它是什么再谈安装1.1 为什么不在板子上直接编译非要交叉编译先回答一个经常被问到的问题树莓派、RK3588、全志H6这些板子性能已经挺强了为什么还要在电脑上费劲交叉编译不直接在板子上跑gcc编译答案是成本和效率。开发板的CPU频率、内存、存储和散热条件再好和现代x86工作站比也有明显差距。一个大型项目的源码在PC上可能几分钟编完放到板子上可能要几十分钟甚至一两个小时而且编译过程会占满板子的CPU严重影响你验证程序、调设备树、看日志的节奏。另外很多嵌入式设备的rootfs本身就是裁剪过的你甚至找不到完整的gcc、make、头文件更别提你在PC上装的依赖库了。所以交叉编译是嵌入式开发的主流做法或者说是绕不开的基本功。简单来说在A机器上编译出B机器能运行的程序这个动作就叫交叉编译A和B的CPU架构不一样。1.2 aarch64-linux-gnu这个长名字怎么读刚开始看这个字符串确实容易懵。但拆开看其实很直白aarch64目标 CPU 架构。AArch64是ARM公司64位架构的官方叫法对应ARMv8及以上的处理器像Cortex-A53、A72、A76这些还有苹果M系列虽然官方名字不同核心也是ARMv8起跳。linux目标操作系统是Linux。gnu目标C库用的是glibcGNU C Library这套工具链生成的程序要依赖glibc作为运行时库。所以aarch64-linux-gnu-gcc连起来读就是能把源码编译成“运行在Linux系统上的、AArch64架构的、依赖glibc的”可执行程序的编译器。对比看一下另外两个常见兄弟arm-linux-gnueabihf-gcc这是ARM 32位硬浮点hf即hard-float适用Cortex-A7、A8、A9这些老一点的板子arm-none-eabi-gcc则是面向裸机或者RTOS的没有Linux也不带glibc跑在MCU上。搞清楚这几种前缀你下载工具链的时候就不会选错。1.3 工具链不是只有一个gcc而是一个“团队”很多人以为交叉编译器就是一个gcc命令其实它是一整套工具集合核心有三大块编译器前端就是gcc和g负责把C/C源码翻译成ARM汇编。binutils工具集包含as汇编器、ld链接器、objcopy、objdump、readelf、strip等负责把汇编转成机器码、把多个目标文件链接成可执行文件、以及后续的格式转换和二进制分析。C/C运行库即glibc和libstdc提供printf、malloc、std::vector这些底层实现还包括ld-linux-aarch64.so.1这个动态链接器/解释器。打个比方gcc是翻译官把C语言译成外语as是抄写员把译文抄在特定的稿纸上ld是装订员把若干章节装订成册glibc是词典提供了大量现成的优美表达。交叉编译和本地编译的区别就是这个团队的参考语言是ARM架构而目标读者是ARM处理器。安装一个“完整的工具链”意味着上面这套班子都要齐活缺一个编译出来的程序到板子上都可能跑不起来。2. 安装前的准备四个问题先问自己2.1 目标板是ARM32还是ARM64这是个看似废话但特别容易翻车的问题。有人拿了块全志H3的板子跑的是32位rootfs却拿aarch64-linux-gnu-gcc编了一堆二进制拷上去全部报Exec format error。所以安装前一定先确认看板子的CPU型号和datasheet确认是ARMv732位还是ARMv8及以上通常支持64位。更直接的办法是登录板子执行uname -m。输出aarch64就是64位输出armv7l就是32位。还要注意很多SoC本身支持64位但厂商提供的rootfs仍然是32位的比如某些跑在Cortex-A53上的精简系统就是armhf。这时候你要用的是arm-linux-gnueabihf-gcc而不是aarch64那套。2.2 目标板的glibc版本是多少这个问题最初也坑过我。交叉编译器生成的动态程序依赖的glibc版本不能高于目标板上的glibc版本。你可以在板子上执行ldd --version如果板子输出的是glibc 2.31而你的PC比如Ubuntu 22.04上默认的交叉编译器链接的是glibc 2.35那你编出来的程序拷过去大概率会报GLIBC_2.34 not found。这也是嵌入式开发里最常见的跨机部署问题之一。所以安装工具链之前先了解一下目标板Linux发行版、BSP版本、glibc版本。如果板子用的是Buildroot或者Yocto维护的rootfs最好直接使用他们配套编译出来的工具链而不是一味追求宿主机上的最新版。2.3 三种获取方式怎么选交叉编译工具链的获取常见有三种渠道我直接列个表格对比方式优点缺点适合场景系统包管理器apt安装快、依赖自动处理、命令清晰版本由发行版决定不一定和板子匹配新手体验、快速验证、板子系统较新官网下载预编译工具链版本可选、有官方支持、自带sysroot手动配置PATH环境隔离要自己做指定工具链版本、和厂商BSP对齐源码构建完全可控、可定制编译耗时、依赖复杂老手定制、特殊需求、学习研究对绝大多数刚开始做嵌入式Linux应用开发的人来说apt直接装就够了如果你要和特定的BSP配合比如用NXP、Rockchip厂商提供的SDK那他们通常会指定一个ARM官方工具链版本这时走官网下载更稳。2.4 宿主机环境要求本文的实操以Ubuntu 22.04 LTSx86_64为例Debian系也适用。你只需要一个能联网的Linux环境虚拟机、WSL、实体机都行。准备阶段sudo apt update sudo apt install -y build-essential这里装的build-essential是宿主机本地编译用的保证你系统里有基础的头文件和make等工具它和交叉工具链不冲突。如果你要用qemu-aarch64在PC上模拟运行ARM程序后面我会提到安装qemu-user的方法建议一并装了调试效率会高很多。3. 保姆级安装实操两种方式任选3.1 方式一apt一行命令安装推荐新手Ubuntu/Debian的软件源里已经有交叉编译工具链的现成包安装非常简单sudo apt update sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu如果还想让宿主机自带一套AArch64的头文件和库即sysroot用于交叉编译时链接目标板的库再执行sudo apt install -y libc6-dev-arm64-cross装完以后工具链的前缀是aarch64-linux-gnu-与你需要的命令名完全一致。验证一下aarch64-linux-gnu-gcc --version能正常输出版本信息就说明安装成功。检查sysroot的时候可以运行ls /usr/aarch64-linux-gnu里面通常有include、lib目录有这些才说明目标架构的C库和头文件齐了。这套方式的核心优势就是省心apt自动把binutils、gcc、glibc和头文件一起装上路径也直接放在了系统默认搜索路径里不用配环境变量。3.2 方式二从ARM官网下载工具链适合对齐指定版本很多时候特别是使用厂商SDK时需要精确到某个gcc版本。这时候推荐从ARM官方GNU Toolchain页面下载预编译好的工具链。下载时选择AArch64 GNU/Linux对应的x86_64 Linux版本文件名类似gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu.tar.xz。拿到之后mkdir -p ~/tools cd ~/tools # 把下载的压缩包放在这里然后解压 tar -xJf gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu.tar.xz解压完把bin目录加进PATH。为了每次都能直接用可以把这行写进~/.bashrcexport PATH$PATH:$HOME/tools/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/bin然后重启终端或者source ~/.bashrc。注意官网下载的这套工具命令前缀是aarch64-none-linux-gnu-gcc不是发行版的aarch64-linux-gnu-gcc。两者目标架构一致但名字确实不同容易搞混。官网版本的优点是自带一套完整的sysroot路径就在工具链目录的内部编译时不需要指定一堆-I和-L而且你完全知道编译器是哪一天生成的出了bug也好找官方反馈。3.3 安装后的检查清单不管用哪种方式装完都建议做这几步确认# 1. 确认命令能被找到 which aarch64-linux-gnu-gcc # 2. 确认版本注意看Target这一行 aarch64-linux-gnu-gcc -v # 3. 写一个最简C文件测试编译 echo int main(){return 0;} test.c aarch64-linux-gnu-gcc -o test test.c # 4. 用file确认文件真的是AArch64架构 file testfile test输出里应该包含ELF 64-bit LSB executable, ARM aarch64, dynamically linked看到这个你的工具链就算真正“活”了。4. 第一次交叉编译让Hello World在AArch64上跑起来4.1 写代码、交叉编译、复查架构工具链装着不练等于白装。我们直接写一个hello程序#include stdio.h int main(void) { printf(Hello from AArch64!\n); return 0; }保存为hello.c然后交叉编译aarch64-linux-gnu-gcc -o hello hello.c如果编译顺利当前目录会出现一个hello。用file看一下file hello输出类似hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]..., for GNU/Linux 3.7.0, not stripped重点看ARM aarch64和dynamically linked两处。前者说明架构正确后者说明它依赖动态库需要在运行时找到动态链接器/lib/ld-linux-aarch64.so.1。4.2 没有开发板也能先跑起来qemu-user模拟运行有时候开发板不在手边或者板子还没到货又急着想验证交叉编译出来的程序能不能跑这时候可以用QEMU的用户态模拟功能。安装sudo apt install -y qemu-user qemu-user-static然后用它直接运行qemu-aarch64 -L /usr/aarch64-linux-gnu ./hello关键点是-L /usr/aarch64-linux-gnu这个参数告诉QEMU“你去找动态链接器和动态库的时候去这个目录找”。因为刚才的程序是动态链接的如果你直接运行qemu-aarch64 ./hello它会报找不到/lib/ld-linux-aarch64.so.1毕竟你的PC上本来就没有这个东西除非用静态编译。这其实是非常高效的工作流改代码、交叉编译、用qemu验证逻辑最后再统一部署到板子上。很多复杂调试不用在板子上反复烧写、重启。4.3 拷贝到开发板运行的两种做法静态与动态如果你手头就有开发板那怎么把程序拷上去运行两种方式方式一动态编译拷过去需要配套库。在当前这种方式下aarch64-linux-gnu-gcc默认动态链接产生的hello依赖glibc。你要把整个程序拷过去除了文件本体还要确保板子上有匹配版本的glibc。如果板子的glibc版本和工具链一致或更高直接./hello就能跑如果不匹配就会出现我在第5章要讲的GLIBC_X.XX not found。方式二静态编译硬是把所有库都打进去。aarch64-linux-gnu-gcc -static -o hello_static hello.c这样生成的hello_static体积会大不少从十几KB变成700KB甚至1MB但好处是丢到任何aarch64 Linux板子上都能直接跑不再依赖目标环境的库。在调试早期、或者目标板rootfs特别精简的情况下静态编译非常实用。我在实际项目里通常会动态编译一个版本放到板子上看ldd输出板子上执行确定缺什么再补齐或者临时用静态版顶一下。但正式交付时还是建议按目标板的rootfs环境做动态编译体积和启动速度都更优。5. 高频报错排查手册踩坑实录5.1 “No such file or directory”不是文件没找到是解释器没找到这是我遇到最多的第一坑。在PC上交叉编译出二进制scp拷到板子上ls -l看文件明明在chmod x也给了执行权限一执行却报bash: ./hello: No such file or directory原因在于动态链接的ELF文件头部记录了它需要的解释器路径。用刚才的hello举例aarch64-linux-gnu-readelf -l hello | grep interpreter输出是[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]意思是程序启动时系统要先加载/lib/ld-linux-aarch64.so.1这个动态链接器。如果板子的rootfs里没有这个路径或者路径不一致那内核只会简单告诉你“找不到文件”。注意它说的是找不到“解释器”不是找不到你的程序。解决办法程序改用静态编译aarch64-linux-gnu-gcc -static -o hello hello.c检查板子上是否有对应动态链接器ls -l /lib/ld-linux-aarch64.so.1用qemu模拟运行记着加-L指定根目录否则一样会报这个错。5.2 “GLIBC_2.34 not found”工具链和板子的库版本打架这个报错会直接点名./app: /lib/aarch64-linux-gnu/libc.so.6: version GLIBC_2.34 not found (required by ./app)原因前面说过PC上的交叉编译器链接的glibc版本比板子实际安装的glibc高。你用了新版的printf、memcpy实现板子上的老库不认识。常见处理思路改用静态编译绕过动态库版本检查但注意静态编译也可能需要目标板内核兼容。对glibc来说可以试试降低交叉编译器的版本比如用gcc-9替代gcc-11。最稳的方案是使用目标板BSP自带的工具链。比如Buildroot编译输出目录里往往有host/bin/aarch64-linux-gnu-gcc用它编出来的程序版本一定对得上。我自己现在做项目拿到板子第一件事就是把ldd --version和uname -a记录下来然后照着这个版本去选工具链后面能少一堆破事。5.3 “cannot find -lxxx”缺目标架构的库和头文件编译大型程序时常常需要链接-lz、-lpthread等库但交叉编译时系统不会自动去找宿主机的/usr/lib/x86_64-linux-gnu而是从工具链的sysroot里找aarch64版本的库。如果没找到报错长这样aarch64-linux-gnu-gcc: error: zlib.h: No such file or directory 或 /usr/bin/ld: cannot find -lz解决办法分几步来确认你是不是真的需要这个库有些库可以通过不用、或者换实现来避免。用apt安装aarch64版本的开发包最典型的是前面提到的libc6-dev-arm64-cross。其他库要自己去源里找比如apt search aarch64 | grep zlib或者直接安装libz-dev:arm64这种带架构限定符的包。如果依赖的是私有SDK库把它放到某个目录后编译时手动指定aarch64-linux-gnu-gcc app.c -I/path/to/arm64/include -L/path/to/arm64/lib -lz -o app顺带提一下编译时如果看到关于GLIBCXX_的报错那多半是libstdc版本不匹配处理思路和glibc一致优先考虑静态编译或对齐BSP工具链。5.4 “relocation truncated to fit”代码段太大跳不出去了这个报错看起来比较吓人其实原理不复杂aarch64-linux-gnu-ld: main.o: in function func(): (.text0x10): relocation truncated to fit: R_AARCH64_JUMP26 against symbol xxxARM的跳转指令在几十MB到128MB范围内才有效如果整个可执行文件太大比如你把好几个超大静态库合在一起链接器就找不到一种能跳到目标的指令编码方式。常见的修正方式编译时加上-fPIC或-fPIE生成地址无关代码函数之间跳转走额外寄存器计算不再依赖近距离跳转。加-mcmodellarge让编译器采用更宽的地址模型代价是一丢丢性能损耗。检查是不是把静态库一股脑全链进来了有时候用-Wl,--gc-sections加-ffunction-sections -fdata-sections能去掉大量无用代码。这种问题在我自己写裸机代码、或者把多个第三方静态库拼到一起时最容易出现。嵌入式设备内存本来就紧张架构和链接模型都得提前规划。5.5 “unrecognized command-line option -m64”32位习惯的误伤很多做x86开发的老手编译64位程序习惯了加-m64。换成交叉编译aarch64时顺手就写成aarch64-linux-gnu-gcc -m64 -o app app.c结果cc1: error: unrecognized command-line option -m64在aarch64工具链里64位是唯一目标没有-m32/-m64这种切换开关等价的做法是使用-mabilp64。不过用默认配置编译就是64位所以这个选项大多数时候根本不用加。如果你是从x86迁过来的习惯尽量改掉直接用工具链前缀就足够了。同样地看到-marcharmv8-a、-mfpuneon这些选项时也要确认对应的编译器和架构支持不能盲目照抄网上的旧参数。5.6 其他高频坑权限、sysroot、软链接除了上面列的几个还有一些零零碎碎的坑权限问题交叉编译出来的文件拷到板子上执行时报Permission denied。先chmod x ./app再检查目标目录是不是noexec挂载比如有些板子的/tmp和/var是以noexec方式mount的程序放那里永远跑不了。sysroot没配对用官网工具链时如果没注意--sysroot编译器会去找宿主机系统里的库然后混用路径导致链接到x86的库。安装完毕后用aarch64-linux-gnu-gcc -print-sysroot看看输出路径是否正常。软链接和strip板子上空间紧张时很多人习惯对二进制执行strip减小体积。但如果误strip了动态库比如libc.so.6那程序运行时会直接嗝屁。strip只对可执行文件和你自己编译出来的库做系统库不要碰。6. 进阶整合用CMake和VSCode把交叉编译变得顺手一点6.1 用CMake管理交叉编译命令行直接敲gcc固然简单但一个正经项目少说也有几个源文件加上第三方库和编译选项CMake几乎成了行业标配。要让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) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)三点说明CMAKE_FIND_ROOT_PATH指向AArch64的sysroot这样CMake查找头文件和库时才会去正确位置。MODE_PROGRAM NEVER表示查找工具时仍用宿主机的工具比如cmake自身、代码生成器。MODE_LIBRARY/INCLUDE ONLY表示库和头文件只在目标sysroot里找防止误用x86的库。使用时cmake -DCMAKE_TOOLCHAIN_FILEtoolchain-aarch64.cmake .. makeCMakeLists.txt里的普通写法不用改CMake在配置阶段就把它当成“Linux aarch64”的环境来处理。6.2 在VSCode里配置交叉编译嵌入式开发常用VSCode写代码。想让C/C插件的智能提示IntelliSense和编译行为都指向交叉编译器可以编辑项目中.vscode/c_cpp_properties.json{ configurations: [ { name: AArch64-Linux, includePath: [ ${workspaceFolder}/**, /usr/aarch64-linux-gnu/include ], defines: [], compilerPath: /usr/bin/aarch64-linux-gnu-gcc, cStandard: c11, cppStandard: c17, intelliSenseMode: linux-gcc-arm64 } ], version: 4 }关键点是compilerPath指向交叉编译器的绝对路径intelliSenseMode设为linux-gcc-arm64。这样写代码时__aarch64__之类的宏会被正确定义头文件解析也不会乱飘。如果你的项目用CMake还可以配置CMakePresets配合工具链文件一键切换本地编译和交叉编译。个人觉得这一套组合拳下来嵌入式开发体验已经非常接近在PC上写业务代码了。6.3 给常用命令起个别名如果你同时维护多种架构的项目命令前缀不一样容易打错。可以在~/.bashrc里加几行aliasalias arm64-gccaarch64-linux-gnu-gcc alias arm64-gaarch64-linux-gnu-g alias arm64-cmakecmake -DCMAKE_TOOLCHAIN_FILE$HOME/cmake/toolchain-aarch64.cmake虽然只是简单封装但实际用起来确实能避免低级失误尤其是调试长时间命令行的时候少打几个字心里也不烦躁。最后说几句实在话这套工具链我用了很多年从最早在虚机上折腾ARM32的arm-linux-gnueabihf到现在满屏的aarch64其实核心逻辑没变过先认清目标板是什么架构、什么C库版本再选工具链然后让编译、链接、部署路径保持一致。很多人一上来直接apt install最新版编完拷到老rootfs上报错又开始到处找解决办法。其实一开始花五分钟确认板子的glibc版本后面的坑至少能少一半。如果你现在遇到的是工具链安装都装不上的问题大概率不是命令问题而是源的问题换国内镜像源再sudo apt update重复一遍第3章的步骤基本就顺了。工具链一旦跑通后面迎接你的就是更刺激的uboot移植、内核裁剪和驱动调试了先把这第一步走稳。
返回列表