Rust 静态编译可执行文件指南

  • Rust简介
  • Rust默认运行时环境
  • 使用MUSL进行静态编译
  • 使用预置好的Docker容器进行MUSL编译


Rust简介

Rust是一门强调安全、并发、高效的系统编程语言。无 GC 实现内存安全机制、无数据竞争的并发机制、无运行时开销的抽象机制,是 Rust 独特的优越特性。它声称解决了传统 C 语言和 C++ 语言几十年来饱受责难的内存安全问题,同时还保持了很高的运行效率、很深的底层控制、很广的应用范围,在系统编程领域具有强劲的竞争力和广阔的应用前景。

Rust默认运行时环境

Linux 下 rust 默认使用 gcc 作为链接器,编译后的文件在运行时需要glibc 运行库和其他的一些库。

这就导致在某个Linux版本下编译的执行文件,无法在另一个Linux版本上顺利运行。而且,如果你的程序还使用了OpenSSL动态库,那这样的问题会更加突出。

下面,我们做个最简单的实验。

  1. 用Cargo创建一个可执行项目。
  2. 编译这个项目
  3. 用ldd命令查看编译出来的执行文件依赖了哪些动态链接库
$ cargo -V
cargo 1.31.0 (339d9f9c8 2018-11-16)
$ cargo new --bin hello
$ cd hello
$ cargo build
$ ldd target/debug/hello
        linux-vdso.so.1 (0x00007ffdc47e4000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f6db611a000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f6db5f12000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f6db5cf3000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f6db5adb000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6db56ea000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f6db6587000)

可以看到这个执行文件依赖了多个.so的动态链接库。而且这种依赖是基于绝对路径的。一旦运行时环境下没有这些动态库文件,那程序执行的结果就只有一个:报错!

那么,Rust能否像Golang那样编译成独立的静态可执行文件呢?答案是可以的,这需要使用MUSL 静态库。

使用MUSL进行静态编译

使用MUSL编译,首先需要安装musl环境。命令如下:

$ rustup target add x86_64-unknown-linux-musl
$ rustup target add x86_64-unknown-linux-musl --toolchain=nightly

然后,我们编译前面创建的hello工程。

$ cd hello
$ cargo build --release --target=x86_64-unknown-linux-musl
$ ldd target/x86_64-unknown-linux-musl/release/hello
        not a dynamic executable

可以看到新的可执行文件已经不再依赖任何动态链接库。我们可以将这个文件放到任何一个Linux操作系统里运行了。

当然,这只是一个最简单的例子。实际工作中,我们可能会遇到更复杂的场景。如:依赖了OpenSSL库。在这样的场景里,我们还需要做很多配置才能获得我们想要的静态编译文件。这里就不再详细介绍了。

笔者想介绍的是如何避免各种繁复的配置,尽可能快捷的进行MUSL编译。就是下面章节的内容。

使用预置好的Docker容器进行MUSL编译

为解决使用MUSL编译配置繁琐的问题,国外的开发者贡献了一个预置好的容器。用这个容器来进行MUSL编译会非常方便快捷。

项目地址是:https://gitlab.com/rust_musl_docker/image

我们直接用这个容器来编译我们前面创建的hello工程。然后依然用ldd来查看编译好的可执行文件。

$ cd hello
$ docker run -it --rm \
> -v $PWD:/workdir \
> -v ~/.cargo/git:/root/.cargo/git \
> -v ~/.cargo/registry:/root/.cargo/registry \
> registry.gitlab.com/rust_musl_docker/image:stable-latest \
> cargo build --release -vv --target=x86_64-unknown-linux-musl

$ ldd target/x86_64-unknown-linux-musl/release/hello
        not a dynamic executable

这个容器镜像里已经配置了对OpenSSL库的静态编译。笔者亲测可用。更详细的内容,读者可以去看项目里的注释,已经非常详尽了。