MikanOSをデバッグする話その1(おま環?)
どんなバグ?
MikanOSで「ゼロからのOS自作入門」通りに勉強していくとおそらくタグのosbook_day18cでrpnというアプリケーションが出てくると思うんですが、これがずっと実行できないのでデバッグしていきます。
osbook_day20dで、OSクラッシュしたときにログを吐くようになるんですが、ターミナルでrpnを叩くとなんとページフォルトが起きていることがわかりました。こいつをどうにかしたいです。
環境
何を書けばいいかわからないのでとりあえず羅列します...
OS : Ubuntu 20.04 focal
kernel : x86_64 Linux 5.13.0-40-generic
fish, version 3.1.0
GNU bash, バージョン 5.0.17(1)-release (x86_64-pc-linux-gnu)
clang version 7.0.1-12 (tags/RELEASE_701/final)
LLD 10.0.0 (compatible with GNU linkers)
QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.21)
こんなもんですかね...
どこでクラッシュしてるのか
とりあえずタグはosbook_day20eで話を進めます(現在の進捗的に)。 この前にもデバッグしようとしたことはありましたが断念しています。その時の記憶を頼りに
__asm__("int3");
をterminal.cpp(下のやつ)の403行目と404行目の間に挟んでみます。
403 auto file_entry = fat::FindFile(command); 404 if (!file_entry) { 405 Print("no such command: "); 406 Print(command); 407 Print("\n"); 408 } else if (auto err = ExecuteFile(*file_entry, command, first_arg)) { 409 Print("failed to exec file: ");
すると
こんな感じにクラッシュしてログが吐かれました。int3は割り込みベクタ番号の3の割り込みを発生させます。
次に、このint3を取り除いた上で408行目と409行目の間にint3を挟み込みます。すると
とまぁ、問題のページフォルトが発生するわけですね。一応405~407行が実行されていないことを確認するために404,405行目の間にint3を挟んでも上の図の通りページフォルトが発生しました。
つまり、問題はExecuteFile関数にありそうですね。この調子でどこでクラッシュしているか探していきます。
下はExecuteFile関数の中の一部です。
452 auto entry_addr = elf_header->e_entry; 453 CallApp(argc.value, argv, 4 << 3 | 3, 3 << 3 | 3, entry_addr, 454 stack_frame_addr.value + 4096 - 8);
rpnを実行するときの話になりますが、rpnのバイナリのELFヘッダからプログラムのエントリポイントを指すelf_header->e_entryをCallAppに渡してrpn自身を実行します。そこで、int3を452, 453行目の間にint3を挿入して実行するとページフォルトが返されました。ここの行以前でクラッシュしていそうです(CallAppの処理をよく把握していないのでホッとしている)。とまぁ、こんな感じにどんどん特定を進めると
429 if (auto err = LoadELF(elf_header)) { 430 return err; 431 }
ここでクラッシュしていることがわかりました。LoadELF(elf_header)が実行されていますね。LoadELF関数は同じくterminal.cppの173行目にあります。このLoadELF関数のどこでクラッシュしているかを探ると
183 if (auto err = CopyLoadSegments(ehdr)) { 184 return err; 185 }
ファイル内を検索しても他にCopyLoadSegments関数を呼び出している箇所はないのでさらにこの関数の中にint3を差し込みます。
そして特定がとうとう完了しました(唐突)!
165 const auto src = reinterpret_cast<uint8_t*>(ehdr) + phdr[i].p_offset; 166 const auto dst = reinterpret_cast<uint8_t*>(phdr[i].p_vaddr); 167 memcpy(dst, src, phdr[i].p_filesz); //←ここ
ここです。ここはforループの中ですが、i=3のときのメモリのコピーをするのに失敗している感じですかね。
何が起こっているのか
166 const auto src = reinterpret_cast<uint8_t*>(ehdr) + phdr[i].p_offset; 167 const auto dst = reinterpret_cast<uint8_t*>(phdr[i].p_vaddr); 168 char str[64]; 169 sprintf(str, "ehdr: 0x%p",ehdr); 170 WriteString(*screen_writer, {500, 100+i*68}, str, {0,0,0}); 171 sprintf(str, "phdr[%d].p_offset: 0x%0p", i, phdr[i].p_offset); 172 WriteString(*screen_writer, {500, 117+i*68}, str, {0,0,0}); 173 sprintf(str, "phdr[%d].p_vaddr: 0x%0p", i, phdr[i].p_vaddr); 174 WriteString(*screen_writer, {500, 134+i*68}, str, {0,0,0}); 175 sprintf(str, "phdr[%d].p_filesz: 0x%0p", i, phdr[i].p_filesz); 176 WriteString(*screen_writer, {500, 151+i*68}, str, {0,0,0}); 177 memcpy(dst, src, phdr[i].p_filesz);
sptintf関数を使うためにインクルードファイルにstdio.hを増やしました。(その関係で行番号がずれています。) 右上に黒文字で出るクラッシュログのコードを参考にmemcpyに渡される引数を構成する変数を表示するようにしました。 その結果がこちらです。 これらが実際に作成されているrpnのバイナリと合致するかをまず確認します。 readelfにより表示させました。値を見ると、確かにファイルから読み込まれた値に間違いがないことがわかります。
と、こんなところで今日は疲れたのでまたこんど。