cross compiling and statically linking against Rust libraries

Tiago Seco
May 16, 2019 · 3 min read
Crustaceans are adaptable and can be found in all kinds of environments.

At CSIS we have traditionally written our back-end in Python, together with some C/C++ code for our Incident Response Toolkit.

A few years ago, mainly due to performance reasons, we started rewriting specific back-end services from Python to Rust, with great success. Now, for the sake of ease of development and testing, we are exploring the idea of moving parts of our C/C++ code base to Rust, too.

In order to do so, instead of re-writing everything in one swoop, we decided to try integrating Rust into our existing code base.

Following is a summary of our experiments, and a skeleton for writing a Rust library and calling it from a C/C++ application.

Targets

In order to be able to cross compile code, we need to first make sure that we have the desired targets installed. A list of available targets can be obtained via rustc --print target-list and new ones can be installed via rustup target add <target>.

Installed targets can be shown with rustup show:

$ rustup show
Default host: x86_64-unknown-linux-gnu

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

installed targets for active toolchain
--------------------------------------

i686-pc-windows-gnu
i686-pc-windows-msvc
i686-unknown-linux-gnu
i686-unknown-linux-musl
x86_64-pc-windows-gnu
x86_64-pc-windows-msvc
x86_64-unknown-linux-gnu
x86_64-unknown-linux-musl

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.31.0 (abe02cefd 2018-12-04)

Project Setup

Once the targets have been properly setup we need to setup our project. Starting with Cargo.toml :

[package]
name = "mylib"
version = "0.1.0"
authors = ["john doe<jdo@csis.dk>"]

[dependencies]
libc = "*"

[lib]
crate-type = ["staticlib"]

Now the source code for our library src/lib.rs:

extern crate libc;
use libc::uint32_t;

#[no_mangle]
pub extern "C" fn addition(a: uint32_t, b: uint32_t) -> uint32_t {
a + b
}

And the code for our application caller.cpp:

#include <stdio.h>
#include <stdint.h>

extern "C" {
uint32_t
addition(uint32_t, uint32_t);
}

int main(void) {
uint32_t sum = addition(1, 2);
printf("%u\n", sum);
}

If this were a C application, only the extern "C" needed to be removed.

Next we need to indicate our desired target, which is done in .cargo/config, for example:

[build]
target="i686-pc-windows-gnu"

Linux binaries

In order to statically link Linux binaries the target needs to be set to -linux-musl.

[build]
target="i686-unknown-linux-musl"

Alternatively we could choose x86_64-unknown-linux-musl for 64 bit binaries.

$ g++ -m32 -c -o linux_cpp_caller.o caller.cpp
$ g++ -static -m32 linux_cpp_caller.o -o linux_cpp_caller \
-L./target/i686-unknown-linux-musl/debug/ -lmylib

If we were targeting 64 bit, the -m32 option needed to be removed and the -L flag adjusted accordingly.

Windows Binaries

In order to statically link Windows binaries the target needs to be set to -pc-windows-gnu . In the case of 32 bit binaries special care is needed with regards to the exception handling model. A good summary can be found on the “rust-lang” Github repository and on stack overflow.

[build]
target="i686-pc-windows-gnu"
rustflags = "-C panic=abort"

Alternatively we could choose x86_64-pc-windows-gnu for 64 bit binaries. If this were the case, the rustflags option could be removed.

$ i686-w64-mingw32-g++ -c -o win_cpp_caller.o caller.cpp
$ i686-w64-mingw32-g++ -static win_cpp_caller.o -o win_cpp_caller \
-L./target/i686-pc-windows-gnu/debug/ \
-lmylib \
-ladvapi32 \
-lws2_32 \
-luserenv

If the target was a 64bit environment, x86_64-w64-mingw32-g++ should be used instead and the -L flag adjusted accordingly.

Conclusion

With this post we hope to have provided a simple, working example of how Rust and C/C++ can inter-operate, which can be used as a starting point.

We would like to thank the contributors of the resources linked below.

CSIS TechBlog

CSIS Security Group software development and security…

CSIS TechBlog

CSIS Security Group software development and security research teams are sharing their experiences building systems to detect, monitor and take down malware infrastructure.

Tiago Seco

Written by

CSIS TechBlog

CSIS Security Group software development and security research teams are sharing their experiences building systems to detect, monitor and take down malware infrastructure.