LoginSignup
2
1

More than 5 years have passed since last update.

BitVisor上でマイクロコードアップデート

Posted at

BitVisor上でマイクロコードアップデートがどのように行われるかについて紹介します。

AMDプロセッサの場合

AMDプロセッサではPATCH_LOADER MSR(0xC0010020)への書き込みでマイクロコードアップデートが行われます。このMSRに書き込まれる値は仮想アドレスなので、単純にVMMで同じ値を書き込んでも正しく適用されません。

BitVisorではこの書き込みをパススルーに設定しているので、マイクロコードアップデートは仮想マシン上から直接適用されます。

Intelプロセッサの場合

IntelプロセッサではIA32_BIOS_UPDT_TRIG MSR(0x79)への書き込みでマイクロコードアップデートが行われます。このMSRに書き込まれる値は仮想アドレスなので、単純にVMMで同じ値を書き込んでも正しく適用されません。

Intelプロセッサの制約により、このMSRへの書き込みをパススルーにしても、マイクロコードアップデートは適用されず、スキップされます。そのため、以前のバージョンのBitVisorでは、パススルーにせず、代わりに以下のようなメッセージを出して、マイクロコードアップデートをスキップしていました:

msr_pass: microcode updates cannot be loaded.

しかし、最近のLinuxは、マイクロコードアップデートが適用されないと永遠に再試行するようになってしまったので、このような実装では起動しなくなりました。本当は、マイクロコードアップデートなど必要ないと、ゲストオペレーティングシステム(OS)に伝えることができれば良いのですが、CPUIDなどもほぼパススルーとしているBitVisorではそれもなかなか難しいようですので、現在は、ゲストOSが行うマイクロコードアップデートを、VMMが代わりに実行する処理が実装されています。

代わりに実行するというのは、つまり、ゲストOSの仮想アドレス空間をVMM側にマップして、そのVMM側の仮想アドレスをIA32_BIOS_UPDT_TRIG MSRに書き込んでやるということです。ここで問題なのは、マイクロコードアップデートのデータサイズがわからないことです。マニュアルを見ると、マイクロコードアップデートのヘッダーなどの説明があり、一見するとデータサイズもわかりそうに見えます。ところが、これはIntelから配布されるアップデートファイルに含まれるヘッダーの話で、OSがこのヘッダーを見て、アップデートデータのアドレスをMSRに書き込むということになっています。アップデートデータの構造はマニュアルに載っていませんので、サイズがわかりません。

そこで、どうやるかというと、ページフォールトを使います。マニュアルを見ても特に書かれていませんが、どうも試したところでは、このMSRに書き込んだ仮想アドレスがマップされていないなどの場合は、普通にWRMSR命令でページフォールトが発生するようです。よって、不在ページのアドレスをMSRに書き込み、ページフォールトが発生するたびに、そのページをマップしてやり直していけば、プロセッサが本当に必要としているページだけをマップして、マイクロコードアップデートを実行できます。

ソースコードはこのあたりにあります:
https://bitbucket.org/bitvisor/bitvisor/src/34fa14de2421cd3564323eec998f8e0c465e99a0/core/msr_pass.c?at=default&fileviewer=file-view-default#msr_pass.c-126

mm_process_alloc()関数とmm_process_switch()関数を使って、プロセス機能が用いる仮想アドレス0-0x3FFFFFFFをあけます。その上で、アドレス0を使うのはさすがにまずいかなということで、アドレス0x1000に、ゲストOSが指定するアドレスの下位12ビットを加えた値をMSRに書き込みます。この時、callfunc_and_getint()関数を用いて書き込みを行うことで、その際に例外が発生してもクラッシュせず、例外の割り込み番号を取得することができます。

例外が発生しなければ成功ですが、最初は例外が発生するはずです。一般保護例外に関してはそのままゲストOSに対して同じ例外を生成します。ページフォールトの場合はそのアドレスをCR2から取り出し、そこからゲストOS側の仮想アドレスguest_addrを計算して、cpu_mmu_get_pte()関数でゲストOSのページテーブルを読み出します。この関数は、large page/huge pageであっても、ページテーブルエントリーがあるならこうなっているだろう、という値を生成するので、特に気にせずページテーブルエントリーを読み出したものとして扱うことができます。もしページがなければゲストOSに対してページフォールトを生成します。そうして得られた物理アドレスを、念の為MMIOフックがないかチェックしたうえで、mm_process_map_shared_physpage()関数を用いてマップします。うまくいったら、MSRの書き込みをやり直します。

今のところ、ページテーブルエントリーにアクセスビットをセットする処理や、ページテーブルエントリー内のキャッシュに関わるビットの処理が欠けていますが💧、実用上は問題ないでしょう。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1