aarch64-linux-gnu Qt 5.12.12交叉编译与部署实战
2026/9/20 7:58:05 网站建设 项目流程

简介:这份教程面向需要在 ARM64 平台上部署 Qt 应用的嵌入式开发者与 Linux 应用工程师,系统整理了在 Ubuntu 20.04 上搭建 Qt5.12.12 与 aarch64-linux-gnu 交叉编译环境的完整流程,旨在解决工具链缺失、依赖库零散、qmake 配置不当等常见障碍。资源为单个 pdf 文档,压缩包约 4MB,内容以图文排版呈现,便于按章节对照操作。教程从提取并放置交叉编译器、编辑 /etc/profile 环境变量、验证 Linaro GCC 版本讲起,进而修改 linux-aarch64-gnu-g++ 的 qmake.conf、设置 eglfs 平台参数,再到安装 make、g++、fontconfig、OpenGL 等依赖库,运行 configure 与 make install 完成 Qt 库编译,并补充了常见报错与解决思路。已有 12360 人学习下载,适合希望一次性走通交叉编译链路、减少重复踩坑的读者参考。

1. 为什么这套组合在 aarch64 板卡上仍然是主流选择

宿主机上双击就能跑的 Qt 程序,拷到 RK3399、i.MX8、树莓派 64 位或者工控板上,第一件事往往是cannot execute binary file: Exec format error。原因不是代码写错了,而是 ELF 头的机器类型从 x86-64 变成了 AArch64。aarch64-linux-gnu 是一组 GNU 三元组命名的交叉工具链前缀,它描述的是「在 x86_64 的 Ubuntu 20.04 上编译、产出运行在 aarch64 目标板上的二进制」。注意这里的 target 是 aarch64-linux-gnu,不是 aarch64-none-linux-gnu(裸机或 musl),两者混用会在链接 libc 时直接崩掉。

Qt 5.12.12 被大量 BSP 和嵌入式项目长期锁定,因为它是 5.x 里维护周期长的版本,QWidget 与 QML 的行为在板子上足够稳定。宿主选 Ubuntu 20.04,很大程度是它的 glibc 2.31 与 gcc 9 跟 Qt 5.12 的构建脚本兼容性最好,官方源的gcc-aarch64-linux-gnu也能直接apt install。整件事要落地其实就四块:交叉工具链、目标板 sysroot、Qt 库的交叉编译产物、以及 Qt Creator 或命令行 qmake 的调用配置。下面按这个顺序推。

2. aarch64-linux-gnu 工具链与 sysroot 的落地准备

2.1 先分清 build、host 和 target 三个角色

交叉编译里最容易被忽略的是三元组方向。build是编译动作发生的那台机器,host是编译产物将来运行的那台机器,target只在编译编译器本身时才有意义。做应用和 Qt 库的交叉编译时,我们只关心 build 与 host 的差异。工具链前缀里的每一段都有含义,写错一段链接就会失败。

字段本方案取值含义写错后的典型现象
架构aarch64目标 CPU 为 ARMv8-A 64 位编出 ARM32 产物,板端报格式错误
厂商linux使用 Linux 系统调用与 bare-metal 混淆,缺 libc
ABIgnuglibc + GNU ABI和 musl 目标混用,缺 ld-linux-aarch64.so.1
前缀aarch64-linux-gnu-工具名统一前缀找不到 gcc、ar、strip

2.2 安装工具链并确认产出的是 aarch64 ELF

Ubuntu 20.04 官方源里已经有交叉编译器,不需要自己 buildroot 从头编。装完先验证版本和默认 sysroot 路径,再写一个最小 C 程序确认链接器行为。

# 安装交叉工具链的 C/C++ 编译器与常用 binutils sudo apt update sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \ binutils-aarch64-linux-gnu # 确认编译器版本与默认搜索路径 aarch64-linux-gnu-gcc -v aarch64-linux-gnu-gcc -print-sysroot aarch64-linux-gnu-gcc -print-search-dirs

-v用来确认编译器确实是 9.x 系列,Qt 5.12 的源码里有部分 C++ 特性检测对过新的编译器会告警;-print-sysroot输出的是工具链自带的默认 sysroot,通常是/usr/aarch64-linux-gnu这类路径。如果这里为空,说明工具链是用--sysroot显式指定的模式,编译时必须自己带 sysroot 参数。

接着写一个最小的验证程序,看产出文件的 ELF 头:

/* hello.c: 只依赖 libc,用来验证工具链与动态链接器 */ #include <stdio.h> int main(void) { printf("aarch64 cross toolchain ok\n"); return 0; }
aarch64-linux-gnu-gcc hello.c -o hello_arm file hello_arm # 期望输出: ELF 64-bit LSB executable, ARM aarch64, dynamically linked, # interpreter /lib/ld-linux-aarch64.so.1 aarch64-linux-gnu-readelf -d hello_arm | grep NEEDED # 期望看到 libc.so.6

file输出里的ARM aarch64是判断成功的硬指标,如果显示x86-64说明用成了宿主 gcc。readelf -d看的是动态段,NEEDED里应该只有 libc,出现 libstdc++ 以外的奇怪依赖通常是工具链被覆盖安装过。hello_arm还不能在本机运行,直接执行会报格式错误,这是正常的。

2.3 sysroot 从板子拉还是用厂商 SDK

sysroot 是交叉编译时头文件与库的根目录,它决定了链接阶段能找到哪个版本的 libc、libstdc++ 和第三方库(比如 boost 交叉编译出来的那一份)。Qt 编译时会大量依赖 sysroot 里的 X11、fontconfig、freetype、dbus 头文件,缺一个就会在 configure 阶段被判定为不可用。

两种来源各有适用面:

  • 从目标板拉取:rsync -avz root@<board_ip>:/lib /lib64 /usr/include /usr/lib /opt/sysroot-aarch64/。优点是版本一定匹配;缺点是板上通常只有运行时库,缺-dev包的头文件,需要手工补。
  • 用厂商 SDK:板卡厂商给的 SDK 里一般带sysrootenvironment-setup-*脚本,直接source即可。这是最省事的路径,SDK 里的工具链和 sysroot 是配套验证过的。

提示:Qt 5.12.12 的 configure 只接受绝对路径的-sysroot,写相对路径会在生成 qmake 缓存时被展开成源码目录下的路径,后面编译器找不到 libc 时会报cannot find crt1.o

拉取的时候注意把符号链接一起带过去,用rsync -a而不是cp -r,否则libc.so.6这类软链会变成副本或者断链,链接阶段会报undefined reference to 'printf'这种看着很荒谬的错误。

2.4 用一个 C++ 程序预演链接器搜索顺序

Qt 是 C++ 工程,在编译 Qt 之前先用 C++ 验证一遍链接器对 sysroot 的解析,能提前暴露 90% 的环境问题。

# 显式指定 sysroot 链接一份 C++ 程序,验证 libstdc++ 可被找到 aarch64-linux-gnu-g++ hello.cpp -o hello_arm_cpp \ --sysroot=/opt/sysroot-aarch64 \ -Wl,-rpath-link,/opt/sysroot-aarch64/usr/lib/aarch64-linux-gnu \ -Wl,--verbose 2>&1 | grep -E 'succeeded|libstdc'

--sysroot会同时影响头文件搜索(-isysroot)与库搜索(-L的相对基准);-Wl,-rpath-link是告诉链接器在解析间接依赖时去哪找 so 文件,交叉编译第三方库(例如 boost、OpenCV)时这一步几乎必加,否则会出现「明明有这个库却报 undefined reference」的诡异现象。--verbose会打印链接器真实尝试过的路径,排查环境问题时比猜要快得多。

3. Qt 5.12.12 源码交叉编译的关键参数与 mkspec 配置

3.1 宿主 Qt 与目标 Qt 的分工

先把概念拆开:交叉编译 Qt 库,最终要得到两份东西。一份是给板子用的库文件(libQt5Core.so.5.12.12这一批,架构是 aarch64),另一份是给宿主机用的构建工具(qmake、moc、uic、rcc),这些工具必须是 x86-64 才能在编译过程中被调用。Qt 5 的 qmake 本身就是宿主程序,所以-prefix下的bin/qmake是可以直接在 Ubuntu 上执行的,但-prefix/lib下的库是给板子的。

源码包建议放在一个干净目录,构建过程用 shadow build,不要污染源码树:

mkdir -p /opt/qt-build && cd /opt/qt-build # 解压 qt-everywhere-src-5.12.12.tar.xz 到源码目录 tar -xf /opt/src/qt-everywhere-src-5.12.12.tar.xz -C /opt/

如果手上拿的是 qt-everywhere-src-5.15.10 的包,configure 参数整体思路一样,但 5.15 起部分模块改用 CMake 构建、部分参数名有调整,需要对照./configure -help逐个确认。5.12.12 的 configure 是纯 qmake 体系,参数更稳定,也是很多 BSP 仍然锁这个版本的原因。

3.2 自建 linux-aarch64-gnu-g++ mkspec 的 qmake.conf 写法

Qt 通过 mkspec 决定用哪套编译器、哪个架构标志。不要直接改linux-g++,复制一份并改名,避免污染其他构建。

cp -r /opt/qt-everywhere-src-5.12.12/qtbase/mkspecs/linux-g++ \ /opt/qt-everywhere-src-5.12.12/qtbase/mkspecs/linux-aarch64-gnu-g++

然后把qmake.conf改成下面这样:

# qtbase/mkspecs/linux-aarch64-gnu-g++/qmake.conf MAKEFILE_GENERATOR = UNIX CONFIG += incremental QMAKE_INCREMENTAL_STYLE = sublib include(../common/linux.conf) include(../common/gcc-base-unix.conf) include(../common/g++-unix.conf) # 工具链前缀,必须与 apt 装的命令名完全一致 QMAKE_CC = aarch64-linux-gnu-gcc QMAKE_CXX = aarch64-linux-gnu-g++ QMAKE_LINK = aarch64-linux-gnu-g++ QMAKE_LINK_SHLIB = aarch64-linux-gnu-g++ QMAKE_AR = aarch64-linux-gnu-ar cqs QMAKE_OBJCOPY = aarch64-linux-gnu-objcopy QMAKE_NM = aarch64-linux-gnu-nm -P QMAKE_STRIP = aarch64-linux-gnu-strip # 目标板的基线指令集,按实际 SoC 调整 QMAKE_CFLAGS += -march=armv8-a QMAKE_CXXFLAGS += -march=armv8-a # 解析间接依赖时去哪找 so,QT_SYSROOT 由 configure 的 -sysroot 注入 QMAKE_LFLAGS += -Wl,-rpath-link,$$[QT_SYSROOT]/usr/lib/aarch64-linux-gnu load(qt_config)

QMAKE_LINK一定要指向 g++ 而不是 gcc,否则 C++ 标准库不会自动链上,Qt 会报大量undefined referenceQMAKE_AR后面的cqs是参数,漏掉会出现静态库索引不生成的问题;$$[QT_SYSROOT]是 qmake 的内建属性,它取的是 configure 传入的-sysroot值,所以 mkspec 本身不写死路径,可以复用。-march=armv8-a是安全基线,如果板子是 Cortex-A76 这种较新的核,可以改成对应的-mcpu,但换板子时要重新编译。

3.3 configure 参数逐条对照与必须跳过的模块

Qt 的 configure 参数上百个,交叉编译真正需要关心的就十几个。下面这张表按「必须写 / 强烈建议 / 看情况」三档划分。

参数作用不写的后果
-prefix /opt/qt5.12.12-aarch64库在板子上的安装路径默认装到 /usr/local/Qt-5.12.12,部署时要改环境变量
-sysroot /opt/sysroot-aarch64目标头文件与库的根找不到 libc、freetype、fontconfig
-xplatform linux-aarch64-gnu-g++指定刚写的 mkspec用默认 linux-g++,编出 x86 库
-opensource -confirm-license免交互确认许可configure 卡在交互提示
-release只产 release 库同时编 debug,时间翻倍、体积翻倍
-nomake examples -nomake tests跳过示例与测试编译时间成倍增加
-skip qtwebengine跳过 WebEngine5.12 的 WebEngine 不支持交叉编译,直接中断整个构建
-no-opengl-opengl es2图形后端板子无 GPU 驱动时链不上 libGL
-qt-zlib -qt-libpng -qt-libjpeg用 Qt 自带第三方库依赖 sysroot 里的对应 dev 包,缺一个就 disable 掉功能

命令行形态如下,建议写进一个build.sh里,方便重跑:

#!/bin/bash set -e cd /opt/qt-build /opt/qt-everywhere-src-5.12.12/configure \ -prefix /opt/qt5.12.12-aarch64 \ -sysroot /opt/sysroot-aarch64 \ -xplatform linux-aarch64-gnu-g++ \ -opensource -confirm-license \ -release \ -no-pch \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz \ -no-opengl \ -no-cups -no-icu -no-glib -no-dbus \ -nomake examples -nomake tests \ -skip qtwebengine \ -v

-no-pch关掉预编译头,交叉编译时 PCH 会因为宿主与目标的头文件路径差异产生难懂的报错,关掉后编译慢一些但过程可控;-no-icu会去掉国际化文本处理的高级能力,板子上如果只需要中英文界面可以关;-no-glib -no-dbus在有 systemd 的板子上不一定要关,关掉能减少对 sysroot 里 dev 包的依赖。-v打开详细输出,configure 结束时会把每个模块的启用情况打出来,这份日志是后面排错的第一手材料。

3.4 make 与 install:日志留存和产物确认

configure 成功后会生成 qmake 缓存,构建阶段用多核并行,但一定要留日志。

cd /opt/qt-build make -j$(nproc) 2>&1 | tee build.log # 若中断,先看日志尾部 grep -nE 'error:|Error [0-9]' build.log | tail -30 make install

-j$(nproc)会吃满 CPU,内存小于 8GB 的虚拟机上建议降到-j4,QtCore 与 QtGui 的大文件编译峰值内存不低,OOM killer 杀掉 g++ 的报错是cc1plus: out of memory,看到这个就把并行度降下来。tee是为了保住完整日志,交叉编译里同一个错误往往要往前翻几百行才能找到第一个失败点。make install之后重点确认三个目录:/opt/qt5.12.12-aarch64/bin/qmake能不能在宿主机上运行、/opt/qt5.12.12-aarch64/lib/libQt5Core.so.5.12.12file输出是不是 aarch64、/opt/qt5.12.12-aarch64/plugins/platforms/下有没有libqlinuxfb.solibqeglfs.so

/opt/qt5.12.12-aarch64/bin/qmake -query # 关注 QT_SYSROOT、QT_INSTALL_PREFIX、QT_INSTALL_LIBS、QMAKE_XSPEC file /opt/qt5.12.12-aarch64/lib/libQt5Core.so.5.12.12 ls /opt/qt5.12.12-aarch64/plugins/platforms/

qmake -queryQMAKE_XSPEC应该显示linux-aarch64-gnu-g++,如果还是linux-g++,说明安装的 qmake 是宿主环境的版本,工程会编出 x86 产物。这一步不确认,后面在 Qt Creator 里配好的 Kit 全部白费。

4. Qt Creator Kit 配置与 qmake 命令行交叉编译工程

4.1 把编译器、Qt Versions、Kit 三件套注册进去

Qt Creator 的 Kit 本质是把「编译器 + Qt 版本 + 设备」三者绑定。先在「工具 → 选项 → Kits → 编译器」里手动添加一个 C++ 编译器,路径填/usr/bin/aarch64-linux-gnu-g++,类型选 GCC。再在「Qt Versions」里添加/opt/qt5.12.12-aarch64/bin/qmake,Qt Creator 会调用它并显示版本号,如果这一步报「无法执行」,说明安装出来的 qmake 是 aarch64 架构,需要回到第 3 章检查 mkspec 与 hostprefix 的设置。

Kit 的字段填写方式如下:

Kit 字段填写值说明
名称aarch64-Qt5.12.12便于和宿主 Kit 区分
设备类型Generic Linux Device / 不配置只用命令行部署时可不填
编译器 Caarch64-linux-gnu-gcc第 2 章安装的那个
编译器 C++aarch64-linux-gnu-g++必须是 g++
Qt version/opt/qt5.12.12-aarch64/bin/qmake决定 include 与 lib 路径
sysroot/opt/sysroot-aarch64影响调试器的符号查找

如果只是想快速验证,完全可以跳过 Qt Creator,直接在终端用交叉 qmake 跑一遍,出问题更容易定位,因为每一步的命令都看得见。

4.2 用命令行 qmake 验证一个最小 Widgets 工程

准备一个能体现「板子上能看见」的最小工程,比只编一个控制台程序更能暴露插件和平台后端的问题。

# helloqt.pro QT += core gui widgets TARGET = helloqt TEMPLATE = app SOURCES += main.cpp # 让二进制自己记住 Qt 库位置,减少板端环境变量依赖 QMAKE_RPATHDIR += /opt/qt5.12.12-aarch64/lib target.path = /opt/helloqt INSTALLS += target
// main.cpp: 最小 Widgets 程序,用于验证平台插件链路 #include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(QStringLiteral("hello aarch64")); label.resize(320, 120); label.show(); return app.exec(); }

构建并检查产物的动态段:

mkdir -p build-arm && cd build-arm /opt/qt5.12.12-aarch64/bin/qmake ../helloqt.pro make -j$(nproc) file helloqt # 应为 ARM aarch64 aarch64-linux-gnu-readelf -d helloqt | grep -E 'NEEDED|RPATH|RUNPATH'

QMAKE_RPATHDIR会写进 ELF 的 RUNPATH 段,效果等价于在板子上设LD_LIBRARY_PATH,但优先级更高、更不容易被别的进程污染。readelf -dNEEDED应该出现libQt5Widgets.so.5libQt5Gui.so.5libQt5Core.so.5,如果这几项没出现却编译成功,说明链接的是静态库或别的 Qt 版本。RUNPATH里应该能看到/opt/qt5.12.12-aarch64/lib

仓库里如果还有第三方库,比如交叉编译过的 boost,在.pro里追加以 sysroot 为基准的搜索路径:

QMAKE_INCDIR += $$[QT_SYSROOT]/usr/include QMAKE_LIBDIR += $$[QT_SYSROOT]/usr/lib/aarch64-linux-gnu QMAKE_LFLAGS += -Wl,-rpath-link,$$[QT_SYSROOT]/usr/lib/aarch64-linux-gnu

QMAKE_INCDIRQMAKE_LIBDIR会分别加进编译和链接命令,-Wl,-rpath-link只在链接期生效、不写进最终 ELF,专门用来解决间接依赖找不到的问题,和QMAKE_RPATHDIR的用途完全不同,不要混用。

4.3 部署到板子:库、插件与平台后端

部署不是拷一个可执行文件就完事。Qt 程序运行时需要平台插件、图像格式插件、字体和 Qt 自身的 so。最省事的做法是把整个 prefix 里的libplugins同步过去,板子空间够就别挑。

# 同步可执行文件 scp build-arm/helloqt root@<board_ip>:/opt/helloqt/ # 同步 Qt 运行库与插件 rsync -a /opt/qt5.12.12-aarch64/lib/ root@<board_ip>:/opt/qt5.12.12-aarch64/lib/ rsync -a /opt/qt5.12.12-aarch64/plugins/ root@<board_ip>:/opt/qt5.12.12-aarch64/plugins/ # 板端运行 export LD_LIBRARY_PATH=/opt/qt5.12.12-aarch64/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM=linuxfb # 无 GPU 时用 linuxfb,有 EGL 时用 eglfs export QT_QPA_FONTDIR=/usr/share/fonts /opt/helloqt/helloqt

QT_QPA_PLATFORM决定加载哪个平台插件,板子上没有 X11 却设了xcb,报错是This application failed to start because no Qt platform plugin could be initialized,换linuxfbeglfs即可。QT_QPA_FONTDIR不设的话中文界面容易显示成方块,因为 Qt 在目标板上找不到字体目录。插件路径一般由 Qt 根据库位置自动推导,如果 prefix 被裁过,用QT_PLUGIN_PATH显式指定。

5. 交叉编译常见报错的定位手法与板端验证技巧

5.1 三类高频报错与对应的判断动作

交叉编译的报错信息大多指向明确,只是第一次见容易误判方向。下面三个占了实际项目里的大部分情况。

报错片段真实原因处理动作
skipping incompatible libQt5Core.so链接器扫到了 x86 版 Qt检查-L顺序,qmake -query QT_INSTALL_LIBS是否指向交叉 prefix
cannot find -lQt5Widgets库没编出来或被-skip看 build.log 里 qtbase 的模块启用列表
version 'GLIBC_2.34' not found板端 glibc 比编译时用的旧换用板端 sysroot 重编,别用宿主的 libc 头

第一类最隐蔽,因为编译能过、链接在最后一步才失败。判断方法很直接:对报错里提到的那个 so 执行file,输出是x86-64就说明路径配错了。第二类看构建日志最有效,qtbase/config.summary里会列出每个模块的启用状态,被 disable 的模块会有原因标注。第三类属于环境不匹配,只能靠 sysroot 统一,Qt 库和应用程序必须用同一份 sysroot 编译,否则 glibc 符号版本对不上。

5.2 用 qemu-aarch64-static 在主机上先跑一遍

把程序拷到板子再调试的反馈周期太长,中间还隔着网络和串口。可以用 qemu 的用户态模拟在主机上先跑一遍,验证动态链接和插件加载路径是否正常。

sudo apt install -y qemu-user-static # 把 Hello 程序与所需库放进 sysroot 的目录结构里 sudo cp build-arm/helloqt /opt/sysroot-aarch64/opt/helloqt/ sudo cp -a /opt/qt5.12.12-aarch64/lib/*.so* /opt/sysroot-aarch64/opt/qt5.12.12-aarch64/lib/ sudo cp /usr/bin/qemu-aarch64-static /opt/sysroot-aarch64/usr/bin/ # 直接指定 sysroot 运行,查看链接是否成功 qemu-aarch64-static -L /opt/sysroot-aarch64 \ -E LD_LIBRARY_PATH=/opt/qt5.12.12-aarch64/lib \ /opt/sysroot-aarch64/opt/helloqt/helloqt -platform minimal

-L指定模拟运行时的根目录,动态链接器ld-linux-aarch64.so.1会从这里面找;-platform minimal用最小平台插件,避免在没有显示设备的环境下因为开窗口失败而退出;-E用来注入环境变量。能跑起来说明库依赖和插件目录结构没问题,剩下的才是板端显卡驱动和输入设备的事情。注意 qemu 只验证用户态,不模拟 GPU,eglfs相关行为必须在真板上测。

5.3 发布前的 strip 与体积压缩

Qt 库的调试符号占了很大比例,交叉编译出来的libQt5Core.so.5.12.12动辄几十兆。上板之前 strip 一遍能省下大量存储空间,但顺序和备份要做对。

# 先备份带符号的版本,再生成裁剪版 cp /opt/qt5.12.12-aarch64/lib/libQt5Core.so.5.12.12{,.debug} aarch64-linux-gnu-strip --strip-unneeded \ /opt/qt5.12.12-aarch64/lib/libQt5Core.so.5.12.12 # 批量处理插件目录里的 so find /opt/qt5.12.12-aarch64/plugins -name '*.so' \ -exec aarch64-linux-gnu-strip --strip-unneeded {} \; # 确认裁剪后仍是合法 aarch64 动态库 file /opt/qt5.12.12-aarch64/lib/libQt5Core.so.5.12.12 readelf -d /opt/qt5.12.12-aarch64/lib/libQt5Core.so.5.12.12 | head -5

--strip-unneeded而不是-s,前者只删不会被动态链接需要的符号,保留必要的动态符号表,删多了会在板端启动时报undefined symbol。插件目录里的.so要单独处理,find走一遍最省事。裁剪后再用readelf -d看一遍动态段,能正常输出就说明文件结构没被破坏。带符号的.debug副本留好,板子上出现崩溃栈时,本机得靠它把地址还原成函数名。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询