各位做嵌入式或者工控的朋友肯定都有过这种经历板子用的是aarch64架构的ARM处理器但开发机是x86的Ubuntu要在开发机上直接编译出能在板子上跑的程序就得搞交叉编译。如果项目里还要带上Qt界面那就更麻烦了光是交叉编译工具链选型、依赖库处理、Qt源码配置这几关就能劝退一批人。我这次拿到一块基于瑞芯微RK3568的板子跑的是64位Linux系统需要在开发机上编译一个带图形界面的采集程序最后部署到板子上。因为板子系统是定制的环境比较干净不想往上面装一堆依赖库而且程序要放到几块不同的板子上跑所以最终要是静态链接的可执行文件也就是常说的静态编译。调研一圈之后决定用Qt 5.14.2配合aarch64交叉编译工具链目标文件静态链接这样编出来的程序拷到板子上就能跑不需要额外的动态库依赖。这篇手册就是我从零开始搭建整个交叉编译环境的完整记录包括工具链选择、依赖库处理、Qt源码配置、qmake调整、问题排查等关键环节踩了不少坑也总结了不少经验分享出来给同样要走这条路的朋友做个参考。1. 方案选型与整体设计思路1.1 为什么选Qt 5.14.2和静态交叉编译方案先说版本选择。Qt每个版本都有自己的生命周期和特性变化5.14.2属于Qt 5系列里比较成熟稳定的一个分支LTS版本维护周期长API接口和5.15相近但比6.x版本要保守项目里用到的很多第三方库和旧代码不会因为升级大版本而出现兼容性问题。另外这个版本在ARM嵌入式领域用得非常多社区资料、问题排查方案都很齐全遇到问题搜索一下基本都有现成的解答。再说为什么一定要静态编译。我在实际项目中遇到过这样的场景板子的rootfs是裁剪过的没有Qt运行库也不能随便往板子上安装软件包。动态编译出来一个Qt程序可能只有几MB但运行的时候依赖几十个so文件拷到板子上要么缺库要么版本不对折腾起来非常浪费时间。静态编译就不一样了编译时直接把需要的库函数都打包进可执行文件里出来的文件可能几十MB甚至上百MB但拷到哪里都能直接跑不用关心板子系统里有什么、没什么。还有就是性能和数据分发上的考量。程序要部署到多台板子上如果采用动态库方案每次更新程序版本都得同步更新依赖库一旦某个板子漏拷了一个库程序启动就会报错。静态编译之后程序只有一个可执行文件不管是U盘拷贝还是网络传输都方便很多出问题的概率也小得多。1.2 交叉编译的整体技术路线我采用的交叉编译方案基于开源工具链和Qt源码编译核心思路可以概括为三步准备好宿主机环境交叉编译Qt依赖的第三方库然后配置并编译Qt源码本身最后用Qt生成的交叉编译qmake工具来编译自己的应用程序。整个链条里最关键的其实是“sysroot”这个概念。用Ubuntu软件源直接装一个交叉编译器编译出来的程序默认是在编译机上运行的链接时找的都是编译机上的库文件根本链接不到ARM架构的库。所以必须给编译器准备一个目标系统根目录也就是sysroot里面放的是目标板子上运行环境对应的头文件和库文件。交叉编译工具链通过--sysroot参数指定这个目录后编译和链接时就会去这个目录里找对应的目标架构依赖库。我这里选的工具链是Linaro提供的aarch64-linux-gnu-gcc。Linaro是一套面向ARM嵌入式开发的开源工具链在社区里覆盖度很广既支持裸机开发也支持Linux系统开发。配合Debian/Ubuntu官方的aarch64-linux-gnu-*工具包使用可以解决大部分编译环境问题。实测下来这套组合整体稳定避开了很多闭源工具链的坑。工具链准备好之后就到了最耗时间的部分交叉编译Qt依赖的第三方库。常用的有libjpeg、libpng、freetype、fontconfig、tslib这几个。虽然Qt编译时如果检测不到这些库会自动禁用对应的功能模块但图像编解码、字体渲染、触摸屏输入这些功能对嵌入式界面应用来说又是刚需所以必须先把这些库准备好再编译Qt否则后续如果想加回来就得重新编译Qt源码非常费时间。1.3 开发环境和目标系统信息这里先把搭建环境列出来方便大家对照排查宿主机系统Ubuntu 20.04.4 LTS x86_64目标架构aarch6464位ARM交叉编译器aarch64-linux-gnu-gcc 9.3.0Qt版本Qt 5.14.2目标板RK3568Linux系统编译方式静态编译为什么选择Ubuntu 20.04作为宿主机环境因为它的软件包管理和工具链版本相对统一apt源里直接就有aarch64交叉编译器省去了手动安装配置的繁琐过程。如果使用其他Linux发行版比如CentOS或Arch Linux也可以操作只是软件包名称和安装方式有所不同原理完全一样。需要提醒的是宿主机内核版本和glibc版本会影响交叉编译工具的运行建议找一个干净的Ubuntu 18.04或20.04系统来做这件事避免系统库混乱带来不必要的麻烦。注意宿主机系统中的gcc版本和交叉编译器的版本不需要一致它们各自独立工作。交叉编译器负责生成ARM架构的代码宿主机的gcc只负责编译宿主机上的工具两者互不干扰。2. 交叉编译工具链准备与依赖库处理2.1 交叉编译工具链安装与验证在Ubuntu 20.04上安装交叉编译器直接使用apt命令即可sudo apt update sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu安装完成之后先用简单的测试程序验证工具链是否能正常工作。写一个hello.c文件交叉编译看看能不能编出aarch64架构的可执行文件cat hello.c EOF #include stdio.h int main(void) { printf(Hello, aarch64!\n); return 0; } EOF aarch64-linux-gnu-gcc hello.c -o hello file hello执行file hello之后输出内容里会显示“ELF 64-bit LSB executable, ARM aarch64”这就说明交叉工具链已经可用了。这里的file命令能查看可执行文件的架构信息是验证交叉编译结果最直接的手段。工具链安装之后还需要几个辅助工具比如cmake和ninjaQt编译时会用到。用apt安装即可sudo apt install cmake ninja-build2.2 sysroot目录规划和依赖库交叉编译这里重点讲一下sysroot目录规划。为了方便管理我把所有ARM架构相关的内容都放在了/opt/aarch64-qt目录下其中源码放在/opt/aarch64-qt/src安装路径统一用/opt/aarch64-qt/sysroot。实际操作的时候先在sysroot下建立usr/include和usr/lib目录再把工具链自带的ARM架构头文件和库文件复制进来。为什么要做这一步因为有些依赖库在configure的时候会去sysroot目录里查找依赖项的头文件和库文件如果sysroot目录结构不完整很多功能模块会被判为不可用。依赖库的交叉编译顺序也是有讲究的一般遵循“底层无依赖的库先编上层有依赖的库后编”的原则。我这里的顺序是zlib libjpeg libpng freetype fontconfig tslib。其中zlib最简单基本上只要设置好交叉编译参数就能直接编译fontconfig依赖freetype必须先编freetype再编fontconfig。以libpng为例交叉编译configure命令如下cd /opt/aarch64-qt/src/libpng-1.6.37 ./configure --prefix/opt/aarch64-qt/sysroot/usr \ --hostaarch64-linux-gnu \ --buildx86_64-linux-gnu \ CCaarch64-linux-gnu-gcc \ ARaarch64-linux-gnu-ar \ STRIPaarch64-linux-gnu-strip make -j8 make install这里有几个参数要解释一下。--host指的是编译出来的程序运行在什么平台上这里填aarch64-linux-gnu--build指的是编译所在的平台这里填x86_64-linux-gnu。这两个参数一组合编译器就会自动用aarch64的交叉编译方式来编译代码。--prefix指定安装路径所有编译产物二进制文件、头文件、库文件都会安装到sysroot目录里。freetype编译要注意一个配置开关--with-harfbuzzno。harfbuzz是一个复杂字体处理库在嵌入式环境里编译容易出现版本冲突问题而且Qt自带了harfbuzz处理逻辑不需要外部再引入一份。加上这个开关能省去不少麻烦。fontconfig依赖freetype和expat编译时需要通过PKG_CONFIG_PATH环境变量告诉configure到哪里找依赖库的.pc文件export PKG_CONFIG_PATH/opt/aarch64-qt/sysroot/usr/lib/pkgconfig ./configure --prefix/opt/aarch64-qt/sysroot/usr \ --hostaarch64-linux-gnu \ --buildx86_64-linux-gnu \ --enable-libxml2no这个--enable-libxml2no是为了让fontconfig使用自己内置的xml解析器避免再去交叉编译libxml2减少依赖链路长度。这一步在实际操作中很关键能省掉不少时间。tslib是触摸屏相关的库如果板子上用的是电阻屏或者需要tslib做触摸校准的场景这个库就必须编。编译tslib相对独立没有什么特殊依赖cd /opt/aarch64-qt/src/tslib-1.21 ./autogen.sh ./configure --prefix/opt/aarch64-qt/sysroot/usr \ --hostaarch64-linux-gnu \ --buildx86_64-linux-gnu \ CCaarch64-linux-gnu-gcc make -j8 make install2.3 交叉编译依赖库时的几个常见坑交叉编译第三方库的时候最常见的错误是“undefined reference to ...”这类错误出现的原因通常是某个库编译时没找到依赖的头文件或者链接时没找到依赖的库。排查思路也比较固定先看configure阶段的输出日志检查是否提示“cannot find -lxxx”或者“xxx not found”再确认PKG_CONFIG_PATH和CFLAGS、LDFLAGS环境变量是否设置正确。第二个常见的坑是configure脚本通过编译链接一个小程序来检测库是否可用如果编译环境不对检测脚本会直接报错。比如在编译fontconfig检测expat时如果编译器用的还是宿主机gcc而不是交叉编译器检测就会成功但最后链接时又会出问题。所以configure命令里的CC和CXX参数一定要显式指定为aarch64-linux-gnu-gcc和aarch64-linux-gnu-g不能图省事依赖环境变量否则很容易出现“明明白白配置成功最后make却报错”的情况。第三个容易被忽视的点是CMAKE_CROSSCOMPILING相关的配置。如果用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 /opt/aarch64-qt/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_FIND_ROOT_PATH_MODE_PROGRAM NEVER的意思是查找程序时不在sysroot里找防止找到sysroot里不合适的工具程序LIBRARY和INCLUDE设为ONLY则是让CMake只在sysroot里查找库文件和头文件避免误用宿主机上的x86库。这段配置如果写错了CMake虽然也能编译成功但最后链接出来的程序会包含x86的目标文件放到板子上无法执行而且报错信息还很不明显。3. Qt源码配置与交叉编译过程3.1 获取Qt源码并配置configure参数依赖库全部就位之后就可以开始编译Qt本身了。Qt 5.14.2源码压缩包可以从Qt官方仓库下载下载后解压cd /opt/aarch64-qt/src tar xf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2接下来是最关键的configure阶段。Qt的configure脚本负责检测编译环境、查找依赖库、生成编译配置所有编译开关都在这一步决定。我这里用的配置参数如下./configure \ -prefix /opt/aarch64-qt/qt5.14.2-static \ -release \ -static \ -opensource \ -confirm-license \ -xplatform linux-aarch64-gnu-g \ -nomake examples \ -nomake tests \ -no-opengl \ -no-icu \ -no-dbus \ -no-xcb \ -no-avx \ -no-avx2 \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -qt-harfbuzz \ -fontconfig \ -tslib \ -no-feature-cups \ -no-feature-printdialog \ -no-feature-printer下面逐个解释这些参数的意思。-static是核心参数告诉Qt要编译静态库版本。编译出来的库文件后缀是.a而不是.so。-release表示编译发布版本不包含调试符号信息生成的可执行文件体积会小一些。-xplatform linux-aarch64-gnu-g指定交叉编译平台。Qt的configure脚本会从qtbase/mkspecs目录下找对应的平台配置文件。linux-aarch64-gnu-g对应的就是mkspecs目录里针对aarch64的配置它会告诉Qt交叉编译工具链的名称和路径。如果这里指定错了后面编译直接报找不到编译器。-opensource和-confirm-license表示同意开源协议不用交互式确认这在自动化脚本里很有用。-nomake examples -nomake tests的意思是只生成Qt本身库的编译规则不编译示例和测试程序这两个选项能显著减少编译时间特别是静态编译情况下能省出一大截时间。接下来几个-no-开头的参数比较关键。-no-opengl、-no-icu、-no-dbus、-no-xcb分别禁用了OpenGL、ICU国际化库、D-Bus进程总线和X Window系统协议的支持。对于纯嵌入式LinuxQt应用来说这些功能通常用不到而它们恰恰是交叉编译的重灾区——ICU库本身编译量巨大DBus和xcb要依赖很多系统库强行编译极易失败。关掉之后编译流程会顺畅很多。-qt-zlib、-qt-libpng、-qt-libjpeg、-qt-freetype、-qt-harfbuzz这几个参数表示使用Qt自带的第三方库源码来编译。这里要和前面交叉编译的第三方库区分开Qt源码包本身就带了zlib、libpng、libjpeg、freetype等模块的源码副本如果检测不到系统库Qt可以使用这些自带副本来编译保证基本功能可用。但注意-qt-*参数主要影响Qt内部的模块依赖对Qt应用最终是否静态链接外部依赖库没有决定性影响。真正影响静态链接的是-fontconfig和-tslib这两个参数。这两个库Qt没有自带源码如果在configure阶段没有通过编译检测Qt会直接禁用字体配置和触摸屏支持。所以在编译Qt之前必须先确保sysroot里已经有了fontconfig和tslib的ARM版本库文件。此外还要设置环境变量让configure能找到sysroot里的依赖export PKG_CONFIG_PATH/opt/aarch64-qt/sysroot/usr/lib/pkgconfig export CPPFLAGS-I/opt/aarch64-qt/sysroot/usr/include export LDFLAGS-L/opt/aarch64-qt/sysroot/usr/lib3.2 qmake.conf平台配置文件修改configure执行完成是个十字路口过了它才是真正的编译阶段。Qt交叉编译的核心机制在mkspecs目录下的平台配置文件中。我用的linux-aarch64-gnu-g定义在qtbase/mkspecs/linux-aarch64-gnu-g目录下打开该目录下的qmake.conf文件能看到类似下面的内容QT_QPA_DEFAULT_PLATFORM linuxfb QMAKE_CC aarch64-linux-gnu-gcc QMAKE_CXX aarch64-linux-gnu-g QMAKE_LINK aarch64-linux-gnu-g QMAKE_LINK_SHLIB aarch64-linux-gnu-g如果工具链名称或路径不对直接在这里修改。我这次工具链路径都能直接用所以没有改动。这里想提醒的是如果需要自定义工具链名字比如使用了其他厂商定制工具链名字不带默认前缀一定在这里事先改好否则后面编译Qt的过程中会出现大量“command not found”错误。另一个需要关注的点是QT_QPA_DEFAULT_PLATFORM。这个变量指定了Qt应用默认使用的QPAQt Platform Abstraction平台插件。对纯嵌入式Linux场景来说通常使用linuxfb或eglfs其中linuxfb是基于Linux framebuffer的软件渲染方案适用范围最广不依赖GPU在大多数板子上都能直接跑起来。如果板子支持GPU后续可以切换到eglfs来获得硬件加速。我这边配置的是linuxfb确保在没有显示服务器的情况下Qt程序也能直接写屏幕。3.3 Qt源码编译与安装configure执行完成后检查一下输出日志确认没有致命错误然后就开始漫长的编译过程make -j8 sudo make install如果机器核心数多可以把-j后面的数字调大比如8核16线程的机器直接用make -j16能明显加快编译速度。整个Qt源码编译大概需要1到2个小时具体时间取决于机器配置。静态编译比动态编译时间更长因为所有第三方库的源码都要参与编译。编译过程中偶尔会出现几个奇怪的问题比如某个头文件找不到或者某个库链接失败。排查思路是看错误信息里提示的路径是sysroot里的路径还是宿主机路径如果是宿主机路径说明qmake.conf里的交叉编译配置没有生效需要检查工具链变量如果是sysroot路径但文件缺失需要回过去重新编译对应的依赖库。编译安装完成之后检查安装目录/opt/aarch64-qt/qt5.14.2-static里的目录结构确认lib目录下生成的是libQt5Core.a、libQt5Widgets.a这类静态库文件同时bin目录下有qmake可执行文件。用file命令验证qmake的架构file /opt/aarch64-qt/qt5.14.2-static/bin/qmake输出结果应该是aarch64架构的可执行文件。这里面有个容易分不清的点Qt安装目录下的qmake是在宿主机上运行的但它生成的是ARM架构的编译规则所以qmake本身必须是宿主机可执行的格式但同时它里面记录的工具链路径又都指向aarch64交叉编译器。如果观察到qmake运行时报错了第一反应不是检查qmake格式而是检查它运行时依赖的Qt库是否齐全——因为qmake本身也用到了Qt的Core模块动态链接时如果Qt库路径设置不对qmake会直接崩溃。注意这里安装出来的qmake只能用于交叉编译不能用于宿主机本地的Qt开发。如果需要在宿主机上开发调试界面逻辑建议另外安装一套x86版本的Qt两套环境互不干扰。4. 应用编译与静态链接实战4.1 编译环境配置Qt交叉编译环境搭建好之后编译自己的应用就变得简单了。第一次编译自己的应用时还需要配置环境变量。我写了一个环境设置脚本setqt5.14.2-aarch64-env.sh内容如下export QT_ROOT/opt/aarch64-qt/qt5.14.2-static export PATH$QT_ROOT/bin:$PATH export PKG_CONFIG_PATH/opt/aarch64-qt/sysroot/usr/lib/pkgconfig export CPATH/opt/aarch64-qt/sysroot/usr/include export LIBRARY_PATH/opt/aarch64-qt/sysroot/usr/lib每次打开新的终端时先source这个脚本再执行qmake和make就能正确交叉编译应用了。QMake自身的配置直接通过-config参数配合项目定义完成。如果你使用CMake构建Qt 5.14.2也提供了CMake工具链文件支持但网上大部分嵌入式Qt项目还是基于qmake且qmake对交叉编译更直接好理解所以这里用qmake的方式。4.2 静态链接参数配置静态编译的可执行文件体积会比动态编译大很多。一个空窗口的Qt Widgets程序动态编译后体积大约几百KB到1MB静态编译后体积通常在30MB到50MB左右这取决于选用了多少个Qt模块。所以静态编译时需要有一些取舍按需引入模块是控制程序体积的有效方法。我的.pro文件中的关键配置如下QT core gui widgets network CONFIG static CONFIG release QMAKE_LFLAGS -static TARGET myapp TEMPLATE app SOURCES main.cpp MainWindow.cpp HEADERS MainWindow.h关键点有两个。第一个是CONFIG static这个配置会通知Qt的链接流程优先使用Qt的静态库。第二个是QMAKE_LFLAGS -static这个参数会传递给gcc链接器告诉链接器不要链接动态库而是把所有依赖都打包进可执行文件。如果只加了CONFIG维度没有加QMAKE_LFLAGS最后链接出来的文件可能还是动态链接的运行时仍然依赖外部Qt库。编译命令依次执行source setqt5.14.2-aarch64-env.sh qmake myapp.pro make clean make如果一切顺利会在当前目录下生成myapp可执行文件用file命令查看myapp: ELF 64-bit LSB executable, ARM aarch64, dynamically linked, with debug_info, not stripped这里的信息有点误导显示的“dynamically linked”其实指的是可执行文件使用了动态链接器加载libc等系统库但Qt相关的库都已经静态链接进去了。判断是否真正静态编译要看运行时是否还需要外部Qt库而不是看file命令输出里的dynamically linked字样。进一步用readelf -d或ldd来确认aarch64-linux-gnu-readelf -d myapp | grep NEEDED如果输出内容里除了libc、libm、libgcc这类系统基础库之外还出现了libQt5Widgets、libQt5Core等Qt库的名字说明Qt库没有静态链接进去需要检查.pro文件里的配置。如果输出里只有libc等系统库说明Qt库已经静态链接了但系统基础库仍然是动态链接的。这里要补充说明一个关键的技术细节所谓的“完全静态链接”在实际的LinuxQt交叉编译场景里往往很难做到因为glibc不建议以静态方式链接特别是涉及到DNS解析、用户信息查询、线程本地存储等功能时静态链接glibc会产生奇怪的问题。通常的做法是Qt相关库全部静态链接系统glibc保留动态链接这样既能免去部署Qt依赖库的麻烦又不会踩glibc静态链接的坑。4.3 使用linuxfb插件和字体配置如果板子上没有显示器环境只有一块通过HDMI或MIPI接口连接的屏幕并且没有运行显示服务器那么就需要确保Qt程序能通过linuxfb插件驱动屏幕。应用代码里通常不用特殊设置只要在启动程序时用环境变量指定平台插件和显示设备即可export QT_QPA_PLATFORMlinuxfb export QT_QPA_FB_DRM0 ./myapp -platform linuxfb如果代码里没指定平台Qt会默认使用configure阶段设置的平台插件。我配置的默认平台是linuxfb所以直接运行程序就能用framebuffer显示。字体是另一个容易踩坑的点。目标板系统的rootfs通常没有中文字体文件如果程序里显示中文界面上会显示成方框。解决方法是把字体文件打包到程序目录里运行前通过环境变量告诉Qt字体文件位置export QT_QPA_FONTDIR/opt/myapp/fonts或者直接在程序源码里加载字体文件QFontDatabase::addApplicationFont(/opt/myapp/fonts/NotoSansSC-Regular.otf);建议把字体文件放到和程序相同的目录下程序启动时根据可执行文件路径动态拼接字体文件路径这样换目录部署也不用改代码。4.4 程序体积与启动速度优化静态编译出来的程序体积确实比较大我这边一个简单的Qt程序大约40MB。部署时可以通过aarch64-linux-gnu-strip工具去掉符号表来瘦身aarch64-linux-gnu-strip myappstrip之后程序体积通常会减少10%到30%左右功能不受影响。但strip之后程序出问题时生成的core dump里就没有符号信息了调试会变得困难。所以建议保留一个未strip的调试版本部署时用strip过的版本。启动速度方面静态编译的优势是启动时不需要加载大量动态库启动会快一些。但静态程序在启动时仍然需要初始化Qt内部的各种资源所以启动速度提升的效果有限。实际测试下来在RK3568平台上一个Qt程序动态编译版本启动大约需要1.5秒静态编译版本大约需要0.8秒这个提升幅度在嵌入式场景里还是有一定意义的。5. 常见问题与排查技巧实录交叉编译Qt这个链路长、环节多中间出问题实在太正常了。我把实际踩过的坑和排查方法整理成一张速查表希望能帮大家少走弯路。问题现象可能原因排查与解决方法configure阶段报“The test for linking will now be compiled”后卡死测试程序用的编译器不是交叉编译器检查configure命令里的-xplatform参数和CC环境变量用echo $CC确认configure报“Cannot find -lfontconfig”fontconfig没有安装到sysroot回到fontconfig编译目录确认make install执行成功ls /opt/aarch64-qt/sysroot/usr/lib/libfontconfig*检查库文件编译Qt时大量头文件缺失依赖库的include路径没有设置确认CPPFLAGS里包含-I/opt/aarch64-qt/sysroot/usr/include或者直接export CPATH链接时提示“cannot find -lts”tslib没有编入sysroot检查tslib编译是否成功库文件是否安装到sysroot/usr/lib程序在板子上运行时提示“fontconfig error: Cannot load default config file”fontconfig配置文件缺失把sysroot/etc/fonts目录拷贝到板子的/etc目录下字体文件放到/usr/share/fonts程序界面没有文字全是方框字体文件没安装到板子把字体文件放到板子系统字体目录或者用QT_QPA_FONTDIR指定字体目录编译时提示“unknown type name uint32_t”等类型错误sysroot头文件不完整确认工具链的usr/include目录完整可以重新安装gcc-aarch64-linux-gnu对应的头文件包交叉编译程序在板子上无法运行提示“Exec format error”编译出来的程序架构不对用file命令确认程序架构check arm64类型是否正确5.1 字体加载和fontconfig配置详解fontconfig是嵌入式Qt开发经常出问题的地方值得单独拿出来说。fontconfig本身是一套字体管理和匹配框架它不像普通库那样只要把so文件放到sysroot就行还需要一个配置文件告诉它到哪里找字体文件、字体缓存目录在哪里。编译fontconfig时安装目录里会生成etc/fonts/fonts.conf和etc/fonts/fonts.dtd两个关键文件。交叉编译安装完成后需要把这两个文件拷贝到板子的/etc/fonts目录下。否则程序启动时fontconfig初始化会失败Qt界面里的文字会全部显示成方框而且还会在终端输出一些fontconfig error的日志。同时把几个常用字体文件比如NotoSansCJK、DejaVu Sans拷贝到板子的/usr/share/fonts目录下或者专门创建一个字体目录通过export QT_QPA_FONTDIR指定给Qt使用。如果是简单应用不用fontconfig直接用Qt的addApplicationFont加载字体文件那么板子上可以不放fontconfig的配置文件。但如果configure阶段开启了-fontconfigQt链接了fontconfig库那么fontconfig的初始化流程还是会执行配置文件缺失会导致警告信息但不一定会导致程序崩溃。实际测试有警告但界面还能正常显示不过字体匹配行为可能会异常比如选不到正确的中文字体。5.2 常见的链接错误和解决方案静态编译应用时链接错误最让人头疼尤其是一些奇怪的“undefined reference”错误。我遇到过的典型情况有第一种Qt的某些模块需要额外的系统库支持。比如QtNetwork模块在不同平台下可能依赖openssl如果configure阶段检测到了openssl链接时需要加上-lssl -lcrypto。但在交叉编译环境里openssl库往往没有正确链接于是出现“undefined reference to SSL_CTX_new”之类的错误。解决方案有两个一是在configure阶段显式禁用openssl支持加-no-openssl二是在.pro文件里手动添加LIBS路径。第二种是库的链接顺序问题。静态库的链接顺序有讲究被依赖的库要放在依赖它的库后面。Qt的库之间有明确的依赖关系qmake会自动处理这个顺序但如果手动添加了第三方静态库顺序错了就会出现符号找不到的错误。解决方案是检查.pro文件的LIBS配置把被依赖的库放在后面。第三种是inline函数相关的错误。aarch64平台某些编译优化选项可能会影响inline函数展开导致链接时符号解析失败。这类问题比较少见但碰到后往往让人措手不及。解决方法是禁用某些优化选项比如去掉-O3改用-O2或者关闭-flto链接时优化选项——静态编译时-flto和交叉编译工具链的兼容性偶尔会出问题。5.3 tslib触摸屏支持的集成如果板子上用的是触摸屏建议让Qt程序支持tslib。tslib是一个触摸屏校准和采集库很多嵌入式板子的触摸屏驱动都基于tslib。Qt通过QPA的libinput或tslib插件来支持触摸输入。configure阶段加上-tslib参数后Qt会自动链接tslib。在程序运行时需要设置tslib的设备节点和校准文件位置。通常在板子上会有一个ts_calibrate校准工具校准后生成/etc/pointercal文件Qt运行时会读取这个文件来做触摸坐标转换。如果没有使用tslib校准触摸屏的坐标可能不准确点击按钮会出现偏差。这个问题不是Qt的问题而是触摸屏器件参数没有经过校准。部署时建议先跑一遍tslib的校准流程再启动Qt程序。在一些新内核上触摸屏设备走的是input子系统Qt的libinput插件可以直接识别这种情况下不需要tslib只要export QT_QPA_PLATFORMlinuxfb:tslib/dev/input/eventX指定正确的输入设备节点即可。5.4 相互矛盾的错误信息解读交叉编译最迷惑人的地方在于很多错误信息在宿主机上运行是正常的但在交叉编译环境里含义完全不同。比如“Cannot find -lGL”这个错误在x86环境下可能是系统没装OpenGL开发库在aarch64交叉环境下更可能是sysroot的usr/lib目录里没有ARM架构的libGL.so。还有一个典型的错误是“No rule to make target xxx.o”。这种情况通常不是makefile本身的问题而是某个源文件编译失败但错误信息被make的输出淹没了。解决办法是用make -j1单线程编译虽然慢但错误信息会干净很多能直接看到是哪个文件编译失败、具体的报错行是什么。提示交叉编译过程中遇到诡异错误时先别急着改代码或改配置用make -j1重新编译观察第一条错误信息。很多时候看起来玄学的错误都是前面某个小错误导致的连锁反应。6. 部署到目标板与测试验证6.1 部署步骤与运行环境准备程序编译完成后部署到目标板的过程很简单就是把可执行文件拷贝到板子上然后运行。但前提条件要做好以下准备第一确保板子的rootfs中包含Qt程序运行所需的基本库。虽然Qt库是静态链接的但librt、libdl、libm、libpthread这些基础库通常是动态链接的这些在标准Linux rootfs中一般都有不用额外处理。第二确保板子的/dev/fb0设备节点存在并有读写权限。linuxfb平台插件直接操作framebuffer设备如果设备节点不存在或者没有权限程序启动时会报错“linuxfb: Cannot open framebuffer /dev/fb0”。第三如果程序依赖字体文件要提前把字体文件放到板子的正确位置。这一步的操作方式是# 打包字体和配置 tar czf fonts.tar.gz -C /opt/aarch64-qt/sysroot/etc/fonts . scp myapp fonts.tar.gz root板子IP:/opt/myapp然后登录到板子上解压并运行mkdir -p /etc/fonts cd /etc/fonts tar xzf /opt/myapp/fonts.tar.gz export QT_QPA_PLATFORMlinuxfb /opt/myapp/myapp6.2 程序运行测试程序在板子上运行时如果screen显示正常触摸滑动流畅界面无乱码说明交叉编译链路已经整个打通了。如果程序崩溃使用dmesg或者journalctl查看日志大部分情况下会显示libc相关的错误比如段错误。这种问题排查起来比较费劲建议调试阶段保留一个未strip的版本配合gdb一步一步看栈信息。静态编译版本调试时有个便利性不需要在板子上安装Qt源码包的调试符号和库文件gdb加载可执行文件后就能直接看到函数名和行号这对嵌入式环境来说省了很大功夫。6.3 版本管理和后续扩展建议交叉编译环境搭建好之后建议把这个环境固化成镜像或者文档方便后续部署到其他开发机上。尤其是qmake.conf的修改、依赖库的版本号、各个configure参数的含义这些信息如果不记录下来过几个月再看自己都可能忘掉当时为什么这么配。后续如果项目升级Qt版本可以保留现有依赖库只替换Qt源码版本重新configure。qtbase的依赖项相对稳定libjpeg、libpng、freetype这些库不用频繁升级所以升级Qt本身的工作量主要集中在configure参数调整和编译验证上。如果把同样的方案移植到其他平台比如ARM 32位架构步骤完全类似只需把工具链从aarch64-linux-gnu-gcc替换成arm-linux-gnueabihf-gcc把-xplatform linux-aarch64-gnu-g换成对应的32位平台配置就行整体流程无需大改。我个人在实际操作中最大的体会是交叉编译Qt最忌讳急于求成越是到了编译快结束时越要冷静排查很多链接错误其实回到依赖库重新编译一遍就解决了。整个环境搭建一次成功需要一点运气但按这份手册一步一步来即使中间出了问题也能快速定位到是工具链问题、依赖库问题还是Qt配置问题这就够了。