Skip to content

Rust 交叉编译基础

引言

交叉编译是指在一个平台(主机)上编译代码,使其在另一个平台(目标)上运行。对于 LeBot 这样的嵌入式机器人系统,我们经常需要在 x86_64 PC 上开发代码,然后将其编译到 ARM 平台(如 NanoPI)上运行。本章将详细介绍 Rust 交叉编译的原理、工具和实践。

交叉编译基础概念

为什么需要交叉编译

开发机(Host)          目标机(Target)
┌─────────────────┐    ┌─────────────────┐
│  x86_64 Linux   │    │   ARM NanoPI    │
│  强大的处理能力  │    │  嵌入式设备      │
│  便于开发调试    │    │  资源受限        │
└─────────────────┘    └─────────────────┘
         │                     ▲
         │  交叉编译            │  运行编译好的
         │  (Toolchain)        │  二进制文件
         └─────────────────────┘

三个关键概念

  1. 主机(Host):运行编译器的计算机

    • 例如:x86_64 Linux PC
  2. 目标(Target):编译的二进制文件运行的平台

    • 例如:ARM Cortex-A53(NanoPI)
  3. 工具链(Toolchain):包含编译器、链接器、库等工具的集合

    • 例如:arm-linux-gnueabihf

Rust 的三元组目标表示

Rust 使用三元组(triple)来标识编译目标平台。

三元组格式

arch-vendor-os-env

例如:
- x86_64-unknown-linux-gnu      (x86_64 Linux)
- armv7-unknown-linux-gnueabihf (32位 ARM Linux)
- aarch64-unknown-linux-gnu     (64位 ARM Linux)
- thumbv7em-none-eabihf         (ARM Cortex-M 裸机)

三元组的各部分

架构(Architecture):
  - x86_64:Intel/AMD 64位
  - armv7:32位 ARM v7
  - aarch64:64位 ARM v8
  - thumbv7em:Cortex-M 处理器

供应商(Vendor):
  - unknown:未知供应商
  - apple:苹果
  - pc:个人计算机

操作系统(OS):
  - linux:Linux
  - windows:Windows
  - none:裸机(无操作系统)
  - darwin:macOS

环境(Environment):
  - gnu:GNU 库和工具
  - musl:musl 库(轻量级)
  - eabihf:ARM EABI 硬浮点

列出支持的目标

bash
# 列出所有 Rust 支持的目标
rustc --print sysroot

# 查看已安装的目标
rustup target list

# 安装特定目标
rustup target add armv7-unknown-linux-gnueabihf

# 卸载目标
rustup target remove armv7-unknown-linux-gnueabihf

为 NanoPI 编译(ARMv7 32位)

环境设置

NanoPI 使用 ARM Cortex-A53,但我们可能需要编译为 32 位 ARMv7 版本。

bash
# 1. 安装 Rust 目标
rustup target add armv7-unknown-linux-gnueabihf

# 2. 安装交叉编译工具
# 在 Ubuntu/Debian 上
sudo apt-get update
sudo apt-get install -y \
    gcc-arm-linux-gnueabihf \
    g++-arm-linux-gnueabihf \
    pkg-config \
    libssl-dev

# 3. 配置 Cargo
mkdir -p ~/.cargo
cat >> ~/.cargo/config.toml << 'EOF'
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
ar = "arm-linux-gnueabihf-ar"
EOF

基础交叉编译

bash
# 创建 Rust 项目
cargo new lebot_control
cd lebot_control

# 为 ARMv7 编译
cargo build --target armv7-unknown-linux-gnueabihf

# 发布版本(优化后)
cargo build --release --target armv7-unknown-linux-gnueabihf

# 输出文件位置
# target/armv7-unknown-linux-gnueabihf/debug/lebot_control
# target/armv7-unknown-linux-gnueabihf/release/lebot_control

验证交叉编译的二进制文件

bash
# 检查文件类型和架构
file target/armv7-unknown-linux-gnueabihf/release/lebot_control

# 输出应该类似于:
# ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV),
# dynamically linked, not stripped

# 查看二进制文件的详细信息
readelf -h target/armv7-unknown-linux-gnueabihf/release/lebot_control

# 显示二进制文件中的符号
nm target/armv7-unknown-linux-gnueabihf/release/lebot_control

使用 cargo-cross 简化交叉编译

对于更复杂的项目,cargo-cross 提供了更好的支持。

安装 cargo-cross

bash
cargo install cross

使用 cargo-cross

bash
# 在项目目录中
cd lebot_control

# 用 cross 替代 cargo
cross build --target armv7-unknown-linux-gnueabihf

# 发布版本
cross build --release --target armv7-unknown-linux-gnueabihf

# cross 会自动下载并配置工具链

Cross.toml 配置

对于特殊需求,可以创建 Cross.toml 配置文件:

toml
[target.armv7-unknown-linux-gnueabihf]
pre-build = [
    "apt-get update",
    "apt-get install -y libssl-dev"
]

处理依赖项

原生依赖项

对于依赖 C/C++ 库的 Rust crates,需要额外配置。

rust
// build.rs - 构建脚本
use std::env;

fn main() {
    let target = env::var("TARGET").unwrap();
    
    if target.contains("armv7") {
        // 为 ARM 配置
        println!("cargo:rustc-link-search=/usr/arm-linux-gnueabihf/lib");
        println!("cargo:rustc-link-search=/usr/lib/arm-linux-gnueabihf");
    }
    
    // 链接到库
    println!("cargo:rustc-link-lib=ssl");
    println!("cargo:rustc-link-lib=crypto");
}

PKG_CONFIG_PATH

对于使用 pkg-config 的库:

bash
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig

cross build --target armv7-unknown-linux-gnueabihf

为 64 位 ARM 编译

如果 NanoPI 运行的是 64 位系统:

bash
# 1. 安装目标
rustup target add aarch64-unknown-linux-gnu

# 2. 安装工具链
sudo apt-get install -y \
    gcc-aarch64-linux-gnu \
    g++-aarch64-linux-gnu

# 3. 配置 Cargo
cat >> ~/.cargo/config.toml << 'EOF'
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
ar = "aarch64-linux-gnu-ar"
EOF

# 4. 编译
cargo build --release --target aarch64-unknown-linux-gnu

zigbuild:统一的交叉编译工具

zigbuild 使用 Zig 作为后端,提供一致的交叉编译体验。

安装 zigbuild

bash
cargo install cargo-zigbuild

使用 zigbuild

bash
# 编译为 32 位 ARM
cargo zigbuild --target armv7-unknown-linux-gnueabihf --release

# 编译为 64 位 ARM
cargo zigbuild --target aarch64-unknown-linux-gnu --release

zigbuild 的优点

  • 无需安装多个 C 编译器
  • 更一致的编译环境
  • 更好的标准库支持
  • 支持更多目标平台

编译典型的 LeBot 程序

项目结构

lebot/
├── Cargo.toml
├── src/
│   ├── main.rs
│   ├── motor.rs
│   ├── sensor.rs
│   └── lib.rs
├── build.rs
└── Cross.toml

Cargo.toml 配置

toml
[package]
name = "lebot"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
rppal = "0.14"  # Raspberry Pi 的 GPIO 库
linux-embedded-hal = "0.3"

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

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true

编译命令

bash
# 开发版本(调试信息)
cargo build --target armv7-unknown-linux-gnueabihf

# 发布版本(优化和去除符号)
cargo build --release --target armv7-unknown-linux-gnueabihf

# 输出大小对比
ls -lh target/armv7-unknown-linux-gnueabihf/debug/lebot
ls -lh target/armv7-unknown-linux-gnueabihf/release/lebot

远程编译和测试

传输到 NanoPI

bash
# 通过 SCP 传输二进制文件
scp target/armv7-unknown-linux-gnueabihf/release/lebot \
    nanopi_user@nanopi_ip:/home/nanopi_user/

# 通过 SSH 连接到 NanoPI
ssh nanopi_user@nanopi_ip

# 在 NanoPI 上运行程序
./lebot

自动化脚本

bash
#!/bin/bash
# deploy.sh - 编译和部署脚本

TARGET_IP="192.168.1.100"
TARGET_USER="root"
BINARY_PATH="target/armv7-unknown-linux-gnueabihf/release/lebot"

echo "开始编译..."
cargo build --release --target armv7-unknown-linux-gnueabihf

if [ $? -ne 0 ]; then
    echo "编译失败"
    exit 1
fi

echo "传输文件..."
scp "$BINARY_PATH" "$TARGET_USER@$TARGET_IP:/root/lebot

echo "在 NanoPI 上运行..."
ssh "$TARGET_USER@$TARGET_IP" "chmod +x /root/lebot && /root/lebot"

调试交叉编译的程序

远程调试

bash
# 1. 在 NanoPI 上安装 gdbserver
ssh root@nanopi_ip "apt-get update && apt-get install -y gdbserver"

# 2. 在 NanoPI 上启动 gdbserver
ssh root@nanopi_ip "gdbserver :1234 /root/lebot"

# 3. 在主机上启动 gdb
arm-linux-gnueabihf-gdb target/armv7-unknown-linux-gnueabihf/debug/lebot

# 4. 在 gdb 中连接
(gdb) target remote nanopi_ip:1234
(gdb) break main
(gdb) continue
(gdb) next

日志输出调试

rust
// 在代码中添加日志
use std::fs::OpenOptions;
use std::io::Write;

fn log_debug(msg: &str) {
    if let Ok(mut file) = OpenOptions::new()
        .create(true)
        .append(true)
        .open("/tmp/lebot_debug.log")
    {
        writeln!(file, "{}: {}", std::env::args().next().unwrap_or_default(), msg).ok();
    }
}

fn main() {
    log_debug("程序启动");
    
    // 在 NanoPI 上查看日志
    // tail -f /tmp/lebot_debug.log
}

常见问题和解决方案

问题 1:找不到 C 库

error: linking with `arm-linux-gnueabihf-gcc` failed
cannot find -lssl

解决方案

bash
# 安装开发库
sudo apt-get install libssl-dev:armhf

# 或设置 pkg-config 路径
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig

问题 2:浮点数精度问题

ARM 有不同的浮点 ABI 标准。

toml
# 使用硬浮点(推荐,性能更好)
[target.armv7-unknown-linux-gnueabihf]
rustflags = ["-C", "target-feature=+v7,+vfp2"]

问题 3:二进制文件太大

bash
# 在 Cargo.toml 中优化大小
[profile.release]
opt-level = "z"         # 优化大小
lto = true              # 链接时优化
codegen-units = 1       # 单代码生成单元
strip = true            # 去除符号表

# 或手动去除符号
arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/lebot

# 查看大小
ls -lh target/armv7-unknown-linux-gnueabihf/release/lebot

最佳实践

1. 使用 CI/CD 自动化编译

yaml
# .github/workflows/cross-compile.yml
name: Cross Compile

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        target: armv7-unknown-linux-gnueabihf
    
    - name: Build
      run: cargo build --release --target armv7-unknown-linux-gnueabihf
    
    - name: Upload artifacts
      uses: actions/upload-artifact@v2
      with:
        name: lebot-armv7
        path: target/armv7-unknown-linux-gnueabihf/release/lebot

2. 编写可移植的代码

rust
// 使用条件编译处理平台差异
#[cfg(target_arch = "arm")]
fn configure_gpio() {
    // ARM 特定代码
}

#[cfg(target_arch = "x86_64")]
fn configure_gpio() {
    // 模拟代码用于测试
}

#[cfg(target_os = "linux")]
fn setup_signal_handlers() {
    // Linux 特定代码
}

3. 版本控制和发布

bash
# 创建针对特定架构的发布
git tag -a v0.1.0-armv7 -m "Release for ARMv7"
git tag -a v0.1.0-aarch64 -m "Release for AArch64"

总结

Rust 交叉编译关键要点:

  1. 理解三元组:清楚表示目标平台
  2. 安装工具链:使用 rustup 安装目标
  3. 配置编译器:在 .cargo/config.toml 中设置
  4. 处理依赖:管理 C/C++ 库的链接
  5. 优化二进制:大小和性能的平衡
  6. 自动化部署:使用脚本简化流程
  7. 远程调试:使用 gdb 和日志

对于 LeBot 项目,有效的交叉编译流程是保证快速迭代开发的关键。


参考资源

由 LeBot 开发团队编写