刚接触嵌入式Linux开发的同学,十有八九会在“交叉编译”这道坎上卡上一阵子。我自己当年第一次给瑞芯微RK3568开发板搭Qt开发环境时,就是因为在Ubuntu上直接编译了一个Qt程序,scp到板子上之后跑不起来,报了一堆乱七八糟的错误,折腾了两天才把环境彻底捋顺。现在回头看,核心问题其实只有一个:没有把宿主机(PC上的Ubuntu)和开发板之间的编译架构、运行库和工具链理顺。这篇文章就以RK3568开发板为例,完整走一遍在Ubuntu环境下搭建Qt交叉编译环境、配置Qt Creator、远程调试的流程,适合手里有一块RK3568开发板、想在板子上跑Qt界面程序,却不知道怎么把开发环境搭起来的同学。
1. 为什么必须交叉编译:一个崩溃程序的“破案”过程
1.1 RK3568是ARM架构,Ubuntu是x86架构,两者的“语言”不通
RK3568是瑞芯微推出的一款四核Cortex-A55处理器,是典型的ARM架构SoC。而我们的开发主机,无论是笔记本还是台式机,基本上都是x86_64架构。这里说的“架构”可以理解成机器各自使用的指令集:x86_64的机器能直接执行x86_64的二进制指令,ARM的机器则只能执行ARM指令。两个架构之间,CPU不认识对方的“母语”。
所以,当你把Ubuntu上编译出来的程序直接拷贝到RK3568开发板上,系统根本无法把它加载进来。最常见的执行结果只有两种:终端提示cannot execute binary file: Exec format error,或者所有依赖库都找不到、报No such file or directory。前者说明CPU架构对不上,后者常见的坑是我下面会专门展开的——程序是ARM二进制不假,但动态链接器路径不对,本质上是交叉编译时链接配置出错。
1.2 交叉编译的核心:在一台机器上编译出另一台机器能跑的程序
交叉编译(cross compile)的意思很简单:编译行为发生在宿主机(x86_64的Ubuntu)上,但编译产物的目标平台是开发板(aarch64架构)。整个链条里最核心的工具是交叉编译器,Ubuntu仓库里直接有对应的包:gcc-aarch64-linux-gnu和g++-aarch64-linux-gnu。装上之后,用aarch64-linux-gnu-gcc代替平时的gcc,编译出来的就是ARM机器能识别的二进制。
但仅仅有编译器还不够。一个Qt程序会依赖Qt运行库、C/C++标准库、动态链接器等一大堆支撑文件。这些“支撑文件”也必须是aarch64架构的版本。缺了它们,就算编译器生成了正确的ARM指令,程序在板子上依然起不来。这就是为什么交叉编译一个Qt项目,通常要先在Ubuntu上交叉编译出一整套aarch64的Qt库,再让Qt Creator指向这套库来写代码、编译程序。
1.3 为什么不直接在开发板上编译?
有同学会问:RK3568好歹是四核A55,为什么不能在板子上直接装GCC编译Qt程序呢?答案很简单:太慢,且不适合日常开发。板子的性能定位是嵌入式、工控、边缘计算,而不是作为一台编译服务器。我有一次尝试在RK3568板子上直接编译一个中等规模的Qt Widgets项目,光是编译链接就花了近十分钟,而同样的工程在PC上交叉编译只需要十几秒。再加上Qt库本身源码量巨大,在板子上编译整个Qt几乎是不可接受的。开发阶段频繁改代码、频繁编译,还是把Ubuntu当作编译主机来得舒服,板子仅作为程序运行和调试的目标设备。
2. 动手前先摸底:Ubuntu环境、工具链和板子信息一个都不能少
2.1 宿主机Ubuntu版本与基础依赖
我目前使用的宿主机系统是Ubuntu 20.04 LTS和22.04 LTS,两个版本都算稳定。如果你是其他较新的LTS版本,也完全可以,但务必保证软件源能正常更新。首先执行基础的依赖安装:
sudo apt update sudo apt install build-essential git wget bzip2 flex bison \ libx11-dev libxext-dev libxcb1-dev libfontconfig1-dev libfreetype6-dev \ libxkbcommon-dev libgl1-mesa-dev这些依赖中有一部分是交叉编译Qt时configure阶段用来检测宿主环境的,有一部分是编译宿主机辅助工具(如qmake、moc、uic等)时需要的。很多教程只让你装build-essential,最后configure时会报“No XCB”之类的错,就是因为缺少这些X11相关头文件。这里一次装齐,可以省掉后面的连锁麻烦。
2.2 安装aarch64交叉编译工具链
Ubuntu仓库自带了aarch64交叉编译工具链,安装非常方便:
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu安装完成后验证一下:
aarch64-linux-gnu-gcc --version如果终端能正常输出版本信息,说明工具链已经可用。
这里补充一个选型建议:如果你的开发板是跟着厂商SDK来的,比如瑞芯微官方SDK、正点原子或迅为的BSP包里通常带了一份预编译的交叉编译器,位置一般在SDK目录的prebuilt/gcc/linux-x86/aarch64/下面。这种情况下,我建议优先使用厂商自带的工具链,而不是系统包的工具链,因为板子上的内核模块、二进制库很可能是用这份工具链编译的,大家用的GCC版本和C库版本一致,ABI兼容性更好。我的演示沿用apt安装的aarch64-linux-gnu-系列命令,原理完全一样。
2.3 摸清开发板底细:架构、系统与已有Qt
拿到一台开发板,不要急着写代码。先通过串口或SSH登进去,执行下面几条命令,把板子的“基因”摸清楚:
uname -a cat /etc/os-release ldd --version我手上这台RK3568跑的是Debian系统,uname -a输出里有明确的aarch64字样,/etc/os-release能看出是哪个发行版,ldd --version则用于确认板子上的glibc版本。为什么要确认这些?因为后面我们在Ubuntu上交叉编译的程序,最终要动态链接到板子上的C库。如果Ubuntu交叉工具链对应的glibc版本比板子上的高,程序拷过去很可能报“version GLIBC_2.34 not found”。这是一个非常经典的坑,我在第6章会专门说排查方法。
接着看板子上是否已经带Qt运行库:
ls /usr/lib/aarch64-linux-gnu/ | grep libQt5很多官方发布的根文件系统里其实只预装Qt运行库(为了跑预置程序),并不带头文件和开发用的.so链接文件。这意味着,即便板子上能跑Qt程序,想在Ubuntu上写代码链接到这套库也做不到。这也是我们为什么必须自己交叉编译一份完整Qt库的根本原因。如果你在板子上看到了libQt5Core.so等文件,记下它的版本号,尽量选择源码里对应的Qt版本交叉编译,保持两边一致。
3. 编译Qt库:给RK3568准备一整套“合身”的Qt
3.1 获取Qt源码与版本选择
交叉编译Qt,最主流的路线是下载Qt Open Source源码包,在Ubuntu上用交叉编译工具链构建出一套aarch64的Qt SDK。版本选择上,嵌入式领域用Qt 5的仍然非常多,因为对老模块支持好、配置成熟、资料也多。我以Qt 5.15.2为例,它在RK3568这类ARMv8平台上非常稳,也是我做项目的主力版本。Qt 6虽然在桌面端很红火,但在嵌入式板卡上的生态支持还处于过渡期,如果不追求新特性,稳定优先选5.15.2即可。
源码下载:
wget https://download.qt.io/archive/qt/5.15/5.15.2/single/qt-everywhere-opensource-src-5.15.2.tar.xz tar xf qt-everywhere-opensource-src-5.15.2.tar.xz cd qt-everywhere-opensource-src-5.15.23.2 configure参数:核心是 -xplatform、-prefix 和模块裁剪
Qt的交叉编译核心在于configure阶段。我的经历是,这个阶段90%的错误都出在参数没给对。下面给出一个我在实际项目中验证过的配置:
./configure -prefix /opt/rk3568/qt5.15.2 \ -opensource -confirm-license -release \ -xplatform linux-aarch64-gnu-g++ \ -no-opengl -no-glib \ -skip qtwebengine -skip qtwebview -skip qtwayland -skip qt3d \ -skip qtconnectivity -skip qtlocation -skip qtscript \ -no-feature-cups -no-iconv \ -make libs -nomake examples -nomake tests几个关键参数我解释一下:
-prefix /opt/rk3568/qt5.15.2:交叉编译产物的安装目录。这个目录决定后面Qt Creator要选的qmake路径,也决定你要把哪些文件同步到开发板上。-xplatform linux-aarch64-gnu-g++:告诉configure,目标是aarch64架构。Qt 5.15的源码里自带linux-aarch64-gnu-g++这个mkspec,如果你的Qt版本较老找不到,可以拷贝linux-arm-gnueabi-g++改成对应的aarch64版本。-no-opengl:RK3568本身有GPU,但OpenGL在纯Buildroot/Debian环境下配置起来比较复杂,如果没有具体显示需求,先用软件渲染的linuxfb方式跑Qt界面最省事。后续需要GPU加速再重新编译带EGL支持的Qt版本也不迟。-skip qtwebengine:Qt WebEngine编译极慢、体积巨大、依赖又多,嵌入式场景除非做浏览器,否则一定要跳过。类似的webview、wayland、3d模块也一并剪掉。-no-iconv、-no-feature-cups:是为了避免configure阶段探测到宿主机/板子的iconv和CUPS环境不同而报错。实际项目中基本用不到这两个功能,关掉最稳妥。
3.3 make与make install:等待、验证与常见报错
configure跑完后会生成Makefile,接着编译:
make -j$(nproc) sudo make install首次编译Qt库耗时较长,我测试过在8核CPU的机器上大约需要20到40分钟,取决于机器性能。如果中途出现编译错误,不要盲目重来,先看错误日志。最常见的有两类:一类是configure阶段没报错、make阶段报错,基本是缺少宿主工具或依赖头文件,回头补装对应依赖后需要重新configure;另一类是工具链版本过老,比如Qt 6要求GCC 10以上,但系统包默认给的gcc-aarch64-linux-gnu版本是7或8,这时候就需要升级工具链,或者改用厂商或Linaro的较新版本。
编译完成后验证:
file /opt/rk3568/qt5.15.2/lib/libQt5Core.so输出里应该包含ELF 64-bit LSB shared object, ARM aarch64字样,说明这套Qt库确实是aarch64架构的,可以放心提供给后续Qt Creator使用。
4. Qt Creator中的Kit配置:把编译器、Qt和设备串成一个完整链条
4.1 Qt Creator版本选择与基本准备
Qt Creator本身是一个IDE,需要从Qt官网单独下载安装,或者通过Qt在线安装器安装。它最大的价值在于把编译器、Qt库、设备、调试器这些松散组件关联成一套可用的工具链。很多人只会在Qt Creator里新建工程、点运行,却从没理解过底层的Kit逻辑,导致交叉编译时各种“跑不起来”。
安装完成后,先别急着建项目,我们把环境一项项配好。打开Qt Creator,选择菜单栏的“工具”->“选项”,进入后能看到设备、编译器、Qt版本、Debuggers等配置页。
4.2 添加编译器与Qt版本
这一步的作用是告诉Qt Creator:我们有一套专门面向aarch64的GCC编译器,还有一套交叉编译出来的Qt库。在“Kits”->“编译器”页面,点“添加”->“GCC”->“C”,编译器路径选择:
/usr/bin/aarch64-linux-gnu-gcc同样,再添加一个“C++”编译器:
/usr/bin/aarch64-linux-gnu-g++添加完后,Qt Creator会自动识别出它们是aarch64架构。如果识别不准确,可以手动在编译器页面把ABI设置为aarch64-linux-generic-elf-64之类的ARM64选项。
接着在“Kits”->“Qt版本”页面,点“添加”,路径选择:
/opt/rk3568/qt5.15.2/bin/qmake看到版本信息显示为Qt 5.15.2,说明这套Qt库已被Qt Creator识别。
4.3 添加设备:让Qt Creator认识你的RK3568
设备配置是为了后续的远程部署、远程运行和远程调试。在“设备”页面,点“添加”->“泛型Linux设备(Generic Linux Device)”,填写开发板的IP地址、用户名和密码。我的板子系统账号是root,就填root和对应的密码或密钥。填完后点“测试”,确保Qt Creator能通过SSH连上板子并执行基本命令。如果测试失败,先确认板子SSH服务是否开启,以及Ubuntu和板子之间的网络是否互通。
4.4 新建Kit并验证编译产物架构
在“Kits”页,点“添加”,给这个套件起一个清晰的名字,比如“RK3568-qt5.15.2”。然后分别做以下关联:
| 配置项 | 选择内容 |
|---|---|
| 编译器C | aarch64-linux-gnu-gcc |
| 编译器C++ | aarch64-linux-gnu-g++ |
| 调试器 | aarch64-linux-gnu-gdb(没有就先用系统gdb-multiarch) |
| Qt版本 | Qt 5.15.2(/opt/rk3568/qt5.15.2) |
| CMake工具 | 宿主机的cmake(如果你用CMake构建) |
| 设备 | RK3568开发板 |
| Sysroot | 可先留空,后续有需要再指定板子的根文件系统 |
这里我要单独说一下Sysroot。它本质上是一个“.sdk目录”,把开发板的根文件系统拷贝一份到Ubuntu上,里面包含了板子上的/usr、/lib目录。设置Sysroot后,交叉编译时编译器会优先从这个目录里找头文件和库,从而保证编译链接使用的版本与板子实际运行环境完全一致。对于精度要求很高的项目,Sysroot是必需品;如果图省事,很多情况下不设也能跑,但遇到链接到板子独有库的项目,就可能出现“找不到头文件”的诡异问题。
选好Kit后,新建或打开一个最简单的Qt Widgets项目,构建套件选择“RK3568-qt5.15.2”,然后点击“构建”。编译完成后,找到生成的二进制文件(一般在build目录下),用file命令验证:
file ./build-TestApp-.../TestApp如果输出是ELF 64-bit LSB executable, ARM aarch64,说明你的Qt Creator交叉编译搭建已经成功。这一步是最重要的分水岭:能生成aarch64可执行文件,说明编译链路通了;后面要做的只是部署和调试。
5. 部署到RK3568并远程调试:程序和开发板正式“接上头”
5.1 配置部署步骤:把可执行文件送到板子上
程序编译出来并不等于能在板子上跑,还需要把可执行文件和它依赖的Qt库一起部署到开发板。部署最直接的方式是手动scp,但对于频繁开发调试的场景,手动操作太低效。Qt Creator提供了一键部署功能,非常值得配置。
打开项目窗口左侧的“项目”标签页,在你的Kit配置里找到“部署”步骤,点“添加部署步骤”。Qt Creator支持通过“SFTP上传文件”这个方式,把本地的可执行文件上传到开发板的指定目录,比如/opt/app/。配置好之后,每次点绿色三角运行按钮,Qt Creator会先自动编译,再通过SSH把可执行文件复制到板子上,最后在板子上启动程序。
部署时要注意:Qt程序是动态链接的,二进制本身只有几百KB甚至几MB,但运行依赖的libQt5Core.so、libQt5Widgets.so、libQt5Gui.so等一堆动态库加起来可能有大几十MB。编译好的Qt库在Ubuntu的/opt/rk3568/qt5.15.2/lib目录下,你需要把整个lib目录拷贝到板子上:
scp -r /opt/rk3568/qt5.15.2 root@<板子IP>:/opt/然后在板子上设置环境变量:
export LD_LIBRARY_PATH=/opt/qt5.15.2/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM_PLUGIN_PATH=/opt/qt5.15.2/pluginsLD_LIBRARY_PATH让动态链接器优先从新目录寻找Qt库,QT_QPA_PLATFORM_PLUGIN_PATH告诉Qt去哪里找平台插件。这两行不设,后面十有八九会碰到Could not load the Qt platform plugin "xcb"或者libQt5Core.so.5找不到的问题。
5.2 远程运行与远程调试:Qt Creator如何接管板子上的进程
远程运行配置在“项目”->“运行”页面。构建配置里选择“作为远程运行”,远程可执行文件路径写板子上的部署目录,比如/opt/app/TestApp,工作目录也一并设置。参数部分根据程序需要,如果要用linuxfb平台显示,就加-platform linuxfb。
远程调试是这篇文章的重头戏,也是很多人觉得玄乎的地方。简单说,远程调试的原理是:板子上运行一个gdbserver程序,宿主机上的GDB通过网络与它通信,控制板子上的被调试程序。Qt Creator通过SSH在板子上自动启动gdbserver,再把宿主机上交叉编译出的调试信息与板子上的程序符号对应起来,于是你就能像调试本地程序一样,在Ubuntu的Qt Creator界面里看到板子上程序的断点、变量、调用栈。
要使远程调试跑通,三个东西必须齐活:
板子上有gdbserver。通过板子自带的包管理器安装即可,Debian/Ubuntu系:
apt install gdbserver如果板子系统是裁剪过的Buildroot,可能需要自己在Buildroot里开启gdbserver并重新烧录根文件系统。
宿主机有一个支持aarch64的GDB。优先安装
gdb-multiarch:sudo apt install gdb-multiarch然后在Qt Creator的“Kits”->“调试器”里,为“RK3568-qt5.15.2”这个套件手动指定调试器路径为
/usr/bin/gdb-multiarch。被调试的程序保留调试符号,不要用strip处理。平时编译release版本时Qt Creator可能会自动strip,要在项目构建步骤里把“Strip二进制文件”选项关掉,或者干脆用debug模式编译远程调试版。
都准备好后,直接在Qt Creator里点调试按钮(F5),它会自动完成部署到板子、启动gdbserver、建立连接这一连串动作。第一次跑通远程调试时,你会在Qt Creator的Debugger Console里看到类似Remote debugging from host 192.168.1.100的日志,这就说明板子上的gdbserver已经挂上了。
5.3 让程序在板子上正常显示:Qt平台插件选型
Qt程序在嵌入式设备上显示,与桌面Linux环境下“直接弹窗”不一样,它依赖QPA(Qt Platform Abstraction)平台插件。最常用的三种:
| 平台插件 | 适用场景 | 说明 |
|---|---|---|
| linuxfb | 无窗口系统、有framebuffer | 最简单,裸机或Buildroot环境首选 |
| eglfs | 有GPU/EGL环境 | 需要Mali等GPU驱动支持,性能好 |
| xcb | 板子系统带X11显示服务 | 如果板子跑的是完整桌面版Debian/Ubuntu才使用 |
我交叉编译Qt时用了-no-opengl,因此可用的平台插件主要是linuxfb。在板子上执行:
export QT_QPA_PLATFORM=linuxfb ./TestApp如果程序启动后没有任何错误输出,而屏幕上出现了Qt界面,说明显示链路已经通。这里有一个常见误区:很多人在xcb平台上卡了很久,实际上板子根本没有X11服务,怎么等都等不出窗口。搞清楚自己板子的显示架构,再选择对应的平台插件,可以少走很多弯路。
6. 这些坑我踩过:编译、链接与运行阶段的典型问题排查
6.1 “cannot execute binary file”其实是架构不匹配
这是最基础的报错。看到cannot execute binary file,第一反应是检查二进制架构:
file ./TestApp如果输出显示的是x86-64,说明你编译时选错了Kit,Qt Creator里构建套件没有切到“RK3568-qt5.15.2”,而是用了默认的桌面Qt。这种情况不用动代码,重新选择构建套件编译即可。如果输出已经是ARM aarch64,但依旧无法执行,那就不是架构问题,而是动态链接器或库路径的问题。
6.2 动态链接库失踪案:ldd与readelf的用法
程序能加载但报No such file or directory,或者运行时报找不到libQt5Core.so.5,这属于动态链接问题。在开发板上用下面的命令查看程序依赖了哪些动态库:
ldd ./TestApp如果发现libQt5Core.so显示not found,说明LD_LIBRARY_PATH没设置对,或者依赖的Qt库版本与程序编译时不一致。另一种情况是二进制里的动态链接器路径不对,用readelf确认:
readelf -l ./TestApp | grep interp正常情况下应该显示/lib/ld-linux-aarch64.so.1。如果你的交叉编译工具链C库路径与板子不一致,这里可能指向一个不存在的位置,结果就是“No such file or directory”。遇到这种问题,可以在开发板上建立对应的软链接,或者重新用与板子glibc版本匹配的工具链编译。
6.3 gdb远程调试时的两个高频问题
远程调试最让人抓狂的报错是Remote 'g' packet reply is too long。这个错误几乎都是宿主机GDB与板子gdbserver版本不匹配造成的,比如板子上的gdbserver是GDB 10,宿主机上的gdb是GDB 8。解决办法很直观:升级宿主机上的gdb-multiarch到最新版,确保两边的GDB协议版本兼容。
另一个常见问题是断点打不上。程序明明跑起来了,但Qt Creator里怎么点断点都无效,原因多半是二进制被strip过,调试符号丢失。只需关闭strip选项,或者用debug模式重新编译即可。还有一个容易忽略的点:如果程序已经部署到板子上,而宿主机GDB加载的符号文件路径与实际部署路径不一致,也可能导致源码无法关联。Qt Creator里可以通过“调试”->“调试模式”->“源码文件映射”手动纠正宿主源码路径与板子源码路径的对应关系。
6.4 一点实用的调试习惯
最后分享一个我自己的习惯:远程调试之前,永远先在板子上手动跑一遍程序。
gdbserver :2345 ./TestAppgdbserver启动成功后,宿主机上再用命令行GDB连上:
gdb-multiarch ./TestApp (gdb) target remote <板子IP>:2345 (gdb) continue先在命令行方式下跑通一次远程调试,再回到Qt Creator图形界面操作,能避免很多环境类的低级问题。因为当程序无法启动、插件加载失败、环境变量缺失这些基础问题没有解决时,图形化调试只会让错误变得更难定位。先命令行跑通,再图形化调试,这是我整个RK3568开发过程中觉得最省时间的做事顺序。
RK3568的Qt交叉编译环境搭建,说穿了就是“架构匹配”“工具链一致”“路径正确”这三件事。把每一步验证到位,剩下的就是正常的Qt开发流程了。等你把第一个Qt程序成功部署到板子上、在屏幕上看到自己的界面时,那种感觉,值得前面所有折腾。