2013/06/23

OSSの開発活動を可視化するWebサービスをつくりました

Linux Foundation の報告書 "Linux Kernel Development: How Fast it is Going, Who is Doing It, What They are Doing, and Who is Sponsoring It" には Linux Kernle 開発の統計情報が数多く含まれており、開発の規模やスピード感が読み取れます。こういった統計情報は Linux Kernel のみならず、他の多くの OSS 開発プロジェクトにおいても有用です。今回作成した Web サービス "OSS Development Statistics" は git レポジトリのログから、月ごとのコミット数やディベロッパ数、コミット数ランキング、ホットな開発キーワードを表示します。

現在登録してあるプロジェクトは、自分が独断で選んだ以下のものです。要望があれば他にも追加いたします。@Etsukataまでお気軽にどうぞ。git レポジトリであれば簡単に追加できます。
  • libvirt 
  • linux 
  • mongo 
  • node 
  • openstack_nova
  • openstack_quantum 
  • openstack_swift 
  • qemu 
  • riak
現在、一日に一回 git pull してデータベースを更新しています。

サービスの作成にあたっては、MEAN Stack を使用しました。
MEAN Stack とは M : Mongodb, E : Express, A : Angular JS, N : Node.js を指し示します。クライアントサイド、サーバサイドともにJavaScriptで記述できる生産性の高いフレームワーク群です。MEAN Stackについては、mongodb blog に寄稿されている記事: The MEAN Stack: MongoDB, ExpressJS, AngularJS and Node.js が参考になります。

類似のことをするソフトウェアとしては、LWN.net の Jonathan Corbet 氏が主に開発している git-dm があります。Corbet 氏は 新しい Linux Kernel がリリースされる度に開発の統計情報をまとめた記事を投稿しますが、git-dmはその記事を書くために用いられているようです。
Linux Kernel 3.9 リリース時の記事:

今回本サービスを使ってみて、OpenStack-Nova の開発規模が QEMU を上回っていることに驚きました。OpenStack 全体では Linux Kernel の開発規模に迫りつつあるかもしれません。
今後は OSS プロジェクト相互の開発者の乗り入れ状況や、プロジェクト同士の開発規模、開発加速度の比較をしたいと考えています。

参考情報

2013/05/16

CVE-2013-2094(perfバグによるLinux権限昇格の脆弱性)まとめ

NIST により2013/5/14 に、 RHEL6.1 - 6.4 をはじめとする Linux ディストリビューションに、perf のバグをついて権限昇格される脆弱性があることがアナウンスされました。
Vulnerability Summary for CVE-2013-2094

影響範囲が大きいと思いますので、情報をまとめておきました。

Exploit Code:semtex.c
手元のCentOS 6.4 で試したところ、rootを取ることが出きました。

Does CVE-2013-2094 affect Red Hat Enterprise Linux and Red Hat Enterprise MRG?
解決策として、Systemtap スクリプトを guru モードで動作させ、動的にパッチを当てる手法が掲載されています。

RedHat Bugzilla CVE-2013-2094
書き込み「Petr Matousek 2013-05-14 19:36:43 EDT」において、上記のexploit:semtex.cでroot権限昇格についての詳しい説明があります。ありがたや。

IT media「Linuxに権限昇格の脆弱性、エクスプロイトも出まわる」

原因となったコミット

修正コミット

2013/05/09

Qemu のトレース新機能 "ftrace backend" 紹介

はじめに

Qemu のトレース新機能 "ftace backend" は Linux 標準のトレース機構 ftrace を使って Qemu と Linux Kernel(KVM) のトレース情報を併せて取得する機能です。Qemu で KVM を使う場合は、ユーザ空間(Qemu)とカーネル空間(Kernel)を頻繁に遷移するため、両空間のトレース情報を併せて取得できると、デバッグや性能解析がよりはかどります。

ftrace backend の実装には ftrace marker が用いられています。ftrace marker は debugfs の marker file への書き込みを ftrace のリングバッファに送る機能です。Qemu ftrace backend は、Qemu のトレース情報出力先を marker file にすることで実現しています。

関連コミット:
trace: Add ftrace tracing backend

2013年5月3日に Qemu Mainline にマージされました。おそらく Qemu 1.5 で使用できるはずです。Author の名前が自分と酷似していますが。。。

使い方

ftrace backend を使うためには、まず configure 時に trace backend として "ftrace" を指定する必要があります。
# ./configure --trace-backend=ftrace

このままでも ftrace backend はトレース情報を debugfs の trace file に記録できますが、今回は KVM を使うのでKVM 関係のトレースイベント情報も併せて取得するよう設定します。
# echo 1 > /sys/kernel/debug/tracing/events/kvm/enable

Qemu 起動時に、取得対象の Qemu trace イベントを指定します。
Qemu のトレースイベント一覧は Qemu ソースコードの trace-events ファイルに記載されています。また、 Qemu monitor から "info trace-events" コマンドによっても取得できます。
ここでは、すべてのイベントを取得するような設定にします。
% cat /home/eiichi/events 
*
Qemu を起動します。ftrace を使うため、必ず root 権限で起動しましょう。
# ./qemu-system-x86_64 -enable-kvm -trace events=/home/eiichi/events

得られるトレース出力は以下のようになります。
 # less /sys/kernel/debug/tracing/trace
snip...
 qemu-system-x86-23226 [002] d... 116142.685922: kvm_entry: vcpu 0
 qemu-system-x86-23226 [002] d... 116142.685923: kvm_exit: reason IO_INSTRUCTION rip 0xc45b info 700040 0
 qemu-system-x86-23226 [002] .... 116142.685924: kvm_pio: pio_write at 0x70 size 1 count 1
 qemu-system-x86-23226 [002] .... 116142.685925: kvm_userspace_exit: reason KVM_EXIT_IO (2)
 qemu-system-x86-23226 [002] ...1 116142.685943: tracing_mark_write: cpu_set_apic_base 00000000fee00900
 qemu-system-x86-23226 [002] ...1 116142.685946: tracing_mark_write: kvm_run_exit cpu_index 0, reason 2
 qemu-system-x86-23226 [002] ...1 116142.685947: tracing_mark_write: cpu_out addr 0x70 value 143
 qemu-system-x86-23226 [002] ...1 116142.685951: tracing_mark_write: kvm_vcpu_ioctl cpu_index 0, type 44672, arg (nil)
 qemu-system-x86-23226 [002] d... 116142.685954: kvm_entry: vcpu 0
snip...
tracing_mark_write と書かれているのが、Qemu(ユーザ空間)のトレース情報です。ここでは、ゲストCPUのIO port write命令を受けて VM_EXIT(reason KVM_EXIT_IO) したのちユーザ空間でエミュレーションが行われているのがわかります。

libvirt で使う場合

libvirt で ftrace backend を使う場合は、libvirtd の設定ファイルを以下のように変更し、Qemu を起動するユーザを root にする必要があります。
/etc/libvirt/qemu.conf に以下を追加:
user = "root"

 余談

ftrace marker を使うためのライブラリがあれば、もっとftrace backend のコードを短くできますし、他のアプリケーションでも手軽に ftrace marker が使えるようになって便利です。ライブラリ名は、"libftrace" などという名前になるのでしょうか。
最近、ftrace は snapshot 機能や、multiple buffer 機能が追加されています。ユーザ空間のプログラム内からそれらの機能を利用するライブラリがあるとさらに嬉しいです。

2013/03/24

ftrace marker と uprobe-based event tracer オーバヘッド比較

はじめに

ユーザ空間のプログラムのトレース情報を取得する方法には、さまざまなものがありますが、ここでは "ftrace marker" と "uprobe-based event tracer" を取り上げ、トレースオーバヘッドを測定してみます。
"ftrace marker" とは ftrace trace_marker ファイルにトレース情報を書き込むことで、ftrace でトレース情報を取得する機能です。Kernel のトレース情報とユーザプログラムのトレース情報を照らしあわせたい時等に便利です。
使い方は簡単で、tracing ディレクトリの trace_marker ファイルに write するだけです。

# echo test > /sys/kernel/debug/tracing/ftrace_marker
# cat /sys/kernel/debug/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 1/1   #P:4
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
           <...>-23108 [003] ...1 34500.124363: tracing_mark_write: test

"uprobe-based event tracer" とは kprobe-based event tracer のユーザ空間版で、kprobe のように動的にプローブを差し込むことができる機能です。以下でも若干解説しますが、詳しい使い方については、ドキュメントをご覧ください。

比較方法
"ftrace marker" と "uprobe-based event tracer" をそれぞれ使って、トレースポイントを 1000回踏むのにかかる時間を測定します。
測定プログラムは以下を用意しました。
下の3つの関数をそれぞれ 1000回ずつ呼び出し、かかった時間を測定する簡単なものです。
do_nothing()  : 関数呼び出しのオーバヘッドを考慮するための何もしない関数
marker_write() : ftrace marker に write する関数
uprobe_test() :  uprobe を差し込む対象の関数

probench.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/time.h>

int marker_fd;

long time_usec(void) {
    struct timeval tv;

    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000 + tv.tv_usec;
}

void probe_bench(void (*func)()) {
    long start_us, end_us, diff_us;
    int i, loop_max;

    loop_max = 1000;
    start_us = time_usec();
    for (i = 0; i < loop_max; i++) {
        func();
    }
    end_us = time_usec();
    diff_us = end_us - start_us;
    printf("Elapsed time(us): %ld\n", diff_us);
    printf("per trace point(us): %f\n", (double) diff_us / loop_max);
    printf("\n");
}

void do_nothing(void) {
}

void marker_write(void) {
    write(marker_fd, "test\n", 5);
}

void uprobe_test(void) {
}


int main(int argc, char *argv)
{
    char path[256];
    int trace_fd;

    strcpy(path, "/sys/kernel/debug/tracing/tracing_on");
    trace_fd = open(path, O_WRONLY);
    if (trace_fd >= 0)
        write(trace_fd, "1", 1);

    strcpy(path, "/sys/kernel/debug/tracing/trace_marker");
    marker_fd = open(path, O_WRONLY);

    printf("-- nothing --\n");
    probe_bench(do_nothing);

    printf("-- ftrace marker --\n");
    probe_bench(marker_write);

    printf("-- uprobe --\n");
    probe_bench(uprobe_test);

    close(marker_fd);
}
Makefile
probench: probench.c
 gcc -g -o $@ $< -O2

clean:
 rm probench

上記のプログラムを make したあと、uprobe を差し込む場所を指定します。
まず、ELF を読んでプログラムヘッダからテキストセグメント開始アドレスを探しましょう。
# readelf -e probench | less
...
プログラムヘッダ:
  タイプ        オフセット          仮想Addr           物理Addr
            ファイルサイズ        メモリサイズ         フラグ 整列
...
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000be4 0x0000000000000be4  R E    200000
テキストセグメント開始アドレスは0x400000とわかりました。
次に、uprobe_test 関数のアドレスは以下のコマンドで取得できます。
 # readelf -s probench | grep uprobe_test
    59: 0000000000400830     2 FUNC    GLOBAL DEFAULT   13 uprobe_test
アドレスは0x400830とわかりました。
uprobe_test にプローブを差し込みましょう。uprobe_test 関数のアドレス0x400830 から テキストセグメント開始アドレス 0x400000を引き算した 0x830 をuprobe_events に書き込みます。書き込み後はイベントをオンにしてやります。
# echo 'p:test /path/to/probench:0x830' > /sys/kerenel/debug/tracing/uprobe_events
# echo 1 > /sys/kerenel/debug/tracing/events/uprobes/test/enable
以上で準備がととのいました。ベンチマークプログラム : probench を実行しましょう。
# ./probench       
-- nothing --
Elapsed time(us): 8
per trace point(us): 0.008000

-- ftrace marker --
Elapsed time(us): 1742
per trace point(us): 1.742000

-- uprobe --
Elapsed time(us): 4255
per trace point(us): 4.255000

結果は、
ftrace marker : 1.7us/TracePoint
uprobe : 4.3us/TracePoint
でした。関数呼び出しのオーバヘッドは無視できるようです。
uprobe は ftrace marker と比べて、若干オーバヘッドが嵩むようです。

余談
uprobe-based event tracer ではなく、systemtap を使った場合でも測って見ました。
以下のようなスクリプトを実行したあとで probench を走らせます。
#!/usr/bin/stap

probe begin {
 printf("start\n")
}

probe process("/home/eiichi/git/probench/probench").function("uprobe_test") {
}

# ./probench
-- nothing --
Elapsed time(us): 8
per trace point(us): 0.008000

-- ftrace marker --
Elapsed time(us): 1766
per trace point(us): 1.766000

-- uprobe --
Elapsed time(us): 3994
per trace point(us): 3.994000
systemtap の場合は 4.0us/TracePoint でした。uprobe-based event tracer と変わらないようです。

まとめ
"ftrace marker" と "uprobe-based event tracer" のオーバヘッドを比較すると、前者より後者のほうが2倍以上大きいことがわかりました。トレースオーバヘッドを気にする かつ ユーザプログラムを改変できる場合は ftrace marker を使ったほうがいいでしょう。

参考情報
kernel/Documentation/trace/ftrace.txt
kernel/Documentation/trace/kprobetracer.txt
kernel/Documentation/trace/uprobetracer.txt

2013/03/11

Netperf でネットワークレイテンシ測定

Netperf は iperf と並ぶ多機能ネットワークベンチマークソフトです。 Netperf を使って latency(RTT: Round Trip Time) を測定する方法についてまとめました。

omni-test による latency 測定
最近の netperf は "omni-test" による測定を行うのが一般的のようです。
omni test で latency を測定しましょう。
ここで、latency とは送信サーバと受信サーバでパケットが往復するのにかかる時間(RTT)のことを言うとします。 まずは受信側で受信準備します。
# netserver
送信側で UDP で RTT を測定するコマンドを発行します。
# netperf -t omni -H localhost -- -d rr -T UDP -k MIN_LATENCY,MEAN_LATENCY,P90_LATENCY,P99_LATENCY,MAX_LATENCY,STDDEV_LATENCY
OMNI Send|Recv TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to localhost.localdomain () port 0 AF_INET
MIN_LATENCY=11
MEAN_LATENCY=44.34
P90_LATENCY=50
P99_LATENCY=74
MAX_LATENCY=3079
STDDEV_LATENCY=13.49
latency の単位は micro second です。 オプションについて簡単に説明します。
  -t : テストモード指定。ここでは omni テスト。
  -H : 受信サーバのホスト名またはIP addressを指定。
  -- : 以降が omni テストのためのオプションになります。
  -d : direction 指定。rr は request/response = 往復時間測定。
  -T : プロトコルを指定。ここでは UDP。他には TCP, SCTP も指定可能。
  -k : omni test 特有の出力項目セレクタ。
出力は レイテンシの最小値、平均値、90%分位点、99%分位点、最大値、標準偏差、などが出力可能です。

-k オプションで omni test で出力する項目を指定する項目は非常にたくさんあります
-k all とすると、すべて出力されるようになります。測定ログとして残すには十分でしょう。
# netperf -t omni -H localhost -- -d rr -T UDP -k all           
OMNI Send|Recv TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to localhost.localdomain () port 0 AF_INET
SOCKET_TYPE=Stream
PROTOCOL=UDP
DIRECTION=Send|Recv
ELAPSED_TIME=10.00
THROUGHPUT=22333.41
THROUGHPUT_UNITS=Trans/s
...
UUID=46412b12-8a4a-11e2-985e-e91ceb3a7ad7
MIN_LATENCY=11
MAX_LATENCY=2478
P50_LATENCY=44
P90_LATENCY=52
P99_LATENCY=77
MEAN_LATENCY=43.90
STDDEV_LATENCY=15.74
LOCAL_SOCKET_PRIO=-1
REMOTE_SOCKET_PRIO=-1
LOCAL_SOCKET_TOS=0xffffffff
REMOTE_SOCKET_TOS=0xffffffff
LOCAL_CONG_CONTROL=
REMOTE_CONG_CONTROL=
LOCAL_FILL_FILE=
REMOTE_FILL_FILE=
COMMAND_LINE="netperf -t omni -H localhost -- -d rr -T UDP -k all"

ヒストグラム出力

レイテンシの測定では、分位点などの要約統計量だけでなくヒストグラムが欲しいことがあります。
netperf でヒストグラムを出力する際は、コンパイルする際に、configure に --enable-histogram オプションを渡します。
# ./configure --enable-histogram
測定の際には -v 2 オプションを付け、 verbosity を 1 より大きくします。
# netperf -t omni -H localhost -v 2 -- -d rr -T UDP -k MIN_LATENCY,MEAN_LATENCY,P90_LATENCY,P99_LATENCY,MAX_LATENCY,STDDEV_LATENCY
OMNI Send|Recv TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to localhost.localdomain () port 0 AF_INET : histogram
MIN_LATENCY=11
MEAN_LATENCY=44.87
P90_LATENCY=46
P99_LATENCY=71
MAX_LATENCY=1447
STDDEV_LATENCY=7.58

Histogram of request/response times
UNIT_USEC     :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
TEN_USEC      :    0: 3031: 1094:  413: 197318: 9043: 4774: 1942:  441:  135
HUNDRED_USEC  :    0:  163:    4:    2:    3:    0:    1:    0:    0:    0
UNIT_MSEC     :    0:    1:    0:    0:    0:    0:    0:    0:    0:    0
TEN_MSEC      :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
HUNDRED_MSEC  :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
UNIT_SEC      :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
TEN_SEC       :    0:    0:    0:    0:    0:    0:    0:    0:    0:    0
>100_SECS: 0
HIST_TOTAL:      218365

余談

上記の測定には、対象として自身(localhost)を指定していました。
レイテンシの最大値: 1.4 ms という数字は、ping コマンドで RTT を測定した場合に比べて大きいです。これは、Kernel 内でパケットが折り返す ping(ICMP) とは違い、ユーザ空間までのパケット往復時間で図っているためです。 パケットがユーザ空間まで上がる分、スケジュールの関係で netperf や netserver プロセスの実行が遅れてしまうことがあるからです。

 参考文献
Care and Feeding of Netperf

2013/03/09

Qemu ゲストからの virtio-blk cache 設定確認

Qemu/KVM を用いた VPS(IaaS) を使っていると、Qemu virtio-blk Disk の cache  設定がどうなっているのか気になることがあります。実は、ゲストから以下のコマンドで cache 設定を確認できます。
(virtioXの数字Xはlspciコマンドなどで確認してください。)
[eiichi@f1 vda]$ cat /sys/bus/virtio/devices/virtio1/block/vda/cache_type
write back
"write through" ならば Disk Write Cache なし
(Qemu cache mode は "directsync", "writethrough" のどちらか)、
"write back" ならば Disk Write Cache あり
("none", "writeback", "unsafe" のどれか)です。
Qemu の cache mode について詳しくは、拙稿 Qemu cache mode まとめをご覧ください。

ゲストの kernel バージョンによっては上記の cache_type が用意されていないこともあります。そのときは以下のコマンドで virtio-blk デバイスの feature bits を参照することで確認できます。左から10番目が disk cache の有無を示す bit です。 1 なら有り、0なら無しです。
[eiichi@f1]~% cat /sys/bus/virtio/devices/virtio1/features
0010101101110000000000000000110000000000000000000000000000000000
この例では左から10番目が1なので disk cache "有り"です。
ちなみに、自分が借りている、"さくらのVPS"での feature bits は以下でした。
0010101001100000000000000000110000000000000000000000000000000000
さくらのVPSも disk cache 有りのようです。
自分のローカル環境との違いは SCSI コマンドサポートの有無と、disk cache の動的変更の可否です。
他の Feature bits に関する情報は qemu の hw/virtio-blk.h または Linux カーネルの virtio-blk デバイスドライバ drivers/block/virtio_blk.c から手に入ります。
 /* Feature bits */ 
 #define VIRTIO_BLK_F_BARRIER    0       /* Does host support barriers? */
 #define VIRTIO_BLK_F_SIZE_MAX   1       /* Indicates maximum segment size */
 #define VIRTIO_BLK_F_SEG_MAX    2       /* Indicates maximum # of segments */
 #define VIRTIO_BLK_F_GEOMETRY   4       /* Indicates support of legacy geometry */
 #define VIRTIO_BLK_F_RO         5       /* Disk is read-only */
 #define VIRTIO_BLK_F_BLK_SIZE   6       /* Block size of disk is available*/
 #define VIRTIO_BLK_F_SCSI       7       /* Supports scsi command passthru */
 /* #define VIRTIO_BLK_F_IDENTIFY   8       ATA IDENTIFY supported, DEPRECATED */
 #define VIRTIO_BLK_F_WCE        9       /* write cache enabled */
 #define VIRTIO_BLK_F_TOPOLOGY   10      /* Topology information is available */
 #define VIRTIO_BLK_F_CONFIG_WCE 11      /* write cache configurable */
 
 #define VIRTIO_BLK_ID_BYTES     20      /* ID string length */
新しく Qemu/KVM で構築された VPS などを借りてみたときは virtio-blk cache 設定を確認されてはいかがでしょうか。

systemtap で KVM ゲストとホストのTSC値を一致させる

はじめに

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の調査がはかどります。