sunface/codecc

[文章][2022-04-02] Rust 交叉编译

Closed this issue · 9 comments

原文标题: Cross-compilation in Rust

原文链接: https://kerkour.com/rust-cross-compilation

译文链接: https://github.com/studyrs/Rustt/blob/main/Articles/%5B2022-04-04%5D%20Rust%20交叉编译.md


翻译者须知

  1. 在issue中评论:申请翻译,并附上预期完成时间,等待一个 approver 来处理
  2. 请翻译者在翻译后,将声明模版的 Markdown 文本加入到文章的 顶部

声明模版

> 原文链接: https://kerkour.com/rust-cross-compilation
> 
> **翻译: [朕与将军解战袍](https://github.com/a1393323447)**
>
> 选题:[Akagi201](https://github.com/Akagi201)
>
> 本文由 [Rustt](https://Rustt.org) 翻译,[StudyRust](https://studyrust.org) 荣誉推出

申请翻译,预期完成时间 2022-04-04

请问需要翻译作者给他的书打广告的内容吗?

抱歉~~~现在只能先加入 Rustt,才能翻译哈~

抱歉~~~现在只能先加入 Rustt,才能翻译哈~

一开始没看清楚,昨天晚上已经翻译完了,感觉不发出来有点浪费。

Rust 交叉编译

这篇文章摘录自我的书 《Black Hat Rust》

现在我们有一个主要的 secure RAT1,是时候扩大我们的学习范围了。

到目前为止,我们的构建局限在 Linux 上。虽然 Linux 在服务器端占有巨大的市场,但在客户端上就是另一个故事了 —— Linux 仅占有大约2.5% 桌面市场

为了增加潜在目标(target)的数量,我们将会使用交叉编译: 从主机操作系统为不同的操作系统编译程序。例如,在 Linux 上编译 Windows 可执行文件。

然而,当我们讨论交叉编译时,我们不仅仅是在讨论将程序从一个操作系统编译到另一个操作系统。我们还在讨论将可执行文件从一个架构编译到另一个架构。例如,从 x86aarch64 (又称 arm64)。

在本章中,我们将了解为什么要交叉编译 Rust 程序、如何交叉编译 Rust 程序,以及如何避免交叉编译中令人痛苦的边缘情况,所以请紧跟我的脚步。

为什么要多平台

从电脑到智能手机再到智能电视,物联网(如摄像头或“智能”冰箱)…… 今天的计算领域可以说是“碎片化”这个词的完美例证。因此,如果我们希望我们的操作达到更多的目标(target),我们的 RAT 需要支持许多平台。

平台相关 API

不幸的是,操作系统 api 是不可移植的: 例如,在 Windows 或 Linux 上,持久化技术(保存程序运行结果的行为)是非常不同的。

每一个操作系统的特殊性迫使我们编写依平台相关的代码。

因此,我们将需要为 windows 编写 RAT 的某些部分,为 Linux 重写相同的部分,并为 macOS 重写……

我们的目标是尽可能多地编写所有平台共享的代码。

跨平台的 Rust

值得庆幸的是,Rust 让编写根据平台有条件地编译的代码变得简单。

cfg 属性

cfg 属性使得代码有条件地编译。它支持 很多选项,所以你可以选择在什么平台上运行代码的哪一部分。

例如: #[cfg(target_os = "linux")], #[cfg(target_arch = "aarch64")], #[cfg(target_pointer_width = "64")]

下面的示例代码,根据目标平台选择并导出了正确的 install 函数。

ch_12/rat/agent/src/install/mod.rs

// ...

#[cfg(target_os = "linux")]
mod linux;

#[cfg(target_os = "linux")]
pub use linux::install;

#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
pub use macos::install;

#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
pub use windows::install;

然后,在跨平台共享的代码中,我们可以像导入其它模块一样导入并使用它。

mod install;

// ...

install::install();

cfg 属性 还和以同 anyall,以及 not 一起使用:

// 该函数仅当目标平台是 macOS 或 Linux 时被包含(include)
#[cfg(any(target_os = "linux", target_os = "macos"))]
// ...

// 该函数仅当目标平台是 linux **并且** 指针大小为 64 比特时被包含(include)
#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
// ...


// 该函数仅当目标平台不是 windows 时被包含(include) 
#[cfg(not(target_os = "windows"))]
// ...

平台相关的依赖

我们还可以根据目标平台有条件地导入依赖。

例如,我们为了和 Windows 注册表交互,准备导入 winreg crate ,但当目标平台不是 Wndows 时,导入甚至构建这个 crate 都是没有意义的。

ch_12/rat/agent/Cargo.toml

[target.'cfg(windows)'.dependencies]
winreg = "0.10"

支持的平台

Rust 项目将支持的平台分为三个层级:

  • 层级 1 中的目标(target)可以理解为“保证能工作”。
  • 层级 2 中的目标(target)可以理解为“保证能构建”。
  • 层级 3 中的目标(target)是 Rust 代码库支持的目标,但 Rust 项目不能自动构建或测试,因此它们可能能工作,也可能不能工作。

下面是层级 1 中的目标(target):

  • aarch64-unknown-linux-gnu
  • i686-pc-windows-gnu
  • i686-pc-windows-msvc
  • i686-unknown-linux-gnu
  • x86_64-apple-darwin
  • x86_64-pc-windows-gnu
  • x86_64-pc-windows-msvc
  • x86_64-unknown-linux-gnu

你可以在官方文档中找到其他层级的目标(target):https://doc.rust-lang.org/nightly/rustc/platform-support.html。

在实践中,这意味着我们的 RAT 可以保证在层级 1 的平台上正常工作(或者由 Rust 团队处理出现的问题)。对于层级 2 的平台,你需要编写更多的测试,以确保一切按照预期工作。

交叉编译

Error: Toolchain / Library XX not found. Aborting compilation.

在尝试遵循项目的构建说明构建项目或交叉编译时,你收到过多少次类似的信息?

如果我们不再编写不可靠的文档,而是将构建指令放进一个不可变的配方(recipe)中,从而保证100% 成功构建,情况会变成什么样呢?

这就是 Docker 发挥作用的地方:

不可变Dockerfile 是我们的不可变的配方,而 docker 就会是我们的机器人,它一年 365 天完美地执行我们的配方。

跨平台:Docker 可以在三种主要的操作系统(Linux 、Windows 和 macOS)上使用。因此,我们不仅让一个由几个使用不同机器的开发人员组成的团队能够一起工作,而且还极大地简化了我们的工具链。

通过使用 Docker ,我们最终将问题简化成到从 Linux 编译到其他平台,而不是:

  • 从 Linux 编译到其他平台
  • 从 Windows 编译到其他平台
  • 从 macOS 编译到其他平台
  • ...

cross

Rust 的 Tools team 开发并维护了一个名为 cross 的项目。该项目你可以使用 Docker 轻松地交叉编译 Rust 项目,而不用破坏自定义Dockerfiles 。

安装方式:

$ cargo install -f cross

cross 使用预先设置好的 Dockerfiles ,它们是由 Tools team 来维护的,不需要你操心,Tools team 会处理好所有的事情。

支持的目标(target)列表让人印象深刻。在我写这篇文章的时候,目标列表为:https://github.com/rust-embedded/cross/tree/master/docker

Dockerfile.aarch64-linux-android
Dockerfile.aarch64-unknown-linux-gnu
Dockerfile.aarch64-unknown-linux-musl
Dockerfile.arm-linux-androideabi
Dockerfile.arm-unknown-linux-gnueabi
Dockerfile.arm-unknown-linux-gnueabihf
Dockerfile.arm-unknown-linux-musleabi
Dockerfile.arm-unknown-linux-musleabihf
Dockerfile.armv5te-unknown-linux-gnueabi
Dockerfile.armv5te-unknown-linux-musleabi
Dockerfile.armv7-linux-androideabi
Dockerfile.armv7-unknown-linux-gnueabihf
Dockerfile.armv7-unknown-linux-musleabihf
Dockerfile.asmjs-unknown-emscripten
Dockerfile.i586-unknown-linux-gnu
Dockerfile.i586-unknown-linux-musl
Dockerfile.i686-linux-android
Dockerfile.i686-pc-windows-gnu
Dockerfile.i686-unknown-freebsd
Dockerfile.i686-unknown-linux-gnu
Dockerfile.i686-unknown-linux-musl
Dockerfile.mips-unknown-linux-gnu
Dockerfile.mips-unknown-linux-musl
Dockerfile.mips64-unknown-linux-gnuabi64
Dockerfile.mips64el-unknown-linux-gnuabi64
Dockerfile.mipsel-unknown-linux-gnu
Dockerfile.mipsel-unknown-linux-musl
Dockerfile.powerpc-unknown-linux-gnu
Dockerfile.powerpc64-unknown-linux-gnu
Dockerfile.powerpc64le-unknown-linux-gnu
Dockerfile.riscv64gc-unknown-linux-gnu
Dockerfile.s390x-unknown-linux-gnu
Dockerfile.sparc64-unknown-linux-gnu
Dockerfile.sparcv9-sun-solaris
Dockerfile.thumbv6m-none-eabi
Dockerfile.thumbv7em-none-eabi
Dockerfile.thumbv7em-none-eabihf
Dockerfile.thumbv7m-none-eabi
Dockerfile.wasm32-unknown-emscripten
Dockerfile.x86_64-linux-android
Dockerfile.x86_64-pc-windows-gnu
Dockerfile.x86_64-sun-solaris
Dockerfile.x86_64-unknown-freebsd
Dockerfile.x86_64-unknown-linux-gnu
Dockerfile.x86_64-unknown-linux-musl
Dockerfile.x86_64-unknown-netbsd

从 Linux 交叉编译到 Windows

# 在你的 Rust 项目中
$ cross build --target x86_64-pc-windows-gnu

交叉编译到 aarch64 (arm64)

# 在你的 Rust 项目中
$ cross build --target aarch64-unknown-linux-gnu

交叉编译到 armv7

# 在你的 Rust 项目中
$ cross build --target armv7-unknown-linux-gnueabihf

自定义 Dockerfiles

有时候,你可能需要在 Docker 镜像中使用特定的工具,例如一个 packer(什么是 packer ?我们会在后面提及)或用于来剥离(strip)和重写最终可执行文件的元数据(metadata)的工具。

在这种情况下,我们可以创建一个自定义 Dockerfile 并配置 cross 以用于特定目标。

在你的项目根目录(Cargo.toml 文件所在目录)中创建一个 Cross.toml 文件,并写入以下内容:

[target.x86_64-pc-windows-gnu]
image = "my_image:tag"

我们也可以不用 cross 而是构建我们自己的 Dockerfiles。方法如下:

从 Linux 交叉编译到 Windows

ch_12/rat/docker/Dockerfile.windows

FROM rust:latest

RUN apt update && apt upgrade -y
RUN apt install -y g++-mingw-w64-x86-64

RUN rustup target add x86_64-pc-windows-gnu
RUN rustup toolchain install stable-x86_64-pc-windows-gnu

WORKDIR /app

CMD ["cargo", "build", "--target", "x86_64-pc-windows-gnu"]
$ docker build . -t black_hat_rust/ch12_windows -f Dockerfile.windows
# 在你的 Rust 项目中
$ docker run --rm -ti -v `pwd`:/app black_hat_rust/ch12_windows

交叉编译到 aarch64 (arm64)

ch_12/rat/docker/Dockerfile.aarch64

FROM rust:latest

RUN apt update && apt upgrade -y
RUN apt install -y g++-aarch64-linux-gnu libc6-dev-arm64-cross

RUN rustup target add aarch64-unknown-linux-gnu
RUN rustup toolchain install stable-aarch64-unknown-linux-gnu

WORKDIR /app

ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
    CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
    CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++

CMD ["cargo", "build", "--target", "aarch64-unknown-linux-gnu"]
$ docker build . -t black_hat_rust/ch12_linux_aarch64 -f Dockerfile.aarch64
# in your Rust project
$ docker run --rm -ti -v `pwd`:/app black_hat_rust/ch12_linux_aarch64

Footnotes

  1. 译者注:secure RAT 全称为 secure Requirement Automation Tool,译为 安全需求自动化工具 ,是一个辅助管理敏捷开发项目中的安全需求的工具。

😆 ,好棒,不考虑加入 Rustt 吗?没有强制性要求的,一段时间翻译一篇,再加个小群,大家一起交流交流

申请翻译,预期完成时间 2022-04-05

@rubot

选题 + 7

@rubot

翻译 + 23
文章 + 1