RISC-V 開発環境をbuildrootとcrosstool-ngで用意する

最近、glibc本家に32bit RISC-Vのサポートがマージされていることに気付いた( https://sourceware.org/git/?p=glibc.git;a=commit;h=72dfddeffcc993a726bdcbe5e515afa1180095e8 )。64bit RISC-Vのサポートは以前から有ったけど、WASMが32bitなので32bit環境の方が食べ合せが良いかなということで。

今回は、buildrootとcrosstool-ngでRISC-Vネイティブ開発環境を用意し、qemuからVirtfs(9Pfs-root)で起動することで **ファイルシステムイメージなしで** (= ホストのファイルシステムから直接起動する)開発環境を用意することができた。

# さくせん

yoctoで作っても良いかなと思ったけどちょっと記事に書くほどは理解度が足りてないので古き良きbuildrootとcrosstool-ngの組合せで行くことに。どちらも `menuconfig` で項目をポチポチ選ぶだけで任意のオプション入りSDKを作ることができ初心者におすすめできる。

**buildroot** ( https://github.com/buildroot/buildroot ) は、組込み用Linuxディストリビューションをを作るためのツールで、Linuxカーネルと `busybox` 、いくつかのパッケージを事前導入した典型的な組込みLinuxシステムを簡単に作ることができる。ただし、 buildrootは [**意図的にターゲット上で動作するコンパイラ等を排除している** (メンテが大変だから)](http://nightly.buildroot.org/manual.html#faq-no-compiler-on-target) ため、開発環境は別途調達する必要がある。

**crostool-ng** は、以前 [ESP32の開発環境を用意したとき](https://qiita.com/okuoku/items/7593f648e235e537a29e#%E3%83%84%E3%83%BC%E3%83%AB%E3%83%81%E3%82%A7%E3%82%A4%E3%83%B3%E3%81%AE%E6%BA%96%E5%82%99crosstool-ng) にも登場したツールチェインのビルドシステムで、今回は **"カナディアン・クロス" ビルド** (クロスコンパイラをクロスコンパイルすること)によって、amd64 Linux上でRISC-V32上で動作するRISC-V32用のツールチェーンをビルドする。

## ターゲットRISC-V ABIおよび命令セット

今回は、 `rv32imafd` 命令セットと `ilp32d` ABIをターゲットする。それぞれ、

- `rv32imafd` = `RV32I`(Base Integer Instruction Set) + `M` (Multiplication) + `A` (Atomic) + `F` (Single-precision floating-point) + `D` (Double-precision floating-point)
- `ilp32d` = `int` `long` pointer は各32bit、関数の引数渡に浮動小数点レジスタを活用(`d`)

という意味で、 [GCCのマニュアル](https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Options.html) で言及されている。

命令セットはともかく、ABIの選択肢はさらにglibcのサポート範囲でも限定される。 [glibcのコミットのdiff](https://sourceware.org/git/?p=glibc.git;a=blobdiff;f=sysdeps/unix/sysv/linux/riscv/Makefile;h=4b6eacb32f3463a7cf52c61a795a2bcade7f2dff;hp=301b08239861b1898ca931b1b6d896c76a54e107;hb=72dfddeffcc993a726bdcbe5e515afa1180095e8;hpb=30b963c143eaa07f09567f2e0649edb7525c43fd) によると、

```diff
--- a/sysdeps/unix/sysv/linux/riscv/Makefile
+++ b/sysdeps/unix/sysv/linux/riscv/Makefile
@@ -7,11 +7,13 @@ ifeq ($(subdir),stdlib)
 gen-as-const-headers += ucontext_i.sym
 endif
 
-abi-variants := lp64 lp64d
+abi-variants := ilp32 ilp32d lp64 lp64d
 
```

今のところ ABI は `ilp32 ilp32d lp64 lp64d` の4択になっている。

## 用途

今回ビルドした開発環境はGitHubのリポジトリに収まるサイズにしている。 

- https://github.com/okuoku/riscv32-rootfs-proto
- https://github.com/okuoku/riscv32-sdk-proto

[前回の記事](https://qiita.com/okuoku/items/9c72a88662831d774742) から類推できるように、最終的にはこのリポジトリをWebブラウザから直接読み取って動作するRISC-V開発環境を用意したい。既にブラウザ上で動作する優秀なRISC-Vエミュレータとして [BelladのTinyEMU](https://bellard.org/tinyemu/) があるので、あとはファイルさえ用意すれば行けるんじゃないかと予想している。

# buildrootによるLinux環境の準備

buildroot は単純にリポジトリ( https://github.com/buildroot/buildroot )をチェックアウトし、

```
make qemu_riscv32_virt_defconfig
make menuconfig # 適当に欲しいパッケージを指定
make
```

とすればビルドできる。このとき、

- `output/images/fw_jump.bin` - qemuの `-bios` オプションに指定するブートローダー
- `output/images/Image` - Linuxカーネル本体
- `output/images/rootfs.tar` - rootfs

の各ファイルが出力となる。 `rootfs.tar` はどこか適当なところに展開しておく。

# crosstool-ngによるSDKの準備

buildrootで作ったファイルシステムにはコンパイラ類が含まれていないので、それらは crosstool-ng で用意する必要がある。こちらはちょっと設定項目が多い。

## menuconfig の準備

buildroot と異なり、crosstool-ngは事前にビルドする必要がある。リポジトリ( https://github.com/crosstool-ng/crosstool-ng )をチェックアウトしたら、

```
./bootstrap
./configure --enable-local
make
./ct-ng menuconfig
```

のように `ct-ng` をビルドしてから `menuconfig` を起動する必要がある。

## Experimental

RISC-V はbuildrootのExperimental featureなので、明示的に有効化する必要がある。

```
  │ │    [*] Try features marked as EXPERIMENTAL                          │ │
```

## ツールチェインABIの設定

メニューの `Target options` から、RISC-V 32向けにターゲットオプションを設定する:

```
  │ │        Target Architecture (riscv)  --->                            │ │
  │ │        *** Options for riscv ***                                    │ │
  │ │    ()  Suffix to the arch-part                                      │ │
  │ │    [*] Omit vendor part of the target tuple                         │ │
  │ │        *** Generic target options ***                               │ │
  │ │    [ ] Build a multilib toolchain (READ HELP!!!)                    │ │
  │ │    [*]   Attempt to combine libraries into a single directory       │ │
  │ │    [*] Use the MMU                                                  │ │
  │ │        Bitness: (32-bit)  --->                                      │ │
  │ │        *** Target optimisations ***                                 │ │
  │ │    (rv32imafd) Architecture level                                   │ │
  │ │    (ilp32d) Generate code for the specific ABI                      │ │
```

## glibc のパス

これを書いている時点のglibcの最新版 2.13 では、まだRISC-V 32bitはサポートされていない。というわけで、今回はbuildrootが用意しているパッチ済のglibcをcrosstool-ngでも使用することにした。

```
  │ │        C library (glibc)  --->                                      │ │
  │ │        *** Options for glibc ***                                    │ │
  │ │        Show glibc versions from (GNU)  --->                         │ │
  │ │        Source of glibc (Custom location)  --->                      │ │
  │ │          Custom location                                            │ │
  │ │    (/home/oku/repos//buildroot/output/build/glibc-2.32.9000-69-gbd39│ │
```

`Source of glibc` に Custom Location を選び、 `buildroot` の `build` ディレクトリ以下に配置されているglibcを指定する。

何も設定していないと、以下のようなエラーで失敗してしまう。

```
[CFG  ]      mips nios2 powerpc riscv glibc does not yet support 32-bit systems


[ERROR]
[ERROR]  >>
[ERROR]  >>  Build failed in step 'Building for multilib 1/1: '''
[ERROR]  >>        called in step 'Installing C library headers & start files'
[ERROR]  >>        called in step '(top-level)'
[ERROR]  >>
[ERROR]  >>  Error happened in: CT_DoExecLog[scripts/functions@376]
[ERROR]  >>        called from: glibc_backend_once[scripts/build/libc/glibc.sh@265]
[ERROR]  >>        called from: CT_IterateMultilibs[scripts/functions@1608]
[ERROR]  >>        called from: glibc_backend[scripts/build/libc/glibc.sh@74]
[ERROR]  >>        called from: glibc_start_files[scripts/build/libc/glibc.sh@38]
[ERROR]  >>        called from: do_libc_start_files[scripts/build/libc.sh@28]
[ERROR]  >>        called from: main[scripts/crosstool-NG.sh@695]
[ERROR]  >>
```

## tuple-alias と Canadian-cross ビルド

crosstool-ng は、 `riscv32-linux-gnu` というプレフィックスでツールチェーンをビルドしようとするが、buildrootのツールチェーンは `riscv32-linux` という名前なので、これをtuple-aliasに設定する必要がある。

また、今回は RISC-V で動作するコンパイラを作成したいので、Toolchain build typeはCanadianとなる。Build systemは空欄のままでOKだが、Host system(コンパイラの動作するシステム)を `riscv32-linux` に設定する。

それぞれ `Toolchain options` から設定できる。

```
  │ │    (riscv32-linux) Tuple's alias                                    │ │
  │ │        *** Toolchain type ***                                       │ │
  │ │        Type (Canadian)  --->                                        │ │
  │ │        *** Build system ***                                         │ │
  │ │    ()    Tuple        (READ HELP!)                                  │ │
  │ │    ()    Tools prefix (READ HELP!)                                  │ │
  │ │    ()    Tools suffix (READ HELP!)                                  │ │
  │ │        *** Host system ***                                          │ │
  │ │    (riscv32-linux)   Tuple        (READ HELP!)                      │ │
  │ │    ()    Tools prefix (READ HELP!)                                  │ │
  │ │    ()    Tools suffix (READ HELP!)                                  │ │
```

## PATHの設定とビルド

コンパイラをビルドするためのコンパイラとしては、先程buildrootで作成されたSDKをそのまま利用することになる。このため、buildrootが作成したツールチェーンをPATHに入れておく必要がある。

これは、buildrootの `output/host/bin` ディレクトリが相当する。

```
export PATH=/home/oku/repos/buildroot/output/host/bin:$PATH
```

ツールチェーンをビルドには、crosstool-ngのディレクトリで

```
./ct-ng build
```

のようにする。デフォルトでは、ビルドされたツールチェーンは、 `~/x-tools/HOST-riscv32-linux/riscv32-linux-gnu` 以下にインストールされるので、これを仮想マシン側に持っていくことになる。

# qemuでの実行

ひとまず、以下のようなディレクトリ構成で実行する場合、

|パス|内容|
|:---|:----|
|`/home/oku/riscv32-root/rootfs`|buildrootが作ったrootfs( https://github.com/okuoku/riscv32-rootfs-proto )|
|`/home/oku/riscv32-root/sdk`|crosstool-ngが作ったsdk( https://github.com/okuoku/riscv32-sdk-proto )|

```sh
qemu-system-riscv32 \
-M virt \
  -bios ~/repos/buildroot/output/images/fw_jump.bin \
  -kernel ~/repos/buildroot/output/images/Image \
  -append "root=/dev/root rootfstype=9p ro" \
  -fsdev local,id=rootfs,path=/home/oku/riscv32-root/rootfs,security_model=none \
  -device virtio-9p-pci,fsdev=rootfs,mount_tag=/dev/root \
  -fsdev local,id=sdk,path=/home/oku/riscv32-root/sdk,security_model=none \
  -device virtio-9p-pci,fsdev=sdk,mount_tag=sdk \
-nographic
```

手元の環境では `-virtfs` による一括指定はうまくいかなかったので、 `-fsdev` と `-device` をファイルシステム毎に設定している。

起動時にはrootしかマウントされないため、手動で sdk をマウントする必要がある:

```
# mount -t 9p sdk /sdk
```

その後は、適当にコンパイル & 実行できる。

```
# /sdk/bin/riscv32-linux-cc test.c
# ./a.out
Hello.
```

# かんそう

... ここまでは超どうでも良くて、ゆくゆくは、これを使うことでLinuxアプリをわざわざWASMに移植しなくても **直接GitHubにバイナリを置いて実行できる** ようにしたい。例えば、GitHub上のCMakeプロジェクトを直接ビルド & 実行する環境が作れるんじゃないかなと。(ファイルの列挙にはGitHubのAPIキーが要るけど)

ひとまず、Linux環境をVirtIO + 9Pfsから直接起動することはできた。TinyEMUには9pfs ←→ HTTPのブリッジが含まれているので、それを改造してGitHubのリポジトリを直接読めるようにするのが次の一手になる。