I found a guide for cross-compiling Rust which basically involves configuring it with a --target option. The target needs to include the cross-compile target and the host target.

$ wget https://static.rust-lang.org/dist/rustc-1.5.0-src.tar.gz
$ tar xf rustc-1.5.0-src.tar.gz 
$ ./configure --target=arm-unknown-linux-gnueabi,x86_64-unknown-linux-gnu --prefix "$(PWD)"/../bin
$ make -j$(nproc) install

This didn't work with my target arm-unknown-linux-gneabi as at some point in the build it started trying to use arm-linux-gneabi-gcc and arm-linux-gneabi-ar, which didn't work as they are called arm-unknown-linux-gneabi-gcc and arm-unknown-linux-gneabi-gcc. I guess this is a problem with the build script, but I couldn't easily work out a fix, so I just created some symlinks with the names it was expecting. This worked well.

$ export LD_LIBRARY_PATH=~/projects/arm-rust-cross-compiler/bin/lib/
$ export PATH=$PATH:~/projects/arm-rust-cross-compiler/bin/bin/
$ bin/bin/rustc --version
rustc 1.5.0-dev

Cargo can also be built from source. I don't think Cargo needs to be cross-compiled as it supports compiling for different targets and I don't actually want to run Cargo on the target machine.

$ git clone https://github.com/rust-lang/cargo
$ cd cargo
$ git submodule update --init
$ python -B src/etc/install-deps.py
$ ./configure --local-rust-root="$PWD"/../bin --prefix "$(PWD)"/../bin
$ make
$ make install

To cross-compile with Cargo, a linker needs to be specified for the target. In my case:

$ cat > ~/.cargo/config << EOF
linker = "arm-unknown-linux-gnueabi-gcc-5.2.0"

Now Cargo can be used to create a new project and build a binary for an ARM target.

$ cargo new --bin hello-rust
$ cd hello
$ cargo build --target=arm-unknown-linux-gnueabi
$ file target/arm-unknown-linux-gnueabi/debug/hello-rust
target/arm-unknown-linux-gnueabi/debug/hello: ELF 32-bit LSB shared object,
ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter
/lib/ld-linux.so.3, for GNU/Linux 4.3.0, not stripped

I was pretty confident at this point, until I tried to run it in my emulator and the program immediately terminated with the message "Illegal instruction". So far, my cross-compiler toolchain and emulator had worked well. Why is now generating illegal instructions?

I eventually found that I wasn't specifying a CPU type and qemu was using the default ARM926 processor on the Versatile board. This can be fixed by adding --cpu arm1176 (which is the CPU on the first generation Raspberry Pi which I am trying to emulate). However, now the Kernel doesn't boot as it was built for an ARM926 processor as specified by versatile_defconfig.

The Linux ARM Versatile config restricts the possible CPU targets to CPUs that can actually fit in the board. Fortunately I found this patch which removes this restriction. Now make ARCH=arm versatile_defconfig && make ARCH=arm menuconfig allows the System Type -> Support ARM V6 processor option to be selected.

Finally, after rebuilding the Kernel and adding the Rust built hello binary to the filesystem, the program can run in the emulator.

qemu-system-arm -M versatilepb -cpu arm1176 -m 128M -kernel zImage -initrd ./rootfs.img.gz -append "root=/dev/ram rdinit=/sbin/init"

hello rust screenshot