KazuminEngine

プログラマーの日記

sigactionシステムコールによるホスト型ハイパーバイザー開発調査~サイボウズ・ラボユース10期生~

こんにちは、かずみんです。この記事は、サイボーズラボユースの発表会の持ち時間が7分しかなく、詳細なことが話せないので、発表を補うことが目的です。と言っても、掲示板のメモを時系列順に書いて行っただけの物になったりしています。

まずは、どのようにして応募に至ったか

SecHack365のIMでいろいろ呟いているのだけれど、次のように投稿したら、坂井さんから「できるよ」とのことで、「signalシステムコール を使えば、できちゃうと思います。」とのことで、作り始めたのが始まり。私は、前々からハイパーバイザー に憧れがあり作りたくもあり、手始めにエミュレータから作り始めたのである。

投稿:「閃いたんだけど、このエミュレーターに追加実装で、センシティブじゃ無い命令を直接実行させてあげて、ゲスト用のプロセスを作ってやるとハイパーパイザーになりそうだけど。なります?」

システムコール を使うような実装は、初めてだったので一ヶ月ぐらいかけて、signalシステムコール などを使って、cpuで命令を直接実行し、センシティブな命令をトラップするプロトライプが完成した。

基本的な実装方法は、坂井さんのホームページの

各種実験用サンプル

に書いてある。基本的なロジックはこのページに書いてあるので、読んでください。

一月ぐらいかかったのは、osをメモリに読み込んで、実行し、エミュレートした際に、EIPを更新するのを忘れて、同じ命令を繰り返し実行しちゃうとかで、いろいろ試行錯誤していたからである。結果的にはEIP++したけど、試行錯誤している段階では、iretしてコンテキストスイッチして解決しようなどとしていた。また、結果的には使ってないのだけれど、get_sigframe() setup_sigcontext() struct sigframe struct sigcontextをインポートするのにも時間がかかった。(それも初めてだった。)また、__builtin_frame_addressを使って、ゲスト実行時のコンテキスト情報がうまくとれなかったのも原因。これもかなり時間がかかって、諦めて、代わりに、sigactionシステムコール を使うことで、ゲスト実行時のコンテキスト情報を取得した。ちなみに、sigactionの方を使うのがモダンらしい。signalシステムコール をは古くて、代わりにsigactionシステムコール を使ってね。とマニュアルに書いてある。坂井さんがsignalシステムコール を用いて実装しているのは、一般にそれを使っている人が多いので、理解しやすく受け入れやすいためだそうだ。

ここらへんで、12月中頃にラボユースに応募したら、面接して受かったので、1/8日頃から契約を開始した。c++の書き方などがいろいろわからなかったので、『C/C++によるソフトウェア開発』の光成メンターに応募した。ちなみに、面接の時に、星野さんがいたけど、それ以来一度も関わっていない。

面接では、「VMMを作りたい」と話したところ、よくわかってもらえなかったので、なぜか考えていたら、自分の作りたいものはハイパーバイザー だったので、そのことをあとでメールして光成さんと星野さんに伝えてもらった。また、そこまで実装した分で「

ホスト型ハイパーバイザー の作り方 part.1 - KazuminEngine

」という記事を書いたので、メールを送って、二人にリンクを共有してもらった。私のhvの基本ロジックは、この記事に書いてあるので、これを読むと、あとの説明がより理解できると思います。

このメールは気軽に送れた。相手側が原因で、面接が五分程度遅れたこともあり、「何か質問などがございましたら、メールしてください」と言われていたからだ。もちろん、この記事をみて、応募しようかなと思った人も、面接開始時間が遅れなくても、気軽に質問メールなどを送って欲しい。

ラボユース開始

活動中は、サイボウズOfficeの個人作業掲示板にTwittterみたいに、やったこと悩んでいることメモなど、綴っていくのだけれど、それを時系列順に書いていく。決して、見やすいことはないだろうが書いていく。一行が一つの仕事の単位だと思って、読んでいただくと。

作業を開始する前に、メンターの光成さんと打ち合わせをした。勤務の仕方など3月までのゴールについて話した。ここでは、三月までにはりぼてosの画面描画を成功させることを目標としようと話した。なぜ、はりぼてosを動かすのかというと、私がSecHack365で作ったはりぼてOSが動くエミュレータを拡張して実装していくからである。極論、はりぼてOSをうごかしたい思いはないが、xv6などを動かす前に機能が少ないはりぼてosをまずは動かせるようにしようとの魂胆である。また、始まる前は毎日フルで入るつもりだったが、様子見で、二週間程度、月水金で入ることが決まった。

まず、リポジトリを作った。https://github.com/kazuminn/ghyper

メモリ上にあるバイナリをステップ実行する術を探していたら、坂井先生のハローワールド本にちょうど書いていたのを発見した。layout asmすればよかった。 また、display/i $pcとすれば、ステップ実行ごとに命令を表示できることをメンターさんに教えてもらった。前者だと、表示が崩れたり、終了すると画面に何も残らないので、後者を使うことにした。

次に、紹介するのはかなり大きな問題だった。push %es命令がシグナルを出して来れなくて、例外捕虜できない問題にぶち当たった。gdbデバッグして、SIGILLシグナルが連続して発行されていることが確認された。なので、例外ハンドラー実行中に例外が発生しているのが原因なんじゃないかと思い。ハンドラー実行中に例外を発行できないようにした。設定したけど、現状変わらないので、どうやら、それが原因じゃないことがわかった。悩んでいると、毎週水曜日の定例が開催され、そこで、詰まっていることを話したら、どうしていくのがいいのかを教えてもらった。qemu上で同じことをしたら、push %esしたら、大丈夫かどうかをまずは確認してね。とのことである。さらに、正しく動いているものを確認しましょう。とのことで、最小構成のpush %esが動く小さいプログラムが動くコードを書いて、それを実行して、HVのコードで動かないことがあるなら、コメントアウトなどをして、動く状態に持っていき、それからコードを戻しながら、動かなくなった時を見つけて、その原因を探るのが良いと教えてもらいました。バイナリエディタでpush %csに変えると上手く動くことが判明し、push %esの一つ上から順に一つ上に開始位置を変更して実行して、動かなくなる命令を発見した。それは、espを変更する命令だった。それが原因で、スタックを変更するといろいろ狂うのがわかった。その命令をバイナリエディターでnop(何もしない命令)に変更すると、push %esが例外ハンドリングされた。どうやって、解決したらいいのか案が浮かばないので、坂井さんに状況を説明すると、sigaltactionシステムコール とSA_ONSTACKを使って、シグナル処理ようのスタックを別に割り当てると回避できるかもしれない。とのことであった。三十分ぐらいで実装を完了して、この件は解決した。

ここぐらいから、10:00-17:00勤務して行った。夕方だれるので、五時まで。

開発はiMacのメモリ4GBでしているのだが、ブラウザなどの他のアプリを落とさないと、実行できなくなってきたので、物理メモリを12GBに増やした。4000円ほどで購入した。

次に、lock wbinvd命令にぶち当たったのだが、これがおかしかった。objdumpしたものと、ソースコードが違うので、なんでだろうと、悩んでいると。メンターさんによると、wbinvd命令にはlockプレフィックスはつかないらしい。いろいろ考えて判明したのだが、osのはじめは16bit codeなので、16bitモードで実行しないといけなかった。 面倒だったので、32bitモードから開始することにした。push %esもプレフィックスがずれていて、そんな命令実行されないようだった。

とりあえず、eipを飛ばすだけで、32bitモードに入った。

mov_rm32_imm命令を実装。エミュレーターのmodrm関数を流用して、ちょっと変えて、使った。

mov_rm32_r32命令を実装

ここで、レジスタ退避とレジスタ復帰が必要なことがわかり、例外ハンドラーのはじめと終わりに挟むことにした。実装したんだけど、ずっとバグってる。

エミュレーションをインラインアセンブリでできないかと試行錯誤してみたが、結構複雑になり、結局それはやめた。

エミュレーターのメモリバッファを~4GB領域に配置できれば、ほぼトラップされないので、早くなるんじゃないかと考えたが、そこにメモリバッファを置くのは不可能だったので、諦めた。

ここで、push/popが普通に実行されることを話すと、それだと、メモリ不整合になるという話になりました。エミュレーターのメモリバッファ上で作業しているのに、hvのメモリをpush/popで利用しちゃうと、不整合になる。いろいろ案を出したのだが、それだと、上手くいかないとメンバーさんが言い、理由を説明してもらって、納得した。最終的に解決策として、threadを作り、その中で、allocaシステムコール でスタック全体を確保し、push/popで例外ハンドラーを起こすようにした。やり方は、まず、pthreadが動く小さいプログラムを書いて、そこで動いたのが確認できたら、hvのプロジェクトに打ち込んだ。

eflagsのレジスタ退避ができなくて、悩んでいたが、popfを使えば、できそう。と思った。

c++のgoogle testをプロジェクトに組み込んだ。オプションの順番でエラーがでて、少々てこずった。また、main関数周りでエラーが起きて、かなりてこずった。五時間ぐらいかかったのではないだろうか。c++は慣れていないので、大変だった。rustやgolangだと、組み込みのunit testがあるので、なんて便利なんだ思った。

64ビット環境で32ビットがどうやって実行されるかの質問をしていた。少し、理解した。

mov edx, esp; mov eax, 3; push eax; mov ecx, [edx] ; pop eax; バイト列が実行できるようにした。(レジスタ退避と復帰はバグているが

一つ問題を見つけました。

40 00 04 48というバイト列は 32bitモードでは

(A) inc eax add byte ptr [eax+ecx*2], al

64bitモードでは (B) add byte ptr [rax+rcx*2], al です。

だから64bit環境で32bitバイナリで(A)のつもりでビルドされたバイト列を実行すると(B)になってしまいます。そしてraxやrcxの値によってはそのまま動いてしまうでしょう。

との指摘が。

もうちょっと、詳しく説明すると、正確には、[rax+rcx*2]ですが、説明のためにこれをraxと簡略化します。 64bitモードで実行可能な32bit命令において、32bitのeax(raxの下位32bit)が、64bitとしてcpuに直接実行してもらうと、eaxがraxになる。もし、raxが正常値でsigsegvが起こらない時は、ハンドリングのしようがなく、実行したいeaxに置き換えることはできず、そのまま実行されてしまうので、実装不能である。 rcxも同様。 → 32bithvにしたら、回避できそう。ここを考えていると、契約期限が迫ってきました。

今のところ32ビット開始の以下のところが動いています。(レジスタ退避と復帰は動いていませんが。。。

   319 00000000 55                               PUSH    EBP
   320 00000001 89 E5                               MOV EBP,ESP
   321 00000003 57                                  PUSH    EDI
   322 00000004 56                                  PUSH    ESI
   323 00000005 53                                  PUSH    EBX
   324 00000006 8D 5D A4                            LEA EBX,DWORD [-92+EBP]
   325 00000009 81 EC 00000450                      SUB ESP,1104
   326 0000000F C7 85 FFFFFBE0 FFFFFFFF             MOV DWORD [-1056+EBP],-1
   327 00000019 C7 85 FFFFFBDC 00000000             MOV DWORD [-1060+EBP],0
   328 00000023 C7 85 FFFFFBD8 7FFFFFFF             MOV DWORD [-1064+EBP],2147483647
   329 0000002D C7 85 FFFFFBD4 00000000             MOV DWORD [-1068+EBP],0
   330 00000037 C7 85 FFFFFBC8 00000000             MOV DWORD [-1080+EBP],0
   331 00000041 A0 00000FF1                         MOV AL,BYTE [4081]
   332 00000046 C0 F8 04                            SAR AL,4
   333 00000049 89 C2                               MOV EDX,EAX
   334 0000004B 83 E2 07                            AND EDX,7
   335 0000004E 89 95 FFFFFBC4                      MOV DWORD [-1084+EBP],EDX
   336 00000054 C7 85 FFFFFBC0 FFFFFFFF             MOV DWORD [-1088+EBP],-1
   337 0000005E C7 85 FFFFFBBC FFFFFFFF             MOV DWORD [-1092+EBP],-1
   338 00000068 C7 85 FFFFFBB8 FFFFFFFF             MOV DWORD [-1096+EBP],-1
   339 00000072 C7 85 FFFFFBB4 00000000             MOV DWORD [-1100+EBP],0
   340 0000007C C7 85 FFFFFBB0 00000000             MOV DWORD [-1104+EBP],0