組み込み開発では違うCPU向けにコードをコンパイルするためにクロスコンパイルをする必要がありますが、rust-embedded/crossを使うと簡単に出来るらしいので試してみます。
手元に実機が無いので今回はx86_64 (Intel Core i7-8700K)上でAArch64のコードを生成し実行する事を目標とします。
Setup
"Zero setup" cross compilation and "cross testing" of Rust crates
https://github.com/rust-embedded/cross
cargo install cross
でインストールされます。crossはdockerを使ってQEMU等のクロスコンパイル環境を用意してくれるので、ユーザー権限でdockerを使えるようにしておきます。
zero-setupらしいので手動で用意するのはここまでです!
QEMUで実行する
cargo new --bin hello_cross
これはデフォルトで次のようなコードを生成します
fn main() {
println!("Hello, world!");
}
これをクロスコンパイルしてQEMUで実行します
$ cross run --target aarch64-unknown-linux-gnu
Compiling hello_cross v0.1.0 (/project)
Finished dev [unoptimized + debuginfo] target(s) in 0.20s
Running `qemu-aarch64 /target/aarch64-unknown-linux-gnu/debug/hello_cross`
Hello, world!
簡単ですね。あれ、すごい簡単ですね(´・ω・`)??
何が起こっているのか
あまりに簡単だったのでどうやって動いているのか少し見てみましょう。
crossはいくつかの要素技術を組み合わせて自動化してくれています。
rustup/cargo
まずRustはLLVMをベースとしているので、LLVM IR経由でユーザーのコードを別CPU向けにクロスコンパイルできます。それ以外の部分、標準ライブラリ(libstd)やlibcore(core::
名前空間にあるstdより限定された最小限の実行時環境)はコンパイル済みのバイナリが多くのプラットフォーム向けにrustupで配布されており、cargoがリンクします。
rustup target add aarch64-unknown-linux-gnu
で手動でインストールできます。クロスコンパイルするには
cargo build --target aarch64-unknown-linux-gnu
のようにcargo
にターゲットを指定するだけでtarget/aarch64-unknown-linux-gnu
以下にバイナリが生成されます。
libstdやlibcoreをコンパイルする必要がある場合はxargoが必要でしたが、現在ではthumbv*-none-eabi*
やnvptx等の限定的な場合でしか必要ありません (crossはxargoを使う場合もサポートしています、詳しくはREADMEへ)。
QEMU
一方、別CPU向けにクロスコンパイルされたバイナリを実行するにはQEMUが使われています。
QEMUは機械全体をエミュレーションするシステムエミュレーションと呼ばれる環境と、Linuxのユーザーランドをエミュレーションするユーザーエミュレーションと呼ばれる環境がある。
https://ja.wikipedia.org/wiki/QEMU
らしいですが、crossが使用しているのはユーザランドのエミュレーションです。なのでこれを自分で構築しようと思うとQEMUをインストールする手順が必要になりますが (参考:DockerでユーザモードQEMUによるARMエミュレーション環境を構築する)、crossプロジェクトが用意してくれているdockerコンテナを使うことで環境構築が容易になっています。このコンテナにはQEMUだけでなく、Rustが依存しているOpenSSLや、Android NDKなども同梱されているようです。