
在嵌入式 Linux 上做 Qt 开发尤其是在国产化平台、信创项目、工业控制设备这类场景里交叉编译几乎是绕不开的一道坎。我最近刚好把一个老项目从 x86 迁移到 aarch64 架构的板子上整个过程从装工具链到最终跑起静态编译的 Qt 程序踩了一堆坑也把整套流程理顺了。这次我把它整理成一份从零开始的手册目标很明确让一台干净的 x86_64 主机能完整产出 aarch64 架构下静态链接的 Qt 5.14.2 程序。为什么要用 Qt 5.14.2 而不是更新版本说实话Qt 5.15 之后开源版本就切到 LTS 维护策略5.14.2 反而成了很多老项目和新项目认可的一个稳定候选版本模块完整、生态资料多不少板卡厂商的 BSP 也基于它定制。而“静态交叉编译”这四个字意味着产出的可执行文件不依赖目标板上的 Qt 运行库和 C/C 库直接拷过去就能跑。这在工业现场、运维不便的部署场景里非常重要省去了一堆 so 依赖缺失的破事。这篇手册我会按实际操作的顺序来写从环境准备、工具链配置到 Qt 源码的 configure 参数、mkspecs 修改再到最终的编译验证和部署每一步都给出可复现的命令和参数同时把我在实际操作中踩过的坑都标注出来。无论你是刚开始接触交叉编译的新手还是被 Qt 库的依赖问题折腾过的老手这套流程都能直接抄作业。1. 内容整体设计与思路拆解1.1 静态交叉编译到底在解决什么问题先聊聊交叉编译的本质。所谓交叉编译简单说就是“在 A 机器上编译出 B 机器能运行的程序”。A 机器通常是性能强、工具链全的 x86_64 开发主机B 机器则是目标板比如基于 ARMv8 架构的 aarch64 平台。目标板受限于 CPU 算力、内存和存储空间一般不会装完整的编译环境所以开发链路必须放在主机上完成。而“静态编译”就又加了一层约束编译出来的可执行文件要把所有用到的库函数和依赖库代码直接打进二进制里运行时不再依赖外部 so 文件。这在动态链接方案里是没必要做的动态链接时程序只记录依赖了哪些库运行时由系统去加载和重定位。两者的取舍很直接静态编译的程序体积大一些但部署简单、不怕环境差异动态编译的程序体积小、资源占用少但一旦目标板的库版本不对或缺失启动时会直接报错。我在实际项目里见过很多次现场故障程序的二进制拷到设备上一执行就提示 “error while loading shared libraries”要么缺 libQt5Core.so.5要么缺 libstdc.so.6。这些设备多半是跑在无人值守环境里的排查一次的成本远高于省掉的那点磁盘空间。所以我个人在交付这类嵌入式 Qt 程序时只要硬件存储允许一律优先静态链接。这里引出一个关键点我们的目标产物不只是“能在 aarch64 上运行的 Qt 程序”而是“能在最简单的 aarch64 Linux 系统上直接运行的 Qt 程序”。目标板上可以没有 Qt、没有交叉编译器、甚至没有标准 C 开发包但程序照样能跑。1.2 方案选型与技术路线在开始操作前需要先定技术路线。我选择的是 tslib Qt 5.14.2 的组合交叉工具链则采用 Linaro/GCC 提供的 aarch64-linux-gnu 系列。之所以选 tslib是因为我们很多目标板用的是电阻触摸屏Qt 自带的 evdevtouch 插件在某些驱动不完善的板卡上表现不佳tslib 提供了一套统一的触摸校正和滤波机制兼容性要好得多。工具链我选的是 gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu这个版本比较老但稳定可靠和 Qt 5.14.2 的兼容性经过了大量项目验证。如果你手头板卡的 BSP 自带了工具链优先用 BSP 自带的因为内核头文件版本、C 库版本都和板子匹配能少踩很多坑。没有的话再用通用工具链。完整的依赖树大概是这样的aarch64-linux-gnu-gcc/g zlib静态库 libiconv如需转码 tslib触摸输入 Qt 5.14.2 源码configure make qmake交叉版本 Qt5Core / Qt5Gui / Qt5Widgets 等静态库需要特别提醒的是整个链条上任何一环都要用同一套目标架构的库混用不同版本或者动态库放进静态编译在链接阶段会有一堆莫名其怪的报错。2. 主机环境准备与依赖库安装2.1 主机环境与基础软件这里我假设你的开发主机是 Ubuntu 20.04 x86_64其他 Debian 系发行版也一样关键是装好以下基础包sudo apt update sudo apt install -y build-essential bison flex libglib2.0-dev \ libfontconfig1-dev libfreetype6-dev libx11-dev libxext-dev \ libxfixes-dev libxi-dev libxrender-dev libxcb1-dev \ libx11-xcb-dev libxcb-glx0-dev libxcb-util0-dev \ libxcb-xkb-dev libxcb-image0-dev libxcb-keysyms1-dev \ libxcb-randr0-dev libxcb-shape0-dev libxcb-xinerama0-dev \ libxcb-xfixes0-dev libxcb-xkb-dev libxcb-icccm4-dev \ libxcb-icccm4-dev libxcb-render-util0-dev libxcb-cursor-dev \ libxcb-xv0-dev libxcb-xkb1-dev libxcb-icccm4-dev \ libxcb-image0-dev libxcb-keysyms1-dev libxcb-render-util0-dev \ libxcb-xinerama0-dev libxcb-xkb1-dev libxcb-randr0-dev \ libxcb-shape0-dev libxcb-xv0-dev libxcb-sync-dev \ libxcb-xfixes0-dev libxcb-glx0-dev libxcb-util0-dev \ libxcb-icccm4-dev libxcb-keysyms1-dev libxcb1-dev \ libxcb-render-util0-dev libxcb-shape0-dev libxcb-xkb-dev \ libxcb-xv0-dev这些包大部分是 Qt xcb 平台插件需要的依赖虽然我们在静态编译时最终目标是不依赖宿主机库但 configure 阶段 Qt 会检查宿主机上的交叉编译相关能力。如果只是做纯嵌入式 aarch64 目标没有 xcb 需求可以适当精简。我的建议是一步到位都装上省得 configure 时因为缺了某个扩展库而自动裁剪功能。另外确保你的主机已经安装了 Python 2 或兼容脚本环境。Qt 5.14.2 的 configure 脚本对 Python 3 的某些版本兼容性不太好如果你主机默认 Python 是 3.8建议加装 python2 备用。我之前在 Ubuntu 20.04 上第一次跑 configure直接报 Python 版本不支持后来装好 python2 并调整 PATH 才顺利通过。2.2 交叉工具链的安装与验证交叉工具链是整个编译链路的引擎必须保证版本与目标板系统匹配。我采用的方式是直接下载 Linaro 的预编译工具链解压到 /opt然后通过环境变量加入 PATH。wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz sudo mkdir -p /opt sudo tar -xJf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz -C /opt export PATH/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH解压完成后先验证工具链能否正常工作aarch64-linux-gnu-gcc --version如果能正常打印版本信息说明工具链没问题。接下来编译一个最简单的 C 程序验证交叉编译能力cat hello.c EOF #include stdio.h int main() { printf(hello aarch64\n); return 0; } EOF aarch64-linux-gnu-gcc hello.c -o hello file hello看到输出里包含 “ELF 64-bit LSB executable, ARM aarch64” 就说明交叉编译环境可用了。这一步必须要做不要跳过。很多人一上来就去编译 Qt最后编译失败才回头查工具链浪费大量时间。交叉工具链、内核头文件、C 库这三者如果版本不匹配Qt 编译中会出现各种诡异的语法错误或链接失败提前验证能帮你在问题排查时排除一整类原因。2.3 依赖库 tslib 的交叉编译tslib 是触摸屏驱动层的重要一环尤其在电阻屏场景下几乎是必需品。这里我把它的交叉编译流程也完整列出来git clone https://github.com/libts/tslib.git cd tslib ./autogen.sh ./configure --hostaarch64-linux-gnu \ --prefix/opt/tslib \ --enable-staticyes \ --enable-sharedno make -j$(nproc) sudo make install编译完成后把头文件拷到工具链目录或单独目录后续 Qt configure 会用到。这里选择静态编译 tslib是为了和 Qt 静态编译保持一致避免后续链接时还要单独处理 tslib 的动态库依赖。需要特别注意的是Qt 编译时通过-tslib参数启用 tslib 支持它会去查找tslib.h头文件和libts.so或libts.a库文件。如果不把安装路径告诉 Qt这个功能默认是关闭的。我建议在后续的 Qt configure 参数里显式加-I/opt/tslib/include -L/opt/tslib/lib确保能找到。3. Qt 5.14.2 源码准备与 configure 参数详解3.1 下载与解压Qt 源码从 5.15 开始官方提供的是 git 仓库和在线安装包5.14.2 这一代可以直接用归档包。可以从 Qt 官方镜像站下载也可以从国内镜像站下载速度会快很多wget https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.tar.xz tar -xJf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2这个源码包涵盖了 Qt 的所有模块包括 Base、Declarative、Multimedia、Tools 等但交叉编译时我们通常只会编译其中一部分模块。通篇编译所有模块不仅耗时还容易因为某些模块的依赖库不满足而失败。我一般只关注 qtbase 和必要的附加模块比如 qtsvg如果需要图标和矢量图支持、qtimageformats更多图像格式支持。虽然目录很大但 configure 时可以通过-skip参数跳过不需要的模块不需要手动删除目录。3.2 configure 核心参数解析configure 是整个 Qt 交叉编译里最核心、也最容易翻车的环节。Qt 的 configure 参数非常多但针对 aarch64 静态交叉编译我用的核心参数组合如下./configure \ -prefix /opt/qt5.14.2-aarch64 \ -opensource -confirm-license \ -release \ -static \ -xplatform linux-aarch64-gnu-g \ -device linux-aarch64-gnu-g \ -nomake examples -nomake tests -nomake tools \ -skip qtdeclarative -skip qtmultimedia -skip qtscript \ -no-opengl \ -no-xcb \ -no-compile-examples \ -no-feature-cups \ -no-feature-printdialog \ -no-feature-printer \ -tslib \ -I/opt/tslib/include \ -L/opt/tslib/lib \ -sysroot /opt/aarch64-sysroot逐个解释这些参数的含义和取舍-static表示生成静态库这是整个任务的核心产出的 Qt 库是 .a 格式而不是 .so。-xplatform linux-aarch64-gnu-g告诉 configure 使用 aarch64 的编译器组合。Qt 源码的mkspecs目录里会包含对应的 qmake.conf其中定义了编译器的名称和参数。如果这里的名字和 mkspecs 里的目录名对不上configure 会直接报错。-release去掉调试符号缩小体积嵌入式设备存储有限通常都这么做。-tslib启用 tslib 触摸支持配合-I和-L指向 tslib 的头文件和库目录。-no-opengl和-no-xcb如果目标板不需要 GPU 加速和 X11 显示必须关闭这两项否则交叉编译时 configure 会尝试检测 OpenGL 库和 X11 库在 aarch64 的 sysroot 里找不到就报错。-no-feature-cups、-no-feature-printdialog、-no-feature-printer关闭打印相关功能减少依赖尤其是 cups 库在嵌入式环境基本用不到。-skip系列跳过不需要的 Qt 模块加快编译速度同时避免模块缺依赖导致的失败。如果你项目的界面是跑在一个全屏 frame buffer 应用比如用 linuxfb 或者 eglfs 插件那么 xcb 和 opengl 确实不需要。但我需要提醒一句如果你的 Qt 程序想在这个 aarch64 平台上继续使用较完整的 GUI 渲染比如走 QPainter 软件渲染linuxfb 插件就够了但如果你的界面用了很多复杂动画、OpenGL 着色器关闭 OpenGL 会让很多效果直接跑不了。编译前先想清楚目标平台上到底要跑什么界面这决定了 configure 的参数选择。3.3 关键参数调整与 sysroot 概念在 3.2 的参数组合里有一个-sysroot /opt/aarch64-sysroot参数需要展开讲一下。sysroot 这个概念通俗说就是“在主机上模拟一个目标板的根文件系统目录”。交叉编译器在寻找头文件和库时会优先去 sysroot 目录里找而不是去主机的 /usr/include 和 /usr/lib。这样做的原因是交叉编译时不能用宿主机上的 x86 库必须用目标板的 aarch64 库。如果工具链默认的 sysroot 是它自带的目录我们也可以把 tslib、zlib 等依赖都安装到这个 sysroot 里让 configure 和后续编译环节都能找到它们。对于 Qt 5.14.2 的交叉编译我实际使用了一个更简单的方案把 tslib 安装目录直接放在/opt/tslib然后通过-I和-L显式指定同时把工具链自带的 sysroot 通过--sysroot参数传给编译器这一步由 Qt 的 mkspecs 自动完成。如果你用的是 Linaro 工具链默认 sysroot 就是工具链目录下的aarch64-linux-gnu/libc不需要额外创建。sudo ln -s /opt/tslib/include/* /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc/usr/include/ sudo ln -s /opt/tslib/lib/* /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc/usr/lib/这样做的好处是编译器在默认 sysroot 里就能找到 tslib不用每处都手动加-I和-L。但这会污染工具链目录建议用脚本管理方便重装。4. 修改 mkspecs 与 qmake.conf 的关键步骤4.1 创建自己的 qplatformdefs.h 和 qmake.confQt 的编译配置很大程度上由qtbase/mkspecs下的文件决定。Qt 5.14.2 自带的 mkspecs 里其实已经有linux-aarch64-gnu-g这个目录是针对 aarch64 的通用配置。但这个配置在某些交叉编译场景下并不完全适用尤其当你使用特殊工具链路径或者需要传递额外编译参数时最好复制一份自定义配置。我的做法是复制一份并创建自己的 mkspecscd qtbase/mkspecs cp -r linux-aarch64-gnu-g linux-aarch64-mycustom-g cd linux-aarch64-mycustom-g然后编辑qmake.conf把编译器路径和参数改成实际值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 QMAKE_AR aarch64-linux-gnu-ar cqs QMAKE_OBJCOPY aarch64-linux-gnu-objcopy QMAKE_NM aarch64-linux-gnu-nm QMAKE_STRIP aarch64-linux-gnu-strip QMAKE_CFLAGS -I/opt/tslib/include QMAKE_CXXFLAGS -I/opt/tslib/include QMAKE_LFLAGS -L/opt/tslib/lib为什么要自定义一份而不是直接用默认的呢大部分情况下默认文件也能工作但自定义有几个实际好处一是可以明确指定使用哪个交叉编译器特别是当你的 PATH 里同时存在多个 aarch64 工具链时二是可以把 tslib 的头文件和库路径预先固化在配置里后续不用每个项目都手动加三是团队开发时拷贝这份 mkspecs 就能统一环境。4.2 几个容易被忽略的配置开关除了编译器路径qmake.conf 里还有几个直接影响静态编译成败的配置。第一是QMAKE_LFLAGS里的-static开关。有些版本的 Qt mkspecs 会默认使用-static-libgcc或者只静态链接 C 标准库但不静态链接所有库。做个实验验证我建议在 QMAKE_LFLAGS 里加上-static确保最终链接时尽可能静态链接。如果是纯静态编译编译器还需要使用-static-pie或者链配置里的支持。第二是QMAKE_CFLAGS里加-fPIC。虽然静态库通常不需要 PIC但我们编译出来的 Qt 静态库有时会被另一个共享库引用或者程序本身需要加载 Qt 插件比如 platform 插件此时非 PIC 代码会导致链接失败。先加上没有坏处。第三是QMAKE_CXXFLAGS里的-O2。Qt 本身的 configure 会设置优化级别但这里我再强制声明一次防止 mkspecs 覆盖默认值导致编译产物不带优化影响性能。改完这些之后把 configure 命令里的-xplatform linux-aarch64-gnu-g改成-xplatform linux-aarch64-mycustom-g这一步完成之后Qt 在 configure 和编译时就会使用我们定制的编译器配置。经验之谈qmake.conf 里的路径建议多检测几遍。我遇到过把路径写错一个字母configure 阶段不报错编译到一半突然找不到头文件的尴尬情况。排查起来很痛苦因为错误信息往往指向的是某些源码文件而实际上是 mkspecs 里的路径配错了。5. 核心编译流程与静态环境变量脚本5.1 正式运行 configure在编译之前确保环境变量已经设置好。我习惯写一个环境变量脚本放在/opt/qt-aarch64-env.sh内容如下#!/bin/bash export PATH/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH export TSLIB_CFLAGS-I/opt/tslib/include export TSLIB_LIBS-L/opt/tslib/lib -lts export PKG_CONFIG_PATH/opt/tslib/lib/pkgconfig export PKG_CONFIG_LIBDIR/opt/tslib/lib/pkgconfig export PKG_CONFIG_SYSROOT_DIR/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc在每次编译前先source /opt/qt-aarch64-env.sh保证编译环境干净统一。然后运行 configure。因为 Qt 5.14.2 的 configure 是脚本对 PATH 非常敏感我用一个干净的 shell 来执行source /opt/qt-aarch64-env.sh cd qt-everywhere-src-5.14.2 mkdir -p build-aarch64-static cd build-aarch64-static ../configure \ -prefix /opt/qt5.14.2-aarch64 \ -opensource -confirm-license \ -release \ -static \ -xplatform linux-aarch64-mycustom-g \ -nomake examples -nomake tests -nomake tools \ -skip qtdeclarative -skip qtmultimedia -skip qtscript \ -no-opengl \ -no-xcb \ -no-feature-cups \ -no-feature-printdialog \ -no-feature-printer \ -tslib \ -I/opt/tslib/include \ -L/opt/tslib/libconfigure 过程中必须密切关注输出信息。看到类似ERROR: ...基本说明配置失败而WARNING: ...则需要判断是功能性警告还是致命影响。如果一切正常最后会提示 Qt 配置完成并且会打印出相关的功能特性列表。常见 configure 失败原因包括无法找到编译器、无法找到 tslib 库、OpenGL 检测失败、host 工具如 syncqt.pl执行失败等。每一条报错后面都有详细信息顺着错误信息去定位即可。5.2 编译安装make 与 make installconfigure 通过后直接开始编译make -j$(nproc)这里的-j参数表示并行编译。如果主机内存和 CPU 充足并行度可以拉高但我遇到过内存不够导致编译进程被杀的情况。建议先make -j4试水如果内存足够再逐步加。编译 Qt 5.14.2 的 qtbase 模块视主机性能不同大约需要 20 到 60 分钟。如果中途报错先不要慌张错误信息会指明是哪个源文件出的问题。常见的编译错误多半是缺少系统依赖如 xkbcommon 头文件或工具链自身的 bug。前者可以通过安装对应头文件解决后者则可能需要换工具链版本。编译完成后安装到之前指定的前缀路径sudo make install安装完成后在/opt/qt5.14.2-aarch64下会有 bin、lib、include、plugins、qml 等目录。重点检查 lib 目录里的静态库文件ls /opt/qt5.14.2-aarch64/lib/libQt5Core.a如果这个文件存在说明 Qt 静态库编译成功了。5.3 从零写一个交叉编译环境变量脚本在开发过程中交叉编译环境的切换非常频繁。如果不统一管理环境变量很容易出现“在终端 A 能编译在终端 B 找不到 qmake”的问题。我建议把环境变量脚本写得完整一些包括 PATH、库路径、qmake 的别名等。以下是一个我目前在用的完整版脚本可以说它是整个静态交叉编译流程的“中央枢纽”#!/bin/bash # Qt 5.14.2 aarch64 静态交叉编译环境变量 export TOOLCHAIN_ROOT/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu export QT_ROOT/opt/qt5.14.2-aarch64 export TSLIB_ROOT/opt/tslib export PATH$TOOLCHAIN_ROOT/bin:$QT_ROOT/bin:$PATH export ARCHaarch64 export CROSS_COMPILEaarch64-linux-gnu- export CC${CROSS_COMPILE}gcc export CXX${CROSS_COMPILE}g export AR${CROSS_COMPILE}ar export LD${CROSS_COMPILE}ld export RANLIB${CROSS_COMPILE}ranlib export STRIP${CROSS_COMPILE}strip export PKG_CONFIG_PATH/opt/tslib/lib/pkgconfig export PKG_CONFIG_LIBDIR/opt/tslib/lib/pkgconfig export PKG_CONFIG_SYSROOT_DIR$TOOLCHAIN_ROOT/aarch64-linux-gnu/libc export QT_SELECTqt5.14.2-aarch64 export QMAKE$QT_ROOT/bin/qmake # 方便直接使用 qmake 进行交叉编译 alias qmake-cross$QT_ROOT/bin/qmake之后每个新项目编译时只需source /opt/qt-aarch64-env.sh qmake-cross /path/to/project.pro make交叉编译出的可执行文件file一下应该是 ARM aarch64 的 ELF 文件。6. 部署到目标板与运行验证6.1 静态编译程序的最小运行条件这里“最小运行条件”是重点。因为 Qt 静态编译后程序本身不依赖 Qt 的动态库但它还依赖一些底层系统调用和部分 C/C 库。如果目标板是精简的 Linux 系统只要具备以下条件即可运行Linux 内核提供系统调用接口版本不能太老通常不低于 3.10 都能跑libc 库的兼容性程序里依赖的 libc 库函数必须是目标板上的 libc 符号超集触摸设备节点如果使用触摸屏/dev/input/eventX帧缓冲设备节点如果走 linuxfb 插件/dev/fb0必要的文件系统目录和权限我实测过一个典型场景目标板是一个基于 aarch64 架构的定制 Linux系统里只有 busybox 和一些基础库。我把 Qt 静态编译出来的一个 demo 程序拷过去chmod x然后运行界面上直接显示出来了完全不需要装 Qt 库整个体验非常顺畅。6.2 目标板上的显示与触摸验证Qt 在嵌入式 Linux 下有几种显示后端最常用的三个是linuxfb基于 Linux framebuffer由 Qt 直接写 /dev/fb0适合没有 GPU 的简单场景。eglfs基于 OpenGL ES 的显示后端需要有 DRM/KMS 驱动的显示设备。wayland/xcb需要目标板有对应的显示服务。因为是静态编译运行时通过-platform参数指定后端./demo -platform linuxfb如果你的设备使用的是电阻屏并且启用了 tslibQt 会自动通过 tslib 库读取触摸事件但需要告诉 Qt 使用 tslib 的插件./demo -plugin tslib:/dev/input/event0设备节点号要根据实际系统来定可以先用cat /proc/bus/input/devices查看触摸设备对应的 event 节点。如果界面显示但触摸无响应大概率是参数没配对或者 tslib 的校准文件 /etc/pointercal 不存在。在开发阶段可以先跑 tslib 自带的校准工具 ts_calibrate记得交叉编译并部署到板上生成校准文件再跑 Qt 程序就正常了。6.3 字体与资源文件的部署Qt 程序运行时不带 Qt 安装目录所以字体文件也要跟着一起部署。通常需要把/opt/qt5.14.2-aarch64/lib/fonts下的字体文件拷贝到目标板上并通过环境变量QT_QPA_FONTDIR指定字体目录export QT_QPA_FONTDIR/usr/share/fonts/qt ./demo -platform linuxfb这一点极其容易被忽略。我第一次静态编译完程序在板子上跑起来了结果界面上中文全是方块。排查半天才想起来 Qt 默认字体在单独目录里静态程序不会自动携带。把字体一拷重跑中文正常显示。如果目标板有中文字体需求务必记得准备一套完整的 CJK 字体Qt 5.14.2 自带的 WenQuanYi 字体在嵌入式环境表现不错体积适中。如果你的程序只需要英文则可以大幅削减字体体积。7. 常见问题与排查技巧实录7.1 典型的链接错误与处理方法我做过的交叉编译项目不算少静态 Qt 又是其中出问题最多的一类。下面表格里整理的是几个非常典型的坑每个都有对应的解决思路错误现象根本原因解决方案libQt5Core.a: could not read symbols: File format not recognized编译器或链接器混用x86 库被当作 aarch64 库链接确认 CC/CXX/AR 全部指向 aarch64 工具链检查 makefile 里实际使用的 gcc 路径undefined reference to ts_openQt 的 tslib 模块没有被正确链接检查 configure 是否 --enable-tslib确认-lts在 LIBS 中或手动在 pro 文件里加LIBS -ltsGL/gl.h: No such file or directory没有配置 OpenGL 相关头文件但 configure 又启用了 OpenGL 模块用-no-opengl显式关闭或把 Mesa 的 aarch64 头文件放到 sysrootcannot find -lgcc_s静态模式下 gcc 找不到 libgcc 静态库工具链目录里没有 libgcc_s.a安装对应工具链的静态库或者用-static-libgcc妥协The Qt version is too old之类的 qmake 错误目标机上执行程序时动态链接器去加载宿主机上的 qmake编译程序时没有在干净环境中执行确保 qmake 是交叉版本且 PATH 无污染这里我想特别展开说“undefined reference”这一类问题。静态编译时链接顺序非常讲究。如果库 A 依赖库 B那么 A 必须在 B 前面出现在链接命令行里。Qt 的静态库很多依赖关系复杂如果出现“undefined reference”优先检查 pro 文件里的 LIBS 顺序。以 tslib 为例如果你的程序使用了 Qt 的 tslib 插件那么-lts要放在-lQt5Gui和-lQt5Core之后因为 Qt 的库依赖 tslib。GCC 的链接器在处理静态库时是一遍扫描顺序错了就会报告未定义引用。实用技巧遇到链接顺序问题不是手动排几遍顺序就能解决建议把 Qt 的库和有传递依赖的库合成一个--start-group ... --end-group块让链接器反复扫描这些库比如LIBS -Wl,--start-group -lts -lQt5Gui -lQt5Widgets -lQt5Core -Wl,--end-group这能极大缓解静态库循环依赖的烦恼。7.2 configure 失败时的排查套路configure 失败时第一件事是去qtbase/config.log里看详细的错误输出。这个文件记录了 configure 阶段所有检测命令的完整结果比终端输出的信息详细得多。常见的排查流程是检查终端输出最后 10 行的错误提示看是检测哪一项失败打开 config.log搜索关键字error或undefined定位到具体检测项根据检测项的类型判断是编译器问题、头文件缺失问题、还是链接问题逐一修复后再重新 configure注意先清理 configure 缓存make distclean或者删除 build 目录。我遇到最多的一类问题是configure 检测aarch64-linux-gnu-g编译器时能通过但检测某个函数库时因 sysroot 不完整而失败。此时往往不是 Qt 本身的问题而是目标板 C 库不完整。这时要么补充 sysroot要么在 configure 参数里显式禁用对应功能模块。7.3 目标板上运行闪退的排查清单程序按上述步骤没有错误地编译出来了可一放到设备上就闪退这是另外一类高频问题。运行时闪退的排查思路和编译阶段完全不同目标板上没有 gdb 时可以按下面的清单逐项排查确认可执行文件的架构是否正确file 程序名确认动态链接器是否存在readelf -l 程序名 | grep interp静态程序这一步应该是空的确认所有依赖库如果还有动态依赖都存在ldd 程序名静态程序 ldd 会输出“not a dynamic executable”确认显示设备节点存在且有权限ls -l /dev/fb0确认触摸设备节点存在且有权限ls -l /dev/input/event*确认环境变量设置正确QT_QPA_PLATFORM、QT_QPA_FONTDIR、TSLIB_*确认程序运行时有足够的临时目录export TMPDIR/tmp确认字体文件存在且可读如果是纯静态程序ldd 显示 not a dynamic executable那运行时闪退多半是环境或系统调用层面问题。我遇到过一次目标板内核版本太老缺少某个系统调用程序初始化时直接段错误。这种情况只能通过 strace 来定位板子上能跑 strace 的话优先上。8. 实战案例第一个 Qt 交叉编译程序8.1 创建最小 Qt 工程前面讲了这么多理论真正上手才是硬道理。下面我用一个最小的 Qt Widgets 程序演示从写代码到部署运行的全过程。在主机上新建一个 demo 目录创建以下两个文件demo.proQT core gui widgets TARGET demo TEMPLATE app CONFIG c11 link_pkgconfig SOURCES main.cppmain.cpp#include QApplication #include QLabel int main(int argc, char *argv[]) { QApplication a(argc, argv); QLabel label(Qt 5.14.2 Static on aarch64); label.resize(400, 200); label.show(); return a.exec(); }这个程序足够简单但已经可以覆盖 Qt 的 core、gui、widgets 三大基础模块验证交叉编译环境是否完整。8.2 编译与静态链接配置在编译前需要再确认环境变量以及 qmake 版本source /opt/qt-aarch64-env.sh which qmake qmake -v确保输出的 qmake 版本是 5.14.2并且路径在/opt/qt5.14.2-aarch64/bin不是系统自带的 qmake。然后在项目目录下执行qmake-cross demo.pro makemake 完成后用file查看产物file demo # 输出demo: ELF 64-bit LSB executable, ARM aarch64, ...如果输出里能看到statically linked那静态链接的目标就达成了。例如ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped如果显示dynamically linked说明 QMAKE_LFLAGS 里的静态参数没生效需要回到 mkspecs 里检查。8.3 部署到目标板的完整操作示例把 demo、字库文件用 scp 或 U 盘拷贝到目标板# 主机上执行 scp demo root目标板IP:/root/ scp -r /opt/qt5.14.2-aarch64/lib/fonts root目标板IP:/usr/share/fonts/qt到目标板上执行export QT_QPA_PLATFORMlinuxfb export QT_QPA_FONTDIR/usr/share/fonts/qt cd /root ./demo看到界面上显示文字就说明从“交叉编译”到“静态链接”到“目标板运行”的整个链路全部打通了。此刻基本可以说你已经掌握了 Qt 5.14.2 aarch64 静态交叉编译的完整技能。9. 经验心得与后续进阶方向整套流程走下来我个人的体会可以归结为三句话工具链版本比命令参数更关键sysroot 的完整性决定了 configure 能走多远静态编译不是把所有东西塞进一个二进制就完事字体、插件、tslib 这些运行时资源一个都不能少调试别人写好的工程时第一件事永远是看 mkspecs 和环境脚本而不是改代码。关于后续的进阶方向如果你现在用的是 5.14.2将来要升级到 5.15 或 6.x底层逻辑是一样的重点是模块划分变化比如 Qt6 里 qtbase 拆得更细qmake 也逐渐被 CMake 替代但 configure 参数的思路、sysroot 的方案、静态库链接的技巧统统可以继续复用。另一个常见需求是让静态编译出的程序也能支持 Qt 插件机制比如 platform 插件这部分需要用-static配合QTPLUGIN qlinuxfb等方式将插件静态编入方法也值得单独写一篇。最后再分享一个小技巧建议把你整个交叉编译环境的搭建过程写成一个 shell 脚本甚至直接把下载、解压、编译做成自动化。这样换开发机、带新人、交付给客户时就不再依赖反复手动敲命令也不容易漏掉关键的配置步骤。我自己后来就是这样几百行的 setup 脚本一跑一套新的交叉编译环境就搭好了省下来的时间非常可观。