尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

aarch64-linux-gnu Qt 5.12.12交叉编译与部署实战

aarch64-linux-gnu Qt 5.12.12交叉编译与部署实战 简介这份教程面向需要在 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 混淆缺 libcABIgnuglibc GNU ABI和 musl 目标混用缺 ld-linux-aarch64.so.1前缀aarch64-linux-gnu-工具名统一前缀找不到 gcc、ar、strip2.2 安装工具链并确认产出的是 aarch64 ELFUbuntu 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.6file输出里的ARM aarch64是判断成功的硬指标如果显示x86-64说明用成了宿主 gcc。readelf -d看的是动态段NEEDED里应该只有 libc出现 libstdc 以外的奇怪依赖通常是工具链被覆盖安装过。hello_arm还不能在本机运行直接执行会报格式错误这是正常的。2.3 sysroot 从板子拉还是用厂商 SDKsysroot 是交叉编译时头文件与库的根目录它决定了链接阶段能找到哪个版本的 libc、libstdc 和第三方库比如 boost 交叉编译出来的那一份。Qt 编译时会大量依赖 sysroot 里的 X11、fontconfig、freetype、dbus 头文件缺一个就会在 configure 阶段被判定为不可用。两种来源各有适用面从目标板拉取rsync -avz rootboard_ip:/lib /lib64 /usr/include /usr/lib /opt/sysroot-aarch64/。优点是版本一定匹配缺点是板上通常只有运行时库缺-dev包的头文件需要手工补。用厂商 SDK板卡厂商给的 SDK 里一般带sysroot和environment-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 21 | 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 -marcharmv8-a QMAKE_CXXFLAGS -marcharmv8-a # 解析间接依赖时去哪找 soQT_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 本身不写死路径可以复用。-marcharmv8-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) 21 | tee build.log # 若中断先看日志尾部 grep -nE error:|Error [0-9] build.log | tail -30 make install-j$(nproc)会吃满 CPU内存小于 8GB 的虚拟机上建议降到-j4QtCore 与 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.12的file输出是不是 aarch64、/opt/qt5.12.12-aarch64/plugins/platforms/下有没有libqlinuxfb.so或libqeglfs.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 -query里QMAKE_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/qmakeQt Creator 会调用它并显示版本号如果这一步报「无法执行」说明安装出来的 qmake 是 aarch64 架构需要回到第 3 章检查 mkspec 与 hostprefix 的设置。Kit 的字段填写方式如下Kit 字段填写值说明名称aarch64-Qt5.12.12便于和宿主 Kit 区分设备类型Generic Linux Device / 不配置只用命令行部署时可不填编译器 Caarch64-linux-gnu-gcc第 2 章安装的那个编译器 Caarch64-linux-gnu-g必须是 gQt 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|RUNPATHQMAKE_RPATHDIR会写进 ELF 的 RUNPATH 段效果等价于在板子上设LD_LIBRARY_PATH但优先级更高、更不容易被别的进程污染。readelf -d里NEEDED应该出现libQt5Widgets.so.5、libQt5Gui.so.5、libQt5Core.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-gnuQMAKE_INCDIR与QMAKE_LIBDIR会分别加进编译和链接命令-Wl,-rpath-link只在链接期生效、不写进最终 ELF专门用来解决间接依赖找不到的问题和QMAKE_RPATHDIR的用途完全不同不要混用。4.3 部署到板子库、插件与平台后端部署不是拷一个可执行文件就完事。Qt 程序运行时需要平台插件、图像格式插件、字体和 Qt 自身的 so。最省事的做法是把整个 prefix 里的lib与plugins同步过去板子空间够就别挑。# 同步可执行文件 scp build-arm/helloqt rootboard_ip:/opt/helloqt/ # 同步 Qt 运行库与插件 rsync -a /opt/qt5.12.12-aarch64/lib/ rootboard_ip:/opt/qt5.12.12-aarch64/lib/ rsync -a /opt/qt5.12.12-aarch64/plugins/ rootboard_ip:/opt/qt5.12.12-aarch64/plugins/ # 板端运行 export LD_LIBRARY_PATH/opt/qt5.12.12-aarch64/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORMlinuxfb # 无 GPU 时用 linuxfb有 EGL 时用 eglfs export QT_QPA_FONTDIR/usr/share/fonts /opt/helloqt/helloqtQT_QPA_PLATFORM决定加载哪个平台插件板子上没有 X11 却设了xcb报错是This application failed to start because no Qt platform plugin could be initialized换linuxfb或eglfs即可。QT_QPA_FONTDIR不设的话中文界面容易显示成方块因为 Qt 在目标板上找不到字体目录。插件路径一般由 Qt 根据库位置自动推导如果 prefix 被裁过用QT_PLUGIN_PATH显式指定。5. 交叉编译常见报错的定位手法与板端验证技巧5.1 三类高频报错与对应的判断动作交叉编译的报错信息大多指向明确只是第一次见容易误判方向。下面三个占了实际项目里的大部分情况。报错片段真实原因处理动作skipping incompatible libQt5Core.so链接器扫到了 x86 版 Qt检查-L顺序qmake -query QT_INSTALL_LIBS是否指向交叉 prefixcannot 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 只验证用户态不模拟 GPUeglfs相关行为必须在真板上测。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副本留好板子上出现崩溃栈时本机得靠它把地址还原成函数名。本文还有配套的精品资源点击获取
返回列表