强曰为道

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

第 10 章:交叉编译工具链

第 10 章:交叉编译工具链

构建 musl 交叉编译工具链,支持 x86_64、aarch64 等架构,以及与构建系统的集成。

10.1 交叉编译概述

交叉编译是在一种架构(host)上编译出可在另一种架构(target)上运行的程序。

交叉编译流程:
┌──────────────┐                                    ┌──────────────┐
│  Host 机器    │                                    │  Target 机器  │
│  (x86_64)    │                                    │  (aarch64)   │
│              │    交叉编译器                       │              │
│  gcc 编译 ──┼──▶ aarch64-linux-musl-gcc ──────▶   │  运行二进制   │
│  源代码.c    │    生成 ARM64 二进制                │  程序         │
└──────────────┘                                    └──────────────┘

为什么要交叉编译?
1. 嵌入式设备(如路由器 ARM/MIPS)编译能力弱
2. 为多种架构构建 Docker 镜像(多平台镜像)
3. 为特定硬件优化的固件
4. CI/CD 流水线加速(在高性能服务器上为低性能设备编译)

三元组命名规范

三元组示例架构操作系统libc
x86_64-linux-gnux86_64Linuxglibc
x86_64-linux-muslx86_64Linuxmusl
aarch64-linux-gnuAArch64/ARM64Linuxglibc
aarch64-linux-muslAArch64/ARM64Linuxmusl
arm-linux-gnueabihfARM (hard-float)Linuxglibc
arm-linux-musleabihfARM (hard-float)Linuxmusl
mips-linux-gnuMIPSLinuxglibc
mipsel-linux-muslMIPS (little-endian)Linuxmusl
riscv64-linux-muslRISC-V 64Linuxmusl

10.2 musl-cross-make

musl-cross-make 是由 musl 作者 Rich Felker 维护的交叉编译工具链构建系统。

安装

# 克隆仓库
$ git clone https://github.com/richfelker/musl-cross-make.git
$ cd musl-cross-make

# 查看默认配置
$ cat config.mak.dist

# 配置目标架构
$ cat > config.mak << 'EOF'
# 目标架构
TARGET = aarch64-linux-musl

# 可选:指定 binutils、gcc、musl 版本
BINUTILS_VER = 2.42
GCC_VER = 13.2.0
MUSL_VER = 1.2.5
LINUX_VER = 6.6

# 安装路径
OUTPUT = /opt/cross

# 可选:启用 C++
GCC_CONFIG += --enable-languages=c,c++

# 可选:启用 LTO
GCC_CONFIG += --enable-lto

# 并行编译
MAKEFLAGS = -j$(nproc)
EOF

# 构建工具链(可能需要 30-60 分钟)
$ make -j$(nproc)

# 安装
$ make install

# 验证
$ ls /opt/cross/bin/
# aarch64-linux-musl-addr2line
# aarch64-linux-musl-ar
# aarch64-linux-musl-as
# aarch64-linux-musl-c++
# aarch64-linux-musl-cc
# aarch64-linux-musl-g++
# aarch64-linux-musl-gcc
# aarch64-linux-musl-ld
# aarch64-linux-musl-nm
# aarch64-linux-musl-objdump
# aarch64-linux-musl-ranlib
# aarch64-linux-musl-strip

使用交叉编译工具链

# 添加到 PATH
$ export PATH="/opt/cross/bin:$PATH"

# 编译测试程序
$ aarch64-linux-musl-gcc -static -O2 -o hello hello.c

# 验证
$ file hello
# hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV),
#        statically linked, stripped

$ aarch64-linux-musl-readelf -h hello | grep Machine
# Machine: AArch64

# 在 ARM64 设备或 QEMU 上运行
$ qemu-aarch64-static ./hello
# Hello, World!

多架构同时构建

# 为多个架构构建工具链
for TARGET in x86_64-linux-musl aarch64-linux-musl arm-linux-musleabihf riscv64-linux-musl; do
    make TARGET=$TARGET clean
    make TARGET=$TARGET -j$(nproc)
    make TARGET=$TARGET install OUTPUT=/opt/cross
done

10.3 使用发行版工具链

Alpine Linux

# Alpine 提供了预构建的交叉编译包
$ apk add gcc-aarch64-none-elf    # bare-metal ARM64
$ apk add gcc-arm-none-eabi       # bare-metal ARM

# 或者在 Alpine 容器中构建
$ docker run --rm -v $(pwd):/src alpine:3.20 sh -c "
    apk add --no-cache gcc musl-dev make &&
    cd /src &&
    gcc -static -O2 -o hello hello.c
"

Debian/Ubuntu

# 使用 Debian 的交叉编译包
$ sudo apt install gcc-aarch64-linux-gnu      # ARM64 glibc
$ sudo apt install gcc-arm-linux-gnueabihf    # ARM glibc

# 使用 musl 交叉编译包(如果可用)
$ sudo apt install musl-tools                  # 提供 x86_64 的 musl-gcc

# 手动构建 musl 交叉编译器(Ubuntu)
$ sudo apt install gcc g++ make
$ wget https://musl.libc.org/releases/musl-1.2.5.tar.gz
$ tar xzf musl-1.2.5.tar.gz
$ cd musl-1.2.5
$ ./configure --prefix=/usr/local/musl --target=aarch64-linux-musl
$ make -j$(nproc) && sudo make install

10.4 Rust 交叉编译

# Rust 内置 musl 交叉编译支持

# 安装 target
$ rustup target add x86_64-unknown-linux-musl
$ rustup target add aarch64-unknown-linux-musl
$ rustup target add arm-unknown-linux-musleabihf
$ rustup target add riscv64gc-unknown-linux-musl

# 静态编译
$ cargo build --release --target x86_64-unknown-linux-musl
$ cargo build --release --target aarch64-unknown-linux-musl

# 验证
$ file target/x86_64-unknown-linux-musl/release/myapp
# myapp: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
#        statically linked, stripped

$ file target/aarch64-unknown-linux-musl/release/myapp
# myapp: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV),
#        statically linked, stripped
# .cargo/config.toml — 配置交叉编译
[build]
# 默认 target(可选)
# target = "x86_64-unknown-linux-musl"

[target.x86_64-unknown-linux-musl]
linker = "musl-gcc"

[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"

[target.arm-unknown-linux-musleabihf]
linker = "arm-linux-musleabihf-gcc"

10.5 Go 交叉编译

# Go 原生支持交叉编译,非常简单

# 设置目标架构
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o myapp-amd64 .
$ GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o myapp-arm64 .
$ GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build -o myapp-armv7 .
$ GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 go build -o myapp-riscv64 .

# 验证
$ file myapp-amd64 myapp-arm64 myapp-armv7
# myapp-amd64:  ELF 64-bit LSB executable, x86-64, statically linked
# myapp-arm64:  ELF 64-bit LSB executable, ARM aarch64, statically linked
# myapp-armv7:  ELF 32-bit LSB executable, ARM, EABI5, statically linked

# Go 使用 CGO 时需要指定交叉编译器
$ CGO_ENABLED=1 \
  CC=aarch64-linux-musl-gcc \
  GOOS=linux GOARCH=arm64 \
  go build -o myapp-arm64 .

10.6 Docker 多架构构建

使用 buildx 构建多架构镜像

# Dockerfile.multiarch
FROM --platform=$BUILDPLATFORM alpine:3.20 AS builder
ARG TARGETPLATFORM
ARG TARGETOS
ARG TARGETARCH

RUN apk add --no-cache gcc musl-dev go

WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# Go 交叉编译
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
    go build -ldflags="-s -w" -o /myapp .

FROM --platform=$TARGETPLATFORM alpine:3.20
COPY --from=builder /myapp /usr/local/bin/myapp
ENTRYPOINT ["myapp"]
# 创建多平台构建器
$ docker buildx create --name multiarch --use
$ docker buildx inspect --bootstrap

# 构建并推送多架构镜像
$ docker buildx build \
    --platform linux/amd64,linux/arm64,linux/arm/v7 \
    -t myregistry/myapp:latest \
    --push .

# 验证
$ docker buildx imagetools inspect myregistry/myapp:latest
# Manifests:
#   Name: myregistry/myapp:latest@sha256:...
#   Platform: linux/amd64
#   Name: myregistry/myapp:latest@sha256:...
#   Platform: linux/arm64
#   Name: myregistry/myapp:latest@sha256:...
#   Platform: linux/arm/v7

使用 QEMU 模拟

# 安装 QEMU 用户态模拟器
$ docker run --privileged --rm tonistiigi/binfmt --install all

# 现在可以直接在 x86_64 上运行 ARM 容器
$ docker run --rm --platform linux/arm64 alpine:3.20 uname -m
# aarch64

# 可以直接在 Dockerfile 中使用目标架构的基础镜像
$ docker buildx build --platform linux/arm64 -t myapp:arm64 .

10.7 构建系统集成

CMake 交叉编译

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

set(CMAKE_C_COMPILER aarch64-linux-musl-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-musl-g++)
set(CMAKE_AR aarch64-linux-musl-ar)
set(CMAKE_RANLIB aarch64-linux-musl-ranlib)

# 搜索路径配置
set(CMAKE_FIND_ROOT_PATH /opt/cross/aarch64-linux-musl)
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_EXE_LINKER_FLAGS "-static")
# 使用 CMake 交叉编译
$ cmake -DCMAKE_TOOLCHAIN_FILE=toolchain-aarch64-musl.cmake \
        -B build-aarch64 .
$ cmake --build build-aarch64

$ file build-aarch64/myapp
# myapp: ELF 64-bit LSB executable, ARM aarch64, statically linked

Meson 交叉编译

# cross-file-aarch64-musl.ini
[binaries]
c = 'aarch64-linux-musl-gcc'
cpp = 'aarch64-linux-musl-g++'
ar = 'aarch64-linux-musl-ar'
strip = 'aarch64-linux-musl-strip'
pkgconfig = 'aarch64-linux-musl-pkg-config'

[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'

[properties]
c_link_args = ['-static']
cpp_link_args = ['-static']
# 使用 Meson 交叉编译
$ meson setup build-aarch64 --cross-file cross-file-aarch64-musl.ini
$ meson compile -C build-aarch64

Makefile 交叉编译

# Makefile 交叉编译支持
CC ?= gcc
AR ?= ar
STRIP ?= strip

CFLAGS ?= -O2 -Wall -Wextra
LDFLAGS ?= -static

# 源文件
SRCS = main.c utils.c network.c
OBJS = $(SRCS:.c=.o)
TARGET = myapp

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

clean:
	rm -f $(OBJS) $(TARGET)

install: $(TARGET)
	install -m 755 $(TARGET) $(DESTDIR)/usr/bin/

# 使用方法:
# make CC=aarch64-linux-musl-gcc AR=aarch64-linux-musl-ar STRIP=aarch64-linux-musl-strip

10.8 包管理器中的交叉编译库

Alpine 多架构支持

# Alpine 支持多架构安装包
$ docker run --rm --platform linux/arm64 alpine:3.20 apk add --no-cache openssl-dev

# 或使用多架构镜像
$ cat > /etc/apk/repositories << 'EOF'
https://dl-cdn.alpinelinux.org/alpine/v3.20/main
https://dl-cdn.alpinelinux.org/alpine/v3.20/community
EOF

静态库交叉编译

# 为目标架构编译静态库
$ cat > build_openssl_aarch64.sh << 'SCRIPT'
#!/bin/bash
set -e

# 下载 OpenSSL 源码
wget https://www.openssl.org/source/openssl-3.2.1.tar.gz
tar xzf openssl-3.2.1.tar.gz
cd openssl-3.2.1

# 交叉编译
CC=aarch64-linux-musl-gcc \
./Configure linux-aarch64 \
    --prefix=/opt/cross/aarch64-linux-musl \
    no-shared \
    no-tests

make -j$(nproc)
make install_sw

# 验证
file /opt/cross/aarch64-linux-musl/lib/libssl.a
# libssl.a: current ar archive
SCRIPT

10.9 测试交叉编译结果

QEMU 用户态测试

# 安装 QEMU
$ sudo apt install qemu-user-static

# 运行交叉编译的二进制
$ qemu-aarch64-static ./hello-arm64
# Hello, World!

# 在 Docker 中测试
$ docker run --rm -v $(pwd):/work alpine:3.20 /work/hello-arm64
# 如果有 QEMU 支持,可以直接运行

静态分析

# 验证目标架构
$ aarch64-linux-musl-readelf -h hello-arm64
# Class:    ELF64
# Data:     2's complement, little endian
# Machine:  AArch64

# 验证静态链接
$ aarch64-linux-musl-readelf -d hello-arm64
# (no dynamic section)

# 检查符号
$ aarch64-linux-musl-nm -C hello-arm64 | grep main
# 0000000000401234 T main

# 检查大小
$ aarch64-linux-musl-size hello-arm64
#    text    data     bss     dec     hex filename
#   52345    1234    5678   59257    e779 hello-arm64

GDB 远程调试

# 在目标设备(或 QEMU)上启动 gdbserver
$ qemu-aarch64-static -g 1234 ./hello-arm64 &

# 使用交叉 GDB 连接
$ aarch64-linux-musl-gdb ./hello-arm64
(gdb) target remote localhost:1234
(gdb) break main
(gdb) continue
(gdb) print "Hello from remote debug"

10.10 完整交叉编译示例

示例:为 ARM64 编译静态链接的 HTTP 服务

/* http_server.c — 最小 HTTP 服务器 */
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port = htons(8080),
    };
    bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
    listen(server_fd, 128);

    printf("Server listening on :8080\n");

    while (1) {
        int client = accept(server_fd, NULL, NULL);
        if (client < 0) continue;

        char response[] = "HTTP/1.1 200 OK\r\n"
                          "Content-Type: text/plain\r\n"
                          "Content-Length: 13\r\n"
                          "\r\n"
                          "Hello, World!";
        write(client, response, sizeof(response) - 1);
        close(client);
    }
    return 0;
}
# 为多个架构编译
$ for arch in x86_64 aarch64 arm riscv64; do
    CC=${arch}-linux-musl-gcc
    if command -v "$CC" >/dev/null; then
        $CC -static -O2 -o "server-${arch}" http_server.c
        echo "Built: server-${arch} ($(ls -lh "server-${arch}" | awk '{print $5}'))"
        file "server-${arch}"
    fi
done

# 输出:
# Built: server-x86_64 (186K)
# Built: server-aarch64 (165K)
# Built: server-arm (148K)
# Built: server-riscv64 (157K)

10.11 本章小结

工具/方法适用场景优势
musl-cross-make自定义工具链最灵活,可选版本
Alpine 多架构Docker 容器开箱即用
Rust rustup targetRust 项目最简单
Go 交叉编译Go 项目无需额外工具
Docker buildx多架构镜像CI/CD 集成
QEMU 用户态测试验证无需真实硬件

扩展阅读