Tap言語

前回の例でも使っていましたが、Systemtapにはあらかじめいくつかの関数が定義されています。これらの関数のほとんどは、Tapsetというスクリプトライブラリで定義されています(一部の関数は組み込み関数です)。オンラインマニュアルを引けば、Systemtapがどんな関数を提供しているかわかるでしょう。

man stapfuncs

Systemtapには、他にも詳細なチュートリアル(tutorial.pdf)や言語リファレンス(langref.pdf)が含まれています。言語仕様の詳細をここで紹介しても仕方がないので、ここではSystemtapの言語の特徴を挙げてみます。

この他、以下のような特殊な制約もあります。(これらの制約はstap実行時の-Dオプションで変更できます((実装は、単にマクロの定義値を変更するだけです。)))

  • 配列の要素数や文字列の長さは最大値が決まっている。
  • 一つのプローブで実行できる処理の数や、関数のネスト数、ループの回数などに上限がある。
  • プローブのオーバヘッドの最大値も決まっている。あまり重いと強制的にエラーになる。

特徴のリストで挙げたエイリアス機能ですが、これはあらかじめ用意したプローブに別の名前を与える機能です。この機能とワイルドカード指定機能を使うと、より簡潔にスクリプトを書くことができます。

例として、システムワイドでstraceを実行するスクリプトを挙げてみましょう。

#!/usr/bin/stap
probe syscall.* {
  printf("%s: %s(%s)=", execname(), name, argstr)
}
probe syscall.*.return {
  printf("%s\n", retstr)
}

上記スクリプトを、strace.stpと言う名前で保存して、以下のコマンドで実行してみてください。

# stap -v strace.stp

このスクリプトは、ワイルドカード指定を使っているため、実行する前のスクリプトの解釈やコンパイルに結構時間がかかります。そのため、今回は-vオプションをつけて、実行するまでの各フェーズの処理を表示させ、処理が進んでいるかを確認することにします。

Pass 1: parsed user script and 45 library script(s) in 240usr/30sys/281real ms.
Pass 2: analyzed script: 632 probe(s), 108 function(s), 13 embed(s), 1 global(s) in 5990usr/360sys/6350real ms.
Pass 3: translated to C into "/tmp/stapnfW7lJ/stap_b87eaa955bb9474220107821c6f56cb5_308480.c" in 370usr/230sys/595real ms.
Pass 4: compiled C into "stap_b87eaa955bb9474220107821c6f56cb5_308480.ko" in 14220usr/730sys/14657real ms.
Pass 5: starting run.
...
gnome-terminal: read(3, 0x080aa12c, 4096)=Xorg: setitimer(ITIMER_REAL, [0.000000,0.000000], 0x00000000)=0
32
gnome-terminal: read(3, 0x080aa12c, 4096)=Xorg: select(256, 0x0820a240, 0x00000000, 0x00000000, [995962.603000])=-11 (EAGAIN)
gnome-terminal: select(17, 0x00000000, 0xbfab1260, 0xbfab1260, NULL)=1
gnome-terminal: send(16, 0x08460c86, 6, MSG_NOSIGNAL)=gnome-terminal: sendto(16, 0x08460c86, 6, MSG_NOSIGNAL, NULL, 0)=6
6
gnome-terminal: select(17, 0x00000000, 0xbfab1260, 0xbfab1260, NULL)=1
1
gnome-terminal: send(16, 0x08460c50, 25, MSG_NOSIGNAL)=gnome-terminal: sendto(16, 0x08460c50, 25, MSG_NOSIGNAL, NULL, 0)=scim-bridge: recv(6, 0xbfd4e573, 1, MSG_DONTWAIT)=25
...

うまく行けば上記のように処理状態の表示がされたあと、スクリプトの実行結果が出ると思います。今回は2CPUのマシンを使ったので、若干実行結果がごちゃ混ぜになっていますが、まあ上記のような感じでシステムコールの名前や引数が取得できていることが分かると思います。

スクリプトの修正を適切に行えば、ターゲットプロセスの指定もできますし、表示をもう少し綺麗にすることもできますが、ここで注目したいのは、たった6行のスクリプトで、こういった処理を行えるということです。

さて、スクリプトの中身を見てみると、プローブポイントの指定でワイルドカードを使っていることが分かります。

probe syscall.* {
probe syscall.*.return {

syscall.で始まるプローブポイント(エイリアス)の定義も、上記のSystemtapのTapsetライブラリ内にあり、syscall.readやsyscall.openなどが定義されています。
それぞれのエイリアスは、対応するシステムコールの関数の入り口と出口に設定されており、引数の値や戻り値の値を文字列化した上で、argstrやretstrといったローカル変数に記録されています。例えば、acceptシステムコールエイリアスは、以下のように定義されています(/usr/share/systemtap/tapset/syscalls.stpより)。

# accept _____________________________________________________
# long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr,
#                 int __user *upeer_addrlen)
probe syscall.accept = kernel.function("sys_accept") ? {
        name = "accept"
        sockfd = $fd
        addr_uaddr = $upeer_sockaddr
        addrlen_uaddr = $upeer_addrlen
        argstr = sprintf("%d, %p, %p", $fd, $upeer_sockaddr, $upeer_addrlen)
}
probe syscall.accept.return = kernel.function("sys_accept").return ? {
        name = "accept"
        retstr = returnstr(1)
}

Systemtapワイルドカードにマッチする、これらのエイリアスを自動的に展開してくれます。(展開時には、不要な変数などは自動的に削除します)

上記の例のように、ワイルドカードエイリアスを使えば、非常に簡潔に強力なスクリプトを記述できます。
ちなみに、ワイルドカードは以下のようにも使うことができます。

probe kernel.function("sys_*") {...}

この例では、sys_から始まる名前のすべての関数の入り口でプローブを行うことになります。