1.1K Views
April 23, 20
スライド概要
あるバグをデバッグする様子を例に,自作OSのデバッグ技術を紹介する
サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。
自作OSのデバッグ技術 オープンソースカンファレンス2020オンライン 2020年4月24日 by @uchan_nos
自己紹介 内田公太 @uchan_nos サイボウズ・ラボ株式会社 東京工業大学 特任助教 osdev-jpコアメンバー 『30日でできる! OS自作入門』の校正を担当 『自作エミュレータで学ぶ x86アーキテクチャ』の著者
こんな悩み,ありますか? 逆アセンブルってどうやるの? 特定時点のメモリ内容を見たい QEMUが勝手に再起動しちゃう OS自作に関する様々な 悩みを解決したい 低レイヤのデバッグ力を 鍛えよう!
ある日の午後… MikanOSにrpnコマンド追加 起動実験をしてみた 1回目のrpnコマンドは成功 もう1回起動しようとすると… ここでキー入力
ある日の午後… え…?
ある日の午後… MikanOSが再起動してる…
バグを追いかける rpnコマンドによる再起動バグを追ってみた 今日は,その流れを掻い摘んで紹介 詳細はGist https://gist.github.com/uchan-nos/b5af3d033192c90ce1f4f1f0a005e551
rpnコマンドの仕様 RPN=逆ポーランド記法 “rpn 2 3 +”→「5」 終了コードで結果を返す
rpnコマンドの呼び出し ELFファイルをメモリに配置 file_buf .rodata エントリーポイントのアドレスを計算 ELF記載の値から0x100000を引いて call .text ファイルの先頭アドレスを足す エントリーポイントをcall 終了コードを取得して表示 →rpnコマンドはカーネルモード(リング0)で動く .data, .bss
rpnコマンドの構造
int stack_ptr;
long stack[100];
long Pop() {…}
void Push(long value) {…}
extern "C" int main(int argc, char** argv) {
stack_ptr = -1;
グローバル変数
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "+") == 0) {
long b = Pop();
long a = Pop();
Push(a + b);
} else if (strcmp(argv[i], "-") == 0) {
…
} else
…
}
return static_cast<int>(Pop());
stack_ptr
stack
数値が来たらスタックに積む
演算子(+/-)が来たらスタックトッ
プに対して演算する
}
現象の再現条件を特定する 再現条件を特定することがデバッグの一歩目 3つを試した GDTにTSSを設定し,LTRで読み込む 標準例外に対応するハンドラを作りIDTにセットする Task::InitContext()内でLog()を使ってログを出す どれをやっても,現象が治まる…
再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す
再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す
TSSを設定する ユーザーモード(CPL=3)→カーネルモード(CPL=0) の割り込み時に利用されるTSSを設定 rpnコマンドはカーネルモードで動くので,影響を与えないはずだが… なぜか現象が治まる
再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す
標準例外を捕捉する OSが再起動する原因はCPU例外が発生するから 発生した例外を捕捉すると大きなヒントになる しかし,例外ハンドラを追加するとなぜか現象が治まる… どの例外ハンドラも反応しない
再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す
ログを出す タスクを生成するところでログを 出してみた ログを出すだけで現象が治まる…
バグの追い方 GDBでブレークポイントを設定し追いかける いろんな時点でメモリの値を確認する RIP CS RFLAGS RSP SS スタックフレーム (gdb) x /1i $rip => 0x10ec91 <IntHandlerLAPICTimer(InterruptFrame*)+273>: iretq (gdb) x /5gx $rsp 0x24cd68 <kernel_main_stack+1047816>: 0x00000000000012b6 0x0000000000000008 0x24cd78 <kernel_main_stack+1047832>: 0x0000000000000a82 0x000000000024cd98 0x24cd88 <kernel_main_stack+1047848>: 0x0000000000000010
バグの追い方 RIP CS RFLAGS RSP SS GDBでブレークポイントを設定し追いかける いろんな時点でメモリの値を確認する (gdb) x /1i $rip => 0x10ec91 <IntHandlerLAPICTimer(InterruptFrame*)+273>: (gdb) x /5gx $rsp 0x24cd68 <kernel_main_stack+1047816>: 0x00000000000012b6 0x24cd78 <kernel_main_stack+1047832>: 0x0000000000000a82 0x24cd88 <kernel_main_stack+1047848>: 0x0000000000000010 スタックフレーム iretq 0x0000000000000008 0x000000000024cd98 元のRIPの値 明らかにおかしい
バグの真の原因 結論:rpnコマンドが不正なメモリ書き込みをしている long stack[100]が0x102010に配置されている readelf -s rpnでシンボルテーブルを確認 0x100000からカーネルが配置されている →stackに書き込みをするたび,カーネル領域が破壊される
OS自作技術 ステップアップ問題集 OSの作成・デバッグ技術を習得しよう
OSの作成・デバッグ技術を習得する 【宣伝】OSを自作している方にOSのデバッグ技術 を紹介する本を書きました 主な話題 バイナリファイルの調べ方 QEMUとGDBの連携のさせ方 OSが再起動してしまうときのバグの調べ方など BOOTHで頒布中 https://uchan.booth.pm/items/1986378