本記事は自作OS Advent Calendar 2017 16日目の 記事です。
はじめに
printfデバッグをRustでやりたいけど原因特定までに半年くらいかかった話しします
「 バージョンアップしたRustでUEFIと遊ぼうとしたら1年間詰んだ話」もよろしくお願いします。
rustcのバージョン
1.23.0(nightly)
やりたいこと
RustでUEFIしたい!printfデバッグしたい!(Rustだとprint系マクロデバッグ?)
そういうことで、まずは、さくっとコードを動かすところからやりたいので、このあたりをベースにやってみることに。
第13回Kernel/VM勉強会発表資料
writeマクロでハングアップする
今回取り上げているハングアップする部分はこちら。(github)
ハングアップしない例
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トレイトのwrite_fmtメソッドが呼ばれるらしい。
自分でちょっと実装すればいいじゃん俺天才。
core::fmt::ArgumentsはPrivate
俺天才だと思ったけど天才じゃなかった。
Argumentsのメンバは全てPrivateらしい。
(外からいじれないし参照も出来ない)
これをどうにかできるものでもないので
何か固定文字列を表示するものにしてみる。
デフォ実装がコケる箇所の特定
みんな大好きBinary ninjaとgdbのリモートデバッグ機能を使って
Rustのコードをアセンブラレベルで追いかけてみる。
追いかけ方
- 自分でwrite_fmtを実装する。
わかりやすい文字列をそのメソッド内で利用していると、なおよい。
例えば「1234DEBUG_WRITE_FMT1234」みたいな。 - 1で特徴的な文字列を使っているメソッドがwrite_fmtなので、そのアドレスの周辺の命令が把握できたら独自実装のwrite_fmtは消す。
- デフォルト実装のwrite_fmtが呼ばれるようになるので、2で見つけた場所がデフォルトのwrite_fmtに差し替わる。
ここまでの手順で見つけた部分が、以下の画像の関数コール(ここでハングアップと書いてある所)。
- さらにこの関数を掘っていって、jmp命令でわざと怪しい命令ブロックを飛ばしたり、
怪しい呼び出し命令をnopに差し替えたりする。
この作業を繰り返して発見したハングアップする命令はここ。
あたまわるいので、この先は動的解析で頑張る
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
意訳:
- ローダがちゃんとやらないと、既定のモードではダングリングポインタを指す
- ベアメタルではローダなんて無いからリロケーションモードをstaticにするといいぞ
- ちなみにこれはバグではない
- いいな?バグではない。わかったな?
とりあえずリンカフラグを追加する
変更点はこちら
ひじょーに楽な修正。
出来た!!!!!
gif動画(ビルド > VGA > Serial > VGA切り替え)
結論
リンカフラグの設定オサボリはベアメタルでは致命的らしい、ちゃんと設定しましょう。
Let's Rust!
次の目標
アロケータを実装してBoxを使えるようにする。