knurling-rs のツールお試し Get a grip on bare-metal Rust!
はじめに
この記事は Rust Advent Calendar 2020 12日目の記事です。
Our mission is to improve the embedded Rust experience. To achieve this, we build and improve tools and create learning materials.
knurling-rs は Ferrous Systems による組込み Rust の開発経験を向上するためのツールや教材を作成するプロジェクトです。 本記事では、knurling-rs で提供されているツールを紹介します。
ターゲットボードにはアプリケーションテンプレートの手順で説明されている nRF52840-DK を使います。
プロジェクト成果物一覧 (2020/12時点)
次の3つのツールが公開されています。
- probe-run : 組込み Rust のプログラムをネイティブと同じように実行します
- defmt : 効率的な文字列フォーマットを提供します
- flip-link : ゼロコストなスタックオーバーフロー保護機能を提供します
probe-run
Cargo のカスタムランナーで、RTT 経由でプログラムの出力を表示します。 ブレイクポイントでスタックバックトレースを表示してファームウェア実行を終了することができます。
UART を接続して他ターミナルを立ち上げなくても、ファームウェアからの出力を見ることができて、とっても便利です。 バックトレース表示してくれるのも Good です。
defmt
defmt ("de format", short for "deferred formatting") is a highly efficient logging framework that targets resource-constrained devices, like microcontrollers.
ということで、可能な文字列フォーマットはホスト側で行う仕組みになっており、マイコン側の負担が軽減されています。
flip-link
スタックオーバーフローを検出してくれます。組込み開発では気づきにくいバグなので、ありがたいですね。 現在は ARM Cortex-M でのテストが実施されています。
GitHub Sponsor でスポンサーになると、公開前のツールや教材をいち早く試すことができます。 現在で言うと、組込み Rust の教材 knurling-books はスポンサーのみが見ることができます。
サンプルアプリケーションを動かしてみる
セットアップ
Linux では libusb と libudev が必要です。
Knurling のツールをセットアップします。
$ cargo install flip-link $ cargo install probe-run $ cargo install cargo-generate
sudo なしでデバイスにアクセスできるように、udev ルールを設定しておきます。
/etc/udev/rules.d/99-nrf.rules
# udev rules to allow access to USB devices as a non-root user # nRF52840 Development Kit ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1015", TAG+="uaccess"
アプリケーションテンプレートの作成
$ cargo generate \ --git https://github.com/knurling-rs/app-template \ --branch main \ --name my-app
アプリケーションテンプレートの修正
nRF52840-DK 用に修正します。 これも手順通りです。
.cargo/config.toml
-runner = "probe-run --chip $CHIP --defmt" +runner = "probe-run --chip nRF52840_xxAA --defmt"
デフォルトだと Cortex-M0 のターゲットトリプルが指定されているので、Cortex-M4F をターゲットにする。
.cargo/config.toml
-target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ +# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ # target = "thumbv7m-none-eabi" # Cortex-M3 # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) -# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
Cargo.toml
-# some-hal = "1.2.3" +nrf52840-hal = "0.11.0"
src/lib.rs
-// use some_hal as _; // memory layout +use nrf52840_hal as _;
実行
nRF52840-DK を USB ケーブルでホスト PC に接続して、実行します。
$ cargo rb hello Finished dev [optimized + debuginfo] target(s) in 0.02s Running `probe-run --chip nRF52840_xxAA --defmt target/thumbv7em-none-eabihf/debug/hello` (HOST) INFO flashing program (8.14 KiB) (HOST) INFO success! ──────────────────────────────────────────────────────────────────────────────── 0.000000 INFO Hello, world! └─ hello::__cortex_m_rt_main @ src/bin/hello.rs:8 stack backtrace: 0: __bkpt 1: my_app::exit at src/lib.rs:29 2: hello::__cortex_m_rt_main at src/bin/hello.rs:10 3: main at src/bin/hello.rs:6 4: ResetTrampoline at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:547 5: Reset at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:550
Hello, world!
が出力されてスタックトレースが表示されました。
サンプルコードを拝見
サンプルコードの中身を少し見てみます。
まず、src/lib.rs
です。
panic-probe
や defmt
で使うグローバルオブジェクトや関数が実装されているようです。
src/lib.rs
#![no_std] use core::sync::atomic::{AtomicUsize, Ordering}; use defmt_rtt as _; // global logger use nrf52840_hal as _; use panic_probe as _; // same panicking *behavior* as `panic-probe` but doesn't print a panic message // this prevents the panic message being printed *twice* when `defmt::panic` is invoked #[defmt::panic_handler] fn panic() -> ! { cortex_m::asm::udf() } #[defmt::timestamp] fn timestamp() -> u64 { static COUNT: AtomicUsize = AtomicUsize::new(0); // NOTE(no-CAS) `timestamps` runs with interrupts disabled let n = COUNT.load(Ordering::Relaxed); COUNT.store(n + 1, Ordering::Relaxed); n as u64 } /// Terminates the application and makes `probe-run` exit with exit-code = 0 pub fn exit() -> ! { loop { cortex_m::asm::bkpt(); } }
hello.rs ではログレベル INFO で Hello, world
を出力したあとに、my_app::exit()
を呼んでいます。
cortex_m::asm::bkpt()
の実行でバックトレースを吐き出して、プログラムが停止するようになっているみたいです。
src/bin/hello.rs
#![no_main] #![no_std] use my_app as _; // global logger + panicking-behavior + memory layout #[cortex_m_rt::entry] fn main() -> ! { defmt::info!("Hello, world!"); my_app::exit() }
src/bin
下には他にも5つのサンプルがあります。
$ ls bitfield.rs format.rs hello.rs levels.rs overflow.rs panic.rs
panic.rs
は、panic 発生時にバックトレースが出力されることが確認できます。
src/bin/panic.rs
#![no_main] #![no_std] use my_app as _; // global logger + panicking-behavior + memory layout #[cortex_m_rt::entry] fn main() -> ! { defmt::info!("main"); defmt::panic!() }
実行すると次のようになります。
$ cargo rb panic Compiling my-app v0.1.0 (/home/tomoyuki/rust/my-app) Finished dev [optimized + debuginfo] target(s) in 0.25s Running `probe-run --chip nRF52840_xxAA --defmt target/thumbv7em-none-eabihf/debug/panic` (HOST) INFO flashing program (8.22 KiB) (HOST) INFO success! ──────────────────────────────────────────────────────────────────────────────── 0.000000 INFO main └─ panic::__cortex_m_rt_main @ src/bin/panic.rs:8 0.000001 ERROR panicked at 'explicit panic' └─ panic::__cortex_m_rt_main @ src/bin/panic.rs:10 stack backtrace: 0: HardFaultTrampoline <exception entry> 1: __udf 2: cortex_m::asm::udf at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.4/src/asm.rs:104 3: _defmt_panic at src/lib.rs:15 4: defmt::export::panic at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/defmt-0.1.3/src/export.rs:204 5: panic::__cortex_m_rt_main at src/bin/panic.rs:10 6: main at src/bin/panic.rs:6 7: ResetTrampoline at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:547 8: Reset at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:550
overflow.rs
は、スタックオーバーフローを検出する flip-link
crate の動作を確認するものです。
わざと大きなデータを ack
関数の入り口で確保して、ひたすら再帰呼び出しします。
src/bin/overflow.rs
#![no_main] #![no_std] use my_app as _; // global logger + panicking-behavior + memory layout #[cortex_m_rt::entry] fn main() -> ! { ack(10, 10); my_app::exit() } fn ack(m: u32, n: u32) -> u32 { defmt::info!("ack(m={:u32}, n={:u32})", m, n); let mut big = [2; 512]; if m == 0 { n + 1 } else { big[100] += 1; if n == 0 { ack(m - 1, 1) } else { ack(m - 1, ack(m, n - 1)) } } }
実行すると次のようになります。
$ cargo rb overflow Finished dev [optimized + debuginfo] target(s) in 0.76s Running `probe-run --chip nRF52840_xxAA --defmt target/thumbv7em-none-eabihf/debug/overflow` (HOST) INFO flashing program (8.67 KiB) (HOST) INFO success! ──────────────────────────────────────────────────────────────────────────────── 0.000000 INFO ack(m=10, n=10) └─ overflow::ack @ src/bin/overflow.rs:13 0.000001 INFO ack(m=10, n=9) └─ overflow::ack @ src/bin/overflow.rs:13 // ... 0.004313 INFO ack(m=1, n=1) └─ overflow::ack @ src/bin/overflow.rs:13 0.004314 INFO ack(m=1, n=0) └─ overflow::ack @ src/bin/overflow.rs:13 stack backtrace: 0: HardFaultTrampoline <exception entry> 1: {"package":"panic-probe","tag":"defmt_error","data":"{:str}{:str}","disambiguator":"18392481814486563737"} error: the stack appears to be corrupted beyond this point (HOST) ERROR the program has overflowed its stack
スタックオーバーフローしたことを最後に教えてくれていますね。
テスト
testsuite
下にテストコードがあります。defmt
の assert と assert_eq を使っていることと、#[defmt_test::tests]
のアトリビュートを tests モジュールに付与していること以外、普通のテストを同じような感じですね。
testsuite/tests/test.rs
#![no_std] #![no_main] use my_app as _; // memory layout + panic handler use defmt::{assert, assert_eq}; // See https://crates.io/crates/defmt-test/0.1.0 for more documentation (e.g. about the 'state' // feature) #[defmt_test::tests] mod tests { #[test] fn assert_true() { assert!(true) } #[test] fn assert_eq() { assert_eq!(24, 42, "TODO: write actual tests") } }
次のコマンドで実行できます。ちゃんとアサーションが失敗したときにはバックトレースを出力されています。
$ cargo test -p testsuite
0.000000 INFO running assert_true .. └─ test::tests::__defmt_test_entry @ tests/test.rs:9 0.000001 INFO .. assert_true ok └─ test::tests::__defmt_test_entry @ tests/test.rs:9 0.000002 INFO running assert_eq .. └─ test::tests::__defmt_test_entry @ tests/test.rs:9 0.000003 ERROR panicked at 'assertion failed: `(left == right)` left: `24`, right: `42`: TODO: write actual tests', testsuite/tests/test.rs:… └─ panic_probe::print_defmt::print @ /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:140 stack backtrace: 0: HardFaultTrampoline <exception entry> 1: __udf 2: cortex_m::asm::udf at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.4/src/asm.rs:104 3: rust_begin_unwind at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.1.0/src/lib.rs:75 4: core::panicking::panic_fmt at /rustc/04488afe34512aa4c33566eb16d8c912a3ae04f9/src/libcore/panicking.rs:85 5: test::tests::assert_eq at tests/test.rs:18 6: main at tests/test.rs:9 7: ResetTrampoline at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:547 8: Reset at /home/tomoyuki/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:550 error: test failed, to rerun pass '-p testsuite --test test'
素晴らしい。お手軽にベアメタルでもテストが書けますね!
knurling って何?
ハンドルの握り棒や検査具の丸棒など使用中に手や指が滑らないようにするため操作部分に付ける横、または斜めの凹凸をローレットといい、旋盤などで凹凸を付けることをローレット加工、その加工を行うときに使用する工具をローレット工具(またはローレット)という。また、転造法の一種でダイヤモンド形、七子目などの凹凸のあるロール(こま型エ具)を、回転しながら棒材の表面に押し付けてギザギザを付ける加工をいう。
へー!