Over the holidays I wanted to get a very simple Rust application running on my WiFi Pineapple Nano. Since I don’t have much experience with embedded systems and cross-compilation, it sounded like something fun to do and I was sure I might learn a thing or two. Many hours later and lots of frustration, I ended up with a simple solution that I’d like to share in this post.
The major obstacle to overcome is building your own standard library, since Rust doesn’t ship with one for our target out of the box. I first tried it “the old way” by checking out the source and running through all the steps described in rust-cross. While I got it to build in the end it still didn’t work because of some version issues (I compiled with
rustc 1.16.0-dev (4ecc85beb 2016-12-28) and the same version but
rustc 1.16.0-nightly (4ecc85beb 2016-12-28) rejected running it).
Close to giving up at this point after spending hours on it, I found the xargo wrapper around
cargo, an awesome project by Jorge Aparicio. With this tool I got it to work fairly quickly and this is also the approach we’ll be following in this post. We still need to handle a couple of extra steps not outlined in the
xargo documentation, so I hope this post provides extra value to some of you.
Before we get into the weeds we need to do our homework first and figure out the compilation target for cross compilation. Once you SSH into your pineapple and run
uname -a you’ll see something like this:
root@Pineapple:~# uname -a Linux Pineapple 3.18.36 #40 Fri Oct 28 05:42:22 UTC 2016 mips GNU/Linux
This is the output from my WiFi Pineapple NANO running the 1.1.3 firmware. The important part here is mips, which gives us a clue which platform the pineapple is running on. The device is built on top of OpenWRT, a linux distribution for embedded devices.
/etc/openwrt_release file provides more information about the OpenWrt release itself:
root@Pineapple:~# cat /etc/openwrt_release DISTRIB_ID='OpenWrt' DISTRIB_RELEASE='Chaos Calmer' DISTRIB_REVISION='r49403' DISTRIB_CODENAME='chaos_calmer' DISTRIB_TARGET='ar71xx/generic' DISTRIB_DESCRIPTION='OpenWrt Chaos Calmer 15.05.1' DISTRIB_TAINTS='no-all no-ipv6 busybox'
OpenWrt only provides the SDK for Linux as a host platform, so if you are running on Windows or OSX you’ll need a virtual machine to follow the steps.
OpenWRT SDK Setup
To make sure our toolchain works we can compile and run a simple “Hello World” from C code by using the SDK directly. This will make sure that we have everything set up properly, since Rust builds on those binaries at a later stage.
Unpack the archive and add these environment variables to your
.bashrc (or equivalent) so that you don’t run into path issues.
export PATH=$PATH:~/openwrt/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/ export STAGING_DIR=~/openwrt/staging_dir/
We are adding the SDK
gcc binaries to our
PATH and also provide the
STAGING_DIR environment variable that the SDK needs while building. Note that I’ve renamed the
OpenWrt-SDK-15.05.1-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64 (wow…) directory after extraction simply to
openwrt so its easier to read the command line. Note: make sure to reload/source your
.bashrc file if you want to stay in the current terminal or open a new one so the changes take effect.
Here is a very simple hello world in C (
You can compile it with:
$ mips-openwrt-linux-gcc hello.c -o hello
If it compiles without issues, copy it to the device and run it!
$ scp hello [email protected]:~ $ ssh [email protected] root@Pineapple:~# ./hello Hello, World! root@Pineapple:~#
At this point we know that the SDK is set up correctly and we can compile and run C code on the pineapple just fine.
To proceed, we need a nightly rust version, ideally set up through rustup which is the preferred way to manage your rust environments by now.
$ rustup install nightly $ rustup default nightly
Don’t forget to add the source component, otherwise it won’t work down the road:
$ rustup component add rust-src
Finally, you should end up with a version similar to mine:
$ rustc -Vv rustc 1.16.0-nightly (4ecc85beb 2016-12-28) binary: rustc commit-hash: 4ecc85beb339aa8089d936e450b0d800bdf580ae commit-date: 2016-12-28 host: x86_64-unknown-linux-gnu release: 1.16.0-nightly LLVM version: 3.9
xargo and wait for it to compile:
$ cargo install xargo
We are pretty much set now, but we haven’t figured out which
rustc target to use. Looking at the long output of
rustc --print target-list, the one we need to use is mips-unknown-linux-uclibc (since we are compiling against the
mips architecture and this OpenWRT version is compiled with uclibc)
Let’s create a hello world project with cargo:
$ cargo new hello --bin Created binary (application) `hello` project
Before we can build it, there are a couple modifications we need to make. First, we need to adapt the
panic. I don’t know why this is the case, but the
xargo README says we
should do it and it also fails to compile otherwise.
$ cat Cargo.toml [package] name = "hello" version = "0.1.0" authors = ["you"] [dependencies] [profile.dev] panic = "abort" [profile.release] panic = "abort"
Next, we need to tell
cargo to use a different linker for our
mips target since the one on our host doesn’t work for our target. This is done by providing a custom config within the
$ cat .cargo/config [build] target = "mips-unknown-linux-uclibc" [target.mips-unknown-linux-uclibc] linker = "mips-openwrt-linux-gcc"
Finally, we need to add a
Xargo.toml in order to tell
xargo to also build the standard library for us:
$ cat Xargo.toml [target.mips-unknown-linux-uclibc.dependencies.std] features = 
Now we are all set! Run
xargo build (not
cargo build) and watch the magic happen. The first time
xargo will build the standard library for you, but subsequent builds will just reuse the compiled libraries so its even quicker. The second time it will look like this:
$ xargo build Compiling hello v0.1.0 (file:///home/vagrant/src/hello) Finished debug [unoptimized + debuginfo] target(s) in 0.17 secs
Looking into our
target/ directory we can see that there is now a
available for our
~/src/hello$ tree target/ target/ |-- debug | |-- build | |-- deps | |-- examples | `-- native `-- mips-unknown-linux-uclibc `-- debug |-- build |-- deps | `-- hello-e7c80b1b0618ec37 |-- examples |-- hello <----------- this one ------ `-- native
Of course, running it on our host won’t work:
$ ./target/mips-unknown-linux-uclibc/debug/hello -bash: ./target/mips-unknown-linux-uclibc/debug/hello: cannot execute binary file: Exec format error
As a final step, copy the file over to your pineapple and run it (here I’m copying it to the mounted SD card just for fun):
$ scp target/mips-unknown-linux-uclibc/debug/hello email@example.com:/sd $ ssh [email protected] '/sd/hello' Hello, world!
That’s it! We just cross-compiled and ran a Rust program on a WiFi Pineapple.
One last thing…
While researching this topic I was made aware that there is still an open issue for the libc crate to fully support the
mips-unknown-linux-uclibc target. It seems to work mostly but until the issue is resolved keep in mind that you might run into some weird issues.
Let me know if I missed anything or if you are also interested in running Rust on the WiFi Pineapple or similar OpenWrt devices! I’d like to close this post with a special thanks to Jorge Aparicio who is tirelessly working on making the cross compilation and embedded experience with Rust better every day (xargo, trust, cross,…).