Tap言語
前回の例でも使っていましたが、Systemtapにはあらかじめいくつかの関数が定義されています。これらの関数のほとんどは、Tapsetというスクリプトライブラリで定義されています(一部の関数は組み込み関数です)。オンラインマニュアルを引けば、Systemtapがどんな関数を提供しているかわかるでしょう。
man stapfuncs
Systemtapには、他にも詳細なチュートリアル(tutorial.pdf)や言語リファレンス(langref.pdf)が含まれています。言語仕様の詳細をここで紹介しても仕方がないので、ここではSystemtapの言語の特徴を挙げてみます。
- 基本はプローブと関数で成り立っている。
- プローブはエイリアス(別名)を定義できる。
- エイリアスやプローブポイントの指定に、ワイルドカードが使える。
- ローカル変数とグローバル変数がある。
- 変数の基本型はlong, string, statisticsのみ。longは64bit整数、statisticsはグローバル変数としてしか使えない。
- statisticsは簡単な統計処理をすることができる。例えば最大・最小・平均値の算出や、ヒストグラムの作成など。
- 変数の型宣言は不要。systemtapが自動的に判断する。
- 配列は全て連想配列(associative array)のように扱われる。配列はグローバル変数としてしか扱えない。
この他、以下のような特殊な制約もあります。(これらの制約は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_から始まる名前のすべての関数の入り口でプローブを行うことになります。