[Rust] ラズパイ2でベアメタルプログラミング入門(QEMU編)


RustのインストールからQEMU上のラズパイ2でベアメタル(OSなし)でプログラムが動作するところまでを解説。

リポジトリはこちら
https://github.com/maueki/baremetal-raspi2-qemu

# QEMUインストール

筆者の環境(Debian GNU/Linux)だと以下でインストール可能。

```bash
$ sudo apt install qemu-system-arm
```

# Rustのインストール

Rustのインストール・アップデートは[rustup](https://www.rust-lang.org/tools/install)でおこなう。

```bash
$ curl https://sh.rustup.rs -sSf | sh
```

すでにrustをインストールしてある場合は以下のコマンドで最新版にしておく。

```bash
$ rustup update
```

# クロスビルドツールのインストール

Rustを普通にインストールしただけではインストールしたマシン向けのバイナリしか作ることができない。
本来だとラズパイ２のCPUであるARM(cortex-A7)用のtoolchainをインストールして……となるところだが、そういった面倒な作業を設定ファイルに従って自動で行ってくれるツール[cargo-xbuild](https://github.com/rust-osdev/cargo-xbuild)(xargoのfork)をインストールする。

```bash
$ cargo install cargo-xbuild
```

# プロジェクト作成

Rustと一緒にインストールされる`cargo`を使ってプロジェクトの雛形を作る。

```bash
$ cargo new baremetal-raspi2 --bin
$ cd baremetal-raspi2
```

このプロジェクトで使用されるRustをnightlyに変更しておく。

```bash
$ rustup override add nightly
```

Rustにはstable、unstable、と実験的位置づけのnightlyの３つのバージョンが存在するが、ベアメタルプログラミングする際にはnightlyにしか実装されていない機能を使用する必要があるためだ。

# boot.S

いよいよRustでコードを書き始める……と言いたいところだが一番始めに動くコードはアセンブラが都合が良いため、アセンブラで書く。

```:src/boot.S
.section ".text.boot"

.globl _start
_start:
halt:
        wfe
        b halt
```

無限ループするだけのコードだ。`.section ".text.boot"`は後でこのコードをメモリ配置するときの目安となる。

# main.rs

さて本来であればboot.Sをビルドするために[build.rs](https://doc.rust-lang.org/cargo/reference/build-scripts.html)を書くところだが、ちょっと面倒なので後回しにして、ここではRustソースにアセンブラファイルを取り込んでしまえる`global_asm!`マクロを使用したmain.rsを用意する。

```rust:src/main.rs
#![feature(global_asm, asm)]
#![no_std]
#![no_main]

use core::panic::PanicInfo;

global_asm!(include_str!("boot.S"));

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {
        unsafe {asm!("" :::: "volatile")}
    }
}
```

ベアメタルなので普段使用している`libstd`(stdモジュール)は使用できない。そのために`#[no_std]`を書く。
また、通常のように`main()`を書けば自動的にそこがエントリポイントになるということもないため`#[no_main]`を書いて`main()`は書かない。

そして、`global_asm!(include_str!("boot.S"));`で先程のboot.Sを取り込む。

`panic()`は`#[no_std]`環境でパニックが発生した場合に実行される処理として必要となる。ここでは単純に処理を無限ループに落とすだけなのだが、どうやらRustは[副作用なしの無限ループはまずい](https://github.com/rust-lang/rust/issues/28728)らしいので`asm!("" :::: "volatile")`でそれを回避している。

# Cargo.toml

Cargo.tomlに以下を追加。

```rust:Cargo.toml
[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
```

これについては以下参照。
https://os.phil-opp.com/freestanding-rust-binary/#disabling-unwinding

# ビルドターゲットファイル作成

xbuildにこのファイルを読み込ませて、クロスビルド環境を自動で用意してもらう。

```json:armv7-none-eabihf.json
{
    "llvm-target": "armv7-none-eabihf",
    "target-endian": "little",
    "target-pointer-width": "32",
    "target-c-int-width": "32",
    "os": "none",
    "env": "eabi",
    "vendor": "unknown",
    "arch": "arm",

    "linker": "rust-lld",
    "linker-flavor": "ld.lld",
    "pre-link-args": {
        "ld.lld": [
            "--script=linker.ld"
        ]
    },
    "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64",
    "executables": true,
    "relocation-model": "static",
    "no-compiler-rt": true
}
```

指定できる項目については以下参照

* [rustc_target::spec::Target](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.Target.html)
* [rustc_target::spec::TargetOptions](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html)

ポイントとしては`"--script=linker.ld"`でリンカースクリプト（ッ後述）を指定しているところ。
linker.ldは自前のリンカースクリプトであとで作成する。

# リンカースクリプト

## セクションについて

linker.ld解説の前にセクションについて少しだけ説明する。
(C/C++同様)Rustではコンパイルされたコード、データはセクションというものに属することになる。

実際に見てみる。
/tmp等に以下のファイルを作って

```rust:test.rs
#[no_mangle]
pub fn hello() -> &'static str {
    "Hello World"
}
```

以下のコマンドでアセンブリを吐かせてみる。
(`#[no_mangle]`を付けているのは[名前マングリング](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE)を抑制し結果をわかりやすくするためするため)

```bash
$ rustc --emit asm --crate-type lib test.rs
```

結果はtest.sに出力される。

```test.s
	.text
	.file	"test.3a1fbbbh-cgu.0"
	.section	.text.hello,"ax",@progbits
	.globl	hello
	.p2align	4, 0x90
	.type	hello,@function
hello:
	.cfi_startproc
	leaq	.L__unnamed_1(%rip), %rax
	movl	$11, %edx
	retq
.Lfunc_end0:
	.size	hello, .Lfunc_end0-hello
	.cfi_endproc

	.type	.L__unnamed_1,@object
	.section	.rodata..L__unnamed_1,"a",@progbits
.L__unnamed_1:
	.ascii	"Hello World"
	.size	.L__unnamed_1, 11


	.section	".note.GNU-stack","",@progbits
```

`.section`でセクションが指定されている。
関数`hello()`(コード中では`.globl hello`)はセクション`.text.hello`で、文字列リテラル`"Hello World"`はセクション`.rodata..L__unnamed_1`が指定されていることがわかる。

セクション名は各言語処理系が必要に応じて自由に作ることができる(Rustのようにユーザーが指定できる処理系もある)が、共通で見かけるめぼしいものは以下の4つである。

| セクション名 | 役割 |
|:--:|:--:|
| .text | 実行コード領域 |
| .rodata | (文字列などの)固定データ領域 |
| .data | 初期化済みデータ領域 |
| .bss | 初期値ゼロのデータ領域 |

## リンカースクリプトとは

リンカースクリプトはざっくりいうと、上で説明した各セクションのオブジェクトをメモリ上にどのように配置するか決めるためのものだ。

普段は意識しないが、以下のコマンドを実行すると

```bash
$ ld --verbose
```

GCCが使用しているデフォルトのリンカースクリプトが表示される（表示されない環境もあるかも）。
ただしこのデフォルトのリンカースクリプトはOS（筆者の環境だとLinux）上で動作する前提のリンカースクリプトになっているので、ベアメタルで動作するプログラムを作成する場合は使用することができない。


## linker.ld作成

というわけで作成した自前のリンカースクリプトlinker.ldが以下となる。

```:linker.ld

SECTIONS
{
    /* Starts at LOADER_ADDR. */
    . = 0x8000;
    __start = .;
    __text_start = .;
    .text :
    {
        KEEP(*(.text.boot))
        *(.text)
    }
    . = ALIGN(4096); /* align to page size */
    __text_end = .;
 
    __rodata_start = .;
    .rodata :
    {
        *(.rodata)
    }
    . = ALIGN(4096); /* align to page size */
    __rodata_end = .;
 
    __data_start = .;
    .data :
    {
        *(.data)
    }
    . = ALIGN(4096); /* align to page size */
    __data_end = .;
 
    __bss_start = .;
    .bss :
    {
        bss = .;
        *(.bss)
    }
    . = ALIGN(4096); /* align to page size */
    __bss_end = .;

    __end = .;
}
```

RustのリンカーはリンカースクリプトについてGNU ldと互換があるので細かい仕様については`$ info ld scripts`を見てもらうのが良いだろう。

ポイントはラズバイ2のCPUは0x8000から読み込みを開始するので、0x8000にsrc/boot.Sで指定したセクション`.text.boot`を配置していること。これによって起動直後boot.Sの先頭から実行が開始されることになる。

# 最初のビルドと実行

これでようやくビルドできるようになった。プロジェクトディレクトリ直下で以下のコマンドでビルドする。

```bash
$ cargo xbuild --target armv7-none-eabihf.json
```

なお、linker.ldは編集しても再ビルドしてくれないので要注意。

ビルドできたら以下のコマンドでQEMUを動かしてみる。

```bash
$ qemu-system-arm -M raspi2 -kernel target/armv7-none-eabihf/debug/baremetal-raspi2 -d guest_errors -d in_asm
```

今のところ出力も何もないので実行した命令がわかるようにオプション`-d in_asm`を指定している。
結果は以下の通り。

```
----------------
IN:
0x00008000:  e320f002  wfe
0x00008004:  eafffffd  b        #0x8000

----------------
IN:
0x00008000:  e320f002  wfe
0x00008004:  eafffffd  b        #0x8000

----------------
IN:
0x00008000:  e320f002  wfe
0x00008004:  eafffffd  b        #0x8000

----------------
IN:
0x00008000:  e320f002  wfe
0x00008004:  eafffffd  b        #0x8000
```

なんとなくboot.Sの内容が実行されているのがわかると思う。4つ出ているのはラズパイ2にはコアが4つあるため。

# Tips

## コア1〜3を止める

上に書いたとおりラズパイ2では4つのコアが同時に動き出し0x8000から実行を開始してしまう。これは後々いろいろと都合が悪いので当面の間コア1〜3には寝てて（無限ループして）もらうことにする。

```src/boot.S
        // disable core1-3
        mrc p15, 0, r1, c0, c0, 5
        and r1, r1, #3
        cmp r1, #0
        beq 2f

1:      wfe
        b 1b
2:
```

コプロからコアのIDを取得してきて0ならラベル`2`へ、それ以外ならラベル`1`で無限ループすることになる。

## NEONの有効化

上で書いたarmv7-none-eabihf.jsonだが、これに従ってビルドするとSIMD演算器であるNEONコプロセッサを使用するコードが生成される。
しかしラズパイ2の起動時にはNEONコプロセッサは無効になっているため、NEON用のコードを実行しようとすると「未定義命令例外」が発生してしまう。

boot.Sに以下のコードを追加することでNEONコプロセッサが有効となり「未定義命令例外」は発生しなくなる。

```:src/boot.S
        // enable NEON/VFP
        ldr r0, =(0xF << 20)
        mcr p15, 0, r0, c1, c0, 2
        mov r3, #0x40000000
        vmsr FPEXC, r3
```

# このあとは？

長くなってしまったのでここで一旦終わりとする。
次回は割り込みベクタの設定をおこない、シリアルからの入力をエコーバックするところまでを書きたい。

またベアメタルプログラミングとして以下の記事も書いたので参考にしてもらいたい。

* [[Rust] ラズパイ2でベアメタルメモリアロケーション](https://qiita.com/maueki/items/442d30992042b2d24c2a)
* [[Rust] ラズパイ2でベアメタルコンテキストスイッチ](https://qiita.com/maueki/items/93051908858bd4e1cf1f)

# 参考URL

https://os.phil-opp.com/