Contents

交叉编译

方式一

交叉编译器 + 源码

优点:

  • 精准的版本控制
  • 适配范围广,无需重复工作

缺点:

  • 复杂,需要解决各种编译问题
  • 依赖过多的情况下工作量大

对于像qt这样构建依赖很多的库不是很方便。对于这些构建依赖,最好的办法是能有一个目标架构的根文件系统

GCC交叉编译器构建流程 ^68b560

构建的整体流程如下:

  1. 构建合适版本的glibc库(glibc+kernel_header)
  2. 构建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头文件。导出内核头文件的指令是

Installing GCC - GNU Project

#!/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和根文件系统的方式进行快速适配。适用于提供了文件系统镜像的情况下。

  1. 挂载镜像
  2. 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交叉编译构建流程:

  1. 准备好交叉编译器 here
  2. 一个安装好构建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
  1. 添加自定义的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)
  1. 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 :

  1. 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})

实际问题分析

  1. 按照上述添加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