BitVisorのPCIデバイスI/Oモニター

最新のBitVisorにはPCIデバイスのI/Oモニターが実装されています。その使い方を紹介します。

## I/Oモニターとは

『はじめて読む486』という本に、I/Oモニターが出てきます。これは、仮想8086モードを利用して、I/Oポートのアクセスをとらえてメモリー上に保持し、後でそれを読み出して画面に表示するものでした。

BitVisorのI/Oモニターは、任意のPCIデバイスを対象として、I/OポートおよびメモリーマップトI/O (MMIO) のアクセスをメモリー上に記録し、後で参照できるようにするものです。

## PCIデバイスのアドレス空間

PCIデバイスには、コンフィギュレーション空間があり、その中に6つのベースアドレスレジスターがあって、それぞれがI/OポートまたはMMIOのアドレスを持てるようになっています。具体的な用途はデバイスによって異なります。また、PCIブリッジの場合は2つのベースアドレスレジスターがあります。

## 記録対象の選択

記録対象には、コンフィギュレーション空間 (config)、および、ベースアドレスレジスター (bar0からbar5) のアドレス空間があります。

各アドレス空間の、どのオフセットから何バイトのアクセスを対象とするかを設定できます。また、読み取りアクセスと書き込みアクセスのいずれか、または、両方を記録できます。ステータスレジスターのように連続してポーリングされるような状況においては、連続するアクセスをまとめて回数記録にしてしまうこともできます。

## 記録方法の選択

記録する時間 (BitVisor起動からの時間の範囲) と、I/O回数の上限を設定できます。回数の上限を超えた時、古いものから捨てていくかどうかも選択できます。

## BitVisorの設定

ここまで決定したらBitVisorの設定を行います。`vmm.driver.pci`にmonitorドライバーを指定します。対象デバイスをslotあるいはdeviceなどの方法で指定してください。

次にオプションを書きます。簡単なところからいきます。I/O回数の上限はnentriesで設定します。デフォルトは1024です。

```
nentries=I/O回数の上限
```

回数の上限を超えた時、古いものから捨てていくかどうかはoverwriteで設定します。yesまたはnoになります。捨てる場合yesです。デフォルトはyesです。

```
overwrite=yes|no
```

記録する時間はtimeで設定します。BitVisor起動からの時間を時刻とし、秒単位で書きます。デフォルトはすべて記録です。

```
time=開始時刻
```

範囲の設定もできます。

```
time=開始時刻 終了時刻
```

複数の範囲指定もできます。スペースで区切って開始時刻と終了時刻を繰り返します。最後の終了時刻を省略すると、それ以降を表します。

最後に、複雑な記録対象設定です。アドレス空間の名前ごとに記録対象とするアクセスの指定を行います。

```
アドレス空間の名前=記録対象アクセスの指定
```

記録対象アクセスの指定は以下のいずれかになります。

- アクセス
- アクセス オフセット サイズ の繰り返し

アクセスはr|w|rw|R|W|Rw|rW|RWのいずれかです。rとRは読み取り、wとWは書き込みを表し、大文字は同じアドレスに対する連続アクセスをまとめて回数記録にする指定になります。

アクセスだけを指定した場合は、対象アドレス空間の全体が記録対象です。

アクセスに続けてオフセットおよびサイズをスペースで区切って指定することで、一部だけを記録対象にできます。また、繰り返し記述することで複数の対象を指定可能です。

これらをすべてドライバーオプションとして設定します。設定例をいくつか示します。

AHCIのMMIOアクセスを記録する

```
device=ahci, driver=monitor, bar5=rw
```

AHCIのポート0のアクセスを記録する (ポート0はMMIOのオフセット0x100から0x17Fにあります)

```
device=ahci, driver=monitor, bar5=rw 0x100 0x80
```

AHCIのコンフィギュレーション空間をBitVisor開始後10秒だけ記録する

```
device=ahci, driver=monitor, config=rw, time=0 10
```

この他、dbgshを使うので有効にしておきましょう。

```
vmm.dbgsh=1
```

## 記録の参照

記録されたI/Oは、ゲストOS上でdbgshコマンドを実行し、その上でmonitorコマンドを実行して参照します。

monitorコマンドを実行すると、モニター中のデバイス一覧が表示されます。

```
> monitor
0: 00:1F.2/8086:2929
1: 04:00.0/8086:444E
monitor>
```

各デバイスの左端に出ている番号を入力すると、記録されたI/Oが表示されます。

```
monitor> 0
               10000000 bar5 mem r 0xF2926100 0x00000100 l 0x00000001
               10000010 bar5 mem w 0xF2926100 0x00000100 l 0x00000000
monitor>
```

空行を入力するとmonitorコマンドが終了します。

内容は、1回のI/Oにつき1行で、次の項目がスペース区切りで表示されます:

1. BitVisor起動からの時間 (マイクロ秒)
- アドレス空間の名前 (config、または、bar0からbar5)
- メモリーまたはI/Oの区別 (memまたはi/o)
- 読み取りまたは書き込み (rまたはw)
- 物理アドレス
- アドレス空間内のオフセット (物理アドレスからベースアドレスを引いたもの)
- アクセスの長さ (b:8ビット、w:16ビット、l:32ビット、q:64ビット)
- 値
- 回数 (あれば)

## 実行例

iMac Mid 2014でAHCIのアクセスを見てみます。

```
device=ahci, driver=monitor, bar5=rw 0x100 0x80
```

![pic1.png](https://qiita-image-store.s3.amazonaws.com/0/103733/c9176cab-1849-702a-6fa7-fd211830a7ba.png)

このようにOSがたくさんのアクセスをしてくるパターンでは、BitVisor起動直後の記録は消え去ってしまいます。そこでoverwrite=noをつけると、起動直後の記録を見ることができます。

```
device=ahci, driver=monitor, bar5=rw 0x100 0x80, overwrite=no
```

![pic2.png](https://qiita-image-store.s3.amazonaws.com/0/103733/a775c649-e693-583f-3ab2-f763a68e4585.png)

最初のほうはファームウェアによるアクセスです。0x00000138への書き込みアクセスのところがATAコマンド発行のタイミングです。

## 制約事項

何でもかんでも記録対象にするとたぶん動きません。

MMIOのモニターは、対象領域を含むページのVM上の物理アドレスが、ホストの物理アドレスにマップされていない状態とすることで、アクセス時にVMMに制御が戻るようにし、VMMで命令解釈とアクセス代行をしてアクセス内容を特定する仕組みです。しかし、BitVisorの命令解釈は一般的なデバイスドライバーを想定しているもので、不完全なため、以下のような制約があります:

- SIMD命令、浮動小数点演算命令などには対応していません
- メモリーを対象とした演算命令については、読み取りと書き込みが別々に記録されます
- LOCK prefixが使用されているとpanicとなります

命令解釈ルーチンについてはcore/cpu_interpreter.cをお読みください。