强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

C/C++ Linux 开发教程(GCC + CMake) / 跨平台与交叉编译

跨平台与交叉编译

交叉编译是在一个平台(host)上编译出另一个平台(target)可执行代码的过程。这在嵌入式开发、ARM 服务器、WebAssembly 等场景中至关重要。

交叉编译概念

基本术语

术语说明示例
Host运行编译器的平台x86_64 Linux
Target生成代码运行的平台ARM64 Linux
Build编译编译器本身的平台(一般与 Host 相同)
Sysroot目标系统的根文件系统/opt/sysroot
Toolchain交叉编译工具链aarch64-linux-gnu-gcc

为什么需要交叉编译

  1. 目标平台资源有限:嵌入式设备无法运行编译器
  2. 编译速度:在高性能 x86 机器上编译比在 ARM 开发板上快得多
  3. CI/CD:一套构建机器生成多平台产物
  4. WebAssembly:在浏览器中运行 C/C++ 代码

工具链文件(CMAKE_TOOLCHAIN_FILE)

CMake 工具链文件结构

# toolchain-aarch64.cmake
# AArch64 (ARM64) 交叉编译工具链文件

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

# 指定交叉编译器
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)

# 指定 sysroot(目标系统的根文件系统)
set(CMAKE_SYSROOT /opt/aarch64-sysroot)

# 查找库和头文件的路径
set(CMAKE_FIND_ROOT_PATH /opt/aarch64-sysroot)

# 查找策略
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)    # 程序在 host 上运行
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)     # 库只在 sysroot 中找
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)     # 头文件只在 sysroot 中找
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)     # CMake 包只在 sysroot 中找

# 编译标志
set(CMAKE_C_FLAGS_INIT "-march=armv8-a")
set(CMAKE_CXX_FLAGS_INIT "-march=armv8-a")

使用工具链文件

cmake -B build \
    -DCMAKE_TOOLCHAIN_FILE=toolchain-aarch64.cmake \
    -DCMAKE_BUILD_TYPE=Release

cmake --build build --parallel

# 验证产物
file build/app
# build/app: ELF 64-bit LSB executable, ARM aarch64...

常用交叉编译工具链

ARM 工具链安装

# Debian/Ubuntu
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

# Arch Linux
sudo pacman -S aarch64-linux-gnu-gcc arm-linux-gnueabihf-gcc

# 验证安装
aarch64-linux-gnu-gcc --version

各平台工具链文件

# toolchain-arm32.cmake — ARM 32 位
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_C_FLAGS_INIT "-march=armv7-a -mfpu=neon")
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=neon")
# toolchain-riscv64.cmake — RISC-V 64 位
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR riscv64)
set(CMAKE_C_COMPILER riscv64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER riscv64-linux-gnu-g++)
set(CMAKE_C_FLAGS_INIT "-march=rv64gc")
set(CMAKE_CXX_FLAGS_INIT "-march=rv64gc")
# toolchain-mingw64.cmake — Windows 交叉编译
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Clang 交叉编译

# toolchain-clang-aarch64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)

# Clang 使用 --target 指定目标
set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)

set(CMAKE_SYSROOT /opt/aarch64-sysroot)
set(CMAKE_C_FLAGS_INIT "--gcc-toolchain=/usr")
set(CMAKE_CXX_FLAGS_INIT "--gcc-toolchain=/usr")

Sysroot 配置

创建 Sysroot

# 方法 1: 从目标设备复制
# 在树莓派上运行
rsync -avz --exclude={'/dev/*','/proc/*','/sys/*','/tmp/*','/run/*','/mnt/*','/media/*','/lost+found'} \
    pi@raspberrypi:/ /opt/rpi-sysroot/

# 方法 2: 使用 debootstrap 创建
sudo debootstrap --arch=arm64 --foreign jammy /opt/aarch64-sysroot http://ports.ubuntu.com

# 方法 3: 使用 Docker 提取
docker create --name sysroot --platform linux/arm64 ubuntu:22.04
docker cp sysroot:/ /opt/aarch64-sysroot
docker rm sysroot

修复 Sysroot 符号链接

# Sysroot 中的绝对符号链接会导致问题
# 需要转换为相对链接

#!/bin/bash
# fix_symlinks.sh
SYSROOT="/opt/aarch64-sysroot"

find "$SYSROOT" -type l | while read link; do
    target=$(readlink -f "$link")
    # 检查是否指向 sysroot 内部
    if [[ "$target" == "$SYSROOT"* ]]; then
        # 计算相对路径
        rel=$(realpath --relative-to="$(dirname $link)" "$target")
        ln -sf "$rel" "$link"
    fi
done

PKG_CONFIG 与 Sysroot

# 设置交叉编译的 pkg-config
export PKG_CONFIG_PATH=/opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/opt/aarch64-sysroot

# 或者创建包装脚本
cat > aarch64-pkg-config << 'EOF'
#!/bin/bash
export PKG_CONFIG_PATH=/opt/aarch64-sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/opt/aarch64-sysroot
exec pkg-config "$@"
EOF
chmod +x aarch64-pkg-config

交叉编译第三方库

使用 CMake 编译第三方库

# 编译 zlib
wget https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz
tar xzf zlib-1.3.1.tar.gz
cd zlib-1.3.1

# 使用工具链文件
cmake -B build \
    -DCMAKE_TOOLCHAIN_FILE=../toolchain-aarch64.cmake \
    -DCMAKE_INSTALL_PREFIX=/opt/aarch64-sysroot/usr/local
cmake --build build
cmake --install build

使用 Conan 交叉编译

# conanfile.py
[settings]
os=Linux
os_build=Linux
arch=armv8
arch_build=x86_64
compiler=gcc
compiler.version=12
compiler.libcxx=libstdc++11
build_type=Release

[buildenv]
CC=aarch64-linux-gnu-gcc
CXX=aarch64-linux-gnu-g++
conan install . -pr:a profiles/aarch64-linux --output-folder=build
cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake

Docker 交叉编译环境

Dockerfile 示例

# Dockerfile.cross-aarch64
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    ninja-build \
    gcc-aarch64-linux-gnu \
    g++-aarch64-linux-gnu \
    qemu-user-static \
    && rm -rf /var/lib/apt/lists/*

# 可选: 安装 sysroot
COPY sysroot/ /opt/aarch64-sysroot/

WORKDIR /app
COPY . .

# 使用工具链文件
RUN cmake -B build -G Ninja \
    -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-aarch64.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    && cmake --build build

# 使用 QEMU 测试(可选)
RUN qemu-aarch64-static -L /opt/aarch64-sysroot ./build/app --version

多平台 Docker 构建

# docker-compose.yml
version: '3.8'

services:
  build-amd64:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        PLATFORM: linux/amd64
    volumes:
      - ./output/amd64:/output

  build-arm64:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        PLATFORM: linux/arm64
    volumes:
      - ./output/arm64:/output

Docker Buildx 多平台

# 创建 builder
docker buildx create --name cross-builder --use

# 构建多平台镜像
docker buildx build \
    --platform linux/amd64,linux/arm64,linux/arm/v7 \
    -t myapp:latest \
    --push .

多平台构建脚本

构建矩阵脚本

#!/bin/bash
# build_all.sh — 构建多平台版本

set -e

PLATFORMS=(
    "x86_64-linux:cmake/toolchain-x86_64.cmake"
    "aarch64-linux:cmake/toolchain-aarch64.cmake"
    "arm-linux:cmake/toolchain-arm32.cmake"
    "mingw64:cmake/toolchain-mingw64.cmake"
)

for platform_info in "${PLATFORMS[@]}"; do
    IFS=':' read -r platform toolchain <<< "$platform_info"
    
    echo "=== Building for ${platform} ==="
    
    build_dir="build-${platform}"
    output_dir="output/${platform}"
    
    cmake -B "${build_dir}" \
        -DCMAKE_TOOLCHAIN_FILE="${toolchain}" \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_INSTALL_PREFIX="${output_dir}"
    
    cmake --build "${build_dir}" --parallel $(nproc)
    cmake --install "${build_dir}"
    
    echo "=== ${platform} build complete ==="
done

# 汇总产物
echo ""
echo "Build artifacts:"
for platform_info in "${PLATFORMS[@]}"; do
    IFS=':' read -r platform toolchain <<< "$platform_info"
    echo "  ${platform}:"
    ls -la output/${platform}/bin/ 2>/dev/null || echo "    (no binaries)"
done

GitHub Actions 多平台构建

# .github/workflows/multi-platform.yml
name: Multi-Platform Build

on: [push, pull_request]

jobs:
  build:
    strategy:
      matrix:
        include:
          - platform: x86_64-linux
            toolchain: cmake/toolchain-x86_64.cmake
            runner: ubuntu-latest
          - platform: aarch64-linux
            toolchain: cmake/toolchain-aarch64.cmake
            runner: ubuntu-latest
          - platform: mingw64
            toolchain: cmake/toolchain-mingw64.cmake
            runner: ubuntu-latest
    
    runs-on: ${{ matrix.runner }}
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install cross-compiler
        run: |
          sudo apt-get update
          if [ "${{ matrix.platform }}" = "aarch64-linux" ]; then
            sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
          elif [ "${{ matrix.platform }}" = "mingw64" ]; then
            sudo apt-get install -y gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64
          fi
      
      - name: Build
        run: |
          cmake -B build \
            -DCMAKE_TOOLCHAIN_FILE=${{ matrix.toolchain }} \
            -DCMAKE_BUILD_TYPE=Release
          cmake --build build --parallel
      
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.platform }}
          path: build/app*

WebAssembly 编译(Emscripten)

Emscripten 安装

# 安装 Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

# 验证
emcc --version

CMake 工具链文件

# toolchain-emscripten.cmake
set(CMAKE_SYSTEM_NAME Emscripten)
set(CMAKE_SYSTEM_PROCESSOR x86)

# Emscripten 工具链路径
set(EMSDK_PATH "$ENV{EMSDK}")
if(NOT EMSDK_PATH)
    message(FATAL_ERROR "EMSDK environment variable not set")
endif()

set(CMAKE_C_COMPILER emcc)
set(CMAKE_CXX_COMPILER em++)
set(CMAKE_AR emar)
set(CMAKE_RANLIB emranlib)

# Emscripten 特定设置
set(CMAKE_C_FLAGS_INIT "-s WASM=1")
set(CMAKE_CXX_FLAGS_INIT "-s WASM=1")

# 禁用在 sysroot 中查找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)

编译 WebAssembly

# CMakeLists.txt — 适配 Emscripten
cmake_minimum_required(VERSION 3.10)
project(MyWasmApp C)

add_executable(app main.c)

if(EMSCRIPTEN)
    # 设置 Emscripten 链接选项
    set_target_properties(app PROPERTIES
        SUFFIX ".html"          # 生成 HTML 包装器
        LINK_FLAGS "-s WASM=1 -s EXPORTED_FUNCTIONS='[_main,_add]' -s EXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\"]'"
    )
endif()
# 使用 Emscripten 工具链
source /path/to/emsdk/emsdk_env.sh
cmake -B build \
    -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
    -DCMAKE_BUILD_TYPE=Release
cmake --build build

# 生成的文件:
# build/app.html — HTML 包装器
# build/app.js   — JavaScript 加载代码
# build/app.wasm — WebAssembly 二进制

在 HTML 中使用

<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Demo</title>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        var Module = {
            onRuntimeInitialized: function() {
                // 调用导出的 C 函数
                var result = Module._add(3, 4);
                console.log("3 + 4 = " + result);
                
                // 使用 ccall 包装器
                var result2 = Module.ccall('add', 'number', 
                    ['number', 'number'], [5, 6]);
                console.log("5 + 6 = " + result2);
            }
        };
    </script>
    <script src="app.js"></script>
</body>
</html>

交叉编译调试策略

QEMU 用户模式模拟

# 安装 QEMU 用户模式
sudo apt install qemu-user-static

# 直接运行 ARM 二进制(需要注册 binfmt)
sudo apt install binfmt-support
# 注册后可以直接运行:
./build-arm/app   # 自动通过 QEMU 运行

# 指定 sysroot 运行
qemu-aarch64-static -L /opt/aarch64-sysroot ./build-arm/app

# 调试
qemu-aarch64-static -L /opt/aarch64-sysroot -g 1234 ./build-arm/app &
gdb-multiarch -ex "target remote :1234" -ex "set architecture aarch64" ./build-arm/app

远程调试

# 在目标设备上运行 gdbserver
gdbserver :1234 ./app

# 在开发机上连接
gdb-multiarch ./build-arm/app
(gdb) target remote 192.168.1.100:1234
(gdb) break main
(gdb) continue

调试工具对比

方案优点缺点
QEMU 用户模式无需目标设备性能差异
gdbserver 远程调试真实环境需要目标设备
Docker + QEMU隔离环境配置复杂
硬件调试器(JTAG)最准确需要专用硬件

实际案例

案例 1:树莓派交叉编译

# toolchain-rpi.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR armv7l)

# 树莓派专用工具链(来自官方 SDK)
set(CMAKE_C_COMPILER /opt/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /opt/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++)

# 树莓派 sysroot(从设备复制)
set(CMAKE_SYSROOT /opt/rpi-sysroot)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

# 树莓派特定标志
set(CMAKE_C_FLAGS_INIT "-march=armv7-a -mfpu=neon -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=neon -mfloat-abi=hard")

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# 构建
cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain-rpi.cmake
cmake --build build --parallel

# 部署到树莓派
scp build/app pi@raspberrypi:/home/pi/

# 在树莓派上测试
ssh pi@raspberrypi './app'

案例 2:嵌入式 Linux(Yocto SDK)

# 安装 Yocto SDK
source /opt/poky/3.4/environment-setup-cortexa72-poky-linux

# SDK 会设置以下环境变量:
# CC, CXX, CFLAGS, LDFLAGS, PKG_CONFIG_SYSROOT_DIR 等

# 直接使用环境变量编译
cmake -B build \
    -DCMAKE_C_COMPILER="$CC" \
    -DCMAKE_CXX_COMPILER="$CXX" \
    -DCMAKE_C_FLAGS="$CFLAGS" \
    -DCMAKE_CXX_FLAGS="$CXXFLAGS" \
    -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS"

案例 3:Android NDK

# toolchain-android.cmake
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

# Android NDK 路径
set(ANDROID_NDK $ENV{ANDROID_NDK_HOME})
set(CMAKE_ANDROID_NDK ${ANDROID_NDK})

# API 级别
set(CMAKE_ANDROID_API 21)

# ABI
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a)

# 工具链
set(CMAKE_C_COMPILER ${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang)
set(CMAKE_CXX_COMPILER ${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++)

⚠️ 注意点

  1. 不要用 CMAKE_SYSTEM_PROCESSOR 硬编码:使用变量或工具链文件
  2. sysroot 绝对符号链接:必须修复,否则链接会失败
  3. host 与 target 工具区分:构建工具(如代码生成器)用 host 的,编译结果用 target 的
  4. 测试:交叉编译后必须在目标平台或模拟器上测试
  5. 字节序:大端/小端不匹配会导致隐蔽 Bug
  6. 库路径-LCMAKE_FIND_ROOT_PATH 要正确设置

💡 提示

  1. file 命令验证:用 file 检查产物的架构
  2. readelf 检查:用 readelf -h 查看 ELF 头信息
  3. CMake 打印变量cmake -B build -LAH 查看所有变量
  4. Clang 更适合交叉编译:只需 --target 即可切换目标
  5. ccache 加速:设置 CMAKE_C_COMPILER_LAUNCHER=ccache
  6. CMake 预设:用 CMakePresets.json 管理多平台配置

扩展阅读