Compile to statically linked binary like golang?


#1

I have googled this, and all I found was some discussions about MUSL support, but no specific instructions on how it can be done.


#2

http://doc.crates.io/manifest.html#building-dynamic-or-static-libraries

crate-type = ["staticlib"]

#3

Wow, it is that easy?! :slight_smile:

What about statically linked executables that are runnable without external dependencies?


#4

If you use cargo crates and those don’t explicitly compile to “dylib”, then you don’t have to worry about anything, you don’t have external dynamic libs dependencies. Short, your binary has it all.
If you want to link system libraries for example, you would have to use build scripts, and probably gcc crate and depending on the situation may be pkg-config.


#5

Hmm, I just tried to do as you advised. On one ubuntu machine:

// main.rs
fn main() {

    let mut rng = 0..7;
    println!("> {:?}", rng.next()); // prints Some(0)
    println!("> {:?}", rng.next()); // prints Some(1)
    for n in rng {
        print!("{} - ", n);
    }
    println!("\n");
    let aliens = ["Cherfer", "Fynock", "Shirack", "Zuxu"];
    for alien in aliens.iter() {
        print!("{} / ", alien)
        // process alien
    }

    // see code in Chapter 5/code/adapters_consumers.rs
    let rng = 0..1000;
    let rngvec = rng.collect::<Vec<i32>>();
    println!("{:?}", rngvec);

    #[derive(Debug)]
    enum IntOrString {
        I(isize), S(&'static str)
    }

    println!("{:?}", IntOrString::I(0xabcd));
    println!("{:?}", IntOrString::S("What is this?"));
}

In Cargo.toml:

[package]
name = "learn_rust"
version = "0.1.0"
crate-type = ["staticlib"]

[dependencies]

Then I run cargo build and transfer the binary to another ubuntu machine. When running that binary, I got this error:

./learn_rust
./learn_rust: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found (required by ./learn_rust)
./learn_rust: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.18' not found (required by ./learn_rust)

#6

To build a 64-bit statically linked binary for Linux, you need to cross compile for the x86_64-unknown-linux-musl target. To cross compile you’ll need standard crates (like libstd) cross compiled to that target; you can install those with multirust. Example bellow:

$ cargo new --bin hello
$ cd hello
# Install cross compiled crates
$ multirust add-target nightly x86_64-unknown-linux-musl
$ cargo build --target x86_64-unknown-linux-musl
$ file target/x86_64-unknown-linux-musl/debug/hello
target/x86_64-unknown-linux-musl/debug/hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=6e41e2dfcc0fc3f90cdee95630ba66f339393da1, not stripped
$ ldd target/x86_64-unknown-linux-musl/debug/hello
        not a dynamic executable

#7

My guess would be that the other Ubuntu machine is an older version.
You see, in the process of compiling Rust itself, there is a C++ compiler involved.
On Linux, usually GCC is used. When GCC compiles, it needs an implementation of libc. GCC uses by default glibc.
So, when Rust standard library was compiled(it is statically linked in your learn_rust binary), it was compiled with a GCC that uses a newer version of glibc than on that “other ubuntu machine”(Note that GCC links glibc dynamically).
If you want your Linux binary to run on any Linux(Ubuntu, Fedora Core, old-old Linux versions), there are other implementations of the C standard library(libc), one of them is Musl. So, if you use Rust std that was compiled with Musl, then it will work on any Linux system(note that Musl is statically linked, compared to Glibc that is dynamically linked).
Also note that Musl has a stack size of only 80 KB, while Glibc has a stack size of 8 MB. You have to be very careful with Musl not to have stack overflows.


#8

Rust overrides the default stack size to 2MB (or the value of the RUST_MIN_STACK variable). This works fine with both glibc and musl targets:

fn main() {
    thread::spawn(|| {
        let bytes = [0u8; 1 * 1024 * 1024];
        println!("{:?}", bytes.last())
    }).join();
}

Changing bytes to 2MB or 3MB generates a stack overflow on both targets.

GCC uses by default glibc.

gcc links to whichever libc is in the library search path, it doesn’t have a default. Most linux systems tend to ship glibc that’s why one thinks of glibc as the default C library. On alpine linux, gcc links programs to musl because that’s the C library the distribution ships with.


#9

Indeed, it links “by default” because that’s usually the default.
And since I correct my “general” claims:

Sorry @kindlychung for confusing you with this statement.
I thought you will run the binary on the same system, from that point, the crates are not dynamically linked, but there is the issue with the C++ compiler… And that’s mainly on Linux…
You would have the same issue with C/C++ applications, and probably any other compiler that uses LLVM as their basis(and need to compile LLVM with a C++ compiler).

On this one I am not that sure. I know already of a few projects that have/had stack oveflow issues exactly because they use musl.


#10

@japaric I got an error on a mac:

cargo build --target x86_64-unknown-linux-musl
Compiling hello v0.1.0 (file:///private/tmp/hello)
src/main.rs:1:1: 1:1 error: can’t find crate for std [E0463]
src/main.rs:1 fn main() {
^
error: aborting due to previous error
Could not compile hello.

To learn more, run the command again with --verbose.
kaiyin@kaiyins-mbp 10:12:50 | /tmp/hello =>
cargo build --target x86_64-unknown-linux-musl --verbose
Compiling hello v0.1.0 (file:///private/tmp/hello)
Running rustc src/main.rs --crate-name hello --crate-type bin -g --out-dir /private/tmp/hello/target/x86_64-unknown-linux-musl/debug --emit=dep-info,link --target x86_64-unknown-linux-musl -L dependency=/private/tmp/hello/target/x86_64-unknown-linux-musl/debug -L dependency=/private/tmp/hello/target/x86_64-unknown-linux-musl/debug/deps
src/main.rs:1:1: 1:1 error: can’t find crate for std [E0463]
src/main.rs:1 fn main() {
^
error: aborting due to previous error
Could not compile hello.

Caused by:
Process didn’t exit successfully: rustc src/main.rs --crate-name hello --crate-type bin -g --out-dir /private/tmp/hello/target/x86_64-unknown-linux-musl/debug --emit=dep-info,link --target x86_64-unknown-linux-musl -L dependency=/private/tmp/hello/target/x86_64-unknown-linux-musl/debug -L dependency=/private/tmp/hello/target/x86_64-unknown-linux-musl/debug/deps
(exit code: 101)

Best regards,

Kaiyin ZHONG


#11

Did you use multirust to install x86_64-unknown-linux-musl std?
If you installed Rust on a Mac it doesn’t have by default the std for Linux.

Install multirust:

curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh

Switch by default to nightly(multirust add-target doesn’t work with stable currently):

multirust override nightly

Or if you don’t want only for that folder to be nightly and instead for the entire environment:

multirust default nightly

Install x86_64-unknown-linux-musl std:

multirust add-target nightly x86_64-unknown-linux-musl

Now cross-compile:

cargo build --target x86_64-unknown-linux-musl

#12

​Still no luck:

kaiyin@kaiyins-mbp 10:13:04 | /tmp/hello =>
multirust override nightly
multirust: using existing install for 'nightly’
multirust: override toolchain for ‘/private/tmp/hello’ set to 'nightly’
kaiyin@kaiyins-mbp 11:09:38 | /tmp/hello =>
multirust add-target nightly x86_64-unknown-linux-musl
rustup: downloading extra component from
https://static.rust-lang.org/dist/2016-03-26/rust-std-nightly-x86_64-unknown-linux-musl.tar.gz
100.0%
rustup: installing extra component from
/Users/kaiyin/.multirust/rustup/dl/091ea58bba4ddcbff0a9/rust-std-nightly-x86_64-unknown-linux-musl.tar.gz
rustup: extracting installer
install: uninstalling component 'rust-std-x86_64-unknown-linux-musl’
install: creating uninstall script at
/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/uninstall.sh
install: installing component ‘rust-std-x86_64-unknown-linux-musl’

std is standing at the ready.

kaiyin@kaiyins-mbp 11:10:03 | /tmp/hello =>
cargo build --target x86_64-unknown-linux-musl
Compiling hello v0.1.0 (file:///private/tmp/hello)
error: linking with cc failed: exit code: 1
note: “cc” “-Wl,–as-needed” “-Wl,-z,noexecstack” “-m64” “-nostdlib”
"-static" “-Wl,–eh-frame-hdr” “-Wl,-(”
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/crt1.o"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/crti.o"
"-L"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib"
"/private/tmp/hello/target/x86_64-unknown-linux-musl/debug/hello.0.o" “-o”
"/private/tmp/hello/target/x86_64-unknown-linux-musl/debug/hello"
"-Wl,–gc-sections" “-nodefaultlibs” “-L”
"/private/tmp/hello/target/x86_64-unknown-linux-musl/debug" “-L”
"/private/tmp/hello/target/x86_64-unknown-linux-musl/debug/deps" “-L”
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib"
"-Wl,-Bstatic" “-Wl,-Bdynamic”
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/libstd-18402db3.rlib"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/libcollections-18402db3.rlib"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/librustc_unicode-18402db3.rlib"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/librand-18402db3.rlib"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/liballoc-18402db3.rlib"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/liballoc_jemalloc-18402db3.rlib"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/liblibc-18402db3.rlib"
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/libcore-18402db3.rlib"
"-l" “compiler-rt”
"/Users/kaiyin/.multirust/toolchains/nightly/lib/rustlib/x86_64-unknown-linux-musl/lib/crtn.o"
"-Wl,-)"
note: clang: warning: argument unused during compilation: '-nodefaultlibs’
ld: unknown option: --as-needed
clang: error: linker command failed with exit code 1 (use -v to see
invocation)

error: aborting due to previous error
error: Could not compile hello.

To learn more, run the command again with --verbose.​


#13

Seems like you would have to install and set GCC as the default compiler to be able to cross-compile this one.
I don’t really know if that’s the issue(I don’t know if that’s the solution).

Somebody with a Mac?


#14

AFAIK you still need a cross compilation toolchain (GCC) for linking. I don’t think that you can use the system linker on mac to produce linux binaries. I could be wrong though…


#15

Clang is natively a cross-compiler compared to GCC for example, where you would need to compile GCC to be a cross-compiler for the platform you need.
My best guess would be that it has to do with the fact that the x86_64-unknown-linux-musl std was compiled with GCC, although I don’t have strong arguments for this.


#16

Compilation yes, but I think for linking clang still uses the system linker (ld) by default. I didn’t find any up to date information though.
Also, I don’t think it matters for linking what compiler was used for compilation.

I’m playing a bit with zinc and had to install a GCC cross toolchain too. And zinc doesn’t even use std or libc.
But this was before multirust supported cross compilation.


#17

The error kindlychung is having is at the linking stage, on an osx box.

The artifact that’s been produced is an x86-64 ELF object file (osx uses the macho container format). So technically, it’s been compiled, it’s just in need of linking.

Completely correct.

What is happening is that clang is receiving the link arguments (the -Wl,<etc> flags), which it passes to osx’s static linker, /usr/bin/ld, which even if it hadn’t errored out on the unknown command line arguments, does not understand the ELF binary format, and can’t help you anyway (you can see the command line flags that ld accepts with man ld). You need a linker that does (like GNU’s ld or gold), which is compiled to run on your host machine (OSX, mach binary), but which edits and outputs ELF artifacts.

You can try downloading GNU binutils and attempting to compile them on your host machine, and then using something like the -C linker=/path/to/elf/ld (fyi I’ve never tried this, could be difficult/impossible), but even if you got that working, unfortunately, and as best I can tell, while the switches and infrastructure seem to be in place, support for an auto cross compiler toolchain on rust has yet to be implemented, or is buggy at best.

I’ve had limited success in this area.

I believe they’re going to be focusing efforts on first supporting an android target, iirc, and then we’ll see what happens from there.

Of course, you do have that elf object file, which if you’re in possession of an linux box, you can copy over and try linking, or you can try figuring out the correct command line arguments to pass to rust to, which as I said, may or may not be completely implemented yet.

NOTE:

if you just want to output a completely statically linked binary “like golang”, then support for doing this on a Linux box, with a nightly rust is as easy as:

cargo build --target=x86_64-unknown-linux-musl

(assuming you’ve added the musl target already. Also, should mention https://www.rustup.rs/)

Cross-compilation on an OSX box -> ELF x86-64 binary is complicated for the reasons above.

Creating a static executable on OSX for OSX is not possible. OSX requires all binaries be dynamically linked, so golang won’t be able to do this either. (I’m not sure whether this requirement is forced at the link level, or the kernel level, my guess is the former, in which case you could hack around this requirement, but I won’t go into that)


#18

Note that there is work ongoing to add a linker to LLVM itself. I don’t know exactly how that’s going to work when it’s done, but it’s at least conceivable that future versions of Rust will be able to operate without a separately-installed linker.


#19

Cross compilation is definitely possible.
There are precompiled cross toolchains like https://github.com/tpoechtrager/osxcross for OSX or using crosstools like this: http://www.jaredwolff.com/blog/cross-compiling-on-mac-osx-for-raspberry-pi/
There’s also this thread: https://github.com/rust-lang/rust/issues/16259


#20

As I said, the problem the user encountered was not specifically cross-compilation, but rather the final cross linking stage.

As the thread you linked to points out, once you provide the --target on the host, and you’ve setup nightly rust with the correct targets (say using the new rustup), you need to pass the location of the ELF/ELF capable linker to rust using the -C linker flag.

Again, tho, I’ve had limited success with this. The cross toolchain links you’ve provided aren’t really relevant; rust is already cross compiling, it’s just not using the correct linker. The link sorear supplied is interesting though, I hadn’t realized llvm was providing a new cross-platform linker (and there’s never been anyway reason whatsoever for why one shouldn’t exist), that could be an interesting avenue to pursue.

Note also in the rust issue thread, it’s noted that lld can’t produce static executables (yet), so obviously it won’t help in the case the user wants, which is a statically linked binary :confused: