方式一
交叉编译器 + 源码
优点:
- 精准的版本控制
- 适配范围广,无需重复工作
缺点:
- 复杂,需要解决各种编译问题
- 依赖过多的情况下工作量大
对于像qt这样构建依赖很多的库不是很方便。对于这些构建依赖,最好的办法是能有一个目标架构的根文件系统
GCC交叉编译器构建流程 ^68b560
构建的整体流程如下:
- 构建合适版本的glibc库(glibc+kernel_header)
- 构建gcc(gcc + 4个deps + binutils)
构建glibc并安装到指定目录 - glibc wiki
使用安装到指定目录中的glibc - glibc wiki
#!/bin/bash
# 使用交叉编译器构建arm64版本的glibc
export LD_LIBRARY_PATH=
CC=aarch64-linux-gnu-gcc-8
CXX=aarch64-linux-gnu-g++-8
../../glibc-2.28/configure --prefix=/usr --host=aarch64-linux-gnu --disable-werror
# 这里的prefix不是最后安装的那个真是的/usr路径。而是安装时指定的DESTDIR+prefix才是最总的glibc安装路径。DESTDIR相当于虚根
glibc安装后还需要安装内核头文件,这样才是完整的glibc头文件。导出内核头文件的指令是

#!/bin/bash
# 构建arm64交叉编译器
../../gcc-8.3.0/configure -v --enable-languages=c,c++ --prefix=/usr --program-suffix=-83 --enable-shared --enable-linker-build-id --without-included-gettext --enable-threads=posix --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=aarch64-linux-gnu --program-prefix=aarch64-linux-gnu- --with-sysroot=/home/huangym/gnu/out/
其中4个deps指的是gcc构建依赖的外部库(gmp,mpfr,mpc,isl)。这些库的版本和下载地址可以通过gcc源码目录下的 contrib/download_prerequisites 脚本文件中获取到。除了 isl 这个库,其他3个库以及gcc的源码均可以在 中科大开源镜像站中找到 。
binutils的版本是无所谓的,可以用最新的版本。
方式二
交叉编译器 + 目标架构根文件系统(方便包管理工具安装构建依赖) + 源码
采用qemu和根文件系统的方式进行快速适配。适用于提供了文件系统镜像的情况下。
- 挂载镜像
- chroot进入镜像安装编译环境
可以参考:4. 交叉编译Qt库(Qt5) — [野火]嵌入式Qt应用开发实战指南—基于LubanCat-RK开发板 文档
sudo apt install qemu-user-static
update-binfmts --display
echo ':aarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-aarch64-static:' | sudo tee /proc/sys/fs/binfmt_misc/register
sudo mount --bind /dev /mnt/uos/dev
sudo mount --bind /dev/pts /mnt/uos/dev/pts
sudo mount --bind /proc /mnt/uos/proc
sudo mount --bind /sys /mnt/uos/sys
sudo chroot /mnt/uos
sudo umount /mnt/uos/sys
sudo umount /mnt/uos/proc
sudo umount /mnt/uos/dev/pts
sudo umount /mnt/uos/dev
# 镜像空间扩容,解决apt安装空间不足问题
resize2fs 0925_RK_test_rootfs.img 6G
# 设置 locales(语言环境)
apt install locales dialog
dpkg-reconfigure locales
方式三
目标架构根文件系统(提供:编译器+依赖) + 源码
缺点:
- 指令转译导致构建速度慢
- 目标根文件系统不一定容易获取到
Qt编译
方式3 的编译配置命令(直接编译)
rm config.cache
../qt-everywhere-src-5.12.8/configure -release -opensource -confirm-license
-qt-xcb
-nomake examples
-nomake tests
-nomake tools
-skip qtwebengine
-skip qtimageformats
-skip qtdeclarative
-skip qtgraphicaleffects
-skip qtlocation
-skip qtsensors
-skip qtwayland
-skip qtwebchannel
-skip qtwebsockets
-skip qtgamepad
-skip qtquickcontrols
-skip qtquickcontrols2
-skip qtscxml
-skip qtcharts
-skip qtdatavis3d
-skip qtvirtualkeyboard
-skip qtserialbus
-skip qtremoteobjects
-skip qttools
-skip qt3d
-skip qtconnectivity
-skip qtpurchasing
-qt-zlib -qt-pcre -qt-libpng -qt-libjpeg
-prefix /opt/qt5
-recheck-all
-v
-skip xxx : 跳过模块,模块名称对应qt源码根目录下的模块一级目录名称
-qt-xxx : 使用qt源码内置的三方库,不依赖系统的
-v : 详细输出,方便查看配置过程的问题
方式1,2 编译配置指令(交叉编译)
Qt交叉编译构建流程:
- 准备好交叉编译器 here
- 一个安装好构建Qt源码所需的所有依赖库的目标系统的根文件系统。使用rsync来同步依赖的增加
rsync -ar buster/lib/ sysroot/lib/
rsync -ar buster/usr/include/ sysroot/usr/include/
rsync -ar buster/usr/lib/ sysroot/usr/lib/
# buster是通过debootstrap生成的根文件系统
# 修复libpthrad, libm, libdl的软链接
cd sysroot/lib/aarch64-linux-gnu
ln -sf libpthread.so.0 libpthread.so
ln -sf libm.so.6 libm.so
ln -sf libdl.so.2 libdl.so
- 添加自定义的mkspec配置文件。qt源码的目录下(qtbase/mkspec或者qtbase/mkspec/device)创建自定义配置
$ cp -r qtbase/mkspecs/linux-aarch64-gnu-g++ uos-arm64-g++
$ cat ../qtbase/mkspecs/uos-arm64-g++/qmake.conf
#
# qmake configuration for building with aarch64-linux-gnu-g++
#
MAKEFILE_GENERATOR = UNIX
CONFIG += incremental
QMAKE_INCREMENTAL_STYLE = sublib
include(../common/linux.conf)
include(../common/gcc-base-unix.conf)
include(../common/g++-unix.conf)
BIN = /home/huangym/mnt/out/usr/bin/aarch64-linux-gnu
# modifications to g++.conf
QMAKE_CC = $$BIN-gcc-83
QMAKE_CXX = $$BIN-g++-83
QMAKE_LINK = $$BIN-g++-83
QMAKE_LINK_SHLIB = $$BIN-g++-83
# modifications to linux.conf
QMAKE_AR = $$BIN-ar-83 cqs
QMAKE_OBJCOPY = $$BIN-objcopy-83
QMAKE_NM = $$BIN-nm-83 -P
QMAKE_STRIP = $$BIN-strip-83
QMAKE_RPATHLINKDIR_POST += $$[QT_SYSROOT]/usr/lib $$[QT_SYSROOT]/usr/lib/aarch64-linux-gnu $$[QT_SYSROOT]/lib/aarch64-linux-gnu
load(qt_config)
- configure
#!/bin/bash
rm config.cache
ROOTFS=/home/huangym/mnt/sysroot
export PKG_CONFIG_SYSROOT_DIR=$ROOTFS
export PKG_CONFIG_LIBDIR=$ROOTFS/usr/lib/pkgconfig:$ROOTFS/usr/share/pkgconfig:$ROOTFS/usr/lib/aarch64-linux-gnu/pkgconfig
DEVICE=
CROSS_CHAIN_PREFIX=/home/huangym/mnt/out/usr/bin
../configure -release -opensource -confirm-license \
-qt-xcb \
-no-opengl \
-nomake examples \
-nomake tests \
-nomake tools \
-skip qtwebengine \
-skip qtimageformats \
-skip qtdeclarative \
-skip qtgraphicaleffects \
-skip qtlocation \
-skip qtsensors \
-skip qtwayland \
-skip qtwebchannel \
-skip qtwebsockets \
-skip qtgamepad \
-skip qtquickcontrols \
-skip qtquickcontrols2 \
-skip qtscxml \
-skip qtcharts \
-skip qtdatavis3d \
-skip qtvirtualkeyboard \
-skip qtserialbus \
-skip qtremoteobjects \
-skip qttools \
-skip qt3d \
-skip qtconnectivity \
-skip qtpurchasing \
-qt-zlib -qt-pcre -qt-libpng -qt-libjpeg \
-prefix /qt5 \
-v \
-sysroot ${ROOTFS} \
-xplatform uos-arm64-g++
-platform : 编译平台架构配置
-xplatform : 交叉编译需要指定的目标平台选项
-device : 特定设备或芯片集。这个更多面向嵌入式设备,不是常规Linux
-device-option :
- make 和 install
交叉编译后qt的安装的目录在 sysroot/prefix
编译问题解决
- 未生成需要的qt模块
- 生成的qt模块但是功能有缺失(如无法显示字体)
# 上述问题一般都是缺少编译依赖导致的,通过分析
config.log / config.summary
来排查依赖缺失导致的编译问题
单独编译qt某个模块
当明确知道缺少哪个模块的时候,重复执行配置等操作很耗时。可以指定单独编译某个模块
make -j4 module-qtserialport
模块名称就是qt源码根目录下的目录名称,前面再加上module即可
cmake配置交叉编译链及sysroot
在CMake中配置交叉编译链及sysroot是嵌入式开发的关键步骤。核心是使用工具链文件,它可以清晰地与你的项目CMakeLists.txt分离,便于管理和复用。
第一步:创建独立的工具链文件
最佳实践是创建一个独立的 .cmake 工具链文件(如 arm-linux-gnueabihf.cmake)。
# arm-linux-gnueabihf.cmake
# 1. 定义目标系统和处理器架构
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 2. 指定交叉编译工具链的路径和前缀
# 假设你的工具链安装在 /opt/toolchains/arm-linux-gnueabihf/
set(TOOLCHAIN_DIR /opt/toolchains/arm-linux-gnueabihf)
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/arm-linux-gnueabihf-g++)
# 3. 指定 sysroot(目标系统的根文件系统)
# 这可以是本地目录或通过网络挂载的路径
set(CMAKE_SYSROOT /opt/sysroots/arm-linux-gnueabihf-rootfs)
# 或者分别设置查找路径
# set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
# 4. 调整 find_* 命令的搜索行为
# 只在 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) # 头文件:只在目标系统查找
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # 包:只在目标系统查找
# 5. (可选)设置编译器标志
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard" CACHE STRING "C Flags")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "C++ Flags")
第二步:使用工具链文件配置和构建项目
通过 -DCMAKE_TOOLCHAIN_FILE 参数指定工具链文件。
# 在项目根目录下执行
mkdir build_arm && cd build_arm
# CMAKE_TOOLCHAIN_FILE 无法通过set(CMAKE_TOOLCHAIN_FILE) 的方式进行指定。
# 官方推荐的方式是通过CMakePreset.json来进行配置,或者通过命令行来指定
cmake -DCMAKE_TOOLCHAIN_FILE=../arm-linux-gnueabihf.cmake ..
make -j$(nproc)
常用技巧和配置
1. 动态检测和设置sysroot
如果工具链自带 sysroot,可以自动检测:
# 获取编译器的默认 sysroot
execute_process(
COMMAND ${CMAKE_C_COMPILER} --print-sysroot
OUTPUT_VARIABLE COMPILER_SYSROOT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(COMPILER_SYSROOT AND NOT CMAKE_SYSROOT)
set(CMAKE_SYSROOT ${COMPILER_SYSROOT})
endif()
2. 指定目标版本
set(CMAKE_SYSTEM_VERSION 4.19.0) # Linux内核版本
set(CMAKE_C_COMPILER_TARGET arm-linux-gnueabihf)
set(CMAKE_CXX_COMPILER_TARGET arm-linux-gnueabihf)
3. 处理第三方依赖(pkg-config)
确保 pkg-config 也使用目标系统的 .pc 文件:
set(ENV{PKG_CONFIG_DIR} "")
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/pkgconfig:${CMAKE_SYSROOT}/usr/share/pkgconfig")
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
实际问题分析
- 按照上述添加cmake工具链的方式之后,遇到如下问题
/usr/bin/aarch64-linux-gnu-g++-8 --sysroot=/mnt/kylinOS -O3 -DNDEBUG -lpthread -lrt -ldl -lm -rdynamic CMakeFiles/bsp_services.dir/__/__/3rdparty/misc/pid_daemon.cpp.o
...
/usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/bin/ld: /mnt/kylinOS/lib/aarch64-linux-gnu/libdrm.so.2: undefined reference to `mesa_memcpy@GLIBC_2.17'
出现这个问题的原因是什么?
该问题说的是链接器在链接sysroot中的libdrm这个库时发现找不到mesa_memcpy这些实现。
通过如下 readelf 命令的简单验证,分别查看sysroot中的libc和libdrm这两个elf文件的符号表,可以看到sysroot中的libc库是有链接时缺失的mesa相关的定义。
而我们查看交叉编译主机的libc库是搜索不到mesa相关的符号定义的。因此我们可以知道apt安装的交叉编译工具链使用的是自己的libc库,而不是sysroot中的libc
链接器找错了库:虽然你指定了 --sysroot ,但链接器可能在链接过程中优先使用了交叉编译器目录下的 libc.so,而那个 libc.so 是标准的,不包含 KylinOS 特有的 mesa_memcpy 符号。
readelf -sV /lib/aarch64-linux-gnu/libc.so.6 |grep mesa
363: 0000000000086380 84 IFUNC GLOBAL DEFAULT 12 mesa_memmove@@GLIBC_2.17
894: 00000000000863d8 84 IFUNC GLOBAL DEFAULT 12 mesa_memset@@GLIBC_2.17
1184: 0000000000086328 84 IFUNC GLOBAL DEFAULT 12 mesa_memcpy@@GLIBC_2.17
2016: 00000000000cc578 240 FUNC GLOBAL DEFAULT 12 futimesat@@GLIBC_2.17
readelf -sV /lib/aarch64-linux-gnu/libdrm.so.2 |grep mesa
36: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mesa_memcpy@GLIBC_2.17 (2)
41: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mesa_memmove@GLIBC_2.17 (2)
51: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mesa_memset@GLIBC_2.17 (2)
#上面的是sysroot中的libc库和libdrm库中查询到的mesa相关信息。下面是我的交叉编译主机的libc库查询的信息。
readelf -sV /usr/aarch64-linux-gnu/lib/libc.so.6 |grep mesa
2013: 00000000000cc0b0 244 FUNC GLOBAL DEFAULT 12 futimesat@@GLIBC_2.17
我们可以通过gcc的 `-print-file-name=libc.so.6` 的选项来查看编译器自带的动态库
交叉编译器通常在自己的安装目录下带有一份标准库副本。可以找到它的 libc.so 并直接运行(或者查看字符串)。
# 找到编译器对应的 libc.so 路径
/usr/bin/aarch64-linux-gnu-g++-8 -print-file-name=libc.so.6
# 使用 strings 查看版本
strings $(/usr/bin/aarch64-linux-gnu-g++-8 -print-file-name=libc.so.6) | grep GLIBC_
3. 为什么即使指定了 --sysroot 有时还是会找主机库?
这是因为交叉编译器(如 g++-8)在构建时,内部硬编码了几个“预置搜索路径”。即使指定了 --sysroot,它有时也会把主机目录作为备选方案。
可以通过以下命令检查你的编译器到底在看哪里:
/usr/bin/aarch64-linux-gnu-g++-8 --sysroot=/mnt/kylinOS -print-search-dirs
如果输出的 libraries 路径中,主机的 /usr/lib/gcc-cross/... 依然排在 /mnt/kylinOS 前面,链接器就会先抓到主机的 memcpy。
因此问题的解决方案就清晰了,我们需要让交叉编译器不要使用主机上的libc版本,而是使用sysroot中的libc。
默认情况下,g++ 会自动在自己的私有目录(主机路径)中寻找 libc 和 libstdc++。要打破这个行为,你可以在 arm64.cmake 中设置:
--sysroot: 告诉编译器根目录。
-isystem: 强制头文件搜索路径。
-L: 强制库搜索路径,且顺序必须在最前。
因此最终修改如下:
# 定义基础路径
set(CMAKE_SYSROOT /mnt/kylinOS)
# 强制 C/C++ 编译器使用 sysroot
set(CMAKE_C_FLAGS "--sysroot=${CMAKE_SYSROOT}" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "--sysroot=${CMAKE_SYSROOT}" CACHE STRING "" FORCE)
# 关键:告诉链接器只从 sysroot 查找标准库
# -nodefaultlibs 会去掉主机库,但你需要手动补齐依赖(较复杂)
# 更稳妥的方法是使用 -L 覆盖搜索优先级
set(LINK_FLAGS "-L${CMAKE_SYSROOT}/lib/aarch64-linux-gnu \
-L${CMAKE_SYSROOT}/usr/lib/aarch64-linux-gnu \
-Wl,-rpath-link=${CMAKE_SYSROOT}/lib/aarch64-linux-gnu \
-Wl,-rpath-link=${CMAKE_SYSROOT}/usr/lib/aarch64-linux-gnu")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINK_FLAGS}" CACHE STRING "" FORCE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINK_FLAGS}" CACHE STRING "" FORCE)
参考
使用debootstrap构建制作aarch64/arm64 Debian rootfs文件系统-阿里云开发者社区
Building Qt 5 from Git - Qt Wiki
Qt Configure Options | Qt 5.15
Exporting kernel headers for use by userspace — The Linux Kernel documentation