Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

QEMU

まず、Cortex-M3 マイクロコントローラーである LM3S6965 向けのプログラムを 書き始めます。 これを最初のターゲットに選んだのは、QEMU を使ってエミュレートできるためで、 このセクションではハードウェアをいじる必要がなく、ツール類と 開発プロセスに集中できるからです。

重要 このチュートリアルでは、プロジェクト名として “app” という名前を使います。 “app” という単語を見かけたら、その箇所をあなたが選んだ プロジェクト名に置き換えてください。あるいは、プロジェクト名を “app” にして、 置き換えを不要にすることもできます。

標準的ではない Rust プログラムを作成する

新しいプロジェクトを生成するために、cortex-m-quickstart のプロジェクトテンプレートを 使います。作成されるプロジェクトには、最小限のアプリケーションが含まれます。これは 新しい組み込み Rust アプリケーションのよい出発点になります。さらに、プロジェクトには examples ディレクトリも含まれており、いくつかの独立したアプリケーションを通して、 組み込み Rust の主要な機能の一部を示しています。

cargo-generate を使う

まず cargo-generate をインストールします

cargo install cargo-generate

次に新しいプロジェクトを生成します

cargo generate --git https://github.com/knurling-rs/app-template
 Project Name: app
 Creating project called `app`...
 Done! New project created /tmp/app
cd app

git を使う

リポジトリをクローンします

git clone https://github.com/rust-embedded/cortex-m-quickstart app
cd app

その後、Cargo.toml ファイル内のプレースホルダーを埋めます

[package]
authors = ["{{authors}}"] # "{{authors}}" -> "John Smith"
edition = "2018"
name = "{{project-name}}" # "{{project-name}}" -> "app"
version = "0.1.0"

# ..

[[bin]]
name = "{{project-name}}" # "{{project-name}}" -> "app"
test = false
bench = false

どちらも使わない場合

cortex-m-quickstart テンプレートの最新スナップショットを取得して展開します。

curl -LO https://github.com/rust-embedded/cortex-m-quickstart/archive/master.zip
unzip master.zip
mv cortex-m-quickstart-master app
cd app

あるいは、cortex-m-quickstart をブラウザーで開き、緑色の “Clone or download” ボタンをクリックし、その後 “Download ZIP” をクリックしてもかまいません。

その後、「git を使う」版の後半で行ったのと同じように、 Cargo.toml ファイル内のプレースホルダーを埋めます。

プログラムの概要

便宜上、src/main.rs のソースコードの中で最も重要な部分を以下に示します:

#![no_std]
#![no_main]

use panic_halt as _;

use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    loop {
        // ここにコードを書きます
    }
}

このプログラムは標準的な Rust プログラムとは少し異なるので、 詳しく見ていきましょう。

#![no_std] は、このプログラムが標準クレートである stdリンクしない ことを示します。代わりに、そのサブセットである core クレートにリンクします。

#![no_main] は、このプログラムが大半の Rust プログラムで使われる標準の main インターフェースを使わないことを示します。no_main を使う主な(しゃれではなく)理由は、 no_std コンテキストで main インターフェースを使うには nightly が必要だからです。

use panic_halt as _;。このクレートは、プログラムの panic 時の挙動を定義する panic_handler を提供します。これについては、本書の パニックの章でさらに詳しく説明します。

#[entry] は、cortex-m-rt クレートが提供する属性で、 プログラムのエントリポイントを示すために使われます。標準の main インターフェースを使っていないため、プログラムのエントリポイントを示す 別の方法が必要であり、それが #[entry] です。

fn main() -> !。このプログラムはターゲット ハードウェア上で動作する 唯一 のプロセスなので、終了してほしくありません! そのことをコンパイル時に保証するために、発散関数(関数シグネチャ中の -> ! の部分)を使っています。

クロスコンパイル

まず最初に、ターゲットマイクロコントローラー、この場合は LM3S6965 のメモリーレイアウトが必要です。そうでないと、ビルド時にイメージのリンクに失敗します。プロジェクトの ルートに memory.x という名前のファイルを作成し、以下の内容を貼り付けてください:

MEMORY
{
  /* 注 1 K = 1 KiBi = 1024 バイト */
  /* TODO これらのメモリー領域を、使用するデバイスのメモリーレイアウトに合わせて調整してください */
  /* これらの値は、QEMU がエミュレートできる数少ないデバイスの 1 つである LM3S6965 に対応しています */
  FLASH : ORIGIN = 0x00000000, LENGTH = 256K
  RAM : ORIGIN = 0x20000000, LENGTH = 64K
}

/* ここにコールスタックが割り当てられます。 */
/* スタックはフルディセンディング型です。 */
/* この変数を使って、コールスタックと静的
   変数を異なるメモリー領域に配置したい場合があります。以下にデフォルト値を示します */
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */

/* このシンボルを使うと .text セクションの配置位置をカスタマイズできます */
/* 省略した場合、.text セクションは .vector_table
   セクションの直後に配置されます */
/* これは、ベクターテーブルの直後に何らかの設定を格納する
   マイクロコントローラーでのみ必要です */
/* _stext = ORIGIN(FLASH) + 0x400; */

/* 非初期化変数をカスタム RAM 領域に配置する例。 */
/* これは、上で RAM2 領域を定義しており、Rust
   ソースで、そこに配置したいデータに `#[link_section = ".ram2bss"]` 属性を追加している
   ことを前提としています。 */
/* このセクションはランタイムによってゼロ初期化されない点に注意してください! */
/* SECTIONS {
     .ram2bss (NOLOAD) : ALIGN(4) {
       *(.ram2bss);
       . = ALIGN(4);
     } > RAM2
   } INSERT AFTER .bss;
*/

次のステップは、Cortex-M3 アーキテクチャ向けにプログラムを クロスコンパイルすることです。コンパイルターゲット($TRIPLE)が 何であるべきかわかっていれば、cargo build --target $TRIPLE を実行するだけで済みます。 幸いなことに、テンプレート内の .cargo/config.toml にその答えがあります:

tail -n6 .cargo/config.toml
[build]
# これらのコンパイルターゲットのうち 1 つを選んでください
# target = "thumbv6m-none-eabi"    # Cortex-M0 と Cortex-M0+
target = "thumbv7m-none-eabi"    # Cortex-M3
# target = "thumbv7em-none-eabi"   # Cortex-M4 と Cortex-M7(FPU なし)
# target = "thumbv7em-none-eabihf" # Cortex-M4F と Cortex-M7F(FPU あり)

Cortex-M3 アーキテクチャ向けにクロスコンパイルするには thumbv7m-none-eabi を使う必要があります。このターゲットは、Rust ツールチェーンをインストールしただけでは自動的には追加されないので、まだであれば、 このタイミングでツールチェーンに追加しておくとよいでしょう:

rustup target add thumbv7m-none-eabi

thumbv7m-none-eabi コンパイルターゲットはすでに .cargo/config.toml ファイルでデフォルトに設定されているため、 以下の 2 つのコマンドは同じ意味になります:

cargo build --target thumbv7m-none-eabi
cargo build

確認

これで、target/thumbv7m-none-eabi/debug/app にネイティブではない ELF バイナリができました。これを cargo-binutils を使って調べることができます。

cargo-readobj を使うと ELF ヘッダーを表示でき、これが ARM バイナリであることを確認できます。

cargo readobj --bin app -- --file-headers

以下に注意してください。

  • --bin apptarget/$TRIPLE/debug/app にあるバイナリを調べるための糖衣構文です
  • --bin app は必要に応じてバイナリを(再)コンパイルします
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0x0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x405
  Start of program headers:          52 (bytes into file)
  Start of section headers:          153204 (bytes into file)
  Flags:                             0x5000200
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         2
  Size of section headers:           40 (bytes)
  Number of section headers:         19
  Section header string table index: 18

cargo-size は、バイナリのリンカーセクションのサイズを表示できます。

cargo size --bin app --release -- -A

最適化されたバージョンを調べるために --release を使います

app  :
section             size        addr
.vector_table       1024         0x0
.text                 92       0x400
.rodata                0       0x45c
.data                  0  0x20000000
.bss                   0  0x20000000
.debug_str          2958         0x0
.debug_loc            19         0x0
.debug_abbrev        567         0x0
.debug_info         4929         0x0
.debug_ranges         40         0x0
.debug_macinfo         1         0x0
.debug_pubnames     2035         0x0
.debug_pubtypes     1892         0x0
.ARM.attributes       46         0x0
.debug_frame         100         0x0
.debug_line          867         0x0
Total              14570

ELF リンカーセクションのおさらい

  • .text にはプログラム命令が含まれます
  • .rodata には文字列のような定数値が含まれます
  • .data には、初期値がゼロではない静的に確保された変数が含まれます
  • .bss にも、初期値がゼロである静的に確保された変数が含まれます
  • .vector_table は、ベクターテーブル(割り込みテーブル)を格納するために使用する、標準外のセクションです
  • .ARM.attributes.debug_* セクションにはメタデータが含まれており、バイナリを書き込む際にターゲットへロードされることはありません

重要: ELF ファイルにはデバッグ情報のようなメタデータが含まれているため、ディスク上のサイズは、そのプログラムをデバイスに書き込んだときに占有する容量を正確には反映しません。バイナリの実際の大きさを確認するには、必ず cargo-size を使ってください。

cargo-objdump を使うと、バイナリを逆アセンブルできます。

cargo objdump --bin app --release -- --disassemble --no-show-raw-insn --print-imm-hex

注記 上記のコマンドで Unknown command line argument というエラーが出る場合は、次のバグレポートを参照してください: https://github.com/rust-embedded/book/issues/269

注記 この出力はあなたのシステムでは異なる場合があります。新しいバージョンの rustc、LLVM、およびライブラリは、異なるアセンブリを生成することがあります。スニペットを小さく保つために、一部の命令は省略しています。

app:  file format ELF32-arm-little

Disassembly of section .text:
main:
     400: bl  #0x256
     404: b #-0x4 <main+0x4>

Reset:
     406: bl  #0x24e
     40a: movw  r0, #0x0
     < .. truncated any more instructions .. >

DefaultHandler_:
     656: b #-0x4 <DefaultHandler_>

UsageFault:
     657: strb  r7, [r4, #0x3]

DefaultPreInit:
     658: bx  lr

__pre_init:
     659: strb  r7, [r0, #0x1]

__nop:
     65a: bx  lr

HardFaultTrampoline:
     65c: mrs r0, msp
     660: b #-0x2 <HardFault_>

HardFault_:
     662: b #-0x4 <HardFault_>

HardFault:
     663: <unknown>

実行

次に、QEMU 上で組み込みプログラムを実行する方法を見ていきましょう! 今回は、実際に何かを行う hello の例を使います。デフォルトでは、この例は [defmt] と RTT を使ってテキストを出力します。

注記 defmt は、Embedded Rust エコシステムで広く使われているサードパーティ依存関係(つまりコア以外)です。

ホスト側で defmt が生成したメッセージを読み取ってデコードするには、RTT のトランスポート出力を semihosting に切り替える必要があります。実機を使う場合、これにはデバッグセッションが必要ですが、QEMU を使う場合はそのままで動作します。

依存関係を切り替えましょう。

cargo remove defmt-rtt
cargo add defmt-semihosting

src/lib.rs を開き、use defmt_rtt as _;use defmt_semihosting as _; に置き換えてください。

これで、このサンプルをビルドできます。

cargo build --bin hello

出力されるバイナリは target/thumbv7m-none-eabi/debug/hello に配置されます。

このバイナリを QEMU 上で実行するには、通常は次のコマンドで十分です。

qemu-system-arm \
  -cpu cortex-m3 \
  -machine lm3s6965evb \
  -nographic \
  -semihosting-config enable=on,target=native \
  -kernel target/thumbv7m-none-eabi/debug/hello

ただし今回のケースでは defmt を使っているため、ホストは出力をデコードできません。代わりに、Ferrous Systems が提供する qemu-run というツールが必要になります。

git clone git@github.com:knurling-rs/defmt.git
cd defmt/qemu-run/
cargo run -- --machine lm3s6965evb ../qemu-rs/target/thumbv7m-none-eabi/debug/hello
Hello, world!

このコマンドは、テキストを出力したあと正常に終了するはずです(終了コード = 0)。*nix では、次のコマンドでそれを確認できます。

echo $?
0

その QEMU コマンドを分解して見ていきましょう。

  • qemu-system-arm。これは QEMU エミュレータです。QEMU のバイナリにはいくつかの種類があり、これは ARM マシン全体の システム エミュレーションを行うものなので、この名前になっています。

  • -cpu cortex-m3。これは QEMU に Cortex-M3 CPU をエミュレートするよう指示します。CPU モデルを明示することで、いくつかの誤コンパイルを検出できます。たとえば、ハードウェア FPU を持つ Cortex-M4F 向けにコンパイルされたプログラムを実行すると、QEMU は実行中にエラーになります。

  • -machine lm3s6965evb。これは QEMU に LM3S6965EVB をエミュレートするよう指示します。これは LM3S6965 マイクロコントローラを搭載した評価ボードです。

  • -nographic。これは QEMU に GUI を起動しないよう指示します。

  • -semihosting-config (..)。これは QEMU に semihosting を有効にするよう指示します。semihosting を使うと、エミュレートされたデバイスは、とりわけ、ホストの stdout、stderr、stdin を使用したり、ホスト上にファイルを作成したりできます。

  • -kernel $file。これは QEMU に、どのバイナリをロードしてエミュレートされたマシン上で実行するかを指示します。

この長い QEMU コマンドを毎回入力するのは大変です! この手順を簡単にするために、カスタム runner を設定できます。.cargo/config.toml には、QEMU を呼び出す runner がコメントアウトされた状態で入っています。これをコメント解除しましょう。

head -n3 .cargo/config.toml
[target.thumbv7m-none-eabi]
# `cargo run` が QEMU 上でプログラムを実行するようにするには、これのコメントを解除します
runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"

このランナーは、デフォルトのコンパイルターゲットである thumbv7m-none-eabi ターゲットにのみ適用されます。これで cargo run はプログラムをコンパイルし、 QEMU 上で実行します:

cargo run --example hello --release
   Compiling app v0.1.0 (file:///tmp/app)
    Finished release [optimized + debuginfo] target(s) in 0.26s
     Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel target/thumbv7m-none-eabi/release/examples/hello`
Hello, world!

デバッグ

デバッグは組み込み開発において極めて重要です。どのように行うのか見ていきましょう。

組み込みデバイスのデバッグでは リモート デバッグを行います。これは、デバッグ したいプログラムが、デバッガプログラム(GDB または LLDB)を実行している マシン上では動作しないためです。

リモートデバッグにはクライアントとサーバーが関わります。QEMU を使う構成では、 クライアントは GDB(または LLDB)のプロセスであり、サーバーは組み込み プログラムも実行している QEMU プロセスです。

このセクションでは、すでにコンパイルした hello のサンプルを使用します。

デバッグの最初の手順は、QEMU をデバッグモードで起動することです:

qemu-system-arm \
  -cpu cortex-m3 \
  -machine lm3s6965evb \
  -nographic \
  -semihosting-config enable=on,target=native \
  -gdb tcp::3333 \
  -S \
  -kernel target/thumbv7m-none-eabi/debug/examples/hello

このコマンドはコンソールに何も出力せず、端末を占有したままになります。今回は 追加で 2 つのフラグを指定しています:

  • -gdb tcp::3333。これは、TCP ポート 3333 での GDB 接続を待つよう QEMU に 指示します。

  • -S。これは、起動時にマシンを停止したままにするよう QEMU に指示します。 これがないと、デバッガを起動する前にプログラムが main の終わりまで到達して しまいます!

次に、別の端末で GDB を起動し、このサンプルのデバッグシンボルを読み込むよう 指示します:

gdb-multiarch -q target/thumbv7m-none-eabi/debug/examples/hello

注記: インストールの章でどれをインストールしたかによっては、gdb-multiarch ではなく別のバージョンの gdb が必要になるかもしれません。たとえば arm-none-eabi-gdb や単に gdb の場合もあります。

その後、GDB シェル内で、TCP ポート 3333 で接続を待っている QEMU に接続します。

target remote :3333
Remote debugging using :3333
Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473
473     pub unsafe extern "C" fn Reset() -> ! {

プロセスが停止しており、プログラムカウンタが Reset という名前の関数を指して いることが分かります。これがリセットハンドラです。Cortex-M コアはブート時に これを実行します。

環境によっては、上に示したように Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473 の行が表示される代わりに、gdb が次のような警告を表示することがあります:

core::num::bignum::Big32x40::mul_small () at src/libcore/num/bignum.rs:254 src/libcore/num/bignum.rs: No such file or directory.

これは既知の不具合です。これらの警告はそのまま無視して構いません。おそらく Reset() にいます。

このリセットハンドラは最終的に私たちの main 関数を呼び出します。ブレーク ポイントと continue コマンドを使って、そこまで一気に進みましょう。 ブレークポイントを設定するために、まず list コマンドでコードのどこで停止 したいか確認しましょう。

list main

これで、examples/hello.rs ファイルのソースコードが表示されます。

6       use panic_halt as _;
7
8       use cortex_m_rt::entry;
9       use cortex_m_semihosting::{debug, hprintln};
10
11      #[entry]
12      fn main() -> ! {
13          hprintln!("Hello, world!").unwrap();
14
15          // QEMU を終了

“Hello, world!” の直前、つまり 13 行目にブレークポイントを追加したいので、 break コマンドを使います:

break 13

これで continue コマンドを使って、main 関数まで実行するよう gdb に指示 できます:

continue
Continuing.

Breakpoint 1, hello::__cortex_m_rt_main () at examples\hello.rs:13
13          hprintln!("Hello, world!").unwrap();

これで “Hello, world!” を出力するコードの直前まで来ました。next コマンドで先に進みましょう。

next
16          debug::exit(debug::EXIT_SUCCESS);

この時点で、qemu-system-arm を実行している端末に “Hello, world!” が表示 されるはずです。

$ qemu-system-arm (..)
Hello, world!

もう一度 next を実行すると、QEMU プロセスが終了します。

next
[Inferior 1 (Remote target) exited normally]

これで GDB セッションを終了できます。

quit