stableでのアセンブリ
ここまで、デバイスの起動と割り込み処理とを、1行のアセンブリも書くことなくうまくやって来ました。 これはかなりの偉業です!しかし、ターゲットアーキテクチャ次第では、 ここまで到達するためにアセンブリが必要になるかもしれません。 他にも、コンテキストスイッチのようなアセンブリを必要とする操作があります。
問題は、インラインアセンブリ(asm!
)も自由形式アセンブリ(global_asm!
)もunstableなことです。
そして、これらがいつ安定化されるかは分かっていないため、stableでは使えません。
これから説明するように、いくつかのワークアラウンドがあるため、致命的な問題ではありません。
本セクションの動機付けとして、HardFault
ハンドラを、
例外を発生させたスタックフレームの情報を提供するように修正します。
やりたいことは下記の通りです。
ベクタテーブルにユーザーがHardFault
ハンドラを直接配置する代わりに、
rt
クレートがユーザー定義のHardFault
をトランポリンするハンドラをベクタテーブルに配置します。
$ tail -n36 ../rt/src/lib.rs
#![allow(unused)] fn main() { extern "C" { fn NMI(); fn HardFaultTrampoline(); // <- 変更点! fn MemManage(); fn BusFault(); fn UsageFault(); fn SVCall(); fn PendSV(); fn SysTick(); } #[link_section = ".vector_table.exceptions"] #[no_mangle] pub static EXCEPTIONS: [Vector; 14] = [ Vector { handler: NMI }, Vector { handler: HardFaultTrampoline }, // <- 変更点! Vector { handler: MemManage }, Vector { handler: BusFault }, Vector { handler: UsageFault, }, Vector { reserved: 0 }, Vector { reserved: 0 }, Vector { reserved: 0 }, Vector { reserved: 0 }, Vector { handler: SVCall }, Vector { reserved: 0 }, Vector { reserved: 0 }, Vector { handler: PendSV }, Vector { handler: SysTick }, ]; #[no_mangle] pub extern "C" fn DefaultExceptionHandler() { loop {} } }
このトランポリンはスタックポインタを読んで、ユーザーのHardFault
ハンドラを呼びます。
トランポリンはアセンブリで次のように書かなければなりません。
mrs r0, MSP
b HardFault
ARM ABIでは、このメインスタックポインタ(MSP; Main Stack Pointer)の設定は、HardFault
関数/ルーチンの第一引数になります。
このMSPの値は、例外によってスタックにプッシュされたレジスタへのポインタです。
これらの変更により、ユーザーのHardFault
ハンドラは、fn(&StackedRegisters) -> !
というシグネチャを持たなければなりません。
.s
ファイル
stableでアセンブリを書く方法の1つは、アセンブリを外部ファイルに書くことです。
$ cat ../rt/asm.s
.section .text.HardFaultTrampoline
.global HardFaultTrampoline
.thumb_func
HardFaultTrampoline:
mrs r0, MSP
b HardFault
そして、rt
クレートのビルドスクリプト内で、アセンブリファイルをオブジェクトファイル(.o
)にアセンブルし、
アーカイブ(.a
)にするために、cc
クレートを使います。
$ cat ../rt/build.rs
use std::{env, error::Error, fs::File, io::Write, path::PathBuf}; use cc::Build; fn main() -> Result<(), Box<Error>> { // このクレートのビルドディレクトリです let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); // ライブラリサーチパスを追加します println!("cargo:rustc-link-search={}", out_dir.display()); // `link.x`をビルドディレクトリに置きます File::create(out_dir.join("link.x"))?.write_all(include_bytes!("link.x"))?; // `asm.s`ファイルをアセンブルします Build::new().file("asm.s").compile("asm"); // <- 追加! Ok(()) }
$ tail -n2 ../rt/Cargo.toml
[build-dependencies]
cc = "1.0.25"
これで全てです!
とても簡単なプログラムを書くだけで、ベクタテーブルがHardFaultTrampoline
へのポインタを持つことが確認できます。
#![no_main] #![no_std] use rt::entry; entry!(main); fn main() -> ! { loop {} } #[allow(non_snake_case)] #[no_mangle] pub fn HardFault(_ef: *const u32) -> ! { loop {} }
逆アセンブリの結果は、以下の通りです。HardFaultTrampoline
のアドレスを見て下さい。
$ cargo objdump --bin app --release -- -d -no-show-raw-insn -print-imm-hex
app: file format ELF32-arm-little
Disassembly of section .text:
HardFault:
b #-0x4 <HardFault>
main:
trap
Reset:
bl #-0x6
trap
DefaultExceptionHandler:
b #-0x4 <DefaultExceptionHandler>
UsageFault:
<unknown>
HardFaultTrampoline:
mrs r0, msp
b #-0x14 <HardFault>
注記 この逆アセンブリ結果を小さくするために、RAMの初期化をコメントアウトしています。
ここで、ベクタテーブルを見ます。
4つ目のエントリは、HardFaultTrampoline
に1を足したアドレスになっているはずです。
$ cargo objdump --bin app --release -- -s -j .vector_table
app: file format ELF32-arm-little
Contents of section .vector_table:
0000 00000120 45000000 4b000000 4d000000 ... E...K...M...
0010 4b000000 4b000000 4b000000 00000000 K...K...K.......
0020 00000000 00000000 00000000 4b000000 ............K...
0030 00000000 00000000 4b000000 4b000000 ........K...K...
.o
/ .a
ファイル
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
In archive librt.a:
asm.o: file format elf32-littlearm
Disassembly of section .text.HardFaultTrampoline:
00000000 <HardFaultTrampoline>:
0: f3ef 8008 mrs r0, MSP
4: e7fe b.n 0 <HardFault>
次に、rt
rlibにアーカイブをまとめるために、ビルドスクリプトを修正します。
$ cat ../rt/build.rs
use std::{ env, error::Error, fs::{self, File}, io::Write, path::PathBuf, }; fn main() -> Result<(), Box<Error>> { // このクレートのビルドディレクトリです let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); // ライブラリサーチパスを追加します println!("cargo:rustc-link-search={}", out_dir.display()); // `link.x`をビルドディレクトリに置きます File::create(out_dir.join("link.x"))?.write_all(include_bytes!("link.x"))?; // `librt.a`にリンクします fs::copy("librt.a", out_dir.join("librt.a"))?; // <- 追加! println!("cargo:rustc-link-lib=static=rt"); // <- 追加! Ok(()) }
ここで、新バージョンが前のシンプルなプログラムと同じ出力をすることをテストできます。
$ cargo objdump --bin app --release -- -d -no-show-raw-insn -print-imm-hex
app: file format ELF32-arm-little
Disassembly of section .text:
HardFault:
b #-0x4 <HardFault>
main:
trap
Reset:
bl #-0x6
trap
DefaultExceptionHandler:
b #-0x4 <DefaultExceptionHandler>
UsageFault:
<unknown>
HardFaultTrampoline:
mrs r0, msp
b #-0x14 <HardFault>
注記 前回同様、逆アセンブリの結果を小さくするために、RAMの初期化をコメントアウトしています。
$ cargo objdump --bin app --release -- -s -j .vector_table
app: file format ELF32-arm-little
Contents of section .vector_table:
0000 00000120 45000000 4b000000 4d000000 ... E...K...M...
0010 4b000000 4b000000 4b000000 00000000 K...K...K.......
0020 00000000 00000000 00000000 4b000000 ............K...
0030 00000000 00000000 4b000000 4b000000 ........K...K...
あらかじめアセンブルしたアーカイブを配布する欠点は、最悪の場合、 ライブラリがサポートするターゲットごとにビルド生成物を配布しないといけないことです。