ここまで、デバイスの起動と割り込み処理とを、1行のアセンブリも書くことなくうまくやって来ました。 これはかなりの偉業です!しかし、ターゲットアーキテクチャ次第では、 ここまで到達するためにアセンブリが必要になるかもしれません。 他にも、コンテキストスイッチのようなアセンブリを必要とする操作があります。
問題は、インラインアセンブリ(asm!
)も自由形式アセンブリ(global_asm!
)もunstableなことです。
そして、これらがいつ安定化されるかは分かっていないため、stableでは使えません。
これから説明するように、いくつかのワークアラウンドがあるため、致命的な問題ではありません。
本セクションの動機付けとして、HardFault
ハンドラを、
例外を発生させたスタックフレームの情報を提供するように修正します。
やりたいことは下記の通りです。
ベクタテーブルにユーザーがHardFault
ハンドラを直接配置する代わりに、
rt
クレートがユーザー定義のHardFault
をトランポリンするハンドラをベクタテーブルに配置します。
$ tail -n36 ../rt/src/lib.rs
{{#include ../ci/asm/rt/src/lib.rs:61:96}}
このトランポリンはスタックポインタを読んで、ユーザーのHardFault
ハンドラを呼びます。
トランポリンはアセンブリで次のように書かなければなりません。
{{#include ../ci/asm/rt/asm.s:5:6}}
ARM ABIでは、このメインスタックポインタ(MSP; Main Stack Pointer)の設定は、HardFault
関数/ルーチンの第一引数になります。
このMSPの値は、例外によってスタックにプッシュされたレジスタへのポインタです。
これらの変更により、ユーザーのHardFault
ハンドラは、fn(&StackedRegisters) -> !
というシグネチャを持たなければなりません。
stableでアセンブリを書く方法の1つは、アセンブリを外部ファイルに書くことです。
$ cat ../rt/asm.s
{{#include ../ci/asm/rt/asm.s}}
そして、rt
クレートのビルドスクリプト内で、アセンブリファイルをオブジェクトファイル(.o
)にアセンブルし、
アーカイブ(.a
)にするために、cc
クレートを使います。
$ cat ../rt/build.rs
{{#include ../ci/asm/rt/build.rs}}
$ tail -n2 ../rt/Cargo.toml
{{#include ../ci/asm/rt/Cargo.toml:7:8}}
これで全てです!
とても簡単なプログラムを書くだけで、ベクタテーブルがHardFaultTrampoline
へのポインタを持つことが確認できます。
{{#include ../ci/asm/app/src/main.rs}}
逆アセンブリの結果は、以下の通りです。HardFaultTrampoline
のアドレスを見て下さい。
$ cargo objdump --bin app --release -- -d -no-show-raw-insn -print-imm-hex
{{#include ../ci/asm/app/release.objdump}}
注記 この逆アセンブリ結果を小さくするために、RAMの初期化をコメントアウトしています。
ここで、ベクタテーブルを見ます。
4つ目のエントリは、HardFaultTrampoline
に1を足したアドレスになっているはずです。
$ cargo objdump --bin app --release -- -s -j .vector_table
{{#include ../ci/asm/app/release.vector_table}}
cc
クレートを使う欠点は、ビルドマシンにアセンブラプログラムが必要なことです。
例えば、ARM Cortex-Mをターゲットにする時、cc
クレートはアセンブラとしてarm-none-eabi-gcc
を使います。
ビルドマシン上でファイルをアセンブルする代わりに、rt
クレートと一緒にあらかじめアセンブルしたファイルを配布できます。
この方法なら、ビルドマシンにアセンブラプログラムは必要ありません。
しかしながら、rtクレートをパッケージして発行するマシン上には、アセンブラが必要です。
アセンブリファイル(.s
)と、コンパイルしたオブジェクトファイル(.o
)とは、それほど違いがありません。
アセンブラは最適化を行いません。単純にターゲットアーキテクチャ向けに正しいオブジェクトファイル形式を選ぶだけです。
Cargoは、クレートとアーカイブ(.a
)をまとめる機能を提供しています。ar
コマンドを使ってオブジェクトファイルをアーカイブにパッケージできます。
その後、アーカイブをクレートにまとめます。実は、これはcc
クレートが行っていることなのです。
ccクレートが呼び出しているコマンドは、target
ディレクトリのoutput
という名前のファイルを探すと見つかります。
$ grep running $(find target -name output)
running: "arm-none-eabi-gcc" "-O0" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-fno-omit-frame-pointer" "-mthumb" "-march=armv7-m" "-Wall" "-Wextra" "-o" "/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out/asm.o" "-c" "asm.s"
running: "ar" "crs" "/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out/libasm.a" "/home/japaric/rust-embedded/embedonomicon/ci/asm/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out/asm.o"
$ grep cargo $(find target -name output)
cargo:rustc-link-search=/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out
cargo:rustc-link-lib=static=asm
cargo:rustc-link-search=native=/tmp/app/target/thumbv7m-none-eabi/debug/build/rt-6ee84e54724f2044/out
アーカイブを作成するために似たことを行います。
$ # `cc`が使う多くのフラグはアセンブル時には意味がないため、それらは取り除きます
$ arm-none-eabi-as -march=armv7-m asm.s -o asm.o
$ ar crs librt.a asm.o
$ arm-none-eabi-objdump -Cd librt.a
{{#include ../ci/asm/rt2/librt.objdump}}
次に、rt
rlibにアーカイブをまとめるために、ビルドスクリプトを修正します。
$ cat ../rt/build.rs
{{#include ../ci/asm/rt2/build.rs}}
ここで、新バージョンが前のシンプルなプログラムと同じ出力をすることをテストできます。
$ cargo objdump --bin app --release -- -d -no-show-raw-insn -print-imm-hex
{{#include ../ci/asm/app2/release.objdump}}
注記 前回同様、逆アセンブリの結果を小さくするために、RAMの初期化をコメントアウトしています。
$ cargo objdump --bin app --release -- -s -j .vector_table
{{#include ../ci/asm/app2/release.vector_table}}
あらかじめアセンブルしたアーカイブを配布する欠点は、最悪の場合、 ライブラリがサポートするターゲットごとにビルド生成物を配布しないといけないことです。