Qt 5.14.2 ARM交叉编译踩坑全记录:从工具链到上板部署
2026/9/19 16:38:19 网站建设 项目流程

搞嵌入式Qt开发的人应该都有体会:PC上跑得好好的程序,一交叉编译搬到ARM板子上,不是编译报错就是运行段错误,折腾几天都是家常便饭。Qt 5.14.2是我用得比较久的一个版本,在ARM Linux下做应用界面开发,稳定性和模块完整度都比较适中。这篇文章就把我在Qt 5.14.2 ARM交叉编译过程中踩过的坑、试出来的方案、以及最终跑通的配置完整记录下来,希望帮你少走弯路。

文章会覆盖从工具链选择、sysroot准备、configure参数配置,到make编译、上板部署运行的全流程,重点放在那些“报错信息看了半天不知道问题出在哪”的坎上。适合正在用Qt 5.14.2(或者其他Qt 5.x版本)做ARM嵌入式开发、手里有实板要跑起来、但被交叉编译折腾到怀疑人生的朋友。如果你是第一次接触Qt交叉编译,这篇也能帮你建立一个从编译到部署的完整认知框架。

1. 动手之前先想清楚:版本、工具链和sysroot怎么选

1.1 为什么偏偏是Qt 5.14.2

选Qt 5.14.2不是因为什么“最新最强”,相反,它在今天已经不算新版本了。但这类LTS线之外的稳定版本有个特点:问题沉淀充分,网上资料多,踩过的坑基本上都有前人填平。5.14.2在5.12和5.15两个LTS之间,补丁修得比较勤,编译配置习惯和5.15几乎通用,所以你现在用5.14.2踩出来的经验,将来升级到5.15.2也不会白费。

更重要的一点是,Qt 5.15之后官方对开源版本的支持策略变了,离线安装包和源码包下载都更麻烦,很多开发板厂商提供的BSP也跟着停留在5.14左右。如果你用的是全志、瑞芯微、NXP i.MX系列这些常见平台,直接拿Qt 5.14.2源码交叉编译,跟BSP里的内核驱动、GPU库对接会顺很多。

再说一下版本命名的坑:Qt 5.14.2对应源码包是qt-everywhere-opensource-src-5.14.2.tar.xz,但到了5.15就改叫qt-everywhere-src-5.15.x.tar.xz。去官网或者清华镜像下载的时候别按老名字找,不然会一脸懵。

1.2 交叉工具链的选择:不是越新越好

这是我栽过最狠的一个跟头。一开始图省事,直接拿了Ubuntu 22.04自带的gcc-arm-linux-gnueabihf(GCC 11.x),结果Qt 5.14.2源码编译到一半就开始报各种模板错误和隐式声明问题,查了很久才发现是工具链版本太新,对老代码不够友好。

经验教训:Qt 5.14.2是2020年初的代码,配GCC 9/10是最稳的。具体来说,32位ARM硬浮点平台推荐两种方式:

  • 用开发板BSP自带的工具链,比如NXP提供的gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf
  • 用Linaro官方提供的GCC 9或10版本工具链,例如gcc-linaro-10.3.1-2021.07-x86_64_arm-linux-gnueabihf

64位ARM平台则选择aarch64-linux-gnu工具链,同样建议GCC 10左右。

为什么强调“不是越新越好”?因为Qt 5.14.2里有些第三方库和桥接代码写得比较老,新编译器对类型检查更严格,把原本仅是警告的问题直接升级成error。你当然可以靠给源码打补丁解决,但嵌入式开发时间本来就不够用,何苦跟编译器较劲,换条工具链五分钟就清净了。

1.3 sysroot的构建:把头文件和库先铺到“目标世界”

sysroot这个概念很多人第一次碰会懵。简单说,交叉编译的时候,编译器和链接器需要一个“假的根文件系统”,里面放着目标板上需要的头文件、标准库、依赖库,它们的位置结构和ARM板子上的实际文件系统保持一致。这样编译器才知道“目标世界”长什么样。

sysroot怎么来?两种途径:

第一种,直接从开发板或者你制作的根文件系统里拷贝一份。把板子上/lib/usr/lib/usr/include这些目录原样拷到主机的某个目录,例如/opt/arm-sysroot。执行命令大致是:

mkdir -p /opt/arm-sysroot rsync -av root@板子IP:/lib /opt/arm-sysroot/ rsync -av root@板子IP:/usr/lib /opt/arm-sysroot/usr/ rsync -av root@板子IP:/usr/include /opt/arm-sysroot/usr/

第二种,用Buildroot或Yocto生成根文件系统,直接把它作为sysroot。这种方式一般会连带生成配套的工具链,环境一致性最好,缺点是初始学习成本高。

sysroot里特别容易缺的东西是libstdc++.so.6libgcc_s.so.1,这两个库必须从工具链目录拷贝到sysroot里,否则后面链接阶段会报“找不到libstdc++”。还有一点要提醒:拷sysroot的时候别把符号链接弄丢了,Qt编译时经常要通过libm.solibdl.so这类软链接去定位真实的库文件。建议用rsync -a保留符号链接属性。

2. configure配置这一步,决定你后面会不会返工

2.1 源码获取与离线安装的现实问题

Qt 5.14.2官方下载页面现在还在,但速度感人,而且在线安装器对交叉编译场景没什么用——交叉编译必须要源码包,qt-opensource-linux-x64-5.14.2.run是给x86主机安装用的,不能用来交叉编译ARM版本。

推荐到国内镜像站下载源码包,例如清华镜像站或中科大镜像站,路径大概是/qt/archive/qt/5.14/5.14.2/,找文件名里带everywheresrc的tar.xz文件。下载后先校验一下文件大小和解压完整性:

tar -xJf qt-everywhere-opensource-src-5.14.2.tar.xz cd qt-everywhere-opensource-src-5.14.2

注意,完整源码包解压后有好几个GB,建议用-skip参数裁剪不需要的模块,能大幅缩短编译时间。这个在2.2节详细说。

2.2 一份经过验证的configure参数清单(附逐项解释)

我最终跑通的configure命令如下,用的工具链是Linaro GCC 10.3 arm-linux-gnueabihf:

./configure \ -prefix /opt/qt5.14.2-arm \ -hostprefix /opt/qt5.14.2-host \ -release -shared \ -xplatform linux-arm-gnueabi-g++ \ -sysroot /opt/arm-sysroot \ -opensource -confirm-license \ -no-opengl -no-xcb \ -linuxfb -no-eglfs \ -no-dbus -no-iconv -no-ssl \ -nomake examples -nomake tests \ -skip qtwebengine -skip qtdeclarative \ -no-feature-cups

逐个说下关键参数背后的逻辑:

  • -prefix:Qt最终安装到目标板文件系统的哪个目录。这里设为/opt/qt5.14.2-arm,之后make install会把头文件、库、插件都装到这个目录结构下。这个前缀要和板子上实际部署路径一致,否则运行时找插件和库会出问题。
  • -hostprefix:在主机上安装qmake、moc、uic等工具的位置。交叉编译时,这些工具必须是可执行的x86版本,它们负责在编译过程中处理界面文件和元对象。这个参数很多人漏掉,导致make install把所有东西一股脑装到hostprefix里,尤其会有版本冲突。
  • -xplatform linux-arm-gnueabi-g++:指定目标平台mkspec。Qt的mkspecs目录里预置了常见交叉编译平台,linux-arm-gnueabi-g++对应32位ARM硬浮点。如果你的平台不在预设列表里,参考2.3节定制。
  • -sysroot:指向之前准备的ARM根文件系统目录,这是configure能正确找到目标系统头文件和库的关键。
  • -no-opengl -no-xcb:如果你的板子没有GPU或不需要OpenGL,就关掉。很多configure失败都卡在OpenGL检测上,因为找不到GLES2/gl2.h。如果板子有Mali GPU并提供了配套库,可以改成-opengl es2
  • -linuxfb -no-eglfs:显示后端。linuxfb是最朴素、最不容易出问题的方案,基于Linux framebuffer设备。eglfs需要GPU/EGL支持,先关掉保证能跑通。
  • -no-dbus -no-iconv -no-ssl:按需裁剪。如果板子系统里没有dbus、iconv、OpenSSL库,就关掉,避免configure检测失败。
  • -skip qtwebengine:这个大块头模块在ARM上编译时间极长,而且依赖大量系统库,没有特殊需求直接跳过。
  • -no-feature-cups:嵌入式板子几乎没有打印需求,关掉防止检测CUPS库失败。

2.3 定制qmake.conf或使用-device模式

如果你的工具链命名和Qt预设不一致,configure会报错找不到编译器。这时候不用慌,有两种解决办法。

第一种,直接修改mkspecs里的qmake.conf。以linux-arm-gnueabi-g++为例,打开qtbase/mkspecs/linux-arm-gnueabi-g++/qmake.conf,把编译器变量改成你的实际工具链名字:

QMAKE_CC = arm-linux-gnueabihf-gcc QMAKE_CXX = arm-linux-gnueabihf-g++ QMAKE_LINK = arm-linux-gnueabihf-g++ QMAKE_LINK_SHLIB = arm-linux-gnueabihf-g++ QMAKE_AR = arm-linux-gnueabihf-ar cqs QMAKE_OBJCOPY = arm-linux-gnueabihf-objcopy QMAKE_STRIP = arm-linux-gnueabihf-strip

改完保存,再执行configure。第二种,用-device模式,适合开发板有专门mkspec的情况,比如-device linux-imx6-g++。这种方式需要板级mkspec文件,适合特定BSP,通用性差一些。

我自己更推荐先改qmake.conf跑通,因为可控性强,出问题知道去哪儿查。等团队规范了板级支持,再考虑做-device层。

3. make与make install:编译中途报错时该去哪里找原因

3.1 并发参数与资源控制

configure通过之后,进入编译阶段。第一次编译建议先用make -j4试探,不要一上来就-j$(nproc)拉满。原因很简单,Qt源码模块众多,有些模块依赖另一个模块先编译完,并发太高容易遇到诡异的临时文件丢失错误。如果4线程编译正常,再逐步往上加。

我实际使用的命令是:

make -j8 2>&1 | tee build.log

tee把编译日志存下来很重要,编译中途如果断掉,可以快速定位到具体报错文件。编完后:

make install 2>&1 | tee install.log

3.2 编译期的三类典型失败以及对策

编译期遇到的报错,归纳起来大致三类。第一类跟编译器版本有关,比如报error: 'uint32_t' does not name a type,这是工具链GCC版本太新、某些头文件包含顺序不兼容导致的。对策是换回GCC 9/10,或者在该模块的Makefile里追加-include stdint.h的CFLAG。但我不建议硬改源码,编译选项容易埋雷,换工具链更干净。

第二类是链接错误,比如undefined reference to __aeabi_ddiv。这跟浮点ABI有关系,常见于32位ARM平台。工具链是硬浮点eabihf的,编译选项里却带了-mfloat-abi=softfp或没有浮点参数,导致调用约定不一致。解决方案是确认qmake.conf里没有硬性指定软浮点参数,检查工具链默认类型是arm-linux-gnueabihf而非arm-linux-gnueabi

第三类是头文件找不到,典型是GLES2/gl2.h: No such file or directory。这属于configure阶段没检测到某个依赖,但检测失败被跳过了,编译时才爆发。这时候别在Makefile里乱加路径,回到configure参数检查是否漏关了某个模块。比如不需要OpenGL,就用-no-opengl

3.3 make install之后必须检查的几件事

编译安装完成后,别急着打包往板子上拷。先检查/opt/qt5.14.2-host/bin下有没有qmakemoc这些工具,这些是x86版本,之后在PC上配置Qt项目就用它们。再检查/opt/qt5.14.2-arm/lib下是否有libQt5Core.so.5系列库,以及plugins/platforms/目录下是否有libqlinuxfb.so这个framebuffer插件。

还有一个隐蔽问题:make install安装的头文件里,qconfig.hqconfig.cpp会记录configure时的feature配置。如果你改了configure参数重新编译,旧版本的qconfig.h会缓存下来,导致后续qmake生成的项目配置和实际库不一致。尤其当你重新configure但-prefix路径没变时,一定要提前清掉旧安装目录:

rm -rf /opt/qt5.14.2-arm /opt/qt5.14.2-host

4. 上板部署阶段:跑到目标板上才是真正的检验

4.1 运行库拷贝与库路径的正确姿势

编译好的Qt目录打包拷到板子仍然有讲究。最笨也最有效的办法是把整个/opt/qt5.14.2-arm目录用tar或rsync传到板子的/opt/qt5.14.2。这个目录里包括了头文件、库、插件、翻译文件等,虽有些冗余,但有效避免了“缺了某个libts”这种低空飞行式的排错。

拷贝完成后,板子上设置环境变量是重头戏:

export LD_LIBRARY_PATH=/opt/qt5.14.2/lib:/usr/lib:/lib export QT_QPA_PLATFORM=linuxfb export QT_QPA_FONTDIR=/opt/qt5.14.2/lib/fonts:/usr/share/fonts export QT_QPA_PLATFORM_PLUGIN_PATH=/opt/qt5.14.2/plugins/platforms

LD_LIBRARY_PATH千万别把Qt的lib目录放在最后面,否则板子系统原本的Qt库可能被覆盖或者找错。另外,Qt程序启动时会去/opt/qt5.14.2/plugins找插件目录,这个路径默认相对于prefix生成的,如果你部署路径和prefix不同,必须显式设置QT_PLUGIN_PATH,否则程序会报“could not find or load the Qt platform plugin”。

4.2 platform插件和显示后端的取舍

linuxfb是默认最稳的选择,它不需要X11也不需要GPU,直接写/dev/fb0。如果你的板子有LCD屏幕,内核里有fbmem驱动,linuxfb基本就能出画面了。

linuxfb有几个肉眼可见的缺点:没有硬件加速、刷新率低、窗口系统支持有限。如果你的界面比较炫,有半透明、动画,linuxfb可能达不到效果。这时候就需要eglfs后端,配合GPU厂商的OpenGL ES库。

切换到eglfs的前提是configure时用了-opengl es2 -eglfs,并且板子sysroot里放好了Mali或PowerVR的libEGL.so和libGLESv2.so。运行时要设置:

export QT_QPA_PLATFORM=eglfs export QT_QPA_EGLFS_INTEGRATION=eglfs_kms

如果遇到黑屏但没有报错,先排查显示屏对应的DRM/KMS接口是否设置正确,很多情况下是QT_QPA_EGLFS_KMS_CRTC需要指定到具体的crtc编号。这块每个GPU厂商差异很大,建议先从官方BSP的demo配置入手。

4.3 字体、中文、触摸屏这些“细节决定成败”

程序跑起来后,第一眼看到的多半是中文显示成了方块。Qt默认字体查找路径是/usr/lib/fonts或者QT_QPA_FONTDIR指定的目录。板子系统里的字体文件往往很少,需要手动拷入中文字体,比如文泉驿微米黑或Droid Sans Fallback。我习惯把字体放到板子/usr/share/fonts/truetype/wqy/,然后在环境变量里加上这个路径。注意别只设QT_QPA_FONTDIR不设FONTCONFIG_PATH,部分Qt模块仍会扫fontconfig。

触摸屏的问题更麻烦。如果板子用的是电阻屏或通过tslib上报事件,那除了在configure时加-tslib参数,运行时还要设置tslib设备:

export TSLIB_TSDEVICE=/dev/input/event1 export TSLIB_CALIBFILE=/etc/pointercal export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/event1:rotate=90

如果是电容屏,通过/dev/input/eventX直接上报多点触控,Qt的evdevtouch插件会读取它。一个容易犯的错是:设备节点确定不了。可以用cat /proc/bus/input/devices查看触摸设备名,确认event编号后再设置。

4.4 时区与本地化的暗坑

最后说一个不起眼但偶尔让人抓狂的点:Qt程序默认使用UTC时间,如果你的板子没有设置时区,程序显示的时间会和本地时间差8小时。解决方式是在板上设置环境变量:

export TZ=Asia/Shanghai export QT_QPA_FB_NATIVE_FORMAT=rgb32

TZ变量对Qt的QDateTime::currentDateTime()生效,前提是系统里存在/usr/share/zoneinfo数据。embed板子的文件系统经常精简掉这些数据,直接从主机拷贝对应时区文件过去即可。

5. 高频错误避坑速查表(建议收藏)

5.1 configure阶段的问题

configure阶段报错最多,因为它在全面探测系统依赖。常见的错误码和解决思路整理成表格:

报错现象可能原因解决办法
找到不到交叉编译器mkspec里QMAKE_CC没有指向工具链打开对应qmake.conf,改成实际编译器,如arm-linux-gnueabihf-gcc
OpenGL功能测试失败sysroot里缺GLES头文件和库使用-no-opengl,或正确安装GPU厂商库
xcb相关测试失败系统没有libxcb库-no-xcb,或安装libxcb-dev到sysroot
找不到libstdc++等库sysroot里缺少工具链的库软链接从工具链的arm-.../lib目录拷贝libstdc++.so.6、libgcc_s.so.1到sysroot
提示The system library 'icu' must be used缺少ICU库configure加-no-icu,或者先交叉编译ICU到sysroot

5.2 编译阶段的问题

configure过了,编译期遭遇的问题要靠看日志定位。常见情况如下表:

报错现象可能原因解决办法
uint32_t不识别GCC版本太新换GCC 9/10工具链,或加-include stdint.h
__aeabi_ddiv等链接错误软硬浮点ABI不匹配确保编译器为gnueabihf,且编译选项无软浮点参数
GLES2/gl2.h找不到configure阶段漏检测-no-opengl重新configure
某个子模块一直编译失败模块内部依赖缺失-skip跳过该模块,或单独编译该模块查看详细错误
make run出的qmake是x86还是ARM混淆-hostprefix没设置好重新configure时指定-hostprefix /opt/qt5.14.2-host

5.3 运行阶段的问题

程序拷到板子上运行时报错,排查起来比较烦人,因为没有编译期那么直观。这类问题我强烈建议先控制变量:

运行现象可能原因解决办法
could not find platform plugin "linuxfb"plugins目录没找到或缺失插件设置QT_QPA_PLATFORM_PLUGIN_PATH为actual plugin路径
程序启动后秒退无输出缺少依赖库ldd检查可执行文件依赖,逐一补齐
中文乱码或方块缺少中文字体或字体路径不对拷贝中文字体,设置QT_QPA_FONTDIR
触摸屏点击无反应设备节点号不对或未配置tslib检查/dev/input/eventX、设置TSLIB_TSDEVICE
显示画面撕裂framebuffer双缓冲未启用设置QT_QPA_FB_DOUBLE_BUFFER=1
时间差8小时时区未设置设置TZ=Asia/Shanghai并确保zoneinfo存在

你可能会问,lark跑起来之后,怎么判断是不是Qt的问题还是自己的业务代码问题?我自己的习惯是先编一个只有QWidget空窗口的最小demo上板跑,如果空窗口能正常显示,再逐步叠加业务模块。这种二分法定位,比在几百行代码里猜要快得多。

5.4 一个非常隐蔽的坑:hostprefix路径残留

最后分享一个让我排查了很久的问题。以前我在configure时没有设-hostprefix,Qt默认就把host工具装到了/usr/local/Qt-5.14.2。后来重新配置时改了prefix,但host工具路径还残留着,导致qmake生成Makefile时引用了旧路径下的工具,编译出来的库版本和宿主工具版本不一致,界面资源始终加载不出来。

解决办法很简单:再编译之前,把旧的/usr/local/Qt-5.14.2整个删掉,确认qmake -v输出的是新配置的版本信息。这类路径残留问题,我之前在多个版本的Qt上见过,不光5.14.2有,5.12也有,强烈建议每次重新configure前清理干净。

在实际项目里,我会把交叉编译的一次性脚本固化下来,包括configure和make、make install命令,并且把工具链版本、Qt版本、host工具路径都写清楚。这样不光是换电脑、换工程师的时候能快速复现同样环境,自己过俩月回来看也还能记起来当初是怎么配的。

如果你正卡在某个具体报错上,建议按照上面的速查表先定位阶段——configure、编译、运行——然后只关注那个阶段的问题,不要去动无关的模块配置。很多时候交叉编译搞不定,不是技术有多难,而是你一急就乱改导致问题叠加。先稳定住一个可用的最小配置,从-skip模块裁剪开始,能过就跑起来,性能优化和功能扩展留到下一步再说。

我现在自己新接ARM板子的活儿,惯例就是先跑通这个“最小链路”:源码包5.14.2、GCC 10工具链、sysroot、linuxfb、空窗口demo。这套链路通了,后面加业务进度的速度,比一开始就想把所有硬件特性全开要快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询