OSC2020 自作OSのデバッグ技術

1.1K Views

April 23, 20

スライド概要

あるバグをデバッグする様子を例に,自作OSのデバッグ技術を紹介する

profile-image

サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

自作OSのデバッグ技術 オープンソースカンファレンス2020オンライン 2020年4月24日 by @uchan_nos

2.

自己紹介  内田公太 @uchan_nos  サイボウズ・ラボ株式会社  東京工業大学 特任助教  osdev-jpコアメンバー  『30日でできる! OS自作入門』の校正を担当  『自作エミュレータで学ぶ x86アーキテクチャ』の著者

3.

こんな悩み,ありますか?  逆アセンブルってどうやるの?  特定時点のメモリ内容を見たい  QEMUが勝手に再起動しちゃう OS自作に関する様々な 悩みを解決したい 低レイヤのデバッグ力を 鍛えよう!

4.

ある日の午後…  MikanOSにrpnコマンド追加  起動実験をしてみた  1回目のrpnコマンドは成功  もう1回起動しようとすると… ここでキー入力

5.

ある日の午後…  え…?

6.

ある日の午後…  MikanOSが再起動してる…

7.

バグを追いかける  rpnコマンドによる再起動バグを追ってみた  今日は,その流れを掻い摘んで紹介  詳細はGist https://gist.github.com/uchan-nos/b5af3d033192c90ce1f4f1f0a005e551

8.

rpnコマンドの仕様  RPN=逆ポーランド記法  “rpn 2 3 +”→「5」  終了コードで結果を返す

9.

rpnコマンドの呼び出し  ELFファイルをメモリに配置 file_buf .rodata  エントリーポイントのアドレスを計算  ELF記載の値から0x100000を引いて call .text  ファイルの先頭アドレスを足す  エントリーポイントをcall  終了コードを取得して表示 →rpnコマンドはカーネルモード(リング0)で動く .data, .bss

10.
[beta]
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

 数値が来たらスタックに積む
 演算子(+/-)が来たらスタックトッ
プに対して演算する

}

11.

現象の再現条件を特定する  再現条件を特定することがデバッグの一歩目  3つを試した  GDTにTSSを設定し,LTRで読み込む  標準例外に対応するハンドラを作りIDTにセットする  Task::InitContext()内でLog()を使ってログを出す  どれをやっても,現象が治まる…

12.

再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す

13.

再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す

14.

TSSを設定する  ユーザーモード(CPL=3)→カーネルモード(CPL=0) の割り込み時に利用されるTSSを設定  rpnコマンドはカーネルモードで動くので,影響を与えないはずだが…  なぜか現象が治まる

15.

再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す

16.

標準例外を捕捉する  OSが再起動する原因はCPU例外が発生するから  発生した例外を捕捉すると大きなヒントになる  しかし,例外ハンドラを追加するとなぜか現象が治まる…  どの例外ハンドラも反応しない

17.

再現条件の特定 GDTにTSSを設定し,LTR で読み込む 標準例外に対応するハンド ラを作りIDTにセットする Task::InitContext()内で Log()を使ってログを出す

18.

ログを出す  タスクを生成するところでログを 出してみた  ログを出すだけで現象が治まる…

19.

バグの追い方  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

20.

バグの追い方 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の値 明らかにおかしい

21.

バグの真の原因  結論:rpnコマンドが不正なメモリ書き込みをしている  long stack[100]が0x102010に配置されている  readelf -s rpnでシンボルテーブルを確認  0x100000からカーネルが配置されている  →stackに書き込みをするたび,カーネル領域が破壊される

22.

OS自作技術 ステップアップ問題集 OSの作成・デバッグ技術を習得しよう

23.

OSの作成・デバッグ技術を習得する  【宣伝】OSを自作している方にOSのデバッグ技術 を紹介する本を書きました  主な話題  バイナリファイルの調べ方  QEMUとGDBの連携のさせ方  OSが再起動してしまうときのバグの調べ方など  BOOTHで頒布中 https://uchan.booth.pm/items/1986378