很多人第一次做嵌入式或跨平台部署时,都会有一个很自然的判断:

既然开发机和目标板都是 Ubuntu 20.04,那把可执行程序直接拷过去运行,不应该有问题吗?

实际情况往往不是这样。

以一个典型场景为例:Qt 程序最早在 VMware 里的 Ubuntu 20.04 环境完成开发和编译,虚拟机架构是 x86_64;后面需要部署到一块嵌入式 Linux 目标板,而目标设备的 CPU 架构是 aarch64

结果非常典型:

  • 直接把 VMware 里编译出的程序拷到目标板上,报 Exec format error
  • 改成 aarch64 交叉编译后,程序能识别了,但又报缺少 Qt 运行库。

这篇文章把这两个问题一起讲清楚:为什么“同样是 Ubuntu 20.04”仍然跑不起来,以及正确的交叉编译和离线部署流程应该怎么做。

一句话先说结论

Linux 程序能不能运行,至少同时取决于三件事:

  • 操作系统类型
  • CPU 架构
  • 运行时动态库依赖

所以:

Ubuntu 20.04 相同,不代表二进制程序就能直接通用。

一、先看两个最常见的报错

1. Exec format error

把 VMware Ubuntu 上编译出来的程序直接复制到目标板上运行,常见报错如下:

./your_qt_app: cannot execute binary file: Exec format error

或者中文环境下会看到:

二进制文件格式错误

这类错误的本质非常直接:目标板根本不认识这个程序的 CPU 架构。

用下面这条命令检查程序:

file ./your_qt_app

如果输出里包含类似信息:

ELF 64-bit LSB shared object, x86-64, ...

那就说明这个程序是 x86_64 版本,只能在普通 PC、笔记本、VMware 这类 x86_64 Linux 环境运行,不能直接放到 ARM64 目标设备的 aarch64 平台上。

2. 缺少 Qt 运行库

当我把程序改成交叉编译后的 aarch64 版本,再放到目标板上运行时,又遇到了第二类错误:

./your_qt_app: error while loading shared libraries: libQt5Core.so.5: cannot open shared object file: No such file or directory

这个错误和前面的 Exec format error 完全不是一回事。

它说明:

  • 程序架构已经对了,目标设备已经能识别这个可执行文件;
  • 但目标系统里缺少运行所需的 Qt 动态库;
  • 问题已经从“架构不匹配”转成了“运行时依赖缺失”。

这一步其实是好现象,因为它意味着交叉编译方向已经走对了。

二、为什么同样是 Ubuntu 20.04 还是不能直接运行

很多人容易把“系统版本”和“二进制兼容性”混在一起。

Ubuntu 20.04 只是发行版版本,真正决定程序能不能跑起来的,是 CPU 架构和依赖环境。

常见概念可以这样理解:

x86_64   普通 PC、笔记本、VMware 虚拟机常见架构
amd64    Debian/Ubuntu 包管理中对 x86_64 的常见命名
aarch64  64 位 ARM 架构,嵌入式 Linux 设备中常见
arm64    Debian/Ubuntu 包管理中对 aarch64 的常见命名

所以即使两边都是 Ubuntu 20.04,也仍然可能是这样:

开发环境:Ubuntu 20.04 + x86_64
目标环境:Ubuntu 20.04 + aarch64

这两者的二进制并不兼容。

换句话说:

系统版本相同,只说明“软件生态可能接近”;架构相同,才意味着“二进制有机会直接运行”。

三、Qt 程序为什么还会报缺少动态库

如果你的 Qt 程序采用的是动态链接方式编译,那么即使 CPU 架构已经匹配,程序运行时仍然要依赖目标系统提供对应的共享库。

比如这类常见依赖:

libQt5Core.so.5
libQt5Gui.so.5
libQt5Widgets.so.5
libstdc++.so.6
libgcc_s.so.1

所以目标板要能运行程序,至少要满足两件事:

  1. 可执行文件本身是目标架构,比如 aarch64
  2. 系统里安装了同架构的运行库,比如 arm64 的 Qt 库

这也是为什么你解决完架构问题以后,下一步往往会看到“缺少某个 .so”。

四、最有用的判断命令

在排查这种问题时,我最常用的其实就四类命令。

1. 看当前机器是什么架构

uname -m

常见输出:

x86_64

说明当前是普通 PC 或 VMware 的 x86_64 环境。

aarch64

说明当前是 64 位 ARM 平台,常见于很多嵌入式 Linux 目标设备。

2. 看程序本身是什么架构

file ./your_qt_app

如果结果里出现:

x86-64

说明它是 x86_64 程序。

如果结果里出现:

ARM aarch64

说明它是 aarch64 程序,方向就对了。

3. 看程序缺少哪些动态库

ldd ./your_qt_app

更实用的写法是直接过滤缺失项:

ldd ./your_qt_app | grep "not found"

如果没有任何输出,说明当前系统已经能找到所需动态库。

4. 用结果区分问题类型

这一步非常重要:

  • 如果 file 显示是 x86-64,那是架构问题;
  • 如果 file 显示是 ARM aarch64,但 ldd 里有 not found,那是运行库问题;
  • 如果 fileldd 都正常,程序还异常退出,就该继续排查业务逻辑或运行参数了。

五、正确的编译思路

1. 在 VMware Ubuntu 上编译本机版本

如果只是让程序在 VMware Ubuntu 或其他 x86_64 Linux 上运行,那么本机编译即可:

cd /path/to/your-project
chmod +x build-native.sh
./build-native.sh --install-deps
./build-native.sh --clean

生成文件通常在:

build/your_qt_app

验证一下:

file build/your_qt_app

如果显示 x86-64,这是正常结果,因为它本来就是给当前开发机运行的。

2. 为 ARM64 目标板编译 aarch64 版本

如果目标是 ARM64 架构的嵌入式 Linux 设备,就必须输出 aarch64 版本:

cd /path/to/your-project
chmod +x build-arm64.sh
./build-arm64.sh --install-deps --clean

后续重复编译可以简化成:

./build-arm64.sh --clean

生成文件通常在:

build-aarch64/your_qt_app

然后一定要验证:

file build-aarch64/your_qt_app

理想结果应包含:

ARM aarch64

这一步不要省,因为很多“明明我已经交叉编译了”的问题,最后发现输出文件其实还是本机架构。

六、把程序复制到目标板上运行

交叉编译完成后,把 ARM64 程序复制到目标板:

scp /path/to/your-project/build-aarch64/your_qt_app root@<target-board-ip>:/root/

然后在目标板上执行:

cd /root
chmod +x your_qt_app
./your_qt_app --help

如果这一步能显示帮助信息,说明程序本体已经具备运行条件。

如果还报缺少 .so,就继续用 ldd 查依赖,不要再回头怀疑“是不是交叉编译没成功”。

七、目标板不能联网时,怎么做离线依赖安装

这也是实战里很常见的一类问题。

如果目标板不能联网,那么这条命令就没法直接执行:

apt install -y <对应的Qt运行库包>

这时更稳的做法是:在能联网的 Ubuntu 开发机上,提前把 arm64 依赖包下载好,再复制到目标板离线安装。

1. 在 Ubuntu 开发机下载 ARM64 依赖包

前提是你的开发机已经配置好多架构源,可以下载 arm64 包。

mkdir -p ~/arm64_qt_debs
cd ~/arm64_qt_debs

apt download \
  libqt5core5a:arm64 \
  libqt5gui5:arm64 \
  libqt5widgets5:arm64 \
  libglib2.0-0:arm64 \
  zlib1g:arm64 \
  libstdc++6:arm64 \
  libgcc-s1:arm64

再把程序本体也放进去:

cp /path/to/your-project/build-aarch64/your_qt_app .

然后打包:

tar czf arm64_qt_app_offline_pkg.tar.gz *

2. 复制到目标板

如果目标板能通过网络接收文件:

scp ~/arm64_qt_debs/arm64_qt_app_offline_pkg.tar.gz root@<target-board-ip>:/root/

如果网络不方便,也可以用 U 盘、共享目录或者其他离线介质传输。

3. 在目标板离线安装

mkdir -p /root/qt_app_pkg
cd /root/qt_app_pkg

tar xzf /root/arm64_qt_app_offline_pkg.tar.gz
dpkg -i *.deb

如果第一次安装提示依赖顺序问题,可以再执行一次:

dpkg -i *.deb

然后重新验证:

chmod +x your_qt_app
./your_qt_app --help
ldd ./your_qt_app | grep "not found"

如果还有缺失项,就继续补对应的 :arm64 包。

八、安装完成后,离线包还能不能删

可以删。

.deb 包和 .tar.gz 压缩包只是安装介质,安装完成后真正的动态库已经进入系统目录。确认程序可以运行后,完全可以把安装包清掉。

建议先做两步确认:

ldd ./your_qt_app | grep "not found"
./your_qt_app --help

如果没有 not found,并且程序能正常启动或输出帮助信息,就可以删除安装包:

rm -f *.deb
rm -f arm64_qt_app_offline_pkg.tar.gz

但要注意,不要删掉程序本体,也不要误删系统已经安装好的运行库。

九、最后给一个部署检查清单

如果你想快速判断问题出在哪,可以按下面这个顺序:

  1. 在目标板执行 uname -m,确认板子实际架构
  2. 对程序执行 file ./your_qt_app,确认是不是 ARM aarch64
  3. 执行 ldd ./your_qt_app | grep "not found",确认依赖是否齐全
  4. 运行 ./your_qt_app --help,确认程序能否真正启动

这四步足够覆盖大多数跨平台部署问题。

十、结论

这次踩坑最核心的一点,不是 Qt 写错了,也不是 Ubuntu 版本有问题,而是部署平台从 x86_64 切到了 aarch64

一句话总结就是:

同样是 Ubuntu 20.04,不代表可执行程序可以直接通用;架构对了以后,还要把运行库补齐。

对应到这次项目里:

  • VMware 本机编译出来的 build/your_qt_app 只能给 x86_64 Linux 用;
  • ARM64 目标板应该使用交叉编译产物 build-aarch64/your_qt_app
  • 如果目标板不能联网,就提前准备好 arm64.deb 依赖包离线安装。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐