ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Qt 5.14.2 aarch64静态交叉编译从零到上板完整指南

2026/9/19 4:53:32 拓冰建站 浏览量
Qt 5.14.2 aarch64静态交叉编译从零到上板完整指南 一个很典型的画面开发机上Qt程序跑得好好的交叉编译出来的二进制往aarch64板子上拷贝一启动就是一连串的libQt5Widgets.so.5: cannot open shared object file。我在给一块 ARM64 工控板做界面时就被这个场景折磨过后来干脆把 Qt 5.14.2 改成静态交叉编译整个应用的部署从“带一堆 .so 还要反复调 LD_LIBRARY_PATH”变成了“一个二进制文件扔过去就能跑”。Qt 5.14.2 在不少项目里是默认锁定版本官方离线安装包用起来简单但嵌入式场景下必须拿到源码自己编。这篇文章就是一份从零到上板的完整操作手册围绕 aarch64 静态交叉编译这条主线把工具链、依赖、configure 参数、编译验证、目标板部署和容易踩的坑都过一遍。适合正在做 ARM64 平台 Qt 应用移植的开发者尤其是第一次接触静态交叉编译、想少走弯路的人。1. 为什么非要静态交叉编译部署现场的“动态库地狱”1.1 动态部署的典型翻车场景我见过太多项目在交付阶段被动态库问题绊住。最常见的是这几种情况Qt 应用编译成了动态链接部署时需要把libQt5Core.so.5、libQt5Gui.so.5、libQt5Widgets.so.5、平台插件、字体库等一整套东西拷贝到目标板。第一版系统里 Qt 版本是 5.12升级到 5.14 之后只替换了应用忘了替换.so结果 new 一个QPushButton直接段错误。还有一种情况是板子上的 rootfs 是精简过的默认不装 Qt 全套库为了一个 3MB 的应用硬生生多出 60MB 的依赖这在存储空间以 MB 计的嵌入式设备上非常尴尬。更麻烦的是LD_LIBRARY_PATH的全局污染。应用 A 需要 Qt 5.14 的库应用 B 需要 Qt 5.12 的库同时跑的时候把两个路径都加进环境变量运行时到底加载哪个版本的库完全取决于搜索顺序。我遇到过某台设备重启后界面起不来查了半天发现是系统更新时把/usr/lib/aarch64-linux-gnu/libQt5Widgets.so.5指向了另一个版本。动态方案的优点我当然承认占用空间小、内存共享、升级库不用重编应用。但在嵌入式交付场景里目标板的环境是你没法完全控制的尤其是外壳封死、现场不能随意操作设备的场景少一个库就是一次灾难。1.2 静态与交叉两条思路怎么叠加很多刚接触的人分不清“静态编译”和“交叉编译”的关系。简单说交叉编译解决的是“架构不同”的问题你的开发机是 x86_64目标板是 aarch64所以必须用 aarch64 的工具链去生成目标板上能跑的机器码静态编译解决的是“运行依赖”的问题链接器把.a静态库直接打进最终可执行文件里运行时不再去查找.so。把两者叠加得到的产物是“在 x86_64 主机上使用 aarch64 交叉工具链、把所有 Qt 库和第三方库都以静态方式链接进一个可执行文件”的单一二进制。这个二进制复制到任何同架构的 Linux 系统上都能运行只要内核和 C 运行库基础没问题。对于 rootfs 精简、无包管理机制的嵌入式系统这种交付方式省心得多。1.3 本手册的适用边界静态交叉编译不是银弹。如果你的目标板是一块运行完整 Yocto 或 Ubuntu 系统的设备系统里已经预置了匹配版本的 Qt 动态库那么没有必要静态编译动态链接反而更新方便。但如果你的场景是目标板 rootfs 很小、没有包管理器、应用需要发布给客户而不能要求客户装一堆依赖、设备常年无人值守不允许现场调库那就很适合走静态方案。另外有一点要先交代清楚本文默认的目标板是“无 GPU、不带 X11/Wayland、使用linuxfb作为 QPA 平台插件”的普通 ARM64 工控板触摸屏用 tslib 处理。如果你的板子有 GPU 要走eglfs或者跑的是 X11 环境部分 configure 参数需要调整文章里会标出相关位置。2. 构建主机准备与工具链选型扎实的地基决定后面的效率2.1 宿主机环境与基础依赖安装整个搭建过程我是在 Ubuntu 20.04 x86_64 上完成的Ubuntu 22.04 也没问题。不要装一堆看着相关但实际上会产生干扰的宿主库重点只需要这些sudo apt update sudo apt install -y build-essential g make perl python3 ninja-build sudo apt install -y g-aarch64-linux-gnubuild-essential提供宿主编译器g-aarch64-linux-gnu是交叉编译器perl是 Qt configure 脚本的解释器python3是某些 Qt 模块构建脚本依赖的。很多网上教程会让你apt install libxcb-dev libxkbcommon-dev这些是 X11 相关依赖在嵌入式 linuxfb 场景下根本用不到装了反而可能在 configure 阶段引发奇怪的自动检测行为建议别装。一个很容易被忽略的点不要在主机的/usr/include/openssl里安装版本过高的 libssl-dev。后面编译 Qt 的 OpenSSL 支持时configure 的自动探测有时会先看到宿主机的头文件导致目标 sysroot 里的 OpenSSL 版本和它不匹配。我们会在第 7 章专门讲这个坑。2.2 交叉编译工具链的三种选择和对比工具链选型我试过三种分别适用于不同情况工具链方案优点缺点推荐场景发行版自带g-aarch64-linux-gnu安装简单、GCC 版本较新、ABI 一致性好不包含厂商 BSP 里的私有库大多数通用 ARM64 工控板SoC 厂商 SDK 工具链NXP、Rockchip 等带了完整的 sysroot 和板级库文件GCC 版本往往较老4.9/5.4C11/14 支持有坑必须使用厂商私有库时Linaro 独立工具链版本选择灵活、社区资料多老版本不好找新安全补丁老项目维护我最推荐第一种。Qt 5.14.2 对 GCC 版本的最低要求是 4.8但这只是最低门槛实际编译时 GCC 版本太老会有一堆 C ABI 问题。发行版自带的 GCC 一般是 9/10/11编译 Qt 5.14.2 没有问题。执行aarch64-linux-gnu-g --version确认版本。2.3 Qt 源码包、目录规划与 sysroot 骨架很多人搜“qt5.14.2 离线安装包下载”但实际上做交叉编译必须下载qt-everywhere-src-5.14.2.tar.xz源码包而不是 Qt 官方提供的预编译离线安装包。预编译安装包里的库是针对 x86_64 宿主机的跟 aarch64 目标板一毛钱关系都没有。从 Qt 官方或镜像站下载源码包后用sha256sum校验一下完整性。我的目录规划是这样/opt/qt-aarch64/ ├── src/ # Qt 源码解压目录 ├── toolchain/ # 交叉工具链如果手动安装的话 ├── sysroot/ # 目标板根文件系统镜像编译时作为头文件和库的搜索路径 │ └── usr/ │ ├── include/ # tslib、openssl 等第三方库的头文件 │ ├── lib/ # 目标板的第三方库静态库为主 │ └── lib/aarch64-linux-gnu/ └── qt5.14.2/ # Qt 编译安装后的输出目录这里的 sysroot 并不需要是一份完整的目标板 rootfs 镜像只要包含你需要的第三方库的头文件和静态库就够了。在使用发行版工具链时系统自带的/usr/aarch64-linux-gnu可以当作基础 sysroot我自己再新建一个/opt/qt-aarch64/sysroot用于统一管理 tslib、OpenSSL 这些额外依赖两者不冲突。3. 第三方依赖库的交叉编译触摸、加密和基础库一个不能少3.1 tslib触摸屏输入不容跳过的一环大多数 ARM64 工控板的触摸屏是电阻屏或需要校准的电容屏Qt 在 linuxfb 平台下默认处理触摸是靠 evdev但很多老式触摸控制器只有原始 ADC 数据需要 tslib 做滤波、校准和坐标转换。即使你的屏幕是免校准的tslib 作为输入插件也能让 Qt 的事件处理更稳定。交叉编译 tslib 的步骤其实很常规wget https://github.com/libts/tslib/releases/download/1.22/tslib-1.22.tar.bz2 tar xf tslib-1.22.tar.bz2 cd tslib-1.22 ./autogen.sh ./configure --hostaarch64-linux-gnu \ --prefix/opt/qt-aarch64/sysroot/usr \ CCaarch64-linux-gnu-gcc \ CXXaarch64-linux-gnu-g make -j4 make install注意--prefix这个地方我直接指定到 sysroot 下的/usr这样 Qt configure 阶段能通过默认头文件和库搜索路径找到它。网上有些教程把 prefix 设成主机上的临时目录再加-I/-L参数也能跑通但容易在路径上出幺蛾子。部署到目标板时把 sysroot 里的 tslib 相关头文件和库同结构地放到板子根文件系统即可。编译完成后一定要检查/opt/qt-aarch64/sysroot/usr/lib/pkgconfig/tslib.pc是否存在。这个.pc文件是 Qt configure 判断“tslib 是否可用”的关键依据之一后面 pkg-config 配置部分会再提到。3.2 OpenSSL、zlib 的处理静态链接的安全与体积权衡Qt 应用如果需要走 HTTPS 协议底层必须链接 OpenSSL。交叉编译 OpenSSL 静态库的完整命令如下wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1w/openssl-1.1.1w.tar.gz tar xf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./Configure linux-aarch64 -static no-tests no-shared \ --cross-compile-prefixaarch64-linux-gnu- \ --prefix/opt/qt-aarch64/sysroot/usr make -j4 make install这里选了 OpenSSL 1.1.1 的最后一个版本 1.1.1w这是 Qt 5.14.2 稳妥支持的版本线。no-shared是关键它保证只生成.a静态库否则 Qt 在静态模式下做链接时会遇到动态库依赖的麻烦。linux-aarch64是 OpenSSL Configure 脚本里对 ARM64 Linux 的识别名不要手滑写成linux-generic64后者在性能上会差一些。zlib 的处理就简单多了Qt 自带了-qt-zlib选项默认会用 Qt 源码树里的 zlib不需要单独交叉编译。如果你非要用系统 zlib必须保证交叉编译出的 zlib 位于 sysroot并在 configure 时用-system-zlib但确实没必要给自己找麻烦。3.3 ICU、libjpeg、libpng 的取舍ICU 在 Qt 5.14 里默认是“检测到就启用”的但交叉环境下检测 ICU 很容易出问题稍有不慎就会在编译阶段冒出unicode/unistr.h not found。如果你不是做需要完整 Unicode 支持的国际金融应用直接-no-icu关掉。QString 的基础字符串操作不受影响QRegularExpression的高级属性匹配会弱一些但对绝大多数界面程序来说感知不到差异。libjpeg 和 libpng 用-qt-libjpeg -qt-libpng让 Qt 编到内部省掉单独交叉编译的成本。freetype 字体引擎用-qt-freetype内嵌也不要在这里省。这几个内嵌库对最终二进制体积的增加很小换来的却是部署时不需要在目标板 rootfs 里到处找对应的.so值。4. configure 配置逐项拆解每个参数背后都是实测结论4.1 一份经过验证的完整 configure 命令行源码解压后进入qt-everywhere-src-5.14.2目录执行下面的 configure。这是我在无 GPU 工控板、linuxfb 加 tslib 场景下验证过的完整命令cd qt-everywhere-src-5.14.2 export SYSROOT/opt/qt-aarch64/sysroot export PKG_CONFIG_LIBDIR$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/lib/aarch64-linux-gnu/pkgconfig:$SYSROOT/usr/share/pkgconfig export PKG_CONFIG_SYSROOT_DIR$SYSROOT export PKG_CONFIG_PATH ./configure -release -static -opensource -confirm-license \ -prefix /opt/qt-aarch64/qt5.14.2 \ -xplatform linux-aarch64-gnu-g \ -no-opengl -no-eglfs -no-xcb -linuxfb \ -tslib \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype \ -no-icu -no-dbus -no-cups -no-gstreamer -no-pulseaudio -no-glib \ -openssl-linked \ -I$SYSROOT/usr/include \ -L$SYSROOT/usr/lib先别急着复制下面我把几个关键的参数拆开解释明白为什么这么选之后你才能根据自己板子的情况调整。4.2 从 -xplatform 到 -no-feature关键参数的行为逻辑-static是最核心的参数告诉 Qt 构建静态库而不是动态库。这里的静态指的是 Qt 自身编译成.a最终应用在 qmake 的配合下会静态链接 Qt 库。-release是编译优化版本-debug会让静态库体积膨胀几倍还拖慢运行速度嵌入式场景基本不选。-xplatform linux-aarch64-gnu-g是“交叉编译”的开关。Qt 区分-platform运行 configure 和 qmake 的主机平台和-xplatform目标平台linux-aarch64-gnu-g这个值对应qtbase/mkspecs/linux-aarch64-gnu-g目录下的配置。你可以打开这个目录里的qmake.conf看一眼里面明确写死了QMAKE_CC aarch64-linux-gnu-gcc、QMAKE_CXX aarch64-linux-gnu-g。如果你的工具链不是正宗的aarch64-linux-gnu-前缀需要复制这个 mkspec 目录并修改里面的编译器名字。图形栈的选择是最需要根据硬件调整的部分。-no-opengl -no-eglfs -no-xcb -linuxfb组合的意思是不要 OpenGL、不要 EGLFS、不要 X11、只用 linuxfb 作为 QPA 后端。linuxfb 就是直接往/dev/fb0这个 framebuffer 设备上画图驱动要求低适合大多数不带 GPU 的工控板。如果你的板子带 Mali 等 GPU 且需要 GPU 加速应该去掉-no-eglfs并让 configure 找到对应的 EGL 库但这通常意味着要用厂商 SDK 工具链复杂度立刻上一个台阶。-tslib让 Qt 启用 tslib 输入插件。注意这个参数不带路径Qt 是通过“头文件在默认路径 /usr/include、库在默认路径 /usr/lib”的方式探测 tslib 的这也再次说明为什么前面要把 tslib 装到 sysroot 的/usr下。-openssl-linked表示不是运行时动态加载 OpenSSL而是将 OpenSSL 静态链接进 Qt 网络模块。如果漏掉这个参数Qt 可能会用运行时dlopen方式查找目标板上的libssl.so静态部署的意义就少了一半。-no-glib关闭 glib 集成。很多嵌入式系统根本不依赖 glibQt 如果自动检测到宿主机有 glib 的头文件而误启用后面链接可能出错直接关掉最保险。-no-dbus -no-cups -no-gstreamer -no-pulseaudio同理都是减少特性和降低依赖。最后是-I和-L手动把 sysroot 里的 include 和 lib 路径指给 configure。交叉编译场景下自动检测经常不靠谱手动指定是效率最高的排除法手段。4.3 交叉编译环境下 pkg-config 的坑与解决办法Qt configure 脚本在探测 tslib、OpenSSL 这类第三方库时依赖 pkg-config。交叉编译最大的坑就是 pkg-config 不经意间读到宿主机的.pc文件然后给你返回一个 x86_64 的库路径。解决办法是设置了上面命令里的三行环境变量export PKG_CONFIG_LIBDIR... # 把搜索路径限定在 sysroot 内 export PKG_CONFIG_SYSROOT_DIR$SYSROOT # 让路径前缀自动映射到 sysroot export PKG_CONFIG_PATH # 避免额外路径污染这样强制 pkg-config 只在 sysroot 下搜索.pc文件。设置完后再跑 configure输出日志里能看到 tslib 和 OpenSSL 都被识别为 yes。configure 完成后不要急着 make先看输出顶部的 Summary关键要确认这几项tslib ...... yes OpenSSL ...... yes ICU ...... no xcb ...... no linuxfb ...... yes4.4 configure 结束后的快速体检很多人 configure 完直接 make结果编到一半报错才回头排查浪费大量时间。正确做法是先看config.summary文件确认关键模块的启用状态符合预期。另外建议执行一次make module-qtbase之前先跑一个小命令验证编译链路echo int main(){return 0;} /tmp/test.cpp aarch64-linux-gnu-g /tmp/test.cpp -o /tmp/test -static file /tmp/test这一步能快速确认工具链本身是否支持静态链接避免 Qt 编译到一半才发现工具链的 glibc 静态库缺失。5. 编译、安装与第一个 aarch64 静态程序验证5.1 并行编译与 ccache 加速configure 通过后进入编译环节make -j4-j的取值要克制一点。Qt 静态编译时每个编译单元内存占用很高4 核 4G 的机器开-j4容易被 OOM 杀死保守起见-j2更稳。我实测过 8 核 16G 机器上-j8大概 30 分钟编完4 核机器-j4可能需要一小时以上。如果你打算反复调整 configure 参数做二次编译强烈建议用 ccache。把交叉编译器包装一下export CCACHE_PREFIXaarch64-linux-gnu- ccache -M 10G在 configure 之前设置好 ccache后面每次重编 Qt 都能省掉大量重复编译时间。我第一次搭建时没意识到这个重要性改了一个 configure 参数又重新等了四十分钟后来加了 ccache 再调优参数基本几分钟就能出结果。5.2 安装后的目录结构与静态库检索方式编译完成后make install安装目录/opt/qt-aarch64/qt5.14.2下几个重要子目录分别是lib/静态库本体libQt5Core.a、libQt5Gui.a、libQt5Widgets.a等plugins/platforms/QPA 平台插件的源码编译产物静态模式下是libqlinuxfb.aplugins/generic/tslib 输入插件静态模式下是libqtslib.abin/qmake、moc、uic 等主机工具mkspecs/平台配置信息供 qmake 生成 Makefile 时参考注意bin/qmake本身是运行在宿主机上的程序但它生成 Makefile 时会使用交叉编译器和静态库路径。验证一下/opt/qt-aarch64/qt5.14.2/bin/qmake -v输出里应该显示Using Qt version 5.14.2 in /opt/qt-aarch64/qt5.14.2/lib。5.3 编写并编译一个最小的 GUI 测试程序这一步的成就感是实打实的。创建hello/目录写一个最简单的 Qt Widgets 程序main.cpp#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello AArch64 Static Qt); label.resize(400, 200); label.show(); return app.exec(); }hello.proQT widgets TARGET hello TEMPLATE app SOURCES main.cpp然后在hello目录下执行/opt/qt-aarch64/qt5.14.2/bin/qmake hello.pro make编译完成后检查产物file hello输出应该是类似这种hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked注意statically linked这几个字。用ldd hello会得到not a dynamic executable说明它不再依赖目标板的 Qt 动态库。这个二进制在 x86_64 的宿主机上无法直接运行会报Exec format error属于正常现象。6. 目标板部署与验证静态程序的真实运行边界6.1 目标板运行环境检查清单静态二进制的确省掉了 Qt 动态库但“静态”不等于“完全与系统无关”。部署到目标板前先过一遍这个检查清单检查点为什么重要排查命令内核架构是 aarch64静态二进制只认 CPU 架构uname -m/dev/fb0存在linuxfb 后端默认写这个设备ls -l /dev/fb0触摸设备节点存在tslib 需要读取 input 事件ls -l /dev/input/event*libstdc版本匹配若未完全静态要匹配工具链版本strings /usr/lib/libstdc.so.6 | grep GLIBCXX中文字体文件存在Qt 默认不带任何字体find /usr/share/fonts -name *.ttftslib 配置存在tslib 插件需要/etc/ts.confcat /etc/ts.conf字体这块特别容易忘。Qt 5.14 静态编译后字库文件依然是外部文件不会编进二进制。板子上如果没有中文字体中文全部显示成方块。把开发机上任意一个.ttf字体拷贝到/usr/share/fonts/下程序里通过QFont指定即可。6.2 QPA 平台插件的动态加载问题与静态强制指定静态 Qt 程序在启动时不会去plugins/platforms目录找.so所有插件必须提前静态链接进二进制并用QTPLUGIN告诉 qmake 要导入哪些插件。回到hello.pro修改QT widgets TARGET hello TEMPLATE app SOURCES main.cpp QTPLUGIN qlinuxfb qtslibqlinuxfb对应 linuxfb 平台插件qtslib对应 tslib 输入插件。如果不加QTPLUGIN qlinuxfb程序会启动时报could not find or load the Qt platform plugin linuxfb因为静态模式下它不会自动去扫目录。目标板上启动时还需要显式指定 QPA 后端export QT_QPA_PLATFORMlinuxfb export TSLIB_TSDEVICE/dev/input/event2 export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/etc/ts.conf ./helloTSLIB_TSDEVICE要根据你实际的触摸设备节点调整不确定时逐个cat /dev/input/eventX看哪个节点有数据。/etc/ts.conf里一般需要保证有一行module_raw input这是 tslib 读取 Linux input 事件的入口。6.3 静态程序的体积、启动方式与裁剪思路静态链接后的 Qt Widgets 程序-release加上strip之后通常能控制在 20~35MB 左右。裁剪命令aarch64-linux-gnu-strip hello如果体积仍然超出预期可以进一步在 Qt configure 时通过-no-feature-*批量关掉不需要的 Qt 特性。比如-no-feature-concurrent -no-feature-xml等但建议在功能稳定后再做减法。我见过一个极端裁剪的例子把 Qt 5.14.2 静态库压到 8MB 级别但那需要对业务代码有极深的了解不适合作为默认方案。启动方式上可以直接在 shell 里跑也可以写成/etc/init.d/脚本。我个人更推荐直接启动并利用 systemd 的Restarton-failure保证异常退出后能自动拉起比硬编码开机脚本更可控。7. 踩坑实录我在整个搭建过程中翻过的五个大坑7.1 configure 特性探测“假通过”导致编译到一半失败交叉编译时configure 会在宿主机上编译并尝试运行目标架构的测试程序但它无法在 x86_64 上直接运行 aarch64 程序所以很多特性探测会“跳过运行步骤”直接默认 yes 或 no。这个机制导致一个严重问题configure 阶段显示通过的模块编译到一半才可能暴露头文件或库不匹配。我的亲身经历是 ICU 模块。configure 日志里明明显示ICU yes结果make module-qtbase跑到qstring.cpp时狂报unicode/uvernum.h: No such file or directory。原因就是宿主机上恰好装了 ICU 的开发头文件configure 误判目标板也有。后来我在 configure 参数里强制-no-icu问题彻底消失。教训是configure 的输出不能完全相信特别是交叉环境。凡是能在 configure 阶段关闭的、业务用不到的模块一律显式关闭。7.2 链接顺序导致的“未定义引用”静态库链接有一个和动态库完全不同的铁律库的顺序必须从依赖方指向被依赖方。Qt 程序用g手写链接命令时-lQt5Widgets必须在-lQt5Gui之前-lQt5Gui必须在-lQt5Core之前tslib 库要在最后。一开始我手写 Makefile 时习惯把-lts写在最前面于是链接器报了一堆ts_xxx: undefined reference。原因就是链接器是按从左到右的顺序解析符号遇到-lts时没有任何目标文件引用它它就什么都不做等到后面的目标文件需要ts_open时已经来不及了。用 qmake 时一般不会碰到这个问题qmake 会按照LIBS的顺序正确处理。但如果你要手写编译脚本或者把 Qt 库喂给其他构建系统务必保持依赖顺序。7.3 OpenSSL 版本不匹配TLS 握手失败的源头我在一次集成中Qt 网络模块编进去了程序也能跑但一访问 HTTPS 就提示qt.network.ssl: QSslSocket: cannot resolve OpenSSL 1.1.1 API。排查到后面发现 Qt 链接的 OpenSSL 是目标 sysroot 里的 1.0.2版本太旧。Qt 5.14 对 OpenSSL 的 API 要求是 1.1.x1.0.2 虽然能链接过运行时却会缺失很多符号。解决方法是把 OpenSSL 固定在 1.1.1w并且在 configure 的-I/-L里明确指向 sysroot 下的对应路径排除掉宿主机/usr/local/lib/libssl.a的影响。另外 OpenSSL 的no-shared必须显式给出否则 Qt 链接静态库时可能出现奇怪的双链接。7.4 tslib 插件静态链接成功但触摸完全无反应这个坑很有迷惑性。configure 里 tslib 显示 yesQTPLUGIN 加了 qtslib程序启动后界面正常但触摸屏怎么点都没反应。排查链路是这样的先readelf -d hello | grep NEEDED看二进制里有没有libts发现没有说明 tslib 没有真正链进去。检查hello.pro确实写了QTPLUGIN qtslib。仔细看 qmake 生成的 Makefile原来QTPLUGIN只会自动处理 Qt 官方插件库但libqtslib.a依赖外部的libts.aqmake 不会自动把-lts加进链接命令。解决办法是在 pro 文件里显式追加LIBS -lts必须放在最后。改完重新 make再用readelf确认libts已经链入上板后触摸恢复正常。这个坑的通用性在于所有 Qt 插件依赖的外部库都需要你手工补LIBSqmake 不会替你操心。7.5 C 运行时 ABI 不一致有一次我图省事直接用了一款老旧的厂商工具链GCC 版本是 5.4。Qt 5.14 编出来了应用也编出来了但跑到目标板上直接报./hello: /lib/aarch64-linux-gnu/libstdc.so.6: version CXXABI_1.3.12 not found原因是 Qt 5.14 的某些代码使用了较新的 C 标准库符号而目标板 rootfs 里的 libstdc.so.6 来自 GCC 5.4符号表太老。工具链版本和 rootfs 里 libstdc 版本不一致是嵌入式开发里最容易忽略的 ABI 问题。解决办法有三个方向一是统一工具链和 rootfs 的 libstdc 版本二是把工具链自带的高版本libstdc.so.6直接部署到目标板对应路径三是在 pro 文件里加QMAKE_LFLAGS -static-libstdc -static-libgcc让 C 运行库也静态链入。第三种方式最省事也避免了目标板上动态库版本冲突的隐患缺点是二进制体积再增加几 MB。考虑到静态交叉编译本来就不是为了追求极限体积我的建议是直接上这个选项。整套 Qt 5.14.2 aarch64 静态交叉编译环境从工具链准备到第一个静态 GUI 程序上板跑通我第一次完整走下来差不多花了两个完整工作日。大部分时间都花在 configure 参数调整和依赖库链入上。如果让我再做一次我一定会先把 tslib、OpenSSL 这类第三方依赖全部交叉编译好并验证完.a文件存在再开始跑 Qt 的 configure而不是等 Qt 编译到一半才回头去补环境。静态交叉编译不复杂复杂的是顺序和细节。按这条路线走你大概率比我当初顺利得多。