Rustでベアメタル(UEFI)するときにprintfデバッグできなくて半年たった話

本記事は自作OS Advent Calendar 2017 16日目の 記事です。

# はじめに
printfデバッグをRustでやりたいけど原因特定までに半年くらいかかった話しします

「 [バージョンアップしたRustでUEFIと遊ぼうとしたら1年間詰んだ話](https://qiita.com/segfo/items/41b1774ba141fe440aae)」もよろしくお願いします。

# rustcのバージョン
1.23.0(nightly)

# やりたいこと
RustでUEFIしたい！printfデバッグしたい！（Rustだとprint系マクロデバッグ？）

そういうことで、まずは、さくっとコードを動かすところからやりたいので、このあたりをベースにやってみることに。
[第13回Kernel/VM勉強会発表資料](https://speakerdeck.com/orumin/vmmian-qiang-hui-fa-biao-zi-liao)

[上記資料とは別のリポジトリから大昔に拝借したもの](https://github.com/segfo/rust_uefi_test)

# writeマクロでハングアップする
[今回取り上げているハングアップする部分はこちら。(github)](https://github.com/segfo/rust_uefi_test/blob/bb17ac274edf45fd8917ef4bf55d221ef1b1f76b/loader/loader.rs#L106)

ハングアップしない例
write!("hello world!");
write!("hello {}",world!);

ハングアップする例
write!("1+1 = {}",1+1);
write!("2.1+2.2 = {}",2.1+2.2);

（余談：rustc 1.20nightlyの頃からこの症状は確実にあった。）

# デフォ実装がハングするなら、自分でwrite_fmtを実装すればいいじゃん
プレースホルダがwriteマクロに指定されるとどうも[core::fmt::Write](https://doc.rust-lang.org/core/fmt/trait.Write.html)トレイトのwrite_fmtメソッドが呼ばれるらしい。
自分でちょっと実装すればいいじゃん俺天才。

# core::fmt::ArgumentsはPrivate
俺天才だと思ったけど天才じゃなかった。
[Arguments](https://doc.servo.org/core/fmt/struct.Arguments.html)のメンバは全てPrivateらしい。
（外からいじれないし参照も出来ない）
これをどうにかできるものでもないので
何か固定文字列を表示するものにしてみる。

# デフォ実装がコケる箇所の特定
みんな大好きBinary ninjaとgdbのリモートデバッグ機能を使って
Rustのコードをアセンブラレベルで追いかけてみる。

追いかけ方
1. 自分でwrite_fmtを実装する。
わかりやすい文字列をそのメソッド内で利用していると、なおよい。
例えば「__1234DEBUG_WRITE_FMT1234__」みたいな。
2. 1で特徴的な文字列を使っているメソッドがwrite_fmtなので、そのアドレスの周辺の命令が把握できたら独自実装のwrite_fmtは消す。
3. デフォルト実装のwrite_fmtが呼ばれるようになるので、2で見つけた場所がデフォルトのwrite_fmtに差し替わる。
ここまでの手順で見つけた部分が、以下の画像の関数コール（ここでハングアップと書いてある所）。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/30037/6602fb88-cc6d-4b6e-3da5-7379521e7b7b.png)
4. さらにこの関数を掘っていって、jmp命令でわざと怪しい命令ブロックを飛ばしたり、
怪しい呼び出し命令をnopに差し替えたりする。
この作業を繰り返して発見したハングアップする命令はここ。
あたまわるいので、この先は動的解析で頑張る
![image.png](https://qiita-image-store.s3.amazonaws.com/0/30037/58011084-cf26-54f4-bd37-bf2a61c7bfd5.png)

# gdbを使う（リモートデバッグ）

```
$ gdb build/bootx64.efi
gdb-peda$ tar rem :9000
gdb-peda$ b *0x41f66d
gdb-peda$ c
gdb-peda$ x/10i $rip-10
   0x41f663 <_ZN4core3fmt5write17h072626bec6187d22E+675>:       add    r13,0x10
   0x41f667 <_ZN4core3fmt5write17h072626bec6187d22E+679>:       mov    rdi,QWORD PTR [r15]
   0x41f66a <_ZN4core3fmt5write17h072626bec6187d22E+682>:       mov    rsi,r12
=> 0x41f66d <_ZN4core3fmt5write17h072626bec6187d22E+685>:       call   QWORD PTR [r15+0x8]
   0x41f671 <_ZN4core3fmt5write17h072626bec6187d22E+689>:       lea    r15,[r15+0x10]
   0x41f675 <_ZN4core3fmt5write17h072626bec6187d22E+693>:       add    rbp,0xfffffffffffffff0
   0x41f679 <_ZN4core3fmt5write17h072626bec6187d22E+697>:       add    r14,0xfffffffffffffff0
   0x41f67d <_ZN4core3fmt5write17h072626bec6187d22E+701>:       test   al,al
   0x41f67f <_ZN4core3fmt5write17h072626bec6187d22E+703>:
    je     0x41f640 <_ZN4core3fmt5write17h072626bec6187d22E+640>
   0x41f681 <_ZN4core3fmt5write17h072626bec6187d22E+705>:
    jmp    0x41f6ab <_ZN4core3fmt5write17h072626bec6187d22E+747>
gdb-peda$ info register
rax            0x0      0x0
rbx            0x0      0x0
rcx            0x445038 0x445038
rdx            0x7      0x7
rsi            0x3ff94658       0x3ff94658
rdi            0x444010 0x444010
rbp            0x20     0x20
rsp            0x3ff94650       0x3ff94650
r8             0xaf     0xaf
r9             0x3ffb95df       0x3ffb95df
r10            0x3ed87dba       0x3ed87dba
r11            0x40     0x40
r12            0x3ff94658       0x3ff94658
r13            0x43bf40 0x43bf40
r14            0x10     0x10
r15            0x3ff94a60       0x3ff94a60
rip            0x41f66d 0x41f66d <core::fmt::write::h072626bec6187d22+685>
eflags         0x202    [ IF ]
cs             0x28     0x28
ss             0x8      0x8
ds             0x8      0x8
es             0x8      0x8
fs             0x8      0x8
gs             0x8      0x8
gdb-peda$
```
お分かりになられただろうか。

```
命令
=> 0x41f66d <_ZN4core3fmt5write17h072626bec6187d22E+685>:       call   QWORD PTR [r15+0x8]
レジスタ
r15            0x3ff94a60       0x3ff94a60
```
これ、ダングリングポインタな気がする・・・
とりあえず、r15+0x08のアドレスの中身を見てみましょう。

```
gdb-peda$ x/25i $r15+0x8
   0x3ff94a68:  push   rbx
   0x3ff94a69:  sub    rsp,0x30
   0x3ff94a6d:  mov    r8d,DWORD PTR [rdi]
   0x3ff94a70:  adc    BYTE PTR [rax+0x44],al
   0x3ff94a73:  add    BYTE PTR [rax],al
   0x3ff94a75:  add    BYTE PTR [rax],al
   0x3ff94a77:  add    BYTE PTR [rax],dl
   0x3ff94a79:  rex
   0x3ff94a7a:  add    BYTE PTR [rax],r8b
   0x3ff94a7d:  add    BYTE PTR [rax],al
   0x3ff94a7f:  add    BYTE PTR [rax],al
   0x3ff94a81:  add    BYTE PTR [rax],al
   0x3ff94a83:  add    BYTE PTR [rbx],al
   0x3ff94a85:  add    BYTE PTR [rax],al
   0x3ff94a87:  add    BYTE PTR [rax],al
   0x3ff94a89:  add    BYTE PTR [rax],al
   0x3ff94a8b:  add    BYTE PTR [rbx],al
   0x3ff94a8d:  add    BYTE PTR [rax],al
   0x3ff94a8f:  add    BYTE PTR [rax],al
   0x3ff94a91:  add    BYTE PTR [rax],al
   0x3ff94a93:  add    BYTE PTR [rbx],al
   0x3ff94a95:  add    BYTE PTR [rax],al
   0x3ff94a97:  add    BYTE PTR [rax],al
   0x3ff94a99:  add    BYTE PTR [rax],al
   0x3ff94a9b:  add    BYTE PTR [rax],al
```

なるほどわからん。（そりゃハングアップするわ）

# 現実逃避
writeマクロで数字を出力しなければOSを書ける。
Rustはいいぞ。UEFIいいぞ。

いつもはしっかり者のRustちゃんの萌えポイントですよ。


# 類似の問題を見つけた
なんか、これとすごく症状が似ている記事をRustのissuesに見つけた。
[Write::write_fmt causes page fault on a bare metal #27673](https://github.com/rust-lang/rust/issues/27673)

意訳：
1. ローダがちゃんとやらないと、既定のモードではダングリングポインタを指す
2. ベアメタルではローダなんて無いからリロケーションモードをstaticにするといいぞ
3. ちなみにこれはバグではない
4. いいな？バグではない。わかったな？


# とりあえずリンカフラグを追加する
[変更点はこちら](https://github.com/segfo/rust_uefi_test/commit/4e2f42166ed86071ce38c1d617e3bdb24f1f84c2#diff-f8abc6e7ebe4be7a831dd83ee67df851)
ひじょーに楽な修正。


出来た！！！！！

VGA表示モード
![image.png](https://qiita-image-store.s3.amazonaws.com/0/30037/9daf412d-7013-a2c4-fc3b-edb8b0277ac7.png)

シリアル表示モード
![image.png](https://qiita-image-store.s3.amazonaws.com/0/30037/b3526ac3-990f-5eb3-905b-149a89afae41.png)

gif動画（ビルド > VGA > Serial > VGA切り替え）
![uefi.gif](https://qiita-image-store.s3.amazonaws.com/0/30037/da9b29dd-b161-7968-0bbb-bc83a1e5d952.gif)


# 結論
リンカフラグの設定オサボリはベアメタルでは致命的らしい、ちゃんと設定しましょう。
Let's Rust!

# 次の目標
アロケータを実装してBoxを使えるようにする。