十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

aarch64 Qt 5.14.2 静态交叉编译完整实战手册

aarch64 Qt 5.14.2 静态交叉编译完整实战手册 如果你接过 aarch64 工控板或嵌入式设备的 Qt 开发任务大概率会被“静态交叉编译”这几个字折腾一遍。我调试嵌入式 Linux 也快十年了从 arm32 一路做到 aarch64最近用 Qt 5.14.2 给一块 ARM64 网关做显示程序本以为老路子照旧结果还是被 configure 参数、依赖库链接、平台插件静态导入连续坑了三天。先把结论放前面这套方案完全可行成品就是一个拷过去就能跑的 ELF 可执行文件但你必须把工具链、第三方依赖、configure 参数、静态插件导入四件事一次性理顺否则会在编译的不同阶段反复返工。这篇文章就是把从零到一的完整过程整理成手册覆盖宿主环境、交叉工具链、OpenSSL 和 SQLite 依赖编译、Qt 5.14.2 源码裁剪、应用编译、目标板验证以及一份我自己踩过的坑的记录。适合刚上手 aarch64 开发、或者以前只做过动态编译想做静态版的朋友参考也适合遇到“编译过了但目标板跑不起来”的人来对照找原因。1. 方案选型为什么是 Qt 5.14.2、aarch64、静态链接1.1 目标场景aarch64 设备上最常见的部署诉求aarch64 是 ARM 64 位架构树莓派 4/5 的 64 位模式、瑞芯微 RK3568/RK3588、飞腾 D2000、以及很多国产工控机和边缘网关都是这个架构。这类设备的共同点是性能够用但资源紧张存储空间通常以 GB 计算系统环境五花八门生产环境又往往不允许随便装库、改环境。在这样一块板子上跑 Qt 程序最常见的方式有两种一是把 Qt 运行时库编译成 .so 动态库连同一个目录拷到目标板用 LD_LIBRARY_PATH 指定路径运行二是把所有 Qt 库以静态方式编进可执行文件做一个“一个文件走天下”的二进制。我在实际项目里两种都做过在对外交付、设备数量大、现场维护成本高的情况下静态编译明显更省心这也是这篇文章选择“静态交叉编译”这个方向的重要原因。1.2 版本选择的现实逻辑很多读者会问Qt 都出到 6.x 了为什么还要死守 5.14.2这件事要分两面看。一方面Qt 5.12 和 5.15 是官方定义的 LTS 版本但 5.15 之后对开源用户的服务策略发生了变化很多企业项目出于授权风险和长期维护考虑把版本锁在了 5.14.2 或 5.12.x。另一方面5.14.2 是 qmake 体系非常成熟的版本对 C14 友好QWidget 和 QML 都能稳定支撑网上资料充足遇到问题基本都能搜到答案。相比 Qt 65.14.2 的构建系统仍然是 configure qmake 的老流程文档多、坑位少、mkspec 完备。对嵌入式项目来说追求的不是最新的语言特性和框架能力而是“一个版本用三年编译一次吃遍所有板子”的稳定性。所以我最后选择了 5.14.2并且把整套流程固定下来作为团队的内部标准搭建手册。1.3 静态链接和动态链接到底怎么选静态编译和动态编译没有绝对好坏关键是匹配项目阶段和交付模式。我用一个表格把两类方案在我这个目标场景下的表现列出来对比维度静态链接动态链接部署复杂度单个可执行文件 少量资源需要拷贝 Qt 库目录配置运行路径二进制体积明显偏大hello world 也能到 8MB 左右小但整个发布目录加起来不小现场调试改代码要重新编译整包换 .so 更灵活但容易版本错乱组件更新必须重编整个应用可以单独换库对目标板环境依赖极小只要内核能跑 ELF 即可依赖 glibc 版本、库扫描路径等这个表基本反映了我做选择时的真实思考。项目要交付给现场、放在几十台网关上长期运行一旦出现库缺失、版本冲突远程指导运维处理成本极高。静态编译把所有 Qt 库揉进一个文件目标板只需要一份二进制加可能的字体资源彻底绕开“开发机上能跑、生产上一启动就报找不到 .so”的尴尬局面。当然静态也不是无代价。如果应用要联网DNS 解析依赖 glibc 的动态模块我不建议把 glibc 也全部静态进可执行文件所以本文的“静态”统一指“Qt 库静态链接”glibc 仍使用目标板的系统库这个细节会在后面应用编译部分专门解释。2. 搭建宿主机开发环境与交叉编译工具链2.1 宿主机系统与必要软件包宿主机我建议用 Ubuntu 20.04 或 22.04 的 64 位版本Windows WSL 里也行但工具链和路径坑多不推荐新手踩。我实际用的是 Ubuntu 20.04 虚拟机8 核 16G 内存编译 Qt 全量模块花了约 50 分钟。如果你的机器内存只有 4G编译时一定要把并行线程调小比如 make -j2否则很容易在链接阶段被 OOM 杀掉。进入正题之前先把基础的编译工具装齐sudo apt update sudo apt install -y build-essential cmake python perl git \ gcc-multilib g-multilib libgl1-mesa-devgcc-multilib 和 g-multilib 主要解决一些 32 位宿主辅助工具的问题libgl1-mesa-dev 是 Qt configure 在检测 OpenGL 相关选项时可能需要的东西即使我们最终关闭 OpenGL提前装上能少一些检测阶段的干扰。装完后在终端里确认一下gcc --version cmake --version能正常输出版本就行。接下来是主角aarch64 交叉编译工具链。2.2 安装 aarch64 交叉工具链交叉编译工具链的作用是在 x86_64 的宿主机上产出能够在 ARM64 目标板上运行的代码。Ubuntu 官方源里就有现成的交叉工具链安装最简单sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu装完后工具链位置在 /usr/bin 下带 aarch64-linux-gnu- 前缀比如 aarch64-linux-gnu-gcc、aarch64-linux-gnu-g、aarch64-linux-gnu-strip。这套工具链对应 glibc 版本相对较新适合大多数 aarch64 Linux 目标板。如果你要针对一个特别老的 BSP 定制板那最好用厂商提供的工具链压缩包解压到 /opt 下并把 ./bin 加入 PATH。但我更推荐普通项目先用 Ubuntu 官方这套因为它路径固定、符号链接完整配合 Qt 的 linux-aarch64-gnu-g mkspec 几乎零配置。工具链装好只是第一步真正影响后续编译效率的是目录结构。我的习惯是把所有交叉编译产物统一放到一个“伪 sysroot”下避免多个项目散落各处、连接失败时四处找不到 .a。后面的所有依赖库都会安装到这个前缀下面。sudo mkdir -p /opt/aarch64-cross/{bin,lib,include,usr}2.3 统一依赖安装前缀目录结构我见过很多新手在做交叉编译时只把编译器换成 aarch64-linux-gnu-gcc但 ./configure 的 --prefix 仍然写 /usr/local结果编译依赖库时直接把 x86_64 架构的 .a 文件覆盖进了系统目录后续 Qt configure 又错拿到宿主机的库最后链出一堆架构不匹配的中间产物运行必段错误。为了避免这种问题我给这篇文章定了一个目录约定所有交叉编译出的第三方库统一安装到/opt/aarch64-cross/usr下面。这样依赖库和宿主机系统库完全隔离Qt configure 时用 -I 和 -L 指定路径即可想清理掉整个目录也只需要一行 rm。后续文章里出现的/opt/aarch64-cross/usr/include和/opt/aarch64-cross/usr/lib就是 OpenSSL、SQLite 等依赖库存放的位置。Qt 最终安装则独立使用/opt/qt5.14.2-aarch64-static方便随时删了重编不影响其它项目。2.4 工具链自检编译第一个 aarch64 程序正式开始 Qt 之前先用工具链做一个最小自检排除工具链本身的问题。写一个 hello_arm.c#include stdio.h int main(void) { printf(hello aarch64\n); return 0; }编译并查看文件格式aarch64-linux-gnu-gcc hello_arm.c -o hello_arm file hello_arm正常情况下输出hello_arm: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked ...看到 “ARM aarch64” 就说明工具链工作正常。这个自检每次搭建新环境我都先跑一遍省得后面 Qt 编了半小时才发现编译器前缀配错浪费的时间根本不值。3. 交叉编译第三方依赖库OpenSSL 与 SQLite 实战3.1 为什么不用宿主机的 so 库初学者最容易踩的一个坑就是 configure 报找不到某个库于是直接把宿主机 /usr/lib/x86_64-linux-gnu 下的 .so 路径塞进编译命令。这在交叉编译里是致命的宿主机库是 x86_64 架构链接器不会明确报“架构不对”而是在链接完成、目标板一执行就段错误排查成本极高。正确的姿势是所有依赖库都拿 aarch64 工具链重新编译一份静态版本统一放入 /opt/aarch64-cross/usr。本文场景里Qt 编译时主要需要 OpenSSL如果启用网络加密和 SQLite如果使用数据库。其它如图片、字体、zlib为了减少麻烦我直接让 Qt 使用自带实现后面我会解释原因。3.2 OpenSSL 静态交叉编译实战Qt 5.14.2 的 QSslSocket 默认依赖 OpenSSL。如果你不管configure 会检测到缺失并自动禁用 TLS程序能编译但 HTTPS 请求全部失败。我做的是网关程序必须和云端通信所以 OpenSSL 是刚需。选版本要注意OpenSSL 3.x 的内部类型和 1.1.1 有变化Qt 5.14.2 配合 1.1.1 系列最省心。我用的版本是 openssl-1.1.1w这是 1.1.1 系列的尾声版本稳定且兼容性好。wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar xf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./Configure linux-aarch64 no-shared \ --prefix/opt/aarch64-cross/usr \ --cross-compile-prefixaarch64-linux-gnu- make -j$(nproc) sudo make install_sw这条命令里有几个关键点linux-aarch64OpenSSL 针对 aarch64 Linux 的配置目标。no-shared让 OpenSSL 只生成 libcrypto.a 和 libssl.a不生成 .so这是“静态依赖”的基础。--cross-compile-prefixaarch64-linux-gnu-告诉 OpenSSL 使用交叉工具链。install_sw只安装软件库和头文件不装 man 文档节省时间。完成后确认文件ls /opt/aarch64-cross/usr/lib/libcrypto.a /opt/aarch64-cross/usr/lib/libssl.a如果两个 .a 都在说明 OpenSSL 交叉编译成功。后面 Qt configure 时使用 -openssl-linkedQt 就会直接链接这两个静态库。3.3 SQLite 静态交叉编译实战Qt 自带了 SQLite但默认编译选项相对保守而且不同 Qt 版本对 SQLite 的裁剪方式不同。项目里如果要做复杂 SQL、需要启用特定扩展建议还是使用系统 SQLite 静态库并用 -system-sqlite 让 Qt 走外部库。SQLite 有两个源码分发渠道一个是 autoconf 打包的完整源码一个是 amalgamation 合并版源码。这里我用 autoconf 版本的源码因为它自带 configure 脚本交叉编译流程最简单wget https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz tar xf sqlite-autoconf-3420000.tar.gz cd sqlite-autoconf-3420000 ./configure --hostaarch64-linux-gnu \ --prefix/opt/aarch64-cross/usr \ --disable-shared --enable-static make -j$(nproc) sudo make install--hostaarch64-linux-gnu 是 autoconf 体系交叉编译的关键参数它告诉 configure 生成能在 aarch64 上运行的目标产物。--disable-shared 与 --enable-static 保证只生成 libsqlite3.a。如果你的工程完全不使用数据库这一步可以直接跳过Qt configure 时加 -no-sql-sqlite 即可。但考虑到很多嵌入式应用都要存配置、存日志把 SQLite 编好备用性价比很高。3.4 图像、字体、zlib 等依赖的取舍策略不要盲目给所有依赖库都“手动交叉编译”那是自找麻烦。Qt 5.14.2 的 configure 支持许多-qt-*参数意思是“使用 Qt 源码包内置的第三方库实现”。在静态交叉编译场景下优先使用这些内置实现可以有效避免外部库版本对不上、路径指错等一连串问题。我在实际编译中的策略是zlib、libpng、libjpeg全部用-qt-zlib -qt-libpng -qt-libjpegQt 自己编一份 aarch64 静态版本不依赖外部。freetype使用-qt-freetype保证 Qt 有完整的字体渲染引擎中文显示不会因为宿主缺少 freetype 开发包而崩溃。fontconfig直接-no-fontconfig。嵌入式目标板不带 fontconfig 很常见Qt 自己具备字体管理能力。xcb、OpenGL-no-xcb -no-opengl。我们是纯 framebuffer/linuxfb 场景不需要 X11 和 GPU 加速。cups-no-cups工控机上谁会用 Qt 去调打印服务。这样一套取舍下来第三方依赖从“四个库”降到“OpenSSL SQLite 两个真需要”其余交给 Qt 内部实现。configure 阶段干净的代价最小。4. Qt 5.14.2 源码获取与 configure 参数详解4.1 源码准备选择 qt-everywhere 还是 qtbaseQt 5.14.2 的源码分发主要有 qt-everywhere-src 合并包和按模块拆分的仓库。如果你想少踩依赖坑直接下载 qt-everywhere-src-5.14.2.tar.xz 合并包里面包含了 qtbase、qtdeclarative、qtsvg、qtimageformats 等几乎所有常用模块configure 时用 -skip 参数把不需要的模块砍掉。下载地址推荐使用国内镜像比如清华镜像源目录wget https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.14/5.14.2/qt-everywhere-src-5.14.2.tar.xz tar xf qt-everywhere-src-5.14.2.tar.xz解压后源码目录是 qt-everywhere-src-5.14.2。请记住一个 Qt 交叉编译的习惯不要在源码目录内直接执行 configure而是新建一个独立的 build 目录在目录里调用源码根目录的 configure。原因很简单Qt 源码树一旦被某个版本的编译配置污染想恢复原状非常麻烦独立 build 目录可以随时删掉重来。4.2 configure 关键参数逐个解读Qt 的 configure 参数几十个真正关键的也就那十几个。我把它们分成几类逐个解读这样你以后遇到 Qt 5.12、Qt 5.15也能自己举一反三。第一类是“必须的”-opensource -confirm-license接受开源协议否则 configure 会停下来问交互问题脚本无法自动化。-xplatform linux-aarch64-gnu-gQt 对交叉编译使用的 mkspec。mkspec 是 qmake 的工具链配置描述这个配置告诉编译器使用 aarch64-linux-gnu-gcc / aarch64-linux-gnu-g。选择它而不是 -device是因为它更通用不绑定某个厂商开发板的 sysroot。-prefix /opt/qt5.14.2-aarch64-static最终安装目录。生产环境建议统一约定不要散落。-static生成静态库而不是 .so这是整个手册的核心诉求之一。-release编译 release 版本去掉调试符号减小体积。第二类是“嵌入式裁剪”-no-pch关闭预编译头。交叉编译时预编译头的生命周期管理容易出问题关闭能少踩坑。-no-opengl关闭 OpenGL避免依赖目标板的 GPU 驱动和 Mesa 库。-no-xcb -no-xkbcommon -no-xkbcommon-evdev关闭 X11 和键盘扩展协议支持。-linuxfb启用 linuxfb 平台插件这是 Qt 跑在 Linux framebuffer 上的关键支持。没有它目标板没有显示输出。-no-fontconfig禁用 fontconfig前文已经解释嵌入式环境用 Qt 自带字体引擎。-no-cups禁用打印支持。第三类是“外部依赖”-qt-zlib -qt-libpng -qt-libjpeg -qt-freetypeQt 内置这些第三方库实现减少外部依赖。-openssl-linked启用 OpenSSL并采用链接方式相对于运行时动态加载。-system-sqlite使用外部 SQLite对应 3.3 节交叉编译好的 libsqlite3.a。第四类是“编译范围”-nomake examples -nomake tests -nomake tools不编译示例、测试和几乎用不到的工具节省一半编译时间。-skip qtwebengine -skip qtwebview -skip qtdoc显式跳过 WebEngine 等巨型模块。qtwebengine 的构建极其耗时且依赖非常多嵌入式静态编译通常用不到。4.3 我的完整配置命令把上节的参数拼到一起就是我在实际项目中使用的命令。执行之前务必确认 PATH 里能找到 aarch64-linux-gnu-gccexport PATH/usr/bin:$PATH export CFLAGS-I/opt/aarch64-cross/usr/include export CXXFLAGS-I/opt/aarch64-cross/usr/include export LDFLAGS-L/opt/aarch64-cross/usr/lib mkdir -p ~/dev/build-qt cd ~/dev/build-qt ~/dev/qt-everywhere-src-5.14.2/configure \ -prefix /opt/qt5.14.2-aarch64-static \ -opensource -confirm-license \ -xplatform linux-aarch64-gnu-g \ -release -static -no-pch \ -no-opengl -no-xcb -no-xkbcommon -no-xkbcommon-evdev \ -linuxfb \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype \ -no-fontconfig -no-cups \ -openssl-linked \ -I/opt/aarch64-cross/usr/include \ -L/opt/aarch64-cross/usr/lib \ -nomake examples -nomake tests -nomake tools \ -skip qtwebengine -skip qtwebview -skip qtdoc -skip qtconnectivity这里特别说明一下 CFLAGS 和 configure 里的 -I/-L 都写了看似重复实际上前者是给依赖库编译用的统一环境变量后者是让 Qt 的 configure 检测脚本能找到 OpenSSL 头文件和 SQLite 库的保证。两条路都通畅configure 的自动检测阶段才能零阻碍通过。configure 跑完先别急着 make。仔细看终端输出的汇总信息重点确认以下几项“Build type: linux-g (aarch64, CPU features: ...)” 说明平台识别正确。“OpenSSL: yes” 说明 OpenSSL 检测通过。“SQLite: system” 说明使用的是外部 SQLite。“Platform plugins: linuxfb” 说明 linuxfb 插件已启用。如果这些关键项不对直接 CtrlC 停下来检查不要抱着“可能没事”的心态继续否则 make 两小时后发现问题再重跑 configure心态会崩溃。4.4 编译安装与结果验证确认 configure 汇总信息无误后开始正式编译make -j$(nproc) sudo make install整个编译过程根据机器配置大约 40 到 90 分钟。我这边 8 核机器全量编译大约 50 分钟。编译期间机器负载会很高建议不要同时跑其它重活。安装完成后验证产物/opt/qt5.14.2-aarch64-static/bin/qmake -v file /opt/qt5.14.2-aarch64-static/lib/libQt5Core.a ls /opt/qt5.14.2-aarch64-static/plugins/platforms/qmake -v 应显示 Qt 版本 5.14.2libQt5Core.a 的 file 输出应带“current ar archive”字样plugins/platforms 目录下应能看到 libqlinuxfb.a。看到这些Qt 静态库主体就算成功了。5. 用静态 Qt 编译第一个 aarch64 应用5.1 工程文件 pro 的关键配置Qt 本体编好之后编译应用就简单多了。但静态 Qt 的 .pro 文件和动态 Qt 有些区别核心在于平台插件的处理。我以一个最简单的 Widgets 程序为例。工程文件 app.proQT core gui widgets TARGET app TEMPLATE app CONFIG c14 SOURCES main.cpp QTPLUGIN qlinuxfbQTPLUGIN 这一行是关键。静态链接时插件不会像动态库那样被自动加载qmake 需要知道你要把哪个平台插件链入可执行文件。qlinuxfb 对应 plugins/platforms/libqlinuxfb.a它是最小 framebuffer 平台插件。main.cpp 内容如下#include QApplication #include QtPlugin #include QLabel Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin) int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label(Hello aarch64 Qt); label.resize(320, 200); label.show(); return app.exec(); }Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin) 是第二个关键点。这句话告诉静态链接器把 linuxfb 插件的对象代码从 .a 里拉进来。没有这一行即使 .pro 写了 QTPLUGIN也经常会出现运行时报 “could not find or load the Qt platform plugin linuxfb” 的错误。这是 Qt 静态编译最经典的坑没有之一。5.2 静态导入平台插件最容易被忽略的一步理论上 QTPLUGIN 加 Q_IMPORT_PLUGIN 双管齐下平台插件就稳了。但为什么经常还看到有人栽在这里我分析下来主要有三个原因。第一Q_IMPORT_PLUGIN 的类名必须严格对应插件内部导出的类名。不同 Qt 版本、不同插件的类名可能不同Qt 5.14.2 里 linuxfb 插件的类名是 QLinuxFbIntegrationPlugin如果你的 Qt 版本不同要去阅读插件源码确认实际类名。第二Q_IMPORT_PLUGIN 只能出现在一个翻译单元里而且必须在 main.cpp 中包含不能放进一个冷门的源文件否则链接器可能不会拉入那个对象文件。第三如果你同时需要多个平台插件比如 linuxfb 和 offscreen后者适合无屏幕调试就要写多个 Q_IMPORT_PLUGIN 宏。我一般在工程里默认开启 linuxfb 用来跑真机遇到调试环境没有 fb 设备时临时改成 offscreen 插件也能凑合。5.3 编译、strip、部署三步走编译命令非常简单cd app /opt/qt5.14.2-aarch64-static/bin/qmake make -j$(nproc) aarch64-linux-gnu-strip app file appfile 的输出会显示app: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked ...这里有一个非常重要的现象值得说明显示 dynamically linked是因为我们的程序仍然依赖目标板的 glibc 动态库。这符合设计预期。只有 Qt 库是静态的glibc、libstdc 这些基础系统库以及 C/C 运行时仍然动态链接到目标板环境。如果我连 libstdc 和 libgcc 都想静态可以在 .pro 里追加QMAKE_LFLAGS -static-libgcc -static-libstdc我不建议再加 -static也就是不把 glibc 全静态。glibc 全静态会导致 DNS 解析依赖的 nss 模块无法加载应用一联网 getaddrinfo 就失败或卡死排查起来非常痛苦。strip 之后一个带 QLabel 的最小 Qt Widgets 程序大约 5 到 8MB可以接受。部署只需要把这一个文件 scp 到目标板scp app userboard:/opt/ ssh userboard /opt/app -platform linuxfb5.4 目标板运行验证与显示测试如果目标板没有连接显示器或者没有 /dev/fb0 设备运行时会报错qt.qpa.plugin: Could not load the Qt platform plugin linuxfb in even though it was found.这句话非常容易误导人它嘴上说 “even though it was found”实际上意味着插件对象没有被静态链进可执行文件或者 /dev/fb0 不存在。排查步骤如下第一步确认内核有没有 framebuffer 设备ls /dev/fb*如果列表为空说明板子的内核没开 framebuffer 或启动参数没配这时需要用 offscreen 插件做无显示测试在 main.cpp 里把导入类改成 QOffscreenIntegrationPlugin或者保留 linuxfb 的同时再加一个 offscreen 导入。第二步确认 Q_IMPORT_PLUGIN 是否真的写对了。拿掉它重新编译对比正常情况下程序会因为找不到插件而崩溃加了它就能跑这是确认插件是否链入的最直接方法。第三步目标板如果接的是 HDMI 转 VGA 或者 LVDS 屏幕请检查内核启动参数里的 video 配置很多 ARM 板的 framebuffer 需要内核参数指定分辨率比如 video1920x108060。这一步和设备树相关属于目标板 BSP 层面不是 Qt 能解决的。6. 常见错误与排查经验速查6.1 错误类型总览与定位手法交叉编译的错误大概分四类configure 检测错误、编译错误、链接错误、运行错误。其中 configure 检测错误最友好报错位置明确链接错误最常见信息也相对直白运行错误最坑因为目标板上的日志有限很多时候只能靠试。我的排查顺序是确认目标板架构与可执行文件架构一致用 file 命令。确认链接器实际使用的库路径用 readelf -d 查看动态段或 ldd 观察。确认所有手动指定的 -I/-L 路径下没有混入 x86_64 架构的库文件。对运行时报错优先怀疑平台插件没有静态导入。这几条定位思路可以应付绝大多数静态交叉编译问题。6.2 高频报错逐项拆解报错信息可能原因解决方案ERROR: Cannot detect pkg-config依赖库头文件缺失或路径不对检查 -I/-L 是否指向 /opt/aarch64-cross/usr确认依赖库先编译完openssl/ssl.h: No such file or directoryOpenSSL 头文件没有安装进 sysroot返回 3.2 节确认 make install_sw 已执行cannot find -lGL / -lfontconfig依赖了宿主机 GL/fontconfig 库configure 加 -no-opengl -no-fontconfig用 Qt 内置 freetypecould not find or load the Qt platform plugin linuxfb平台插件未静态导入检查 QTPLUGIN 和 Q_IMPORT_PLUGINrelocation R_AARCH64_ABS32 out of range混用了 x86_64 编译的 .a 文件检查依赖库架构重新交叉编译程序启动秒退dmesg 看到 Illegal instruction工具链目标架构和板子 CPU 特性不匹配确认板子是否 armv8必要时换厂商工具链configure 提示 target linux-aarch64-gnu-g is not valid使用的 Qt 源码不完整或版本不对确认下载的是完整 qt-everywhere-src-5.14.2 合并包collect2: error: ld returned 1 exit status未定义引用 Qt5Core链接器没用 Qt 的 qmake 生成的 Makefile必须用 /opt 路径下的 qmake不要用宿主系统 qmake6.3 体积优化技巧静态 Qt 二进制确实偏大但对现代嵌入式设备来说8MB 甚至 15MB 通常不是问题。如果项目对体积敏感有三个优化手段可以叠加使用。第一个是 strip这个我在 5.3 节已经做过了能去掉符号表体积能减少 30% 左右。第二个是在 Qt configure 阶段加入-optimize-size参数让整个 Qt 库按体积优化编译和系统 Linux 发行版的 -Os 优化思路一致体积能再下降一些。第三个是 UPX 压缩aarch64 架构的 UPX 支持已经比较成熟upx --best app能把可执行文件压到原来的三分之一左右代价是启动时解压会稍慢几十毫秒运行期间内存占用不变。要注意 UPX 压缩后的程序在某些内核配置下可能被 seccomp 或安全模块拒绝工控系统里如果有严格的 SELinux 策略建议先压一版跑几天再看。我目前的生产版本没有上 UPX因为多出的几 MB 对设备存储来说完全无感压缩带来的额外不确定性不值得。写在最后的一点经验把整套流程走通之后回头再看真正吃时间的不是 make而是理解 configure 和链接阶段“为什么”。我最想提醒后来者的一件事是静态交叉编译的本质是“架构隔离”宿主机的任何 .so 和 /usr/include 下的头文件都不能随手拿来用所有东西从编译器到依赖库再到 Qt 自身必须是一套完整的 aarch64 体系。统一前缀目录、统一工具链、统一参数模板这三点做到位Qt 5.14.2 静态交叉编译就是一件一次配置、长期复用的标准活儿。最后再分享一个小技巧搭建这套环境的第一次尝试强烈建议在虚拟机里做并在每个阶段的成功节点拍一个快照。工具链装好拍一个OpenSSL/SQLite 编完拍一个Qt 编完拍一个。后续给新板子适配时直接回滚到对应快照改参数比从头再编一遍省出整整一个下午。
返回列表