RustでZephyrのアプリケーションを書く
最近、ZephyrというRTOSで遊んでいます。 新しい環境が手に入ると、Rustと結びつけたくなりますよね!
ということで、ZephyrのアプリケーションをRustで書いてみます。 ズルしましたけど、hello worldは動きました。
知見として、no_std
だけど完全なfree standing環境じゃない場合、一工夫必要そうな感触でした。
基本戦略
Zephyrをqemu_cortex_m3
ターゲットでビルドして、hello worldをします。
ベースとするのは、Zephyrのサンプルアプリです。
|> zephyr/samples/hello_world
を使います。中身は下のような感じです。
main.c
#include <zephyr.h> #include <misc/printk.h> void main(void) { printk("Hello World! %s\n", CONFIG_BOARD); }
Zephyrのプロジェクトでは、アプリケーションコードはライブラリ(libapp.a)としてビルドされ、最後にZephyr本体とリンクされます。。
|> <build>/app/libapp.a
Rustのプロジェクトをライブラリ(libapp.a)として作って、C言語で書かれたアプリケーションの代わりにリンクしてあげれば良いはずです。
Let's Hello World...できない!
ライブラリとしてプロジェクトを新規作成します。
cargo new --lib hello
プロジェクトの設定をします。
$ tail -n 3 Cargo.toml [lib] name = "app" crate-type = ["staticlib"]
Cortex-M3をデフォルトのビルドターゲットにします。
$ cat .cargo/config [build] target = "thumbv7m-none-eabi"
|> src/lib.rs
#![no_std] #[no_mangle] pub extern "C" fn main() -> ! { loop{} } use core::panic::PanicInfo; #[panic_handler] #[no_mangle] pub fn panic(_info: &PanicInfo) -> ! { loop{} }
とりあえず無限ループでQEMUが起動することを確認します。Zephyrでは、アプリケーションのエントリポイントはmain
という名前の関数なので、Rust側もそれに合わせます。
では、Rust側をビルドしましょう。
$ cargo build $ ls target/thumbv7m-none-eabi/debug/libapp.a target/thumbv7m-none-eabi/debug/libapp.a
無事、libapp.aができています。
Zephyr側も一度ビルドします。 そして、Zephyrのlibapp.aを、Rustで作ったlibapp.aに置き換えます。 これで、再度Zephyrをビルドすれば、Zephyr本体とRustで書いたライブラリがリンクされてめでたしめでたし、のはずです!
ninja arm-none-eabi/bin/ld: zephyr/lib/libc/minimal/liblib__libc__minimal.a(string.c.obj): in function `memcmp': zephyr/lib/libc/minimal/source/string/string.c:179: multiple definition of `memcmp'; app/libapp.a(compiler_builtins-f49b1fd2880b372d.compiler_builtins.9n9954vw-cgu.0.rcgu.o):/rustc/9fda7c2237db910e41d6a712e9a2139b352e558b//src/rustc/compiler_builtins_shim/../../libcompiler_builtins/src/mem.rs:55: first defined here ... # 以下、memmove, memcpy, memsetに関する同様のエラー collect2: error: ld returned 1 exit status % ninja: build stopped: subcommand failed.
ということで、memcmp
, memmove
, memcpy
, memset
の多重定義エラーが発生しました。忍者もしょんぼりです。
リンクエラーの調査
ZephyrはしっかりとしたOSなので、ユーザアプリケーションを書くために、最低限のlibcを提供しています。この中には、memcmpやmemmoveが含まれています。
一方、no_stdなRustでは、core libraryの機能を提供するために、compiler_builtinsというクレートを利用しています。 この中に、問題になっているmemcmpやmemmoveといった関数が、C言語のインタフェースで定義されています。
下記からソースコードを一部抜粋します。
compiler-builtins/mem.rs at master · rust-lang-nursery/compiler-builtins · GitHub
#[cfg_attr(all(feature = "mem", not(feature = "mangled-names")), no_mangle)] pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { let mut i = 0; while i < n { *dest.offset(i as isize) = *src.offset(i as isize); i += 1; } dest }
これらのシンボルがリンク時に衝突するため、リンクエラーになっていまいます。
応急処置
ということで、応急処置をして無理矢理動かします。
具体的には、Rust側のmemcmpやmemmoveのシンボルをweak
に書き換えます。
シンボルをweakにすれば、通常の定義があれば、weakな方は無視されます。
arm-none-eabi-objcopy --weaken-symbol=memmove --weaken-symbol=memcpy --weaken-symbol=memset --weaken-symbol=memcmp libapp.a
これでRust側のlibapp.a内にある邪魔なmemcmpなどのシンボルはweakになります。
$ ninja run [3/7] Linking C executable zephyr/zephyr_prebuilt.elf ... [6/7] Linking C executable zephyr/zephyr.elf ... [7/7] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3 ***** Booting Zephyr OS zephyr-v1.13.0-3321-g7f956a9 *****
リンクが通って、Zephyrが起動しました!
無理矢理Hello World
Zephyrのprintk()は次のように宣言されています。
void printk(const char *fmt, ...);
C言語のフォーマット形式をRustから呼び出すのは面倒くさそうなので、ラッパー関数を作ります。
void puts_c(const char *str) { printk("%s", str); }
面倒くさいので、Zephyr内(zephyr/misc/printk.c)に直接突っ込んでいます。良い子は真似しちゃダメだぞ★
後はRust側からこのputs_c()を呼び出すだけです。
extern "C" { fn puts_c(c_str: *const u8); } #[no_mangle] pub extern "C" fn main() -> ! { const HELLO: &[u8] = b"Hello from Rust.\0"; unsafe { puts_c(HELLO.as_ptr()) }; loop{} }
改めて、Rustのプロジェクトをビルドして、libapp.aを置き換えて、シンボルをweakにして、
$ ninja run ***** Booting Zephyr OS zephyr-v1.13.0-3321-g7f956a9 ***** Hello from Rust.
いぇーい!