资讯动态

ARM架构与交叉编译实战:从工具链选型到Qt移植踩坑指南

发布时间:2026/9/12 10:42:18 来源:尧图企业网站定制
1. 先从“为什么要有 ARM 架构”说起做嵌入式或者 Linux 底层开发的人迟早会撞上“ARM”这个词。可能你已经写过不少 C 语言跑过好几个 Linux 发行版甚至能在 x86 台式机上轻松编译内核但一旦切换到 ARM 开发板之前那些“直接 ./configure make”的招数全部失灵程序要么跑不起来要么报一堆奇怪的找不到头文件的错。我当年第一次拿到一块 ARM 开发板时就吃了不少这样的亏今天这篇 DAY17 的笔记就是想把这套东西掰开揉碎讲清楚。ARM 全称是 Advanced RISC Machine最早由英国 Acorn 公司设计后来成立了 ARM 公司专门做 IP 授权。注意这里的关键词是“授权”ARM 自己不直接生产芯片而是把指令集架构和 CPU 核心设计图纸授权给各芯片厂商像高通、三星、联发科、海思、全志、瑞芯微这些做手机和嵌入式芯片的厂商买来 ARM 核心后再加上自己的外设、GPU、ISP、视频编解码器打磨成一颗完整的 SoC片上系统。所以市面上你见到的 RK3588、全志 H6、树莓派的 BCM2711本质上都是“基于 ARM 架构的 SoC”只是具体实现和外围资源各不相同。为什么嵌入式领域几乎被 ARM 统治单说一个功耗比就很能说明问题。x86 处理器走的是高性能大核心路线功耗动辄几十上百瓦一块 ATX 电源轻松 500W 起步而 ARM 核心走的是精简指令集加低功耗设计路线单片能效比通常能做到每瓦数 GFlops 级别的浮点性能尤其适合电池供电、密闭环境、无风扇散热的硬件平台。再加上 ARM 授权模式灵活、SoC 集成度高一颗芯片里从 CPU、GPU、NPU 到各种 IO 控制器全包圆BOM 成本可以压得很低所以从手机、路由器、智能家电到工业控制器、汽车 ECU几乎都能看到 ARM 的身影。不过光理解了“ARM 功耗低、集成度高”还不够真上手开发的时候第一个绕不开的问题就是我手上的电脑是 x86 的怎么给 ARM 板子编译程序这就引出了今天真正的主角——交叉编译。我见过不少初学者在这块儿卡住所以在展开工具链的细节之前想先把 ARM 架构的几个核心概念梳理一遍否则后面看任何交叉编译配置都像在看天书。2. ARM 架构基础地址空间、寄存器、大小端与总线很多人有个误解觉得“ARM 就是一样的我在树莓派上能跑换一块 RK3568 肯定也能跑”。这句话只对了一半。ARM 是一个庞大的家族从 Cortex-A 应用处理器、Cortex-R 实时处理器到 Cortex-M 微控制器指令集、寄存器数量、内存映射方式都有差异。最容易踩坑的几个点我列在下面。先看地址空间设计。Cortex-A 系列支持 MMU内存管理单元地址空间通常是 32 位或者 64 位Linux 跑在这种核心上没有任何问题。Cortex-M 系列则往往不支持完整的 MMU取而代之的是 MPU内存保护单元地址空间直接映射到 Flash、SRAM 和外设寄存器所以裸机程序或者 RTOS 才是它的主战场。你在 x86 上写一个 printf 到处跑换到 Cortex-M 上会发现 printf 重定向都要费半天劲这就是典型的内存模型不同造成的差异。再看寄存器组。ARM 32 位模式下通用寄存器有 R0-R15 共 16 个其中 R13 是栈指针 SPR14 是链接寄存器 LRR15 是程序计数器 PC。64 位 AArch64 模式下变成 X0-X30 共 31 个通用寄存器X30 是 LRSP 是独立的栈指针寄存器。这套设计跟 x86 那种“通用寄存器数量少、专用寄存器多”的思路差别很大。有一点特别容易迷惑新手ARM 里 PC 是可以直接读取和修改的所以某些老式病毒扫描技术基于 x86 EIP 不可直接访问设计的防护逻辑在 ARM 上不一定有效。我们在写汇编或做底层调试时必须习惯这种“PC 可见”的模型。大小端也是个经典坑位。ARM 核心在大端和小端之间通常可以通过配置引脚或者寄存器切换但绝大多数 SoC 默认跑小端模式Linux 更是强制使用小端。很多人在做嵌入式移植时程序突然不正常排查到最后发现是某些外设寄存器库写死了大端地址然后数据全乱了。我的建议是统一全部用小端除非你确认外设手册明确要求大端否则不要碰这个开关。像 DDR 控制器、DMA 描述符这类模块大小端一旦切错调试信息基本全是乱码极其痛苦。还有总线结构。在一颗 ARM SoC 内部CPU 核心通过 AMBA 总线跟各种外设相连。老一点的 AHB、APB新一点的 AXI、ACE、CHI优先级、带宽、延迟特性都不一样。很多人看 SoC 数据手册时看到 NIC400、NIC450 之类的东西不知道是什么这其实是 ARM 官方的互连总线生成器。你有多少个 CPU 核心、多少个外设主从口就用它生成一套符合 AXI 协议的总线矩阵把 DDR 控制器、GPU、USB、Ethernet MAC 这些部件统统挂上去。这里提一句热搜词里那个“arm socrates 生成 nic400”Socrates 就是 ARM 用来图形化配置 SoC 总线矩阵和时钟域的工具生成完以后会输出 RTL 代码和验证环境做芯片验证的工程师天天跟它打交道但应用层开发基本不用关心。如果感觉这节内容有点抽象我打个比方ARM 架构就像一栋商品房的框架户型、强弱电井位置都是标准化的但每个开发商装修出的效果千差万别。你要写的应用程序就像搬家只要开发商提供的房间编号寄存器地址和你手里的户型图外设手册对得上你就能正常摆放家具对不上或者编号规则搞混了程序就直接崩了。交叉编译的意义则是让你在 A 小区的样板间x86 开发机里把家具预先组装好再拉到 B 小区ARM 板子上摆进去这中间的门道就是我们接下来要讲的重点。3. 为什么非要交叉编译直接在板子上编译不香吗先回答一个新手的灵魂拷问我可以把 ARM 板子接上键盘显示器用板载 Linux 执行 gcc 编译源码那为什么还要在 x86 电脑上搞一套交叉编译环境原因总结下来就三个字快、全、便。快是指编译速度。ARM 开发板受制于功耗和成本CPU 性能通常远不如台式机或者服务器。你拿一块四核 Cortex-A53 的板子编译一个稍微大点的嵌入式程序比如 Qt 或者 OpenCV耗时可能是 x86 工作站的五六倍。我做树莓派上跑 Qt 的测试项目时在树莓派 4B 上编译 Release 版本整整跑了四十分钟后来切到 x86 上用交叉编译两分钟就出了产物。要是你的 CI 流水线每天跑几十次构建这个时间差距绝对是致命的。全是指工具链和库的完整度。交叉编译工具链通常自带完整的 sysroot里面包含了目标系统对应的 libc、libstdc、内核头文件、各种基础库。你可以直接在开发机上安装高性能版工具链、静态分析工具、调试器甚至利用 ccache 做编译缓存。如果都在目标板上编译每次都要手动装一遍依赖装错一个版本就得推倒重来环境管理会非常痛苦。便是指开发迭代效率。日常开发中你改一行代码、重新编译、再上传到板子执行如果每轮都要在板子上进行考虑到板子的 IO、SSD 性能、网络传输速度整个循环会变得冗长。而有了交叉编译工具链你在开发机上编译完直接 scp 或者 NFS 挂载到板子上运行几秒钟就能完成一次迭代。这个体验差异谁用谁知道。当然还有一个特殊情况值得说明某些 ARM 板子因为架构特殊、发行版软件源里缺少预编译包只能自己处理。比如我在某台 ARM 服务器上跑 Mariadb 或者 MySQL 的时候官方源的包版本太旧直接源码编译这种“三板斧”方案依赖的编译工具和库又没装全而且在板子上源码编译效率极差。这种场景下宿主机是 x86 或者是同一 ARM 架构都能做交叉编译只要工具链版本和 sysroot 正确出来的产物一样能跑。只因为你是在 x86 电脑上做就叫“交叉编译”如果你在一台 ARM 服务器上为另一块 ARM 板子编译同样是交叉编译只是架构差异小一些而已。4. 交叉编译工具链选型翻车重灾区先亮我自己的习惯不折腾特殊环境时首选目标平台官方提供的工具链。树莓派有官方 cross-compile 工具链Ubuntu 有 arm-linux-gnueabihf 和 aarch64-linux-gnu 系列包Buildroot 和 Yocto 也内置了工具链生成流程。所谓“工具链选型”核心要解决的是两件事一是目标机器的架构对不对二是库版本和编译器版本匹配不匹配。4.1 架构和 ABI 参数ARM 32 位平台常见的是 armhf 和 armel 两种 ABI。armhf 代表硬件浮点使用 VFP/NEON 指令来执行浮点运算性能好很多armel 是软件浮点方案适用于早期不支持硬件浮点的芯片。现在绝大多数 ARM 32 位板子都是 armhf选择 gcc-arm-linux-gnueabihf 基本没错。到了 64 位 ARM也就是 aarch64情况简单很多Devuan/Ubuntu/Debian 等发行版都提供 aarch64-linux-gnu-gcc 工具链直接 apt 安装即可。图省事的话在 x86 Ubuntu 下执行下面这行命令就能搞定 64 位 ARM 的基础交叉编译环境sudo apt update sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu如果你要做 32 位 ARM 的编译那么安装的是sudo apt install gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf装完之后编译器命令分别是 aarch64-linux-gnu-gcc 和 arm-linux-gnueabihf-gcc交叉编译时用 CMAKE_C_COMPILER 或者 CC 环境变量指定即可。这里有一个坑很多教程会推荐拉到 Linaro 或者 ARM 官网下载预编译工具链老版本工具链一般是 32 位可执行文件在纯 64 位 Ubuntu 上跑不起来。常见报错是 bash: ./arm-none-eabi-gcc: cannot execute binary file: Exec format error。解决办法是安装 32 位运行库sudo dpkg --add-architecture i386 sudo apt update sudo apt install libc6:i386 libncurses5:i386 libstdc6:i386如果用的是 Ubuntu 24.04 这类新版本系统可能连 libncurses5 都装不上了只能容器化处理或者改用新工具链。我后来养成了一个习惯新项目一律用系统源里的工具链不得已才用老工具链而且一定会放在 Docker 里跑避免污染主机环境。4.2 ARM 编译器 5.06u7 和老代码的恩怨热搜词里出现了“arm compiler 5.06u7 download”“arm 编译器 v5.06 update 7 (build 960) 该版本未安装”这套东西是 ARM 官方提供的“Arm Compiler 5”用于 ARM 裸机开发、RTOS 以及一些老项目。Arm Compiler 5 和开源的 GCC 体系完全是两码事。它使用的是 armcc / armclang 这样的编译器前端不是 gcc它默认的 C 库是 ARM 自己的微库不是 glibc它的编译选项、汇编语法和 GCC 有诸多细微差别。很多芯片厂商的老 SDK 和伪代码都基于 Arm Compiler 5 编写如果你用 GCC 去编译往往会出现各种内联汇编语法错误、attribute 写法不兼容、库函数行为不同的问题。这就是为什么很多人下载 Arm Compiler 5.06u7 并安装后依然报“该版本未安装”因为开发者根本不知道要配合什么许可证、什么环境变量才能把它跑起来。Arm Compiler 5 的安装一般需要 Keil MDK 或者 Arm Development Studio 套件。你下载的独立安装包即使装好运行 armcc 也往往需要 license这个 license 通常由 ARM License Manager 统一管理。如果你在网上找的是那种绿色版或者破解版我不建议用于项目因为编译器版本不一致导致的诡异行为会浪费你大量时间。更合理的做法是如果目标工程要求 Arm Compiler 5.06u7就装上 Keil MDK 5 或者 Arm Development Studio按官方文档申请评估 license然后把编译器路径写进项目的构建脚本。如果工程允许用 GCC那就直接用 arm-none-eabi-gcc 这类开源工具链少掉一层麻烦。4.3 host 工具链和 target 工具链不要混用这是交叉编译里最隐蔽也最致命的问题。你可能会想“我用系统自带的 gcc 编译出来的可执行文件放到 ARM 板子上当然跑不了这我懂。那我用 arm-linux-gnueabihf-gcc 编译一个 hello world应该没问题了吧”这里其实还藏着一个更深层的坑如果编译过程中调用了某些构建工具比如 cmake、autoconf、make 的某些辅助程序这些程序必须在宿主机上能够执行而不是编译成 ARM 架构。所以交叉编译时“CC/CXX 环境变量指向交叉编译器”只是第一步你还得检查构建系统生成的各种中间工具是否以宿主架构运行的。比如你在交叉编译一个用 CMake 管理的项目CMake 本身是 x86 程序它生成的测试用例如果被设置成直接执行那就会报 Exec format error。正确做法是设置 CMAKE_CROSSCOMPILING 相关策略或者用交叉编译工具链文件告诉 CMake 哪些测试可以在宿主机执行。5. 从 Hello World 到百级依赖交叉编译的实际流程5.1 一个标准的最小交叉编译流程假设我们要为 64 位 ARM 的嵌入式 Linux 设备编译一个“你好交叉编译”程序。在开发机上先写好源代码// hello.c #include stdio.h int main(void) { printf(hello, cross compile!\n); return 0; }然后执行aarch64-linux-gnu-gcc hello.c -o hello_arm这时候用 file 命令查看产物file hello_arm正常输出会显示 ELF 64-bit LSB executable, ARM aarch64说明生成的是 ARM 架构的可执行文件。把它传输到 ARM 板子上chmod x 后直接 ./hello_arm就能看到输出。这个过程看着简单但背后有几个容易踩的点第一动态链接问题。如果 hello.c 只用了 libc那么一个正常的嵌入式 Linux 系统都带 glibc直接运行没问题。但如果你的目标系统是精简的 BusyBox 环境没有 glibc那么必须用-static参数静态链接或者把依赖的 .so 一并拷贝过去。我经常看到有人把交叉编译产物传上去后报No such file or directory但 ls 看文件明明存在。这时候十有八九是动态链接器的路径和板子上的不一致。用aarch64-linux-gnu-readelf -l hello_arm查看 INTERP 段就能看到它期望的动态链接器路径比如 /lib/ld-linux-aarch64.so.1。如果板子上这个文件不存在自然跑不起来。第二编译选项不匹配。CPU 微架构差异会导致非法指令错误。比如你在开发机上用默认选项编译可能生成了包含较新指令如 LSE 原子指令的代码而目标板子的 A53 内核不支持。实际项目里armv8-a 的板子一般不会有问题但在 armv7-a 上就得小心 NEON/VFP 版本差异。5.2 带额外依赖的交叉编译sysroot 是核心单文件交叉编译只是开胃菜嵌入式项目往往依赖第三方库比如 OpenSSL、libuuid、zlib。交叉编译这些库的前提是要有一个目标板的 sysroot。sysroot 就是目标系统根文件系统的副本里面包含目标 ARM 平台的头文件和 .so/.a 库文件。你在编译自己程序时让交叉编译器去 sysroot 中寻找头文件和库而不是去宿主机自带的 x86 系统路径找这样才能保证链接到的库是 ARM 版本的。以 OpenSSL 为例如果你在开发机上直接执行./config make生成的库是 x86 的拿到 ARM 板子上根本用不了。正确做法是./Configure linux-aarch64 --cross-compile-prefixaarch64-linux-gnu- --prefix$SYSROOT/usr make make install这里的关键是告诉 OpenSSL 的构建系统所有编译命令都要加 aarch64-linux-gnu- 前缀并且安装路径要指向目标板的 sysroot。如果忽略了 cross-compile-prefixconfigure 阶段虽然能跑但生成的 Makefile 里 CC 还是系统 gcc最后产物架构完全不对。我见过最经典的翻车是把宿主机 /usr/include 下的 x86 头文件目录直接加到交叉编译的 include 路径里。这样编译时能找到头文件但链接阶段会去 x86 库路径找 .so要么链接失败要么“侥幸”成功但运行时各种崩溃。所以记住一句话交叉编译时必须让编译器和链接器只看 sysroot不要额外添加宿主机的 include/lib 路径。5.3 CMake 交叉编译文件常规写法现在的 C/C 项目基本都用 CMake 管理。CMake 的交叉编译依赖一个工具链文件内容大概长这样# arm_linux_toolchain.cmake set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g) set(CMAKE_FIND_ROOT_PATH /opt/arm-sysroot) 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 -B build -DCMAKE_TOOLCHAIN_FILEarm_linux_toolchain.cmake cmake --build build其中 CMAKE_FIND_ROOT_PATH_MODE_PROGRAM 设为 NEVER 很关键它让 CMake 在宿主机上查找程序使用比如 find_package 找 find 命令、编译器内部工具时保持宿主架构而查找库和头文件时只在 sysroot 中找。如果三个 MODE 都设成 ONLY很多 find_program 操作会找不到宿主程序配置阶段就会失败。5.4 静态库与动态库怎么选交叉编译时静态库和动态库的选择策略跟宿主机编译很不一样。嵌入式系统往往空间紧张拷贝 .so 过去如果漏掉依赖还得用 ldd 在板子上反复检查麻烦得要命。所以在工程允许的前提下我经常选择静态链接关键依赖尤其是 OpenSSL 这类安全敏感的库静态链接还能避免运行时库版本和编译时不一致导致的诡异问题。静态链接的做法是在编译命令里加-static或者链接所有 .a 文件。但静态链接也不是万能的NSSName Service Switch这类依赖动态加载模块的库静态链接会带来很大麻烦比如 getaddrinfo 解析不出来。所以项目里的库是否静态链接要看实际情况不能一刀切。6. Qt 交叉编译实战从 5.12.10 到 5.9.9 的趟坑记录Qt 是嵌入式 Linux 应用开发最常见的 UI 框架之一。热搜词里出现了“qt5.12.10交叉编译”“qt5.9.9交叉编译(openssl)”“ubuntu-20.04 安装 qt 交叉编译环境”说明很多人在这个环节被反复折磨。我拿 Qt 5.12.10 在 Ubuntu 20.04 上交叉编译到 ARM 开发板为例把流程捋一遍。先明确一个概念交叉编译 Qt 核心库本质上是编译两套东西。一套是在宿主机上运行的“宿主 Qt”用于运行 moc、uic、rcc 这些 Qt 的工具程序另一套是目标板用的“目标 Qt”才是最终烧到板子上的库。Qt 官方用 “host build” 和 “target build” 区分这两个概念交叉编译时必须先构建一个宿主 Qt再用宿主 Qt 的工具配合交叉编译器去构建目标 Qt。不过现在 Qt 提供了 -xplatform 参数能够使用交叉编译方式构建目标 Qt同时让构建系统自动处理 moc 等工具的宿主版本省事很多。我常用的简易流程如下。先安装依赖与交叉编译工具链sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu libxcb-*-dev build-essential然后拿到 Qt 源码包执行 configure。需要注意的是 Qt configure 选项非常多千万别直接拿来就用我在实际开发时会根据是否要支持 OpenSSL、是否要裁剪 Qt 模块来调整参数。以下是一个能跑起来的最小配置示例./configure -prefix /opt/qt5.12.10-arm \ -xplatform linux-aarch64-gnu-g \ -release \ -opensource -confirm-license \ -nomake examples -nomake tests \ -no-opengl \ -no-gtk \ -skip qtwebengine \ -openssl-linked \ -I$SYSROOT/usr/include \ -L$SYSROOT/usr/lib几个参数解释一下-xplatform linux-aarch64-gnu-g 告诉 Qt 使用哪个平台的构建规则这个文件位于 qtbase/mkspecs/devices/linux-aarch64-gnu-g 下如果目标板子很特别还可以自己 cp 一份来改。-no-opengl 表示不编译 OpenGL 模块避免因为 EGL/GLES 头文件缺失而卡死。板子如果支持 GPU后面再单独配置 EGLFS、LinuxFB 或者 Wayland。-openssl-linked 表示链接 OpenSSL如果板子上需要 HTTPS 证书、SSL 通信这个选项很有用。但前提是你已经按照上一节交叉编译好了 OpenSSL 并装到 sysroot 中。-skip qtwebengine 是因为 WebEngine 模块在交叉编译时对架构依赖太重编译耗时极长没特殊需求就先跳过。configure 通过之后接着执行make -j$(nproc) make install最终产物在 /opt/qt5.12.10-arm 下。把这个目录整个打包拷贝到 ARM 板子上然后设置板子的环境变量export QT_ROOT/opt/qt5.12.10-arm export PATH$QT_ROOT/bin:$PATH export LD_LIBRARY_PATH$QT_ROOT/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORMlinuxfb如果板子没有显示服务器Qt 程序可以通过 linuxfb 插件直接操作帧缓冲设备 /dev/fb0。运行一个 helloworld 窗口程序如果看到屏幕上出现了窗口说明交叉编译 Qt 基本成功。我踩过的最大的坑是宿主机上因为装了 x86 版本的 Qt5 开发包导致 configure 阶段找到了错误路径生成了一些指向 x86 库的 Makefile 片段结果 make 到一半各种符号找不到。解决方法是把交叉编译用的 sysroot 里有 x86 的痕迹清得干干净净同时确保 AC_CROSS_COMPILE 这类宏没有被打扰。另一个经典坑是 libGL.so 的缺失或错误 symlink只要 -no-opengl 能避免就避免。对于那些问“qt5.9.9 交叉编译 openssl 失败”的同学我顺便说一句OpenSSL 1.0.2 系列用老式 ConfigureOpenSSL 1.1.0 之后改成了 config 宏定义的形式很多教程里的命令已经过时了。基于 Qt 5.9.9 做嵌入式项目时我建议 OpenSSL 直接用 1.1.1 版本交叉编译好然后通过-I和-L指定到 Qt 的构建参数里别去折腾兼容旧版本。7. 常见问题速查与避坑指南把这些年踩过的坑和常见问题整理成下面这张表按概率排序。碰到问题先对照看看能省下好多查资料的功夫。问题现象可能原因解决办法编译产物在 ARM 板上提示 No such file or directory动态链接器路径不对或文件格式不是 ELF用 readelf -l 查看 INTERP 段把板子上的链接器补齐或者改静态链接 -static编译产物在 ARM 板上提示 illegal instruction编译器生成了目标 CPU 不支持的指令变体用 -mcpu/-march 指定目标 CPU 型号比如 -mcpucortex-a53或用 -marcharmv8-a 保守设置CMake configure 时 find_package 失败sysroot 配置不完整缺少开发包在 sysroot 中安装目标系统的 xxx-dev 包然后在 sysroot 里测试 include/lib 是否齐全configure 报 cannot run C compiled programs构建系统试图运行 ARM 架构的可执行文件通知构建系统使用交叉编译模式比如 CMake 用工具链文件并设置 CMAKE_CROSSCOMPILINGopen SSL 交叉编译出来的 libssl.so 在 Qt 中链接失败OpenSSL 没有正确交叉编译或者 sysroot 中混入了 x86 的 .so按照第 5.2 节重新构建 OpenSSL并把 make install 路径严格指向 sysrootQt configure 报 This file is not available on this platform选择了错误的 -xplatform 或缺少平台头文件检查 qtbase/mkspecs/devices/ 下的 mkspec 是否匹配必要时自行创建编译好的 Qt 程序在板子上显示 no plugin foundQPA 插件路径不对或者没有编译对应插件设置 QT_QPA_PLATFORM_PLUGIN_PATH 指向目标 Qt 的 plugins/platforms 目录链接错误找不到 -lGLsysroot 中缺 GL 库如果不需要 OpenGLQt 配置时加 -no-opengl如果需要则交叉编译 Mesa 或安装目标平台的 libgles 开发包避坑指南有一条是核心永远不要往交叉编译器的默认搜索路径里塞宿主机自带的头文件和库。所谓“默认搜索路径”指的是 /usr/include、/usr/lib这些是 x86 系统的组件交叉编译器如果在其中搜索轻则编译警告重则链接出“半 x86 半 ARM”的畸形产物。我在 Qt 交叉编译初期就吃过这种亏链接器同时看 sysroot 和宿主机 /usr/lib导致最终链接出来的可执行文件有的符号是 x86 的有的符号是 ARM 的放到板子上直接段错误。后来为了排查这个问题用 readelf 一个个符号看折腾了好久才查出原因。再补充一个小技巧如果你调试交叉编译的依赖问题Linux 下有 ldd 和 readelf 两个武器。在宿主机上用aarch64-linux-gnu-readelf -d 你的程序查看动态段能看到它依赖哪些共享库再把 sysroot 里的库整理成和目标系统一致的目录结构用aarch64-linux-gnu-objdump -p查看某个 .so 提供了哪些符号。这些命令在手工检查依赖时必不可少比盲目在板子上试错高效太多。8. 现场实录x86 环境里跑 ARM 系统的几个奇技淫巧“vmware 运行 arm 系统”“vmware安装ubuntu虚拟机选择arm架构”这类热搜词说明很多人想在 x86 电脑上虚拟出一套 ARM 环境。VMware 跑 ARM 系统相对麻烦因为 VMware 的处理器虚拟化默认面向 x86。现在市场上有 QEMU 这个通用模拟器可以模拟 ARM 架构Ubuntu 也有 ARM 版镜像配合 QEMU 就能在 x86 机器上跑一套 ARM 系统。一种常见的方案是用 QEMU 的 user-mode 模拟配合 binfmt_misc让 Linux 内核自动用 QEMU 解释 ARM 可执行文件。这样你在 x86 Ubuntu 下可以直接执行 ARM 的二进制文件效果有点像“不用板子的嵌入式体验”。安装方式如下sudo apt install qemu-user binfmt-support安装后执行 ARM 程序时内核遇到 ELF 格式为 ARM 的可执行文件会自动调用 qemu-aarch64 或 qemu-arm 来运行。我常拿这个功能快速验证交叉编译产物能不能跑省去了拷贝到板子的步骤尤其适合在 CI 里跑冒烟测试。但要注意user-mode 模拟只能跑用户态程序不能模拟设备驱动、设备树、内核模块。如果你想完整模拟一块 ARM 开发板还得靠 QEMU 的 system-mode 加设备树和内核镜像一起跑这类方案的构建步骤比较长适合做内核编译验证或系统集成测试。另一个场景是 .so 迁移。热搜词“ .so从x86迁移arm文件”说明有人想把 x86 下的动态库直接拿到 ARM 上这种想法基本不可行。二进制 .so 和架构强绑定唯一靠谱的办法是拿源码交叉编译一遍保证所有依赖库都换成 ARM 版本。如果原始库没有源码那就不能用。在嵌入式项目中三方闭源库的 ARM 版本通常由厂商提供没有的话只能换库或者自己重写。至于“arm gpu csdn”“arm halcon”“mariadb arm客户端”“mysql arm”这些热搜词说明有不少人正在把 x86 上的业务组件迁移到 ARM 平台。以数据库为例MySQL/MariaDB 在 ARM 上的编译并不是难事关键是依赖的库要一应俱全。像 MariaDB 的官方源就有 ARM 版本你可以直接用预编译包。但如果公司要求定制化编译比如改字符集、改内存池、加审计插件那就得老老实实交叉编译或者直接在 ARM 设备上编译。其实对于数据库这类大型软件我的经验是优先在 ARM 设备上本机编译省去 sysroot 维护的麻烦反正数据库部署后不追求频繁重编。只有做应用程序(如 Qt GUI、业务守护进程)时才需要反复交叉编译提升开发效率。9. 个人使用体验与进一步扩展建议交叉编译这套东西文字上看着步骤不多但实际操作中会因为环境差异出现很多奇怪问题。我自己做过无数个项目下来最大的体会就是环境隔离非常重要。我现在会在开发机上维护一个专门的 sysroot 目录比如 /opt/arm-sysroot里面放某个目标板系统的根文件系统副本。所有交叉编译库和最终应用程序都严格限制在这个 sysroot 中查找头文件和库环境变量里绝不欠妥地加入宿主机路径。这个习惯让我的工程能够在不同机器间迁移换台电脑也能迅速复现构建。如果你正在往 ARM 架构方向深入学习我把接下来的建议按优先级列一下先用交叉编译工具链编译一个 hello world再看一下 readelf 输出里的 Machine 字段、入口点、段信息建立对产物格式的直观认识。挑一个真实的开源库推荐 OpenSSL 或者 SQLite做交叉编译学会看 configure 脚本的帮助信息理解交叉编译选项的意义。搭好 QEMU user-mode 环境保障交叉编译成功后快速在开发机上跑通不必依赖实物板子。如果你的项目用 Qt、GTK 等大型框架先跑通官方文档的交叉编译教程同时一定看清 mkspec 和 sysroot 的对应关系。遇到 Linux 下各种 ELF/依赖问题熟练使用 readelf、objdump、file、ldd、patchelf 这组工具而不是靠肉眼猜。有句老话叫“基础不牢地动山摇”ARM 架构的理解和交叉编译的技能几乎是嵌入式 Linux 开发的地基。这篇笔记的内容是我多年踩坑、填坑积累下来的实践心得希望能帮你少走几个弯路。最后再分享一个小技巧我写 Makefile 和 CMakeLists 时总会加一个明显的“架构变量”开关比如 ARCH ? arm64CROSS_COMPILE ? aarch64-linux-gnu-。这样切换目标平台时只需改一处变量构建系统就能自动选取正确的工具链前缀。虽然是很简单的设计但确实让我的多个项目避免了很多痛苦的重复配置过程。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价