Rust The Embedonomiconをやる①
良いものを発見したので、やってみましょう。
ターゲットはCortex-Mなので、一通り終わったら、RISC-Vをターゲットにやってみます。
Preface
次の3つのことを習得できるようです。
#[no_std]
アプリケーションのビルド- memory layout (linker)
- 静的にoverride可能なデフォルト機能の実装
People that are curious about the unusual implementation of runtime crates like cortex-m-rt, msp430-rt and riscv-rt
riscv-rtを読み解く上でも役に立ちそうです。
下準備です。
$ # Rust toolchain $ # If you start from scratch, get rustup from https://rustup.rs/ $ rustup default beta $ # toolchain should be newer than this one $ rustc -V rustc 1.30.0-beta.1 (14f51b05d 2018-09-18) $ rustup target add thumbv7m-none-eabi $ # cargo-binutils $ cargo install cargo-binutils $ rustup component add llvm-tools-preview $ # arm-none-eabi-gdb $ sudo apt-get install gdb-arm-none-eabi $ # QEMU $ sudo apt-get install qemu-system-arm
The smallest #![no_std]
program
std crateはOS上で動くことを前提としており、coreはbare metalを前提としている、このあたりは既知の内容ですね。
Rust 2018のプロジェクトを作成します。
$ cargo new --edition 2018 --bin app $ cd app
src/main.rsに次のコードを書きます。
#![no_main]
アトリビュートを付けないと、mainをエントリーポイントとするインタフェースが必要です。ベアメタル環境では、誰もmainに来る前の面倒を見てくれないので、忘れずにつけておきましょう。
#![no_main] #![no_std] use core::panic::PanicInfo; #[panic_handler] fn panic(_panic: &PanicInfo<'_>) -> ! { loop {} }
この時点では、動くバイナリはできません。
目次を見ていると、リンカスクリプトとboot strapを書く手順を踏んでいくのでしょう。
.cargo/config
を作成し、下記を書いておくことで、cargo実行時のターゲットがarm v7になります。
[build] target = "thumbv7m-none-eabi"
Memory Layout
Cortex-M3をターゲットにするようです。
Background information
- Cortex-Mはvector tableを必要とする
- リンカスクリプトでメモリレイアウトをコントロールできる
- Rustコンパイラはsymbolをmangleするので、
#![no_mangle]
アトリビュートをつける #![link_section]
アトリビュートでsymbolが置かれるセクションを指定できる
The Rust side
Cortex-Mリセット後に実行される関数を実装します。
#[no_mangle] pub unsafe extern "C" fn Reset() -> ! { let _x = 42; // can't return so we go into an infinite loop here loop {} } // The reset vector, a pointer into the reset handler #[link_section = ".vector_table.reset_vector"] #[no_mangle] pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset;
extern "C"でC言語のABIが使われるようになります。余談ですが、RustのABIは未定義です。
なるほど。これで、".vector_table.reset_vector
セクションに、RESET_VECTORシンボルが作成されるわけですね。
The linker script side
link.xを次の通り作成します。
/* Memory layout of the LM3S6965 microcontroller */ /* 1K = 1 KiBi = 1024 bytes */ MEMORY { FLASH : ORIGIN = 0x00000000, LENGTH = 256K RAM : ORIGIN = 0x20000000, LENGTH = 64K } /* The entry point is the reset handler */ ENTRY(Reset); EXTERN(RESET_VECTOR); SECTIONS { .vector_table ORIGIN(FLASH) : { /* First entry: initial Stack Pointer value */ LONG(ORIGIN(RAM) + LENGTH(RAM)); /* Second entry: reset vector */ KEEP(*(.vector_table.reset_vector)); } > FLASH .text : { *(.text .text.*); } > FLASH /DISCARD/ : { *(.ARM.exidx.*); } }
最後の、.ARM.exidx.*
は、例外ハンドラ用のセクションだそうです。Flashの領域には不要なので、捨てています。
Putting it all together
.cargo/config
にrustflagsを追加します。
[target.thumbv7m-none-eabi] rustflags = ["-C", "link-arg=-Tlink.x"] [build] target = "thumbv7m-none-eabi"
objdumpでバイナリを確認します。
$ cargo objdump --bin app -- -d -no-show-raw-insn Finished dev [unoptimized + debuginfo] target(s) in 0.00s app: file format ELF32-arm-little Disassembly of section .text: Reset: 8: sub sp, #4 a: movs r0, #42 c: str r0, [sp] e: b #-2 <Reset+0x8> 10: b #-4 <Reset+0x8>
.vector_table
セクションの内容を確認します。
$ cargo objdump --bin app -- -s -section .vector_table Finished dev [unoptimized + debuginfo] target(s) in 0.00s app: file format ELF32-arm-little Contents of section .vector_table: 0000 00000120 09000000 ... ....
0x2001_0000
となっており、リンカスクリプトで指定したスタックポインタと一致します。
Testing it
ターミナルを2つ用意し、一方でqemuを動かします。
$ qemu-system-arm \ -cpu cortex-m3 \ -machine lm3s6965evb \ -gdb tcp::3333 \ -S \ -nographic \ -kernel target/thumbv7m-none-eabi/debug/app
もう一方のターミナルでGDBを実行します。
$ arm-none-eabi-gdb -q target/thumbv7m-none-eabi/debug/app Reading symbols from target/thumbv7m-none-eabi/debug/app...done. (gdb) target remot :3333 Remote debugging using :3333 Reset () at src/main.rs:12 12 pub unsafe extern "C" fn Reset() -> ! { (gdb) print/x $sp $1 = 0x20010000 (gdb) step Reset () at src/main.rs:13 13 let _x = 42; (gdb) step 16 loop {} (gdb) print _x $2 = 42 (gdb) print &_x $3 = (i32 *) 0x2000fffc (gdb) quit
無事動作確認できました。