ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

aarch64 静态 Qt 交叉编译实战:从工具链到部署

aarch64 静态 Qt 交叉编译实战:从工具链到部署 1. 为什么值得折腾 aarch64 静态 Qt1.1 这个手册解决的是什么问题如果你手上有块 ARM 开发板——比如树莓派、Orange Pi、瑞芯微或者鲲鹏平台的服务器——想在上面跑一个 Qt 界面程序大概率会遇到一个很现实的问题板子上的系统版本五花八门glibc 版本、libstdc 版本、各种依赖库的版本全都不一样。你在一台机器上编译好的动态链接程序拷到另一台机器上直接报version GLIBC_2.29 not found或者cannot mix incompatible Qt library这种让人头大的错误。静态交叉编译就是来解决这个问题的。它的核心思路是在 x86_64 的开发机上用一套针对 aarch64 架构的交叉编译工具链把 Qt 库和你的应用程序全部编译成静态库最终链接出一个几乎不依赖目标系统任何动态库的可执行文件。这个文件拷到板子上只要内核架构对得上直接就能跑。我这次选的是Qt 5.14.2这个版本。为什么不是 5.15 或者 6.x原因很实际5.14.2 是 LTS 版本稳定性经过大量项目验证而且它的构建系统还是传统的 qmake configure 脚本交叉编译的坑相对少。5.15 之后有些模块的构建逻辑变了6.x 更是全面转向 CMake交叉编译的复杂度直接上一个台阶。对于第一次做静态交叉编译的人来说5.14.2 是最友好的起点。1.2 静态编译和动态编译的本质区别很多人对静态的理解停留在文件大一点这个层面其实远不止如此。动态链接的程序在运行时需要加载器去找到对应的.so文件这些文件可能来自系统路径、可能来自你设置的LD_LIBRARY_PATH一旦找不到或者版本对不上程序直接起不来。而静态链接是把所有用到的代码在编译期就焊死进可执行文件里运行时不需要再去找任何外部库。这带来的好处很直接部署极简一个文件拷过去就能跑不用管目标机的库版本环境隔离不会因为目标机升级了某个库导致你的程序崩溃启动略快省去了动态链接器的符号解析过程代价也很明显体积膨胀一个简单的 Qt 窗口程序静态编译后可能 15MB 起步动态的可能只有几百 KB许可证问题Qt 的 LGPL 协议对静态链接有额外要求商业项目需要购买商业许可或者开源你的代码编译时间长Qt 本身几百万行代码全量静态编译在普通机器上要跑一两个小时提示如果你的项目是内部工具、不对外分发静态链接的许可证风险基本可以忽略。但如果是商业产品务必先确认 Qt 的授权条款。1.3 适用人群和前置知识这份手册面向的是有一定 Linux 基础、了解基本编译流程的开发者。你不需要是 Qt 专家但至少得知道configure、make、make install这三板斧是干什么的。如果你之前做过任何形式的交叉编译——哪怕只是编译过一个 hello world——那上手会更快。需要提前说明的是整个过程涉及大量的命令行操作和参数配置我会尽量把每个参数为什么这么写讲清楚而不是让你无脑复制粘贴。因为交叉编译这件事环境差异太大了照抄别人的配置大概率会在某个环节卡住只有理解了原理才能自己排查。2. 工具链选型与环境准备2.1 交叉编译工具链的选择逻辑aarch64 的交叉编译工具链有好几个来源常见的有工具链来源特点适用场景Linaro GCC官方维护版本更新快通用 ARM 开发ARM 官方 GNU ToolchainARM 自家发布针对性强Cortex-A 系列Bootlin Toolchains预编译好开箱即用快速验证发行版自带如 gcc-aarch64-linux-gnu安装方便版本较旧Ubuntu/Debian 开发机我个人的选择是ARM 官方发布的 GNU Toolchain具体版本用 10.3-2021.07 这一版。原因有三第一它是 ARM 自己维护的对 aarch64 架构的支持最到位第二这个版本对应的 glibc 是 2.33兼容性够用又不会太新第三它是预编译好的 x86_64 可执行文件解压就能用不需要自己从头编译工具链。如果你用的是 Ubuntu也可以直接apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu省事但版本可能偏旧。对于 Qt 5.14.2 来说GCC 7 以上就够用了所以发行版自带的也能凑合。2.2 开发机环境的具体配置我的开发机是 Ubuntu 20.04 LTSx86_64 架构配置是 8 核 16GB 内存。这个配置编译 Qt 大概需要 40 分钟到 1 小时。如果你只有 4 核时间可能要翻倍而且内存不足的话链接阶段容易 OOM。先装一批基础依赖这些是编译 Qt 本身需要的sudo apt update sudo apt install -y build-essential perl python3 git \ libgl1-mesa-dev libglu1-mesa-dev \ libxkbcommon-dev libxkbcommon-x11-dev \ libfontconfig1-dev libfreetype6-dev \ libssl-dev libdbus-1-dev \ libinput-dev libts-dev \ flex bison gperf这里有几个包值得单独说一下。libxkbcommon-dev是处理键盘映射的没有它 Qt 的 GUI 模块编译会失败。libfontconfig1-dev和libfreetype6-dev是字体渲染的基础静态编译时字体会被嵌进去。libinput-dev和libts-dev是触摸屏输入相关的如果你的板子有触摸屏就需要。2.3 工具链的下载与验证从 ARM 官网下载工具链压缩包解压到/opt目录下cd /opt sudo tar -xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz sudo mv gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu aarch64-toolchain然后把工具链的 bin 目录加到 PATH 里。我习惯在~/.bashrc里加一行export PATH/opt/aarch64-toolchain/bin:$PATH验证工具链是否可用aarch64-none-linux-gnu-gcc --version aarch64-none-linux-gnu-g --version正常的话会输出 GCC 10.3.1 的版本信息。如果提示 command not found检查一下 PATH 有没有生效或者工具链的目录结构是不是和我说的一致。注意工具链的路径里不要有空格和中文否则 configure 脚本解析路径时会出问题。这个坑我踩过排查了半天才发现是路径里的空格导致的。2.4 目标系统 sysroot 的处理sysroot 是交叉编译里一个核心概念简单说就是目标系统的根文件系统的一个副本。交叉编译时编译器需要从 sysroot 里找目标系统的头文件和库文件。理想情况下你应该从目标板子上把/usr/include、/usr/lib、/lib这些目录拷贝出来做成一个 sysroot。但实际操作中很多人包括我会直接用工具链自带的 sysroot因为 ARM 官方工具链里已经包含了一套完整的 glibc 和基础库。工具链自带的 sysroot 在aarch64-toolchain/aarch64-none-linux-gnu/libc这个路径下。用--sysroot参数指定它就行。这样做的好处是简单坏处是如果你的目标系统有特殊的库比如某个厂商定制的硬件加速库就需要手动往 sysroot 里补。3. Qt 源码的获取与配置3.1 源码下载与目录规划Qt 5.14.2 的源码包可以从 Qt 官方下载站获取文件名是qt-everywhere-src-5.14.2.tar.xz大概 500MB 左右。下载完之后解压到一个工作目录mkdir -p ~/qt-build cd ~/qt-build tar -xf qt-everywhere-src-5.14.2.tar.xz解压后会得到一个qt-everywhere-src-5.14.2目录。我建议再建一个独立的构建目录不要在源码目录里直接编译这样源码保持干净出问题了可以删掉构建目录重来mkdir -p ~/qt-build/build-aarch64 cd ~/qt-build/build-aarch643.2 configure 参数的逐项拆解configure 是整个编译过程最关键的一步参数写错了后面全白搭。我先把完整的命令列出来然后逐项解释../qt-everywhere-src-5.14.2/configure \ -prefix /opt/qt-5.14.2-aarch64-static \ -opensource -confirm-license \ -release \ -static \ -nomake examples -nomake tests \ -no-opengl \ -no-xcb \ -no-eglfs \ -linuxfb \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype \ -qt-pcre \ -no-iconv \ -skip qtwebengine \ -skip qtwebkit \ -skip qtdeclarative \ -xplatform linux-aarch64-gnu-g \ -sysroot /opt/aarch64-toolchain/aarch64-none-linux-gnu/libc下面逐项说明-prefix指定安装路径。这个路径是目标系统上的路径不是开发机上的。编译完成后make install会把文件装到开发机的这个路径下方便你后续引用。-static是核心参数告诉 Qt 构建静态库而不是动态库。这个参数一开所有的模块都会以.a文件的形式产出。-nomake examples -nomake tests跳过示例和测试代码的编译。Qt 自带的示例非常多编译它们会浪费大量时间而且对最终产物没有影响。-no-opengl禁用 OpenGL 支持。如果你的板子没有 GPU 或者不需要硬件加速加上这个能省不少事。Qt 的 OpenGL 模块依赖比较复杂静态编译时容易出问题。-no-xcb禁用 X11 后端。嵌入式设备通常不跑 X11用 framebuffer 或者 EGLFS 就够了。-linuxfb启用 Linux framebuffer 后端。这是最基础的显示方式直接往/dev/fb0写数据几乎所有 Linux 板子都支持。-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype这几个参数让 Qt 使用自带的第三方库版本而不是系统上的。静态编译时强烈建议这么做否则链接阶段会因为找不到系统库的静态版本而失败。-skip qtwebengine -skip qtwebkit -skip qtdeclarative跳过这几个重量级模块。qtwebengine 就是 Chromium编译它需要几十 GB 磁盘和几个小时而且静态编译基本不可行。qtdeclarative 是 QML 相关的如果你只用 Widgets 就不需要。3.3 mkspec 文件的定制-xplatform linux-aarch64-gnu-g这个参数指定了使用哪个 mkspec。Qt 源码里自带了很多 mkspec但针对 aarch64 的静态编译最好自己定制一个。在qt-everywhere-src-5.14.2/qtbase/mkspecs/目录下复制linux-aarch64-gnu-g这个目录改名为linux-aarch64-gnu-g-static然后修改里面的qmake.confQMAKE_CC aarch64-none-linux-gnu-gcc QMAKE_CXX aarch64-none-linux-gnu-g QMAKE_LINK aarch64-none-linux-gnu-g QMAKE_AR aarch64-none-linux-gnu-ar cqs QMAKE_OBJCOPY aarch64-none-linux-gnu-objcopy QMAKE_STRIP aarch64-none-linux-gnu-strip QMAKE_CFLAGS -marcharmv8-a -mtunecortex-a53 QMAKE_CXXFLAGS -marcharmv8-a -mtunecortex-a53-marcharmv8-a指定了 ARMv8-A 架构这是 aarch64 的基础指令集。-mtunecortex-a53是针对 Cortex-A53 核心做优化如果你的板子是 A72 或者 A76可以改成对应的。这个优化主要影响指令调度对性能有轻微影响但不会影响兼容性。4. 编译过程与关键环节4.1 启动编译与并行度设置configure 跑完之后会输出一个摘要告诉你哪些模块会被编译、哪些被跳过。仔细检查这个摘要确认Static build是 yesCross compilation是 yes。然后开始编译make -j$(nproc) 21 | tee build.log-j$(nproc)是用满所有 CPU 核心。我 8 核的机器大概跑了 50 分钟。tee build.log是把输出同时写到文件和终端方便出错了回头看。编译过程中最常见的错误是内存不足。Qt 的某些模块比如 qtbase 的 qmake 部分链接时很吃内存如果机器只有 8GB 内存可能会在链接阶段被 OOM Killer 干掉。解决办法是减少并行度比如make -j4或者临时加 swap。4.2 编译过程中的典型报错与处理报错一fatal error: bits/libc-header-start.h: No such file or directory这个通常是 sysroot 路径不对或者工具链的 sysroot 里缺少某些头文件。检查--sysroot参数指向的路径下有没有usr/include目录。报错二cannot find -lstdc静态链接时需要libstdc.a但有些工具链默认只带了.so版本。检查工具链的lib目录下有没有libstdc.a。如果没有需要从工具链的lib64或者lib/gcc子目录里找。报错三error: numeric_limits is not a member of std这是 C 标准库头文件包含顺序的问题通常出现在 GCC 版本和 Qt 源码不完全匹配时。解决办法是在qmake.conf里加上QMAKE_CXXFLAGS -include limits。报错四undefined reference to pthread_create静态链接 pthread 时需要显式指定-lpthread。在qmake.conf里加上QMAKE_LIBS -lpthread -ldl -lrt。4.3 安装与产物验证编译成功后执行安装make install安装完成后检查/opt/qt-5.14.2-aarch64-static目录应该能看到lib、include、bin等子目录。lib目录下应该全是.a文件如果看到.so文件说明静态编译没生效。验证一下 qmake 是否可用/opt/qt-5.14.2-aarch64-static/bin/qmake -v正常的话会输出QMake version 3.1和Using Qt version 5.14.2。5. 应用程序的交叉编译实战5.1 一个最小 Qt 程序的编译写一个最简单的 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.show(); return app.exec(); }对应的.pro文件QT widgets TARGET hello-qt SOURCES main.cpp用交叉编译版的 qmake 生成 Makefile/opt/qt-5.14.2-aarch64-static/bin/qmake hello-qt.pro make -j$(nproc)编译完成后用file命令检查产物file hello-qt应该输出类似ELF 64-bit LSB executable, ARM aarch64, statically linked的信息。如果显示dynamically linked说明静态链接没生效需要检查.pro文件里有没有加CONFIG static。5.2 静态链接的依赖检查用ldd检查一下aarch64-none-linux-gnu-ldd hello-qt静态链接的程序应该输出not a dynamic executable。如果列出了一些.so文件说明还有动态依赖。有时候即使加了-static某些库还是会以动态方式链接。这时候需要检查 Qt 的mkspecs目录下的qconfig.pri确认QT_CONFIG里没有shared选项。5.3 部署到目标板把编译好的可执行文件拷到板子上直接运行scp hello-qt root192.168.1.100:/root/ ssh root192.168.1.100 ./hello-qt如果板子上有 framebuffer 设备/dev/fb0程序应该能正常显示。如果没有显示检查一下QT_QPA_PLATFORM环境变量export QT_QPA_PLATFORMlinuxfb ./hello-qt6. 常见问题排查与避坑经验6.1 运行时问题速查表现象可能原因解决方法could not find the Qt platform plugin linuxfb平台插件没编译进去确认 configure 时加了-linuxfbcannot mix incompatible Qt library链接了系统上的动态 Qt检查.pro里有没有CONFIG static程序启动后无显示framebuffer 设备不对设置QT_QPA_PLATFORMlinuxfb:fb/dev/fb1字体显示为方块字体没嵌入configure 时加-qt-freetype运行时设置QT_QPA_FONTDIR触摸屏无响应输入设备没配置设置QT_QPA_GENERIC_PLUGINStslib或evdevtouch6.2 几个我踩过的坑坑一-static和-static-libgcc的区别-static是链接所有库的静态版本-static-libgcc只是静态链接 GCC 的运行时库。Qt 的 configure 里-static会同时影响 Qt 库和最终程序的链接方式。但有时候即使加了-staticlibgcc 还是动态链接的需要在qmake.conf里额外加QMAKE_LFLAGS -static-libgcc -static-libstdc。坑二sysroot 里的库版本冲突工具链自带的 sysroot 里的 glibc 版本可能和目标板子上的不一致。如果目标板子的 glibc 比 sysroot 里的旧程序跑起来会报version GLIBC_2.xx not found。解决办法是从目标板子上拷贝/lib和/usr/lib到 sysroot 里覆盖工具链自带的版本。坑三编译时间过长导致的中断Qt 全量编译动辄一小时以上如果中途因为网络问题或者误操作中断了重新make会从断点继续但有时候中间产物会损坏。这时候需要make clean然后重新来。我建议在make的时候用nohup或者screen防止 SSH 断线导致编译中断。坑四磁盘空间不足Qt 源码解压后大概 2GB编译过程中会产生大量中间文件最终占用可能到 15-20GB。加上工具链和 sysroot建议预留至少 30GB 磁盘空间。6.3 性能优化的小技巧静态编译出来的程序体积大启动时加载到内存的时间也长。如果对启动速度有要求可以在链接时加-Wl,--gc-sections来移除未使用的代码段配合编译时的-ffunction-sections -fdata-sections使用。这个优化通常能减少 10%-20% 的体积。另外strip命令可以移除符号表进一步减小体积aarch64-none-linux-gnu-strip hello-qt但 strip 之后就没法用 gdb 调试了建议保留一份未 strip 的版本用于调试。7. 后续扩展与个人体会这套流程跑通之后你可以把它固化成一个脚本下次换 Qt 版本或者换工具链时只需要改几个变量。我自己的做法是把 configure 参数、mkspec 定制、编译命令全部写到一个build.sh里配合 Docker 使用这样换一台开发机也能快速复现环境。另外如果你需要用到 Qt 的某个特定模块——比如 Qt SerialPort、Qt SQL——记得在 configure 时不要-skip它们并且确认对应的依赖库在 sysroot 里存在。Qt 的模块依赖关系比较复杂缺一个库可能导致整个模块编译失败。最后说一个实际体会静态交叉编译这件事第一次做的时候会觉得处处是坑但一旦跑通一次后面就是重复劳动。真正花时间的不是编译本身而是排查各种环境问题。所以我的建议是第一次做的时候把每一步的命令和输出都记录下来形成自己的文档下次遇到类似问题直接查自己的记录比在网上搜半天效率高得多。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表