做嵌入式Linux开发的朋友,应该都有过这种经历:在x86开发机上把Qt程序编译好、运行正常,一拷贝到ARM板子上就报“error while loading shared libraries”,然后开始在目标板上满世界找库、对版本、加环境变量。我在这上面吃过不少亏,后来干脆换了一套思路,用Qt5.14.2做aarch64静态交叉编译,把整个依赖链一次性压在编译阶段,目标板上只需一个可执行文件就够了。
这篇文章不是简单的配置记录,而是从零开始搭建完整环境的实操手册——我会把工具链、sysroot、依赖库、Qt源码、目标板验证这条链路全部走一遍,每个步骤都解释为什么这么做、参数怎么理解、出错了从哪里查。适合正在做ARM Linux设备上的Qt应用开发、以及想彻底摆脱目标板动态库噩梦的开发者。哪怕你现在对交叉编译还比较陌生,顺着走一遍也能建立起完整的全局观。
1. 为什么是 Qt5.14.2 + aarch64 静态交叉编译:先看清这套方案到底解决什么问题
先说一个容易被忽略的问题:很多人一上来就照着网上的configure命令敲,却说不清楚自己为什么要做静态交叉编译。如果这个问题没想明白,后面遇到一个报错就会动摇一次,最后很容易放弃。
1.1 选型背后:Qt版本、架构和编译方式的三角关系
Qt版本为什么选5.14.2?Qt 5.14虽然不是LTS版本,但它在嵌入式领域的使用量非常大。一方面是5.14.2这个补丁版本修掉了很多早期5.14.0的稳定性问题,另一方面是5.14系列在aarch64上的适配已经非常成熟,各类目标板厂商的BSP也都验证过这个版本。5.15虽然是LTS,但它的源码包分发方式变了,离线构建时的获取和合规成本反而更高。对于一个需要稳定量产、离线部署的产品来说,5.14.2是一个“刚刚好”的版本。
aarch64意味着什么?简单说就是ARM 64位指令集架构,现在市面上的新板子基本都是它。交叉编译就是在x86开发机上生成aarch64架构的机器码,目标板本身不需要安装编译器,也不需要完整的开发环境。这对现场设备的意义很大——设备上不装工具链,攻击面更小,固件也更干净。
静态编译解决的是什么?目标板上跑Qt程序最痛的点就是动态库依赖。一个QWidget程序,动态链接情况下要带libQt5Core.so.5、libQt5Gui.so.5、libQt5Widgets.so.5、libstdc++.so.6、libgcc_s.so.1,还牵扯平台插件libqlinuxfb.so,哪一个版本不对、路径找不到,程序起都起不来。静态编译把这些全部打包进最终ELF,目标板上不需要任何Qt运行库,拷一个文件就能跑。
1.2 这套方案的边界:能解决什么,不解决什么
有一点必须提前说清楚:静态编译不是银弹。Qt静态编译后程序体积会明显膨胀,一个简单的QWidget程序从几百KB变成几MB甚至十几MB,这是正常现象,因为Qt的很多功能代码被直接编进去了;首次启动也会比动态版稍慢,因为要初始化更多静态数据。另外,如果后续要动态加载第三方插件,静态编译会麻烦一些。
但如果你做的是行业设备、工控终端、车机导航这类需要长期稳定运行的产品,静态编译带来的部署简化、版本隔离、运行环境一致性,远比多出来的几MB体积更有价值。这也是我最终确定这套方案的核心原因:让复杂的运行环境问题在编译期一次性解决。
2. 环境准备与工具链选型:把交叉编译的地基打扎实
交叉编译的环境准备,本质上是两件事:准备一个能编代码的交叉编译器,以及准备一套目标板的系统根目录。前者决定“谁来编译”,后者决定“编译时参考哪些头文件和库”。这两件事做扎实了,后面才会顺。
2.1 开发机系统与基础依赖
我用的开发机是Ubuntu 20.04 x86_64,磁盘建议预留50GB以上——Qt源码解压加上中间产物和最终安装,空间消耗不小。系统本身不需要装太多东西,但有几样是必须的:
sudo apt update sudo apt install build-essential python3 perl g++ gperf bison flex libxkbcommon-dev libfontconfig1-dev libglib2.0-dev这些是编译Qt源码及其依赖时的基础工具。其中python3和perl是Qt构建系统需要的脚本解释器,flex和bison用于处理一些语法文件,gperf用来生成hash查找表。如果这些缺失,configure阶段就会报“找不到命令”或“无法生成文件”之类的错误。
2.2 交叉编译器的两条路径:apt安装 vs 手动部署
aarch64交叉编译器有两条获取路径,我两条都用过,先说结论:推荐从apt源直接安装,简单且不容易错。
路径一:apt直接安装
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu装完验证一下:
aarch64-linux-gnu-gcc --version正常会输出gcc版本号(Ubuntu 20.04上是9.x,22.04上是11.x,都能用)。这套工具链除了gcc/g++,还自带aarch64的glibc运行时库和头文件,默认安装在/usr/aarch64-linux-gnu/目录下,正好可以作为sysroot的一部分来用。
路径二:从ARM官方下载预编译工具链
比如从ARM官网下载gcc-arm-10.3-x86_64-aarch64-none-linux-gnu这类包,解压后手动配置PATH。好处是版本可控,坏处是它自带的头文件和库结构跟目标板不一定完全一致,后期做sysroot时容易混淆。第一次做的话不建议走这条路。
我个人的建议是:先apt跑通整个流程,等真正需要精确控制编译器版本时再切换手动部署。
2.3 工作目录布局与环境变量:让后续步骤不再“找不到文件”
交叉编译最忌讳的是文件散落各处、路径全靠猜。我习惯先建一个清晰的工作目录,然后把这些环境变量写进~/.bashrc,这样每次开终端都自动生效。
export TC_PREFIX=aarch64-linux-gnu- export TOOLCHAIN=/usr/bin export SYSROOT=/opt/qtaarch64/sysroot export QT_OUT=/opt/qtaarch64/qt5.14.2-out export PATH=$QT_OUT/bin:$PATH目录规划大概长这样:
/opt/qtaarch64/ ├── sysroot/ # 目标板系统根目录 ├── src/ # Qt源码和依赖库源码 ├── build/ # 依赖库的编译中间产物 └── qt5.14.2-out/ # Qt最终安装位置QT_OUT这个目录就是后面configure时用的-prefix,qmake、构建工具都会被装到这里。设置环境变量后,建议重新登录一次或者执行source ~/.bashrc,然后确认交叉编译器能正常调用。
aarch64-linux-gnu-gcc -v which aarch64-linux-gnu-g++走到这一步,你的“地基”就算打好了:有一个能跑命令的交叉编译器,有一个清晰的路径规划。下一节开始处理sysroot。
3. Sysroot 的获取与确认:静态编译也绕不开目标系统底座
很多人以为静态编译不依赖目标板的系统库——这是误解。静态编译只是不需要目标板上有动态运行库,但编译时依然需要目标板系统的头文件、libc静态库、以及各种系统库的.a文件。这些内容的集合就是sysroot。
3.1 Sysroot 是什么:交叉编译时的“假目标板”
你可以把sysroot理解成一段“目标板的快照”:交叉编译器编译代码时,如果要include一个头文件、链接一个库,它不会去开发机的/usr/include里找,而是去$SYSROOT/usr/include里找。命令大致是:
aarch64-linux-gnu-gcc --sysroot=$SYSROOT -o hello hello.c如果不设置--sysroot,编译器就会用它自己内部的默认路径,比如/usr/aarch64-linux-gnu。这就要求sysroot里的目录结构必须跟目标板的真实运行环境一致,否则编出来的程序可能在目标板上跑不起来。
3.2 获取sysroot的两种方法:从目标板同步,或用Debootstrap离线构建
方法一:从目标板直接同步。这是最“原汁原味”的方式。前提是你手头有一块能正常启动的目标板,并能通过ssh访问。执行:
ssh root@<目标板IP> "mkdir -p /tmp/sysroot_sync" rsync -aAXvz --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} \ root@<目标板IP>:/ /opt/qtaarch64/sysroot/注意排除/dev、/proc、/sys这些运行时虚拟文件系统,它们没有实际内容,同步过来也没意义。同步完成后,重点检查几个路径:
/opt/qtaarch64/sysroot/lib/aarch64-linux-gnu//opt/qtaarch64/sysroot/usr/lib/aarch64-linux-gnu//opt/qtaarch64/sysroot/usr/include/
方法二:用Debootstrap离线构建rootfs。如果你的目标板还没到手,或者目标板的系统比较精简、缺开发包,可以在开发机上用Debootstrap构建一个Ubuntu for ARM的最小系统:
sudo debootstrap --arch=arm64 --foreign focal /opt/qtaarch64/sysroot http://ports.ubuntu.com/ubuntu-ports/再加上--include=libc6-dev,libstdc++-10-dev,linux-libc-dev等必要的开发包。这个方法构建出的rootfs干净完整,非常适合作为交叉编译的sysroot。
3.3 静态编译必须确认的清单:libc.a 是否存在
静态编译时,链接器需要目标板架构的libc.a。很多嵌入式系统的libc6只装了动态库(libc.so.6)而没有静态库(libc.a),必须单独安装libc6-dev包。检查:
find /opt/qtaarch64/sysroot -name "libc.a" 2>/dev/null如果没找到,说明libc6-dev没装。在apt工具链场景下,这个静态库通常位于/usr/aarch64-linux-gnu/lib/libc.a,你需要把它正确合并到sysroot中,或者用--sysroot=/usr/aarch64-linux-gnu绕过。
这里有个常见误区:有人觉得“我只要交叉编译器自带的库就够了,sysroot无所谓”。但Qt的configure阶段会检查很多系统特性,它需要看到完整的头文件和库结构,sysroot不完整会导致它的检测结果不准确,最终编出的程序在目标板上出现各种诡异问题。
3.4 Sysroot 裁剪:保留关键目录,剔除无用内容
同步回来的sysroot里会包含目标板的日志、用户目录、临时文件,这些对交叉编译毫无用处,还拖慢查找速度。我的裁剪原则是:保留/usr/include、/usr/lib、/lib、/etc(某些库的配置文件需要),删除/home、/var/log、/tmp、/media。
不过要注意:不要动/usr/bin和/usr/sbin下的可执行程序,虽然用不到,但目录结构缺失某些库依赖检查时会造成误导。裁剪后记得再跑一次find . -name "libc.a"确认静态库还在。
4. 依赖库交叉编译:zlib、libpng、libjpeg、OpenSSL 逐个击破
Qt本身不是孤岛,它依赖大量第三方库。虽然Qt源码里自带zlib、libpng、libjpeg这些库,但使用-qt-zlib这类选项会让它们编进Qt内部,后续如果想打安全补丁或统一版本管理,会非常被动。所以在生产环境我更推荐用-system方式,先手动交叉编译这些依赖库,再让Qt链接它们。
4.1 为什么按“zlib → libpng/libjpeg → OpenSSL”的顺序编
依赖关系决定编译顺序:libpng依赖zlib,OpenSSL依赖zlib,libjpeg-turbo依赖zlib但与libpng互相独立。正确的顺序是先编zlib,再编其他库,否则configure阶段检查依赖时会报“cannot find -lz”。
4.2 zlib 交叉编译:最简单的热身项目
zlib是这些库中最容易编的,适合作为交叉编译的第一个练习。下载源码后执行:
wget https://www.zlib.net/zlib-1.2.13.tar.gz tar xzf zlib-1.2.13.tar.gz cd zlib-1.2.13 export CC=aarch64-linux-gnu-gcc export AR=aarch64-linux-gnu-ar export RANLIB=aarch64-linux-gnu-ranlib ./configure --static --prefix=$SYSROOT/usr make -j$(nproc) make install重点在--static:告诉构建系统只生成libz.a静态库。安装完成后检查$SYSROOT/usr/lib/libz.a是否存在。这里有个细节:交叉编译时不能直接跑make,因为zlib的Makefile会根据CC环境变量生成编译器命令,所以先设置环境变量再config,而不是用--host参数。
4.3 libpng、libjpeg-turbo:用标准的GNU configure套路
libpng和libjpeg-turbo都是基于configure脚本的,交叉编译参数明确:
# libpng wget https://download.sourceforge.net/libpng/libpng-1.6.39.tar.gz tar xzf libpng-1.6.39.tar.gz cd libpng-1.6.39 ./configure --host=aarch64-linux-gnu \ --prefix=$SYSROOT/usr \ --disable-shared --enable-static \ CPPFLAGS=-I$SYSROOT/usr/include \ LDFLAGS=-L$SYSROOT/usr/lib make -j$(nproc) make install--host参数指定目标机器架构,--disable-shared --enable-static表示只要静态库。CPPFLAGS和LDFLAGS用来告诉编译器去sysroot里找zlib的头文件和库文件。libpng编译时如果系统里缺libz.a,链接会失败并报cannot find -lz,这就回到编译顺序的问题了。
libjpeg-turbo以cmake构建为主,我通常用交叉工具链文件来编译:
# aarch64-toolchain.cmake set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) set(CMAKE_FIND_ROOT_PATH $ENV{SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)然后执行:
cmake -DCMAKE_TOOLCHAIN_FILE=aarch64-toolchain.cmake \ -DCMAKE_INSTALL_PREFIX=$SYSROOT/usr \ -DENABLE_SHARED=OFF -DENABLE_STATIC=ON \ .. make -j$(nproc) make install这条toolchain文件也在后面其他cmake项目中复用,写一次到处用。
4.4 OpenSSL:静态编译要特别注意“no-shared”
OpenSSL比较特殊,它是Qt开启HTTPS、TLS功能时的底层依赖。交叉编译命令:
wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar xzf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./Configure linux-aarch64 no-shared \ --prefix=$SYSROOT/usr \ --openssldir=$SYSROOT/usr/ssl make -j$(nproc) make installlinux-aarch64是OpenSSL对ARM 64位Linux平台的target名称,no-shared表示只生成静态库。这里有个坑:OpenSSL在交叉编译时经常报“无法运行/usr/bin/perl”或“cannot run test programs”,因为configure阶段它会尝试运行刚刚编出来的测试程序来检测平台特性,但那是aarch64的机器码,在x86开发机上跑不了。通常可以加--cross-compile-prefix=aarch64-linux-gnu-来修正,如果仍然报错,检查一下Makefile里的CC是否确实是交叉编译器。
4.5 编译后检查:四个静态库一个不能少
依赖库全部装完后,统一检查:
find $SYSROOT/usr/lib -maxdepth 2 -name "*.a" | grep -E "libz|libpng|libjpeg|libssl|libcrypto"正常情况下至少能看到libz.a、libpng.a、libjpeg.a、libssl.a、libcrypto.a。如果哪个缺失,后面的Qt configure阶段就会用-system-zlib却找不到库。另外建议把$SYSROOT/usr/lib/aarch64-linux-gnu这个目录也加入库搜索路径,因为很多系统的libc.a在单独的架构子目录里。
5. Qt5.14.2 configure 与构建:参数决定成败
依赖库就绪后,终于到了主角上场。Qt的configure是整个流程中最需要耐心的环节,因为它的参数太多,静态交叉编译的参数更是环环相扣。
5.1 获取源码:官方离线源码包
Qt官方提供完整源码包qt-everywhere-src-5.14.2.tar.xz,这个包包含了qtbase、qtdeclarative、qtmultimedia等所有模块,体积大约1GB左右。建议在/opt/qtaarch64/src下解压:
wget https://download.qt.io/archive/qt/5.14/5.14.2/qt-everywhere-src-5.14.2.tar.xz tar xJf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2Qt的“在线安装包”和“离线安装包”不包含完整的开源编译源文件,下载时注意是everywhere-src备到。这也是你可以自己掌控交叉编译流程的前提——官方预编译的二进制包通常只支持x86平台。
5.2 configure 参数逐条拆解
网上能找到大量Qt交叉编译的configure命令,但很少有人解释每个参数为什么这么写。下面是我验证过的完整命令:
./configure \ -static \ -release \ -opensource \ -confirm-license \ -xplatform linux-aarch64-gnu-g++ \ -sysroot $SYSROOT \ -prefix /opt/qtaarch64/qt5.14.2-out \ -no-opengl \ -no-use-gold-linker \ -system-zlib \ -system-libpng \ -system-libjpeg \ -openssl-linked \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -skip qtdeclarative \ -skip qtquickcontrols2各参数的作用我整理成了一张表:
| 参数 | 作用与原理 | 备注 |
|---|---|---|
-static | 生成静态Qt库,所有模块编译为.a文件 | 核心参数,没有它后面全都白做 |
-release | 编译release版本,去掉调试符号 | 嵌入式设备一般不需要debug版本 |
-opensource | 使用开源许可 | 配合-confirm-license跳过交互 |
-xplatform linux-aarch64-gnu-g++ | 指定目标平台mkspec | 让Qt构建系统知道我们要交叉编译到aarch64 |
-sysroot $SYSROOT | 指定目标板系统根目录 | 替代--sysroot编译器参数,影响后续所有编译 |
-prefix | 指定安装路径 | 这个路径会写入qmake等工具的配置中 |
-no-opengl | 默认不启用桌面OpenGL | 很多ARM板子没有完整GPU驱动,先用linuxfb顶 |
-no-use-gold-linker | 禁用gold链接器 | 老版本binutils与Qt静态链接时有兼容性问题 |
-system-zlib/-system-libpng/-system-libjpeg | 链接第4章编好的系统库 | 与依赖库策略保持一致 |
-openssl-linked | 直接链接OpenSSL静态库 | 开启后HTTPS等网络功能才可用 |
-nomake examples -nomake tests | 不编译示例和测试 | 省时省空间 |
-skip qtwebengine | 跳过WebEngine模块 | WebEngine交叉编译极其痛苦,绝大多数场景用不到 |
-skip qtdeclarative/-skip qtquickcontrols2 | 跳过QML相关模块 | 纯Widgets程序用不到,需要QML时再打开 |
5.3 模块取舍:我为什么跳过QML和WebEngine
Qt 5的模块体系很庞大,但静态编译时“全都要”是灾难。WebEngine基于Chromium,交叉编译不仅耗时极长还会遇到无数编译错误,绝大多数嵌入式产品根本不需要完整的WebView;QML模块体积也不小,如果确定只用Widgets,跳过qtdeclarative和qtquickcontrols2能节省大量时间。等业务确实需要再做增量编译,Qt支持后续单独模块加进去重新make。
5.4 构建与安装:这步是最漫长的等待
make -j$(nproc)可以用-j$(nproc)让所有CPU核一起干活,但我强烈建议如果你的机器内存不足16GB,改成-j4甚至-j2——Qt编译时的内存峰值很高,并行度太高容易直接OOM。完整编译时间在主流x86开发机上大约40到90分钟,取决于机器性能。
编译完成后:
make install安装结束后验证qmake能正常输出交叉平台信息:
/opt/qtaarch64/qt5.14.2-out/bin/qmake -v输出类似:
QMake version 3.1 Using Qt version 5.14.2 in /opt/qtaarch64/qt5.14.2-out/lib如果qmake能输出版本信息,说明Qt的交叉编译基本成功了。这时候你再看看/opt/qtaarch64/qt5.14.2-out/lib目录,里面全是.a结尾的静态库,比如libQt5Core.a、libQt5Widgets.a,这就是我们想要的静态Qt库。
6. 验证程序:交叉编译产物从开发机到目标板的完整链路
Qt装好只能说“编译成功”,真正能不能在目标板上跑起来,必须用一个最小程序验证。这一节是整条链路的收尾,出问题的概率也最高。
6.1 编写最小QWidget工程
我习惯建一个干净目录来验证,比如/opt/qtaarch64/test/hello。里面放两个文件:
hello.cpp:
#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello aarch64, Qt static works!"); label.resize(320, 160); label.show(); return app.exec(); }hello.pro:
QT += widgets TARGET = hello TEMPLATE = app SOURCES += hello.cpp CONFIG += static如果编译环境中有动态库和静态库并存,CONFIG += static可以让qmake优先选择静态Qt库。如果你希望把QPA平台插件也静态编译进来,在.pro里加一行:
QTPLUGIN += qlinuxfb因为Qt的显示后端是一个插件机制,动态编译时插件是个.so,运行时加载;静态编译时必须把插件代码编进可执行文件,否则运行时会报“could not find or load the Qt platform plugin 'linuxfb'”。
6.2 qmake + make 构建:如何确认确实是aarch64静态可执行文件
用安装好的交叉qmake来构建,而不是系统自带的qmake:
cd /opt/qtaarch64/test/hello /opt/qtaarch64/qt5.14.2-out/bin/qmake hello.pro make -j$(nproc)完成后在目录下会生成hello可执行文件。用两条命令验证文件属性:
file hello ldd hellofile输出:
hello: ELF 64-bit LSB executable, ARM aarch64, dynamically linked, with debug_info, not stripped注意:即使采用了-static,file有时仍然显示dynamically linked,这是因为gcc默认的链接方式还是动态的,真正的静态链接需要编译器直接链接libc.a。如果ldd输出not a dynamic executable,说明它确实不依赖动态库;如果ldd列出了许多.so,说明链接出了问题。此时可以检查Makefile里的LFLAGS是否有-static,或者手动补:
aarch64-linux-gnu-g++ -static -o hello hello.o -L/opt/qtaarch64/qt5.14.2-out/lib -lQt5Widgets -lQt5Gui -lQt5Core ...6.3 部署到目标板运行:显示平台和权限是两道关卡
把hello拷贝到目标板:
scp hello root@<目标板IP>:/opt/在目标板上执行:
export QT_QPA_PLATFORM=linuxfb chmod +x /opt/hello /opt/helloQT_QPA_PLATFORM=linuxfb告诉Qt使用Linux Frame Buffer做显示后端。如果你的板子有GPU且支持EGL,可以尝试eglfs;如果是纯控制台或远程ssh环境,linuxfb是最通用的方案。
此时如果出现黑屏但程序不退出的情况,多半是权限问题:/dev/fb0的访问权限不足。用groups查看当前用户组,把用户加入video组:
sudo usermod -aG video $USER或者临时给设备节点加权限:
sudo chmod 666 /dev/fb06.4 中文字体和资源文件:静态编译后的另一个隐藏坑
静态编译后字体也需要处理。目标板系统里如果没有中文字体,Qt控件上的中文会显示成方框。最简单的做法是把开发机上的文泉驿或者其他中文字体拷贝到目标板:
scp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc root@<目标板IP>:/usr/share/fonts/然后在目标板上执行fc-cache -fv刷新字体缓存,程序下次启动就能识别到中文字体。如果不想依赖系统字体缓存,也可以在Qt代码里用QFontDatabase::addApplicationFont("/path/to/font.ttc")直接加载指定字体。
7. 常见错误与排错思路:从实际项目中整理的错误映射表
写到最后这一节,我把这几年实际遇到的坑按错误阶段分类整理出来。交叉编译的排错和普通编译不太一样,最大的特点是“报错信息往往不是真正的原因”,根因经常藏在三层之外。
7.1 阶段判断:先别着急看报错,分清是哪个阶段的错误
我总结了三个阶段的特性:
- configure阶段错误:多半是依赖库缺失、sysroot路径不对、编译器无法运行。这阶段不用着急编译细节,先检查环境变量和文件是否存在。
- make阶段错误:大多是源码编译错误或链接错误,比如找不到头文件、找不到库、链接顺序不对。
- 运行阶段错误:编译都过了,目标板上一跑就崩,多半是运行时找不到动态库、显示插件没导入、权限不足。
不同阶段的排查思路完全不同,混在一起会非常痛苦。
7.2 典型错误映射表
| 错误现象 | 根因 | 解决手段 |
|---|---|---|
GL/gl.h: No such file or directory | 使用了-opengl desktop但sysroot缺OpenGL开发头文件 | 改回-no-opengl或安装libgl-dev-arm64-cross |
cannot find -lz | zlib静态库未安装到sysroot | 回到第4章编译zlib,并确认libz.a在$SYSROOT/usr/lib |
libQt5Core.a: No such file or directory | Qt静态库不存在,可能编的是动态库 | 检查configure是否带-static参数 |
qmake: Could not find qmake configuration file linux-aarch64-gnu-g++ | qmake版本不对或mkspecs路径不对 | 确认用的是/opt/qtaarch64/qt5.14.2-out/bin/qmake |
error while loading shared libraries: libQt5Core.so.5 | 目标板上跑的是动态版或Rpath不对 | 检查ldd输出,重新用静态版本链接 |
/dev/fb0: No such file or directory | 目标板没有FrameBuffer设备或驱动未启用 | 确认内核有fbdev支持,或改用offscreen/QVFB调试 |
QXcbConnection: Could not connect to display | 程序用XCB插件,但嵌入式上无X Server | 设置QT_QPA_PLATFORM=linuxfb或offscreen |
找不到 -lpng | libpng静态库未安装或版本过旧 | 回到第4章编译libpng |
std::bad_alloc持续崩溃 | 目标板内存不足或链接时静态Qt库体积过大 | 减少模块或减小-j编译参数 |
CMake Error: Generator ... does not support ... | 目标板路径里有多余的cmake缓存 | 删除旧的build目录重新configure |
7.3 一个真实排错案例:configure过了,make却找不到OpenSSL
做一个项目时,configure阶段明明输出检测到了OpenSSL,但make过程中QtNetwork模块链接时报libssl.a: error adding symbols: File format not recognized。折腾了很久才发现,问题出在OpenSSL编译时我用的是./config而不是./Configure linux-aarch64——./config在x86开发机上会自动探测宿主架构,编出来的是x86版的libssl.a。交叉编译时一定要用./Configure linux-aarch64显式指定目标架构,不能用自动探测的./config。这个坑很隐蔽,因为configure阶段不会主动检查libssl是不是目标架构。
7.4 一条很实用的最后防线:先把编译期错误留在开发机
目标板上调试是成本最高的方式。建议先在开发机上加-platform offscreen或linuxfb跑一下程序(用qtbase提供的交叉编译版qmake直接构建),确认程序逻辑没问题后再上板。静态编译的程序如果能在开发机上正常启动,至少排除了大部分运行逻辑错误,剩下的权限问题、设备节点问题再在目标板上逐一排查。
最后再分享一个实际体会:整个流程第一次走下来,很多人会卡在configure参数和依赖库配置上反复折腾,但当你完整跑通一遍之后,这套环境就不再是黑盒了——工具链、sysroot、依赖、Qt、目标板,每一层的关系都会变得非常清晰。我现在给新项目搭环境时,最常用的策略是先跑一个QLabel的hello程序验证链路,再往上叠加业务模块。按这个顺序来,交叉编译从“玄学”变成“工程流程”,只是时间问题。