RyzenでBitVisor(2018年版)

AMD Ryzenプロセッサで楽しくBitVisorにさわりましょう。っていうのを去年書きましたが、今年版です。

# 差分の概要

去年は以下の記事でした:
https://qiita.com/hdk_2/items/1111d6c981a7328cf146

今回は、プロセッサを変えたのと、ハードディスクドライブを変えたのと、ソリッドステートドライブを追加したのと、カーネル起動オプションnomodesetを外したのと、sysctlのkernel.randomize_va_space=0を外しました。

# 環境

* マザーボード: ASUS PRIME B350M-A
* プロセッサ: AMD Ryzen 7 2700 BOX (AM4/3.2/20M/C8/T16/65W)
* メモリー: G.SKILL F4-2400C16D-16GFX (DDR4 PC4-19200 8GB 2枚組)
* ビデオカード: 玄人志向 GF-GT710-E1GB/LP (GeForce GT710 1GB LP)
* ハードディスクドライブ: HGST HDN726040ALE614
* ソリッドステートドライブ: Crucial M4-CT064M4SSD2
* オペレーティングシステム: Debian GNU/Linux 9 amd64

昨年からのソフトウェア面の大きな差異として、Spectre対策がLinuxカーネルに入ったことがあげられます。

```shell-session:
$ dmesg|grep Spectre
[    0.015582] Spectre V2 : Mitigation: Full AMD retpoline
[    0.015583] Spectre V2 : Spectre v2 / SpectreRSB mitigation: Filling RSB on context switch
[    0.015583] Spectre V2 : Spectre v2 mitigation: Enabling Indirect Branch Prediction Barrier
$ grep bugs /proc/cpuinfo|uniq
bugs            : fxsave_leak sysret_ss_attrs null_seg spectre_v1 spectre_v2 spec_store_bypass
```

# ビルドする

```shell-session:
a@best:/dev/shm/bitvisor-2.0$ for i in 1 2 3 4 5
> do time make -j > /dev/null 2>&1;make clean >/dev/null;done

real    0m5.356s
user    1m2.160s
sys     0m3.996s

real    0m5.218s
user    1m2.152s
sys     0m3.952s

real    0m5.236s
user    1m2.340s
sys     0m3.784s

real    0m5.256s
user    1m2.432s
sys     0m3.764s

real    0m5.228s
user    1m2.272s
sys     0m3.940s
```

ビルド時間が0.5秒ほど短縮しています。Spectre対策も入ってこれですから、確実に速くなっているかと思います。tmpfsなので、純粋にCPUの性能向上のおかげと思われます。

# 動かす

```
Starting BitVisor...
Copyright (c) 2007, 2008 University of Tsukuba
All rights reserved.
ACPI DMAR not found.
FACS address 0xDAB82E00 0xDAB82DC0
Module not found.
Old log found in RAM
Processor 0 (BSP)
Using SVM.
Processor 0 3194078208 Hz (Invariant TSC)
Loading drivers.
AES/AES-XTS Encryption Engine initialized (AES=openssl)
Copyright (c) 1998-2002 The OpenSSL Project.  All rights reserved.
Generic ATA/ATAPI para pass-through driver 0.4 registered
Generic AHCI para pass-through driver registered
Generic RAID para pass-through driver registered
Generic IEEE1394 para pass-through driver 0.1 registered
Broadcom NetXtreme Gigabit Ethernet Driver registered
VPN for Intel PRO/100 registered
Intel PRO/1000 driver registered
Realtek Ethernet Driver registered
NVMe para pass-through driver registered
NVMe para pass-through driver registered
PCI device concealer registered
PCI device monitor registered
Generic EHCI para pass-through driver 0.9 registered
Generic EHCI para pass-through driver 0.9 registered
Generic UHCI para pass-through driver 1.0 registered
xHCI para pass-through driver 0.1 registered
Intel Corporation Ethernet Controller 10 Gigabit X540 Driver registered
PCI: finding devices...
PCI: 40 devices found
MCFG [0] 0000:00-3F (F8000000,4000000)
Starting a virtual machine.
Processor 1 (AP)
Processor 2 (AP)
Processor 3 (AP)
Processor 4 (AP)
Processor 5 (AP)
Processor 6 (AP)
Processor 7 (AP)
Processor 8 (AP)
Processor 9 (AP)
Processor 10 (AP)
Processor 11 (AP)
Processor 12 (AP)
Processor 13 (AP)
Processor 14 (AP)
Processor 15 (AP)
Processor 10 3194170880 Hz (Invariant TSC)
Processor 9 3194179584 Hz (Invariant TSC)
Processor 1 3194171392 Hz (Invariant TSC)
Processor 3 3194182656 Hz (Invariant TSC)
Processor 4 3194171392 Hz (Invariant TSC)
Processor 8 3194179584 Hz (Invariant TSC)
Processor 7 3194182656 Hz (Invariant TSC)
Processor 2 3194182144 Hz (Invariant TSC)
Processor 5 3194182656 Hz (Invariant TSC)
Processor 13 3194179072 Hz (Invariant TSC)
Processor 14 3194179072 Hz (Invariant TSC)
Processor 6 3194166784 Hz (Invariant TSC)
Processor 12 3194173440 Hz (Invariant TSC)
Processor 15 3194173440 Hz (Invariant TSC)
Processor 11 3194165760 Hz (Invariant TSC)
```

普通に動いていますね?

## 待った

```
Old log found in RAM
```

鋭い皆さんはこれに気づいているかも知れません。これは、ネットワーク等でうまくログを得られない場合のデバッグ用の機能によるもので、RAMが消されずに再びBitVisorが同じアドレスに読み込まれることを期待して、panic時にRAM上にログとチェック用のキーワードとCRCを残し、次回起動時にそこからログが取り出された時に残るメッセージです。dbgshのlogコマンドで参照できます。このログの手前には以下のpanicが残されていました:

```
panic(CPU7): Assertion failed!(n < NUM_OF_ALLOCSIZE) function mm_page_alloc file core/mm.c line 546
CR0 80000019    CR2 00000000    CR3 D08C8000    CR4 000006A0
RFLAGS 00200012  GDTR 428A6808+000000FF  IDTR 40F296E0+00001000
stackdump: 7 428A6B68 0 40126014 7 40138CFA 40138D18 0 D D 0 7 428A6B68 4011F26D 202 401268C7 7 40126DA6 706EF7F3528E5277 926E3CADDD6B28A9 AC5ACE48F512BE5E BD784B7A00000028 428BFB88 428BFB28 364DE5B9684CA7B0 402D7847 40294CB0 402D782C 222 41085000 9E4F4BB6DAA0F1B2 D 
backtrace: 40126014 40138CFA 40138D18 4011F26D 401268C7 40126DA6 4010F4E1 40120FA0 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40121421 40121454 4012D5F5 4012D8FF 4012AF89 40120AB0 4011D41A 4012030D 4012032A 4010EEA7 
Guest state and registers of cpu 7 ------------
RAX 00000000    RCX 00000000    RDX 00000000    RBX 00000000
RSP 00000000    RBP 00000000    RSI 00000000    RDI 00000000
R8  00000000    R9  00000000    R10 00000000    R11 00000000
R12 00000000    R13 00000000    R14 00000000    R15 00000000
Exception 0x0E
ACR   ES 00000093 CS 0000009B SS 00000093 DS 00000093 FS 00000093 GS 00000093
LIMIT ES 0000FFFF CS 0000FFFF SS 0000FFFF DS 0000FFFF FS 0000FFFF GS 0000FFFF
BASE  ES 00000000 CS 00000000 SS 00000000 DS 00000000 FS 00000000 GS 00000000
SEL   ES 00000000 CS 00000000 SS 00000000 DS 00000000 FS 00000000 GS 00000000
RIP 00000000  RFLAGS 00000002  GDTR 00000000+0000FFFF  IDTR 00000000+0000FFFF
EFER 00000000
VMCB 0x42a70000 (phys D0A70000)
EXITCODE: 0=0x0 (VMEXIT_CR0_READ)
EXITINFO1 00000000  EXITINFO2 00000000  EXITINTINFO 00000000
------------------------------------------------
panic(CPU7): Assertion failed!(n < NUM_OF_ALLOCSIZE) function mm_page_alloc file core/mm.c line 546
```

これはメモリーが確保できなかった時のpanicなのです。ヤバイ。

## せっかくなので

少し追っかけてみましょう。

backtrace: 40126014 40138CFA 40138D18 4011F26D 401268C7 40126DA6 4010F4E1 40120FA0 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40120FD1 40121421 40121454 4012D5F5 4012D8FF 4012AF89 40120AB0 4011D41A 4012030D 4012032A 4010EEA7 

これは、CONFIG_BACKTRACE=0にしていたので正確なバックトレースではなく、スタックの中から.textセクションのアドレス範囲の値を最大32個抽出してそれっぽく並べているだけです。なのでスタック上のゴミが混ざっていることがよくあります。真面目にデバッグの必要があるならCONFIG_BACKTRACE=1にしておきましょう。そうするとバックトレースのアドレスは矢印<-で結ばれて最後まで出力されます。

それはいいとして、このアドレスからコード位置を引く方法としてはaddr2lineコマンドを使う方法があります。

```shell-session:
a@best:~/bitvisor$ addr2line -e bitvisor.elf 0x40120FD1
/home/a/bitvisor/core/mm.c:551
```

こんな風にコード位置を示してくれます。これでも物足りない時はobjdumpコマンドで逆アセンブルしてしまいましょう。64ビットの場合はこうなります:

```shell-session
$ objdump -d bitvisor.elf -m i386:x86-64 | pager
```

あるいは-dの代わりに-Sを使えば、ソースコードと混ぜた逆アセンブル結果が出ますので、好みに合わせて選びましょう。

さて、40120FD1はメモリー確保処理内の再帰呼び出し、40121421はalloc_pages()、40121454はalloc_page(), 4012D5F5はsvm_np_init()の中のようです。つまりnested page tablesの準備のところ? 1ページも確保できないとはよっぽどです。そんなにメモリーはギリギリなんでしょうか。ちゃんと起動した時のBitVisorの空きメモリーをdbgshでチェックします。

```
> sendint 
sendint> free 
send 0 to free
sendint>  
> log 
(省略)
2529 pages (10116 KiB) free
```

うーん、一応9.8MiBはあるようですね... 起動時にそんなにメモリーを解放する場面もなさそうですし、どうして動いたり動かなかったりするのか、謎は深まるばかりです。

なお、今回引っ掛かったnested page tablesの割当処理を見ると、論理プロセッサーごとに1024ページ、つまり4MiBを消費しています。Ryzen 7は8コア16スレッド、つまり論理プロセッサーが16個ありますので、これだけで64MiBです。そして、VMM全体のサイズは128MiBです。冷静に考えると、ThreadripperやCore i9のほとんどのモデルでは動かないんだなぁ:cry: