Ubuntu 20.04安装aarch64-linux-gnu-gcc交叉工具链
如果你手头有一台跑着 Ubuntu 20.04 的电脑又正好想把代码编译成 ARM64 架构的可执行文件那你大概率绕不开一个叫 aarch64-linux-gnu-gcc 的交叉编译工具链。我第一次接触它是在给一块 ARM64 开发板写用户态程序的时候。本机明明是 x86_64用普通 gcc 编出来的程序拷到板子上终端直接报 Exec format error后来才意识到必须用交叉编译工具链。这篇文章会把 Ubuntu 20.04 上安装和验证 aarch64-linux-gnu-gcc 的完整流程讲透包括命令、原理、常见错误和解法。不管你是做嵌入式开发、智能硬件还是刚接触 ARM 服务器只要想在 x86 主机上产出 ARM64 程序这篇文章都适合你。很多人以为交叉编译是件特别复杂的事其实只要理解一个核心x86 主机上运行编译器生成的目标文件是 ARM64 指令集。编译器本身是 x86 程序却知道怎么生成 ARM64 代码。整套流程我已经反复跑过好几遍下面按实际操作的顺序写能少踩一个坑就少踩一个坑。1. 先搞清楚aarch64-linux-gnu-gcc 到底是什么帮你解决什么问题1.1 交叉编译是怎么一回事交叉编译这个概念很多人一听就觉得高深其实特别直白。你平时在 Ubuntu 20.04 上敲 gcc main.c生成的是一个 x86_64 架构的可执行文件因为你的电脑 CPU 是 x86_64。可如果你的目标平台是一个 ARM64 开发板比如树莓派 4B、RK3399 或者其他采用 ARMv8 架构的单板电脑板子上的内核和用户空间都是 ARM64 的你用 x86 的 gcc 编出来的程序板子压根不认。这就好比你给一个只讲粤语的人写信却用了普通话的发音规则对方当然看不懂。交叉编译要做的事情就是在 x86 主机上运行一个懂得输出 ARM64 指令的编译器让编译产物能直接拷到目标板子上执行。aarch64-linux-gnu-gcc 就是这样一个交叉编译器。它的名字本身就是一套信息aarch64 是目标架构linux 是目标操作系统gnu 表示编译器工具链来自 GNU 项目gcc 是 C 编译器。大家平时说的交叉编译工具链通常不只包含 gcc还有 g、binutils、C 运行库头文件等一整套东西。所以要真正用起来往往要把配套包一起装齐只装一个光杆编译器会非常难受。1.2 为什么我建议在 Ubuntu 20.04 上用 apt 安装可能有人会想GCC 不是开源的嘛直接下源码自己编不就行了理论上当然可以但实践中我非常不建议。GCC 的构建依赖非常多包括 GMP、MPFR、MPC 等数学库还要选择 target tuple配置源码树编一次可能要几十分钟甚至几个小时中间报一个错又得从头排查。相比之下Ubuntu 20.04 的软件源里本来就有 aarch64 交叉编译相关的二进制包一条 apt 命令就能把所有组件装好依赖关系也会自动处理。还有一类选择是去下载 Linaro 等预编译工具链解压之后把 bin 目录加进 PATH 也能用。问题是这类工具链版本通常比较固定更新不如 apt 源及时而且一旦和 SDK 要求的 GCC 版本不一致很容易出现兼容性报错。我常规的做法是先试 apt除非项目 SDK 明确指定了某个版本的工具链才去用 Linaro 或者官方的预编译包。1.3 适合场景与必备基础聊完原理说说这东西有什么用。最常见的场景就是给 ARM64 开发板写系统级或用户态程序比如修改内核、编写驱动、移植应用、跑机器学习推理框架。另一个场景是 ARM 服务器代码的本地快速构建如果你有一台 x86 的 CI 机器但最终部署环境是 ARM64 云服务器交叉编译可以让构建过程不必跑到目标机上执行。此外很多嵌入式 SDK 在编译驱动模块或动态库时也会通过环境变量直接调用这套工具链。你需要的基础并不高能在 Ubuntu 终端里敲命令、会用 sudo、大概知道源码构建是什么。不需要你之前编译过 GCC 或参与过嵌入式底层开发。不过有一个概念必须清楚交叉编译的产物不能直接在主机上运行如果需要运行验证要么拷到目标板要么借助 QEMU 用户模式模拟。后面我会专门演示怎么验证。好了概念铺垫到这里下面开始实际安装。2. 安装前确认环境系统版本、架构与源状态2.1 用几条命令拉起环境信息在动手之前先确认你的系统确实是 Ubuntu 20.04并且是 x86_64 架构。这步看起来多余但真能拦住不少人。有的机器虽然是 Ubuntu但已经升级到了 22.04或者本身跑在 ARM64 的树莓派上那么安装方式就完全不同了。我用的是下面几条命令lsb_release -a uname -m sudo apt update第一条显示发行版版本第二条显示当前 CPU 架构第三条把软件源索引拉到最新。如果 uname -m 输出的是 x86_64那这篇文章的流程完全适用。如果输出的是 aarch64说明你已经在 ARM64 设备上了这种情况直接本地编译不需要交叉工具链。如果 apt update 有红字报错先别往下装排查一下网络或者源配置。这里多说一句不管你是物理机上的 Ubuntu、双系统里的 Ubuntu、WSL2 里的 Ubuntu还是从一个 Ubuntu 20.04 镜像启动的容器只要内部系统版本和架构正确后面的命令都一样。WSL2 虽然运行在 Windows 上但内部确实是一个完整的 Linux 环境apt 逻辑与物理机完全一致只是注意别把编译产物直接塞到 /mnt/c 下的 Windows 文件系统里访问速度慢还容易触发权限问题。2.2 看懂工具链相关包名Ubuntu 20.04 的包管理对交叉编译有一整套命名规则看懂之后能少很多猜谜时间。核心包是 gcc-aarch64-linux-gnu装完会提供 aarch64-linux-gnu-gcc。如果你还要编 C 代码就装 g-aarch64-linux-gnu。如果发现头文件或 C 库缺失那多半是缺了 libc6-dev-arm64-cross这个包提供了 ARM64 版本的 C 标准库头文件和静态库。搜索相关包可以用apt-cache search aarch64 | grep -E linux-gnu|arm64你会看到很多带 -cross 后缀的包它们都是针对不同架构的交叉编译库。Ubuntu 20.04 的命名规律很统一照着“架构-系统-工具”的思路找就行。比如 libstdc-10-dev-arm64-cross 就是 ARM64 架构的 C 标准库开发文件。不要看到一大堆包名就慌实际核心安装就那几个。2.3 确认 sudo 权限与磁盘空间apt 安装需要写系统目录所以一定要有 sudo 权限。你可以先跑一下 sudo whoami如果返回 root 就说明权限没问题。磁盘空间方面交叉编译工具链加库文件大概会占用 1GB 左右不算夸张但如果你用的是精简安装的 Linux建议先 df -h 看一眼 /usr 所在分区剩余空间别装到一半提示 No space left on device。我见过有人卡在这里到处问为什么安装报错结果一看磁盘满了。如果你以前装过其他交叉工具链或者系统里已经有 aarch64-linux-gnu-* 命令也要先确认版本。命令 aarch64-linux-gnu-gcc --version 能显示版本号如果版本特别老后续编译新代码可能会因为缺某些特性而失败。此时最好先清理旧版本再安装当前源里的新版本避免多个工具链互相干扰。3. 快速安装 aarch64-linux-gnu-gcc 的完整步骤3.1 一条命令安装核心编译套件环境没问题下面就是整个文章最核心的一段操作。我的习惯是把 gcc、g 和 C 库一次性装齐避免后面编译到一半发现少了东西。在 Ubuntu 20.04 上执行sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu libc6-dev-arm64-cross这个命令会同时安装交叉编译器、交叉汇编器、交叉链接器以及 ARM64 的 C/C 运行时库。安装完成后/usr/bin 目录下会多出一批 aarch64-linux-gnu-* 前缀的命令包括 gcc、g、as、ld、objcopy 等。你用 ls /usr/bin/aarch64-linux-gnu-* 就能看到。这里解释一下为什么 libc6-dev-arm64-cross 要一起装交叉编译器生成目标代码后链接时需要去找 ARM64 版本的 C 标准库。没有它最简单的 printf 程序都会链接失败。如果你只需要 C 编译器不编 C可以只装 gcc-aarch64-linux-gnu。但我还是建议顺手把 g 也装了因为你永远不知道项目后面会不会引入 C 模块到时候再补装一次也不麻烦但一次装齐能节省重复等待下载的时间。3.2 验证安装是否成功装完之后不要急着写代码先做两步验证。第一步看命令能不能找到which aarch64-linux-gnu-gcc aarch64-linux-gnu-gcc --version正常会返回类似 aarch64-linux-gnu-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 这样的信息。第二步用 file /usr/bin/aarch64-linux-gnu-gcc 查看编译器自身你会发现它其实是 x86_64 架构的程序但它生成本文要用的目标架构由编译参数决定。这两个验证能帮你快速确认是环境问题还是工具链版本问题。要注意的是如果你是在普通用户登录的状态下验证不需要重新启动系统。apt 安装的二进制在 /usr/bin 下默认就在 PATH 里。如果哪个终端窗口提示 command not found可以先执行 hash -r 清理 shell 的命令缓存或者重新打开一个终端窗口再试。3.3 编译并验证第一个 ARM64 程序现在写一个最基础的 hello world把整个流程跑通。在终端建立文件cat hello.c EOF #include stdio.h int main(void) { printf(hello aarch64\n); return 0; } EOF然后用交叉编译器编译aarch64-linux-gnu-gcc -o hello_aarch64 hello.c file hello_aarch64如果看到输出里有 ELF 64-bit LSB executable, ARM aarch64说明交叉编译成功。对比一下用本机 gcc 编出来的文件会是 ELF 64-bit ... x86-64。这个差异一眼就能看出来也是判断工具链是否起作用的最直观方法。很多教程到这一步就结束了但我建议你再多做一步在同一个目录下用 gcc 编一个本机版本然后用 file 分别查看两个产物这样你能直观理解“交叉”在产物层面的体现。3.4 用 qemu-user 运行 ARM64 二进制做快速验证在没有开发板的情况下怎么确认这个产物逻辑正确可以用 QEMU 用户模式模拟。Ubuntu 20.04 下安装 qemu-user-staticsudo apt install -y qemu-user-static ./hello_aarch64只要 binfmt_misc 内核模块没有被禁用安装后就能直接运行 ARM64 用户态程序终端会打印出 hello aarch64。这个技巧在 CI 或本地测试里特别实用不用反复拷贝文件到板子上。需要注意的是qemu-user 只是模拟指令集不模拟硬件外设。如果程序直接操作寄存器或者依赖特定设备文件那还得放到真实目标板上测试。如果你在执行 ./hello_aarch64 时遇到 Permission denied先 chmod x hello_aarch64。如果遇到 Exec format error最大的可能是你的内核没有加载 binfmt_misc 模块可以临时执行 sudo modprobe binfmt_misc让它重新挂载 /proc/sys/fs/binfmt_misc然后再试。3.5 设置编译选项和 sysroot 的补充说明交叉编译时默认的头文件和库文件路径与本地编译不同。编译器会用 --sysroot、-I、-L 等参数来定位。apt 安装的交叉工具链默认的 sysroot 是 /usr/aarch64-linux-gnu你可以通过 aarch64-linux-gnu-gcc -print-sysroot 查看。如果只是编译简单 C 程序默认值就够用。但如果是要编译带第三方库的项目比如想链接 openssl 或 libcurl就得单独指定头文件和库。常见做法是加aarch64-linux-gnu-gcc -I/path/to/arm64/include -L/path/to/arm64/lib -o app app.c如果项目自带一个完整的 ARM64 rootfs那么用 --sysroot/path/to/rootfs 更省心编译器会优先在 sysroot 里搜索所有依赖。新手最容易犯的错误是把 -I/usr/include 这种本机路径手动加进交叉编译命令结果编译器同时混用不同架构的头文件和库报出一堆莫名其妙的错误。最好的习惯是让编译器自己处理 sysroot只在确实需要额外第三方头文件时再用 -I 指过去。4. 常见错误与解决方案实录4.1 apt 找不到包 gcc-aarch64-linux-gnu很多新手在 Ubuntu 20.04 上执行 sudo apt install gcc-aarch64-linux-gnu结果得到 E: Unable to locate package。一看就是没执行 sudo apt update或者软件源里没有开启 universe 组件。gcc 交叉工具链在 Ubuntu 官方源中主要是 universe 组件的软件包如果你的 source.list 只开了 main自然找不到。解决方式先执行 sudo apt update再尝试安装。如果还是找不到可以用 sudo apt install -y software-properties-common 安装完整工具然后执行 sudo add-apt-repository universe再次 sudo apt update最后装包。如果执行 add-apt-repository 之后源还是没有变化可以手动检查 /etc/apt/sources.list 和 /etc/apt/sources.list.d/ 下的文件确认有没有被注释掉的条目。不过我要提醒一句不要为了省事去网络上随便找第三方源官方源里的工具链已经够用了。4.2 command not found 或工具前缀带有版本号安装后执行 aarch64-linux-gnu-gcc --version却显示 command not found。这种情况多半是 shell 缓存或者 PATH 出了问题。其实 apt 安装的二进制在 /usr/bin 下/usr/bin 默认就在 PATH 中。但如果你用了非交互 shell或者之前手动把 PATH 改坏了可以执行 export PATH/usr/bin:$PATH 再看。另一种情况是系统里同时存在多个 GCC 版本命令被放在 aarch64-linux-gnu-gcc-9 这样的名字下需要检查ls /usr/bin/aarch64-linux-gnu-*如果确实只有带版本号的命令建一个软链接就能解决问题sudo ln -s /usr/bin/aarch64-linux-gnu-gcc-9 /usr/bin/aarch64-linux-gnu-gcc。这一步在 Ubuntu 20.04 上一般用不到但如果你从旧系统迁移过或者手动配置过 alternatives可能会遇到。4.3 编译时提示找不到 stdio.h编译 hello.c 时如果报 fatal error: stdio.h: No such file or directory那基本可以断定是交叉 C 库的头文件没装。Ubuntu 把 ARM64 的 C 标准库放到 libc6-dev-arm64-cross 包里它会把头文件安装在 /usr/aarch64-linux-gnu/include把库放在 /usr/aarch64-linux-gnu/lib。所以解法就是sudo apt install -y libc6-dev-arm64-cross同理如果 C 头文件缺失就要检查 libstdc-dev-arm64-cross 是否安装。记住交叉编译环境里本机的 /usr/include 不应该出现在搜索路径里否则就算头文件找得到里面的机器相关定义也会导致链接错误。这个问题的排查顺序应当是先确认交叉编译器存在再确认交叉库开发包存在。4.4 undefined reference toprintf 或__libc_start_main有时候头文件能通过但链接时报 undefined reference to printf或者更奇怪地指向 __libc_start_main。这通常是 C 运行库缺失或者链接器没有到 ARM64 的 libc 路径里找库。交叉编译器默认的 sysroot 是 /usr/aarch64-linux-gnu如果这个目录下没有 libc.so.6链接必然失败。解决办法还是安装 libc6-dev-arm64-cross。如果已经安装可以用 aarch64-linux-gnu-gcc -print-search-dirs 查看编译器实际搜索的库路径确认是否指向了本机 x86 库目录。如果指向错了多半是编译器选取出现问题这时候检查是否调用的是真正的交叉 gcc而不是本机 gcc。你可以在 Makefile 里强制指定 CCaarch64-linux-gnu-gcc并在编译命令前加 echo Compiling with $(CC) 做输出确认。别小看这个操作很多项目默认使用 CCgcc即使你装了交叉工具链Makefile 没改的话照样用错编译器。4.5 Exec format error拷到开发板后无法运行这个错误我在文章开头就提到了是最典型的“没用交叉编译”的信号。Exec format error 的意思是目标文件格式不对内核不认。执行 file 你的程序如果显示 x86-64说明你用了本机 gcc如果显示 ARM aarch64 但还是这个错误那就要检查目标板系统是不是 32 位内核、程序是否缺少动态链接器。比如目标板是 ARMv7但你编译成了 ARMv8也会出错。另外记得给程序加可执行权限chmod x 程序不要笑真的有不少人忘掉这一步。如果你的目标板是 32 位内核但实际处理器是 64 位你需要的是 arm-linux-gnueabihf 这套工具链而不是 aarch64-linux-gnu-gcc。刻意区分这一点能省下大量排查时间。可以在目标板上执行 uname -m 看输出如果是 armv7l 而不是 aarch64说明系统运行在 32 位模式。4.6 目标板上运行提示 libc.so.6 not found即使交叉编译成功拷到开发板跑起来还是可能报 error while loading shared libraries: libc.so.6: cannot open shared object file。这是因为你动态链接了目标板没有的 glibc 版本。开发板系统比较老而你的工具链带了太新的 GLIBC_XX就会出现这种问题。几个思路一是改用静态编译aarch64-linux-gnu-gcc -static -o app app.c所有 C 库都打进可执行文件不用再依赖目标板 glibc二是为目标板准备匹配的 sysroot三是如果实在要动态链接就要确保目标板的 libc 版本不低于编译环境的版本。在嵌入式开发里这是经典痛点。静态编译虽然能解决动态库依赖但它不是万能的。比如你用了 NSS、dlopen 动态加载等机制静态编译反而会引入新的问题。遇到这类情况我建议优先想办法拿到目标板对应的 rootfs然后用 --sysroot 做配套编译。这样编译出来的程序在目标板上跑动态库版本才是真正对齐的。4.7 常见错误速查表现象原因解决办法找不到包apt 源未更新或缺少 universe 组件apt update启用 universecommand not found未安装或 PATH 异常安装检查 /usr/bin查看带版本号命令缺少 stdio.h缺 libc6-dev-arm64-cross安装 libc6-dev-arm64-crossundefined referenceC 库未安装或链接路径不对安装交叉库检查 sysrootExec format error用了本机编译器/架构不对file 检查用 aarch64-linux-gnu-gccchmod xlibc.so.6 not found目标板 glibc 不兼容静态编译或匹配 rootfs链接第三方库报错-L 路径指向 x86 库改为 arm64 库路径这张表是我实际排错时最常用到的。每次出问题先对着现象看一遍能少走很多弯路。5. 深入一点CMake 交叉编译配置与替代工具链5.1 用 CMake 管理交叉编译项目如果你的项目不是简单单文件而是用了 CMake那就需要写一个 toolchain 文件否则项目还是会默认调用本机 gcc。下面这个文件是我常用的最小配置保存为 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 目标系统是 Linux第二行声明目标架构是 aarch64。编译器一行明确指定交叉编译器。CMAKE_FIND_ROOT_PATH 非常关键它会告诉 CMake 在找第三方库和头文件时只在 /usr/aarch64-linux-gnu 目录下找不要去找本机 x86 路径。三个 MODE 字段分别控制程序、库、头文件的查找范围程序仍然可以在本机 PATH 里找这样 find_package 能找到本机安装的开发工具库和头文件则限定在目标 root 目录。使用方法是cmake -B build -DCMAKE_TOOLCHAIN_FILEtoolchain-aarch64.cmake然后正常 make。这样做的好处是项目里不会到处硬编码路径所有交叉编译配置集中在一个文件里维护起来非常方便。如果你的项目还定义了自定义的第三方库路径可以在 toolchain 文件里继续加 set(CMAKE_PREFIX_PATH /path/to/arm64/deps) 之类的字段。5.2 留意 SDK 自带的工具链版本很多硬件厂商会给自家 ARM 平台提供一套完整的 SDK里面通常自带交叉工具链。这种情况下我不建议再另装一套 apt 工具链因为 SDK 可能针对某个 GCC 版本做过大量兼容性测试自己换一套容易在编译内核模块时出现 ABI 不匹配。如果必须用 SDK 自带工具链记得把工具链的 bin 目录绝对路径写到编译参数里比如make CROSS_COMPILE/path/to/sdk/toolchain/bin/aarch64-linux-gnu-这里 CROSS_COMPILE 的值是带横杠的前缀make 会根据它自动拼接 aarch64-linux-gnu-gcc、aarch64-linux-gnu-ld 等命令。此时可以用 file 看一下 SDK 里的 aarch64-linux-gnu-gcc如果显示是 x86_64 的可执行文件说明这个工具链可以在 Ubuntu 20.04 上运行如果显示其他架构就要检查是不是拿错了版本。5.3 如果 apt 还是不够没找到的库怎么办交叉开发经常遇到依赖库缺失的问题比如 openssl、libpthread、libbluetooth 等apt 不一定都提供 arm64 交叉版。对这种情况我的建议是先检查 apt-cache search 库名 | grep arm64如果官方有 xxx-dev:arm64那可以试试用 dpkg --add-architecture arm64 加上 apt install 库:arm64 的方式把目标库装进系统然后通过 --sysroot 或 CMake 根路径去引用。不过这个方法要谨慎它会把 arm64 库和本机 x86 库混在同一套 dpkg 管理里偶尔会发生文件冲突。更稳妥的办法是单独建一个 rootfs或者利用 SDK 内置的 sysroot不要轻易动系统级 dpkg 状态。我自己的习惯是优先用官方源和 SDK如果两者都没有再考虑从源码交叉编译那个库。源码交叉编译也没多恐怖无非是执行 ./configure --hostaarch64-linux-gnu --prefix/usr/aarch64-linux-gnu然后 make install。真正需要小心的是那些还依赖一堆子库的项目最好先看依赖树。6. 我在实际操作中踩过的几个坑6.1 检查编译链接用的到底是哪个 gcc我最开始做交叉编译时有个项目 Makefile 里写死了 CCgcc我加了一堆环境变量也没用。后来才想到先执行 make clean再 make V1把每条编译命令完整打印出来看看实际执行的编译器是不是 aarch64-linux-gnu-gcc。这个方法以后排查任何交叉编译问题都通用。打印出来如果还是 gcc就去改 Makefile 或者传入 make CCaarch64-linux-gnu-gcc。很多所谓“工具链没生效”的问题最后查下来都是这个原因。6.2 头文件路径不要混用另一个坑是把 -I/usr/include 手动加进了交叉编译命令里结果 stdbool.h、stdint.h 这类基础头文件出现了“无法兼容的架构定义”。后来我把本机包含路径全部去掉只保留 sysroot 和项目自己的 include 目录问题立刻消失。交叉编译的铁律是头文件、库文件、可执行文件必须来自同一目标架构混用任何一层都会产生奇怪的错误。如果你发现程序在链接阶段报“找不到某个符号”但头文件明明已经引入了先不要怀疑编译器先用 aarch64-linux-gnu-nm 查看库文件里的符号确认库本身是不是 arm64 版本。本机 x86 的 .so 文件虽然也能被链接器读但架构不匹配最终产物多半加载不了。6.3 不要盲目加 -static静态编译确实能解决很多运行库缺失的问题但如果你准备链接 glibc 的 NSS 模块、进行 dlopen 动态加载或者使用某些需要与目标板内核交互的库静态编译会带来新的坑。比如 -static 之后的程序虽然不依赖目标板 glibc但 DNS 解析、用户信息查询可能因为 NSS 特性失效。所以 -static 只适合简单的命令行工具如果项目会用到系统能力还是老老实实准备正确版本的 sysroot。这一点我在一个网络工具项目里栽过跟头最后改用 SDK 提供的老版本工具链才解决问题。工具链这件事装起来不难难的是让项目持续用对工具链。我最后再分享一个小习惯在项目根目录建一个 setenv.sh把 CC、CXX、CFLAGS、LDFLAGS 这些变量统一放进去source 之后再 make。这样换项目、换工具链时只需要改一个文件不会因为忘记设置环境变量而重新踩一遍坑。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →