uchan note

プログラミングや電子工作の話題を書きます

セキュリティキャンプ 2018「フルスクラッチ OS を書こう!」テーマまとめ

セキュリティキャンプ 2018 全国大会の集中開発コースの講師をやったので報告です. 「フルスクラッチ OS を書こう!」というテーマで,自作 OS を一から作るお手伝いをしました.

フルスクラッチ OS を書こう!」の概要

フルスクラッチと言いつつキャンプの 3 日間(名目上は 5 日間だが,前後 1 日は作業できない)ではフルスクラッチで OS を作るのは難しいので, 小さな OS を作る,または改造するという方向で受講者とネタを考えました. 結果,2 人はそれぞれ次のネタをやることになりました.

  • 「はりぼて OS」にコマンドを増やす
  • xv6 を UEFI で起動する

「はりぼて OS」にコマンドを増やす

受講者の 1 人は「30 日でできる!OS 自作入門」で題材となっている「はりぼて OS」にコマンドを付けたすことになりました. 受講者本人が記事を書いているので,そちらもどうぞ. ぼくとOS開発とセキュリティキャンプ

やったこと

事前学習期間で本を読んできてもらい,キャンプ本番でがりがりとコマンドを作っていきます. まずチャレンジしたのは rm コマンドです.POSIX1 でおなじみのファイル削除のコマンドですね.

実は「はりぼて OS」に実装された FAT2 ファイルシステムのコードはリードオンリで,ファイルの読み出しはできますが変更や削除はできません. rm コマンドを実装するために FAT を解釈してファイルを削除する処理を書くところから始める必要がありました. 参考になるかと思って持って行った「定番!超軽量マイコン用ファイル・システムFatFs」は正確で分かりやすく FAT の仕様が説明されており,大変役立ちました. 他人が書いた FAT のコードをきちんと読み解き,適切にポインタを使いこなしてファイルシステムのドライバを書くのは大変だったようですが,1 日目の間には rm が出来上がりました.

次にチャレンジしたのが mv コマンドです. 「はりぼて OS」はディレクトリに対応していないため,まずはディレクトリは考えないことにしてただ単に名前を変えるだけのコマンドとして実装しました.

「はりぼて OS」のコマンドには珍しく mv コマンドは 2 つの引数を取ります. 引数をスペースで区切る処理というのが出てきて,地味に頭を使う(プログラミング上級者には簡単だと思いますがポインタや配列などに慣れてないと難しい)処理です. 彼はポインタがあまり理解できていなかったため,ホワイトボードを使ってメモリ上での様子を図示しながら教えました. 最終的にはファイルが見つからないときの処理なども組み込んだ mv コマンドが完成しました.

2 日目には mkdir コマンドを作りました.おなじみディレクトリを作るコマンドですね. ルートディレクトリに 1 階層だけのディレクトリを作る処理は 2 日目の前半で完成しました.

1 階層目のディレクトリ作成機能は比較的すぐに作れたようですが,ディレクトリを再帰的に作るところが難しかったようです. foo/bar/baz というパスを / で分割して文字列として辿りつつ,実際に FAT のディレクトリエントリも辿っていく処理3を並行でやっていくのはちょっと複雑なので,これがちゃんとできると一人前のプログラマと言えるかもしれません. 3 日目には別の講義を担当していた半田先生が集中開発コースの教室に遊びに来てくれて,受講者に丁寧に実装を教えてくれました.(半田先生,ありがとうございました!)

講師から見て

彼はプログラミング(特に C 言語のポインタあたり)はそんなに得意ではなかったみたいですが,コマンド実装を通じて C 言語プログラミングの本筋みたいなところを広く体験し,良いトレーニングになったと思います. 文字列の分割,ネストされた構造をポインタで辿る処理,再帰的に文字列を解釈する処理など,本格的なプログラミングで重要なトピックが盛りだくさんでした. 3 日間でプログラミングスキルが上達するのが目に見えました.こういう難易度のプログラミングを今後も続ければさらに成長できると思います.

xv6 を UEFI で起動する

xv6 とは UNIX V6 の雰囲気を大事にして x86 アーキテクチャ向けに再実装した教育用の OS です. MIT 公式の実装はレガシ BIOS を前提とした 32 ビット向けの実装であり,それを UEFI4 で起動するのにチャレンジしました. こちらも受講者本人が記事を書いているので,そちらもどうぞ. セキュリティキャンプでxv6を64bitUEFIから起動した話

やったこと

それはもうひたすらに xv6 を UEFI で起動できるようにするということです. 上述のコマンドの実装は「プログラミングとして難しい」領域でしたが,こちらは「そもそも何をやれば正解かが謎」という難しさがある問題領域です. そこには私も想像していなかったような壁がありました.

まず最初の壁は 32 ビット UEFI の実装にバグがあるのか,ファイルが読み込めない(ファイル読み込み関数を叩くと QEMU がフリーズ)というものです. 32 ビット UEFI なんて誰も使わないのでバグがあってもおかしくはないな,という感じです5. 仕方がないのでいったん 64 ビット UEFI でブートした後,CPU のモードを 32 ビットに切り替えてから xv6 を起動する戦略をとることにしました.事前学習期間の初期の話です.

さて,64 ビットの IA32e モードから 32 ビットの保護モードに遷移する方法は Intel SDM に書いてあるので楽勝です. と思いきや,ここにも壁がありました. Intel SDM では 1 行でさらっと書いてあるステップでも深い思慮が必要でした. 例えば Intel SDM には PAE ページングのフラグを落とせとは書いてありませんが,実際には PAE ビットが立ったままだとページングがきちんと設定できず,メモリを読み書きしようとすると "can't access memory" と言われたりします.

x86-64 アーキテクチャを使いこなす神でないとこの種のデバッグはなかなか難しいものがあります. 彼は事前学習期間のほとんどをこのバグに費やし,バグが解消したのはキャンプ 1 日目でした.

無事に CPU のモードが切り替わった後はレガシ BIOS との差に苦しみます. BIOS に存在するマルチプロセッサ(マルチコア)のための構造体6UEFI には存在せず,レガシ BIOS を前提とした xv6 の mpinit 関数を完全に書き換える必要がありました. これは UEFI の SystemTable から VendorTable,RSDP,XSDT,ACPI,MADT とたどり,UEFI 流のマルチプロセッサ対応を行いました.

これでうまくいくかと思いましたがまだ無限再起動などしてしまいます. QEMU モニタを使って調べていくと,0 で初期化されているべきグローバル変数にゴミが書き込まれていることが判明しました. xv6 の実行バイナリ(ELF 形式)をメモリに読み込む際,LOAD セグメントのファイル上のサイズがメモリ上のサイズより大きいとき( p_filesz > p_memsz )に,残りを 0 で埋める処理を忘れていたためでした.

最後の大きな問題は xv6 はテキストモードで画面表示を行う機能がありますが,UEFI にはテキストモードのフレームバッファを得る機能が無いため画面表示ができないことです7. 起動できたかどうかも分かりませんので大問題です. この問題は xv6 がシリアル通信をサポートしていることが分かり解決しました.シリアルポート最強.

講師から見て

低レイヤ独特の難しさを体験し,典型的なデバッグ方法に触れました. 彼は C 言語によるプログラミングはポインタ含め難なくこなせていましたが,今回は x86-64 アーキテクチャの細かい機能について知識を伸ばすことができたと思います. 残念ながら当初やりたかった「ネットワークドライバの作成」はできませんでしたが,得た知識をもとに今後の成長が楽しみです.

OS 開発ゼミ全体の話

私は「フルスクラッチ OS を書こう!」テーマの担当でしたが,OS 開発ゼミではほかにも「最先端 OS 談義」と「Linux 開発者を目指そう!」というテーマが開講されていました. 「Linux 開発者を目指そう!」テーマは講師の sat さんによる記事が出ています. セキュリティキャンプ全国大会 2018 集中開発コース 「Linux開発者を目指そう! 」テーマのレポート


  1. UNIXLinux(実は Windows も)準拠している規格.コマンドや API を規定しており,POSIX に従って作られたプログラムは容易に移植できる(ことになっている).

  2. File Allocation Table.普及しているファイルシステムの一つで,フロッピーディスクや USB メモリ用のフォーマットとして業界標準となっている.

  3. FAT のディレクトリは「ディレクトリエントリが複数格納された特殊なファイル」として表現されているため,ツリーを辿るにはルートディレクトリから順に辿ればよい.(FAT には i-node という概念はない)

  4. レガシー BIOS を置き換える次世代の規格.次世代と言いつつ,市販の PC はほぼすべて UEFI に置き換わっている.

  5. ファイルが読み込めないのは本当に UEFI 実装のバグなのかどうかは調べていないので分かりませんが,状況証拠からはバグの可能性が高そうです.

  6. mpinit の中の処理 を見ると分かる通り,レガシー BIOS ではメモリ空間を _MP_ というマジックナンバで検索することでマルチプロセッサ構造体を発見する.

  7. テキストモードとは,文字しか表示できない代わりに簡単に表示できる機能.大昔の表示機器はテキストモードしかなかったが,UEFI の時代では UEFI の処理を抜けた後(つまり OS に処理が移った後)に使えるのはグラフィックモードしかない模様.