KVM でゲストを作成する際、KVMはご丁寧にもゲスト作成時の TSC をゼロから数えるためにホストのTSCからのズレ(オフセット)を用意します。ゲスト内で RDTSC 命令が発行されると、ホストのTSCにゲストのTSC offset を足したものを取得します。
ところで、ゲストからみたTSC値がホストからみたTSC値と異なると、ゲストの動作記録とQemuやホストOSの動作記録を突き合わせるときに、ゲストのTSC offsetを考慮しなければならず面倒です。そこで、systemtap を使ってカーネル無改造で手軽に TSC offset をゼロにしようと言うわけです。TSC offset が 0 になれば、ゲストとホストから見える TSC値は一致します。
TSC offset ゼロ化
KVM は ioctl KVM_CREATE_VCPU が発行されると、Virtual CPU を作成します。この際、現時点でのTSC値を取得し、その負値を tsc_offset にします。(vmx_write_tsc_offset@arch/x86/kvm/vmx.c)
vmx_write_tsc_offst 関数に systemtap の probe を差し込んで、guru モードで $tsc_offset = 0に書き換えてやればいいわけです。
systemtap の '-L' オプションで、probe を差し込む箇所を確認します。
[root@edge2]/home/eiichi/stap# stap -L 'module("kvm_intel").statement("vmx_write_tsc_offset")' module("kvm_intel").statement("vmx_write_tsc_offset@arch/x86/kvm/vmx.c:1872")さらに local 変数を表示させます。
[root@edge2]/home/eiichi/stap# stap -L 'module("kvm_intel").statement("vmx_write_tsc_offset@arch/x86/kvm/vmx.c:1872")' module("kvm_intel").statement("vmx_write_tsc_offset@arch/x86/kvm/vmx.c:1873") $vcpu:struct kvm_vcpu* $offset:u64$offset で tsc offset が取得できます。
以下のような stap script を用意します。
[root@edge2]/home/eiichi/stap# cat tsc_offset_zero.stp #!/usr/bin/stap -g probe begin { printf("start\n") } probe module("kvm_intel").statement("vmx_write_tsc_offset@arch/x86/kvm/vmx.c:1872"){ $offset = 0 printf("VCPU : %x tsc offset changed to 0\n", $vcpu) print_backtrace() }実行するときには、stap -g オプションをつけましょう。'-g' オプションは guru モードといって、カーネル内変数を上書きできる素敵なモードです。
上のスクリプトを実行し、start が表示されたらゲストを起動します。
動作例はこんな感じ:
[root@edge2]/home/eiichi/stap# stap -g -d kernel -d kvm tsc_offset_zero.stp start VCPU : ffff88004ee23dd0 tsc offset changed to 0 0xffffffffa022f9ad : vmx_write_tsc_offset+0xd/0x50 [kvm_intel] 0xffffffffa01badcd : kvm_write_tsc+0x18d/0x2c0 [kvm] 0xffffffffa0232c26 : vmx_create_vcpu+0x386/0x810 [kvm_intel] 0xffffffffa01c43b7 : kvm_arch_vcpu_create+0x47/0x70 [kvm] 0xffffffffa01aeb0e : kvm_vm_ioctl+0x2ae/0x5b0 [kvm] 0xffffffff811a6529 : do_vfs_ioctl+0x99/0x580 [kernel] 0xffffffff811a6aa1 : sys_ioctl+0x91/0xb0 [kernel] 0xffffffff8163f9d9 : system_call_fastpath+0x16/0x1b [kernel] VCPU : ffff88004ee23dd0 tsc offset changed to 0 0xffffffffa022f9ad : vmx_write_tsc_offset+0xd/0x50 [kvm_intel] 0xffffffffa01badcd : kvm_write_tsc+0x18d/0x2c0 [kvm] 0xffffffffa0231f29 : vmx_set_msr+0x179/0x2a0 [kvm_intel] 0xffffffffa01b4f29 : do_set_msr+0x19/0x20 [kvm] 0xffffffffa01bcf60 : msr_io+0xc0/0x150 [kvm] 0xffffffffa01c039b : kvm_arch_vcpu_ioctl+0x59b/0xf50 [kvm] 0xffffffffa01acea8 : kvm_vcpu_ioctl+0x118/0x610 [kvm] 0xffffffff811a6529 : do_vfs_ioctl+0x99/0x580 [kernel] 0xffffffff811a6aa1 : sys_ioctl+0x91/0xb0 [kernel] 0xffffffff8163f9d9 : system_call_fastpath+0x16/0x1b [kernel]
これでゲスト内で RDTSC によって取得する TSC値はホストと同じものになります。ゲストとホストでトレースを突き合わせる場合は、例えば systemtap なら get_cycles() でタイムスタンプを取得してゲストホストそれぞれでトレースデータを保存し、あとでマージします。
一度ゲストが起動してしまえば、ゲスト内で TSC書き換えが起こらない限り、上記の stap script を停止してしまっても大丈夫です。
Systemtap を使えば、ゲストOS-Qemu-ホストOS にまたがる動作記録を一貫して取得できるので大変便利です。Qemu/KVMの調査がはかどります。
0 件のコメント:
コメントを投稿