资讯动态

aarch64嵌入式Qt5.14.2静态编译与交叉编译实战指南

发布时间:2026/9/19 18:27:18 来源:尧图企业网站定制
1. 为什么要在aarch64上折腾Qt静态编译如果你手上有Orange Pi CM5、树莓派这类aarch64开发板又打算用Qt做界面迟早会撞上动态库依赖这堵墙。板子上跑一个Qt程序你得把libQt5Core、libQt5Gui、libQt5Widgets这一大串.so文件全拷过去版本还得对得上稍微漏一个就是error while loading shared libraries。静态编译就是把这堆依赖全部塞进可执行文件里拷一个文件过去就能跑省心。但静态编译Qt不是点个按钮的事。Qt官方预编译包全是动态的你要静态版本只能自己从源码编。而交叉编译又叠了一层复杂度编译机是x86_64的Linux目标机是aarch64工具链、sysroot、configure参数任何一环出错编译过程就会在某个奇怪的地方挂掉报错信息还往往指向不到真正的原因。这篇手册面向的是需要在aarch64目标板上部署Qt应用的开发者尤其是用Orange Pi CM5、树莓派4B/5、各类国产aarch64核心板做产品原型的场景。我会从工具链准备开始一步步走到最终验证把每个参数为什么这么写、每个坑为什么会出现讲清楚。Qt版本锁定在5.14.2这是LTS版本里比较稳的一个很多工业项目还在用。先说清楚一个前提静态编译Qt意味着你的程序不能用LGPL的动态链接豁免条款如果是要发布商业闭源软件得走商业授权或者开源。这个在动手之前就要想明白别编完了才发现授权有问题。2. 交叉工具链与sysroot的准备细节2.1 工具链选型为什么是aarch64-linux-gnu-gcc交叉编译工具链的选择直接决定了后面sysroot能不能对上。aarch64平台常见的有几套命名aarch64-linux-gnu-、aarch64-none-linux-gnu-、aarch64-unknown-linux-gnu-。这些前缀看着差不多但背后的C库实现可能不同。Linaro和ARM官方发布的gcc-arm-*-aarch64-linux-gnu系列用的是glibc和主流发行版兼容性最好我一般优先选这个。Ubuntu下最省事的装法sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu装完之后验证一下aarch64-linux-gnu-gcc -v输出里会显示target是aarch64-linux-gnugcc版本建议在7以上Qt5.14.2对gcc版本有一定要求太老的编译器编不过C14的部分特性。注意不要用aarch64-none-elf-这类裸机工具链那是给没有操作系统的环境用的编Qt会缺一堆系统头文件。2.2 sysroot的获取从目标板同步还是用发行版根文件系统sysroot是交叉编译里最容易出问题的环节。它本质上是目标机根文件系统的一个副本编译器在里面找头文件和库。获取方式有两种第一种是从目标板直接同步。板子上跑着系统用rsync把/usr/include、/usr/lib、/lib拉下来rsync -avz --rsync-pathsudo rsync root板子IP:/usr/include/ ./sysroot/usr/include/ rsync -avz --rsync-pathsudo rsync root板子IP:/usr/lib/ ./sysroot/usr/lib/ rsync -avz --rsync-pathsudo rsync root板子IP:/lib/ ./sysroot/lib/这种方式最准确因为拿到的就是目标机实际运行的库版本。缺点是如果板子系统不干净带了一堆无关的东西sysroot会很大。第二种是用发行版官方的根文件系统包。比如Ubuntu Base的aarch64 rootfs或者Debian的arm64 rootfs解压出来当sysroot用。这种方式干净但要注意根文件系统里的glibc版本不能比工具链自带的还新否则链接时会报版本不匹配。我个人的习惯是先用发行版rootfs搭一个干净的sysroot编Qt本体等要编具体应用依赖的第三方库时再从板子上同步对应的库进来。这样既保证基础环境干净又保证应用依赖准确。2.3 环境变量的设置与验证工具链和sysroot准备好之后需要设置几个关键环境变量。我一般写一个env.sh每次编译前source一下export TOOLCHAIN_PREFIXaarch64-linux-gnu- export SYSROOT/opt/aarch64-sysroot export PATH/usr/bin:$PATH export CC${TOOLCHAIN_PREFIX}gcc export CXX${TOOLCHAIN_PREFIX}g export AR${TOOLCHAIN_PREFIX}ar export LD${TOOLCHAIN_PREFIX}ld export STRIP${TOOLCHAIN_PREFIX}strip export PKG_CONFIG_PATH$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/lib/aarch64-linux-gnu/pkgconfig export PKG_CONFIG_SYSROOT_DIR$SYSROOTPKG_CONFIG_SYSROOT_DIR这个变量特别关键它让pkg-config在输出路径时自动加上sysroot前缀否则编译时找库会找到宿主机上去。验证工具链能不能正常用sysroot里的库写个最简单的测试aarch64-linux-gnu-gcc --sysroot$SYSROOT -o test test.c file testfile命令输出里应该显示ARM aarch64说明交叉编译成功。如果报找不到stdio.h那就是sysroot路径不对或者头文件没同步全。3. Qt5.14.2源码配置configure参数逐个拆解3.1 源码获取与目录规划Qt5.14.2的源码包在官方归档里能找到文件名是qt-everywhere-src-5.14.2.tar.xz。下载解压wget https://download.qt.io/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.tar.xz tar -xf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2目录规划上我建议把编译产物和源码分开用shadow build的方式。在源码同级建一个build目录mkdir ../qt-5.14.2-aarch64-static cd ../qt-5.14.2-aarch64-static这样做的好处是源码目录保持干净编译出问题想重来直接删build目录就行不用重新解压源码。3.2 configure参数每一个都有存在的理由Qt的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 \ -no-linuxfb \ -no-kms \ -no-glib \ -no-icu \ -no-cups \ -no-pch \ -skip qtwebengine \ -skip qtwebkit \ -skip qtdeclarative \ -qt-zlib -qt-libpng -qt-libjpeg \ -qt-freetype -qt-harfbuzz \ -sysroot $SYSROOT \ -platform linux-g \ -xplatform linux-aarch64-gnu-g \ -device-option CROSS_COMPILEaarch64-linux-gnu--prefix指定安装路径这个路径是目标机上的路径不是宿主机上的。编完之后make install会把文件装到宿主机对应目录下但Qt内部记录的路径是目标机路径。-static是核心生成静态库。-release关掉调试符号减小体积。-nomake examples -nomake tests跳过示例和测试能省大量编译时间Qt的examples编起来非常慢。-no-opengl这个要看你目标板有没有GPU。如果板子有Mali或者PowerVR的GPU并且你打算用OpenGL ES那不能关。但如果只是做普通2D界面关掉能避免一堆EGL相关的依赖问题。Orange Pi CM5这类板子如果不用GPU加速关掉最省事。-no-xcb、-no-eglfs、-no-linuxfb、-no-kms这几个是平台插件相关的。静态编译时如果开着会引入一堆系统库依赖。如果你的应用不需要直接操作framebuffer或者DRM全关掉用-platform minimal或者自己写QPA插件。-no-glib关掉glib依赖Qt默认会依赖glib但很多嵌入式环境没有glib关掉能减少依赖。-no-icu关掉ICU这个库很大静态链接进去会让可执行文件膨胀好几MB除非你需要复杂的国际化支持否则关掉。-qt-zlib -qt-libpng -qt-libjpeg这几个是让Qt用自带的第三方库而不是系统库。静态编译时强烈建议用Qt自带的因为系统库的静态版本不一定有而且版本兼容性不好控制。-sysroot指定sysroot路径。-platform linux-g是宿主机平台-xplatform linux-aarch64-gnu-g是目标平台。-xplatform的值对应qtbase/mkspecs/下的目录名Qt自带linux-aarch64-gnu-g这个mkspec可以直接用。-device-option CROSS_COMPILEaarch64-linux-gnu-把工具链前缀传给qmake后面编子模块时会用到。3.3 mkspec的检查与微调虽然Qt自带了linux-aarch64-gnu-g这个mkspec但有时候需要根据实际情况微调。看一下qtbase/mkspecs/linux-aarch64-gnu-g/qmake.confQMAKE_CC aarch64-linux-gnu-gcc QMAKE_CXX aarch64-linux-gnu-g QMAKE_LINK aarch64-linux-gnu-g QMAKE_AR aarch64-linux-gnu-ar cqs QMAKE_STRIP aarch64-linux-gnu-strip如果你的工具链前缀不是aarch64-linux-gnu-要么改这个文件要么在configure时用-device-option覆盖。我遇到过工具链前缀是aarch64-none-linux-gnu-的情况直接改mkspec文件最省事。另外要注意QMAKE_CFLAGS和QMAKE_CXXFLAGS里有没有--sysroot如果没有需要在configure时通过-sysroot参数传入Qt会自动加到编译选项里。4. 编译过程中的典型报错与排查链路4.1 第一个坑找不到sysroot里的头文件configure跑完之后make一开始就报错fatal error: bits/libc-header-start.h: No such file or directory这个报错说明编译器没找到sysroot里的glibc头文件。排查链路是这样的先确认sysroot路径对不对ls $SYSROOT/usr/include/bits/libc-header-start.h如果文件不存在说明sysroot没同步全需要把目标机的/usr/include完整同步过来。如果文件存在那就是编译选项里没带--sysroot。检查configure生成的Makefile里有没有sysrootgrep -r sysroot mkspecs/qmodule.pri如果没有说明configure时-sysroot参数没生效。这种情况我遇到过原因是-sysroot参数的位置不对Qt的configure对参数顺序敏感-sysroot要放在-xplatform之前。4.2 第二个坑链接时找不到libstdc.so编译到链接阶段报错cannot find -lstdc这是因为交叉工具链的libstdc不在sysroot里而在工具链自己的目录下。解决办法是在configure时加上QMAKE_LFLAGS -L/usr/lib/gcc-cross/aarch64-linux-gnu/7或者在mkspec里加。更通用的做法是在qmake.conf里加QMAKE_LIBDIR $$[QT_SYSROOT]/usr/lib/aarch64-linux-gnu QMAKE_LIBDIR /usr/lib/gcc-cross/aarch64-linux-gnu/7具体路径根据你的工具链安装位置调整。用aarch64-linux-gnu-gcc -print-libgcc-file-name能找到libgcc的路径libstdc通常在同一目录。4.3 第三个坑QtSerialPort模块找不到如果你在项目里用了QtSerialPortqmake会报Project ERROR: Unknown module(s) in QT: serialport这个报错的原因是Qt5.14.2默认不编译QtSerialPort需要显式开启。在configure时加上-qt-serialport但要注意QtSerialPort依赖udev如果sysroot里没有libudev编译会失败。要么在sysroot里装上libudev-dev要么用-no-libudev关掉udev支持。嵌入式环境里如果不需要热插拔检测关掉udev也能用。类似的还有QtCharts、QtDataVisualization这些模块默认都不编需要哪个就显式加哪个。但每加一个模块就多一份依赖静态编译时依赖会层层传递加之前想清楚是不是真的需要。4.4 第四个坑编译到一半内存爆了Qt的某些模块编译时非常吃内存尤其是qtdeclarative和qtwebengine。如果你像我一样在虚拟机里编内存给少了会在链接阶段被OOM Killer干掉报错信息可能是collect2: fatal error: ld terminated with signal 9 [Killed]解决办法有两个一是加内存建议至少8GB16GB更稳二是用make -jN控制并行编译数N不要超过CPU核心数内存紧张时用-j2甚至-j1。我试过在4GB内存的虚拟机上编qtbase用-j1勉强能过但qtdeclarative必挂。如果不需要QML直接用-skip qtdeclarative跳过能省大量时间和内存。很多嵌入式项目其实用不上QMLWidgets足够了。5. 安装、验证与部署到目标板5.1 make install与目录结构确认编译完成后make install安装到-prefix指定的目录。装完之后检查一下目录结构/opt/qt-5.14.2-aarch64-static/ ├── bin/ │ ├── qmake │ ├── moc │ ├── uic │ └── rcc ├── lib/ │ ├── libQt5Core.a │ ├── libQt5Gui.a │ ├── libQt5Widgets.a │ └── ... ├── include/ └── mkspecs/bin目录下的qmake、moc这些工具是宿主机上运行的不是aarch64的。用file命令确认一下file /opt/qt-5.14.2-aarch64-static/bin/qmake应该显示x86-64因为qmake是在编译机上跑的。而lib目录下的.a文件是aarch64的可以用aarch64-linux-gnu-ar t查看内容确认。5.2 用qmake编一个测试程序写一个最简单的Qt程序验证静态编译是否成功// 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 core gui widgets TARGET hello SOURCES main.cpp用静态Qt的qmake生成Makefile/opt/qt-5.14.2-aarch64-static/bin/qmake hello.pro make编出来的hello用file命令看应该是aarch64的用ldd看应该是not a dynamic executable说明静态链接成功。5.3 部署到目标板与运行验证把hello拷到板子上scp hello root板子IP:/root/在板子上运行./hello -platform minimal如果板子上没有显示环境用-platform minimal或者-platform offscreen测试。如果程序能跑起来不报错说明静态编译成功。如果报Could not find the Qt platform plugin说明平台插件没编进去需要检查configure时的平台插件配置。静态编译时平台插件也是静态链接的但需要显式导入。在main.cpp里加#include QtPlugin Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin)或者在pro文件里加QTPLUGIN qminimal这样链接器才会把对应的插件静态库链进来。5.4 体积优化strip与编译选项静态链接的Qt程序体积会比较大一个简单的Hello World可能就有10MB以上。优化手段有几个编译时加-Os优化体积在configure时加QMAKE_CFLAGS -Os QMAKE_CXXFLAGS -Os编完之后strip掉符号aarch64-linux-gnu-strip hellostrip之后体积能减小30%到50%。如果还嫌大可以用-ffunction-sections -fdata-sections配合链接器的--gc-sections把没用到的函数和数据段裁掉。这个在configure时加QMAKE_CFLAGS -ffunction-sections -fdata-sections QMAKE_LFLAGS -Wl,--gc-sections但要注意--gc-sections有时候会裁掉Qt元对象系统需要的符号导致运行时崩溃。用之前先测试确认没问题再用。6. 几个容易忽略的实操心得6.1 关于编译时间的预期管理Qt5.14.2完整编译一遍在8核16GB的机器上大概需要1到2小时。如果跳过了qtdeclarative和qtwebengine能缩短到40分钟左右。第一次编译建议留足时间不要中途打断。如果编译中断了重新make会从断点继续不用重新configure。但有一种情况必须重新configure改了configure参数。Qt的configure会生成config.cache参数变了要删掉这个文件重新跑否则新参数不生效。6.2 关于sysroot的版本一致性sysroot里的glibc版本和工具链自带的glibc版本要匹配。如果sysroot里的glibc比工具链的新链接时会报undefined reference to __libc_start_mainGLIBC_2.34解决办法是让sysroot的glibc版本不高于工具链的。用aarch64-linux-gnu-gcc -print-file-namelibc.so.6看工具链自带的glibc版本然后确保sysroot里的不高于这个版本。6.3 关于第三方库的静态编译Qt程序往往还依赖其他库比如OpenSSL、libmodbus、SQLite。这些库也要静态编译成aarch64版本然后放到sysroot里。编译这些库时--hostaarch64-linux-gnu和--prefix指向sysroot编完make install进去。注意顺序先编依赖库再编Qt最后编应用。顺序反了会找不到依赖。6.4 关于qmake的缓存问题静态Qt的qmake在生成Makefile时会缓存一些路径信息。如果你移动了Qt的安装目录qmake生成的Makefile里的路径就失效了。解决办法是删掉项目里的Makefile重新qmake或者用qmake -query检查路径是否正确。我遇到过把Qt从/opt/qt移到/usr/local/qt之后编译报找不到头文件就是缓存路径没更新导致的。6.5 关于调试符号的保留策略静态编译时如果完全strip掉符号程序崩溃时拿不到有意义的backtrace。建议在开发阶段保留一份带符号的版本发布时再strip。或者用objcopy --only-keep-debug把调试信息单独存一个文件发布时用objcopy --add-gnu-debuglink关联回去。这样既减小了发布体积又保留了调试能力。aarch64-linux-gnu-objcopy --only-keep-debug hello hello.debug aarch64-linux-gnu-strip hello aarch64-linux-gnu-objcopy --add-gnu-debuglinkhello.debug hello这套流程在排查现场崩溃问题时特别有用板子上跑的是strip过的版本但拿到core dump后可以用带符号的版本分析。6.6 关于Qt版本的选择Qt5.14.2是个比较稳的LTS版本但如果你不需要5.14特有的功能Qt5.12.10也是不错的选择编译依赖更少社区资料更多。Qt5.15系列对静态编译的支持更好但5.15.2之后的开源版本需要自己编官方不提供二进制包。选哪个版本主要看你的项目需求不要盲目追新。我在实际项目里用5.14.2编过Orange Pi CM5和树莓派4B的静态Qt整体流程是通的主要时间花在sysroot准备和依赖库编译上。Qt本体编译反而是一次性的工作配好参数跑一遍就行。真正麻烦的是后续每加一个第三方库就要重新处理一遍交叉编译这个要有心理准备。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价