How I Finally Cross-Compiled Rust App for Pi Zero (After Many Fails)
A debugging story of segmentation faults, ARM quirks, and Docker containers or how to compile a Rust application for the Pi Zero.
Introduction
I recently decided to create a simple TFT driver in Rust for my pwnagotchi running on Pi Zero, which had been gathering dust on my desk. The plan was to use the Pi Zero's SPI interface, write a Rust driver, and control it from a Python application. It seemed like a great way to explore a new setup and fill in some knowledge gaps.
But before diving into the driver, I figured I should start small and cross-compile a "Hello, World!" program on my PC and run it on the Pi Zero. How hard could it be?
Hello, World
The Pi Zero is a 32-bit ARMv6 device running Linux, so I needed the arm-none-linux-gnueabihf toolchain (the "hf" stands for hard float, which the Pi Zero uses). The latest version at the time of writing is 14.3 rel1.
After installing the toolchain, I set up a basic Rust project and configured .cargo/config.toml to specify the target and linker:
[target.arm-unknown-linux-gnueabihf]
linker = "arm-none-linux-gnueabihf-gcc"
[build]
target = "arm-unknown-linux-gnueabihf"
Next, I wrote the simplest possible "Hello, World!" Rust app:
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
println!("Hello, World!");
Ok(())
}
I built the project with:
cargo build --release --target arm-unknown-linux-gnueabihf
Then, I copied the cross-compiled binary to my Pi Zero and ran it, expecting to see the glorious "Hello, World!" output.
Instead, I got:
Whaaat...? Why...?
Down the Rabbit Hole
I started messing around with different rustc flags, trying -march=armv6 and even -mcpu=arm1176jzf-s (the Pi Zero’s CPU). Some builds resulted in "Illegal instruction" errors, which made me suspect I was targeting the wrong architecture.
Let me tell you, this part doesn’t capture the hours of Googling, forum-diving, and Stack Overflow I fell into... It was painful (╥﹏╥).
To rule out Rust doing something weird, I created an equivalent C program and compiled it with the same toolchain. Same result: "Segmentation fault".
I checked the binary’s attributes with:
$ arm-none-linux-gnueabihf-readelf -A hello
Attribute Section: aeabi
File Attributes
Tag_CPU_name: "7-A"
Tag_CPU_arch: v7
Ah! The target architecture was still ARMv7, even though I specified ARMv6 with -march=armv6. It turns out that ARMv7 is the baseline for most Debian-based ARM Linux distributions, and the Pi Zero’s ARMv6 is the odd one out.
I tried installing older toolchain versions (v4.9, v7.5, v9.2) that rumors on the internet suggested might support ARMv6 with -march=armv6. No luck.
I could compile the toolchain myself with ARMv6 support, but that seemed like overkill. Plus, most pre-built ARMv6 toolchains I found didn’t include Windows versions, which I needed.
Containers to the Rescue!
During my research, I stumbled upon cross, a tool for cross-compiling Rust in Docker containers. Perfect for cases where the mainline toolchain doesn’t support your target.
I installed cross:
cargo install cross
Then, I created a Cross.toml file in my project directory with the following configuration:
[target.arm-unknown-linux-gnueabihf]
image = "ghcr.io/cross-rs/arm-unknown-linux-gnueabihf:latest"
rustflags = [
"-C", "target-feature=-vfp2,-vfp3,-vfp4", # Pi Zero W doesn't have VFPv3+
]
[build]
target = "arm-unknown-linux-gnueabihf"
The Dockerfile for this image is available here.
Now, I could build my project using cross just like I would with cargo:
cross build --release --target arm-unknown-linux-gnueabihf
This time, the build succeeded! I double-checked the binary’s attributes:
$ arm-none-linux-gnueabihf-readelf -A hello
Attribute Section: aeabi
File Attributes
Tag_CPU_name: "6"
Tag_CPU_arch: v6
Success! The binary was now correctly compiled for ARMv6. I copied it to the Pi Zero and ran it:
./hello
Hello, World!
Finally!
Wrap-Up
So that’s how I failed, then succeeded at cross-compiling Rust for the Pi Zero. Maybe there are other ways to compile for ARMv6, but this method worked for me.
If you have more insights or alternative approaches, let me know! I’d love to hear about them! I will be revisiting cross-compiling C applications for the Pi Zero and even experimenting with building a custom Pi Zero kernel.
How to Use RTT in Embedded Rust: Setup and Logging
Learn how to use RTT in Embedded Rust for fast, non-blocking debug logging. This guide covers setup, rtt-target usage, OpenOCD configuration, and VS Code auto-start.
Prototyping in an Hour: A Small PCB and a Quiet Shift in Embedded Work
Designing and ordering a custom PCB in under an hour — a reflection on modern KiCad workflows and how rapid prototyping has quietly changed embedded development.
Remote View Raspberry Pi Camera Stream with Docker
Learn how to containerize the Raspberry Pi Camera Module using MediaMTX and Docker for low-latency remote debugging and visual feedback directly in VS Code.
Whether you're building something new, fixing stability issues, or automating what slows your team down — we can help.