3.2.2. Systemtap ハンドラー/ボディー
以下のサンプルスクリプトを見てみましょう。
例3.4 helloworld.stp
probe begin { printf ("hello world\n") exit () }
例3.4「helloworld.stp」 では、
begin
イベント (セッションの開始) が { }
で囲まれているハンドラーを始動させます。これは単に hello world
をプリントして改行し、終了するものです。
printf ( ) ステートメント
printf ()
ステートメントは、データをプリントする最も簡単な関数の 1 つです。printf ()
を以下の書式で使用すると、多くの SystemTap 関数を使用するデータを表示できます。
printf ("format string\n", arguments)
format string では、arguments のプリント方法を指定します。例3.4「helloworld.stp」 の書式文字列は、SystemTap に
hello world
のプリントを指示するだけで、書式指定子は含みません。
引数によっては、
%s
(文字列用) や %d
(数字用) といった書式指定子を書式文字列に使用することもできます。書式文字列には複数の書式指定子を使用することが可能で、それぞれを対応する引数に一致させます。複数の引数はコンマ (,
) で区切ります。
注記
セマンティックの面では、SystemTap
printf
関数は、C 言語の関数に非常によく似ています。上記の SystemTap の printf
関数における構文と書式は、C 言語の printf
と同一のものです。
以下のプローブの例を見てみましょう。
例3.5 variables-in-printf-statements.stp
probe syscall.open { printf ("%s(%d) open\n", execname(), pid()) }
例3.5「variables-in-printf-statements.stp」 では、SystemTap がシステムコール
open
への全エントリーをプローブするように指示しています。各イベントでは、現行の execname()
(実行可能ファイル名の付いた文字列) と pid()
(現行のプロセス ID 番号) に続けて open
という単語をプリントします。このプローブ出力の抜粋は以下のようになります。
vmware-guestd(2206) open hald(2360) open hald(2360) open hald(2360) open df(3433) open df(3433) open df(3433) open hald(2360) open
SystemTap 関数
SystemTap は、printf ()
引数として使用可能な多くの関数をサポートしています。例3.5「variables-in-printf-statements.stp」 では、SystemTap 関数 execname()
(カーネル関数を呼び出した、またはシステムコールを実行したプロセス名) および pid()
(現行プロセス ID) を使用しています。
一般的に使用される SystemTap 関数を以下に挙げます。
- tid()
- 現行スレッドの ID。
- uid()
- 現行ユーザーの ID。
- cpu()
- 現行の CPU 番号。
- gettimeofday_s()
- Unix epoch (1970 年 1 月 1 日) からの秒数。
- ctime()
- UNIX epoch からの秒数を日にちに換算。
- pp()
- 現在処理されているプローブポイントを記述する文字列。
- thread_indent()
- この関数はプリント結果をうまく整理するので、便利なものです。この関数はインデント差分の引数を取ります。これは、スレッドの「インデントカウンター」に追加する、またはそこから取り除くスペースの数を示すものです。その後、適切なインデントスペースの数と一般的な追跡データの文字列を返します。ここで返された文字列の一般的なデータに含まれるのは、タイムスタンプ (スレッドの
thread_indent()
への最初の呼び出しからのマイクロ秒)、プロセス名、およびスレッド ID です。これによりどの関数が呼び出されたか、誰が呼び出したか、各関数呼び出しの長さが特定できます。それぞれの呼び出しが終わり次第、次の呼び出しが始まれば、エントリーと終了の一致は容易です。ただし、ほとんどの場合は、最初の関数呼び出しエントリーがなされた後、これが終了する前に他の複数の呼び出しがエントリーおよび終了することがあります。インデントカウンターがあることで、最初の呼び出しが終了していない場合、次の関数呼び出しをインデントして、エントリーとそれに対応する終了が一致しやすくなります。以下でthread_indent()
の使用例を見てみましょう。例3.6 thread_indent.stp
probe kernel.function("*@net/socket.c") { printf ("%s -> %s\n", thread_indent(1), probefunc()) } probe kernel.function("*@net/socket.c").return { printf ("%s <- %s\n", thread_indent(-1), probefunc()) }
例3.6「thread_indent.stp」 では、各イベントでのthread_indent()
とプローブの関数を以下の書式でプリントします。0 ftp(7223): -> sys_socketcall 1159 ftp(7223): -> sys_socket 2173 ftp(7223): -> __sock_create 2286 ftp(7223): -> sock_alloc_inode 2737 ftp(7223): <- sock_alloc_inode 3349 ftp(7223): -> sock_alloc 3389 ftp(7223): <- sock_alloc 3417 ftp(7223): <- __sock_create 4117 ftp(7223): -> sock_create 4160 ftp(7223): <- sock_create 4301 ftp(7223): -> sock_map_fd 4644 ftp(7223): -> sock_map_file 4699 ftp(7223): <- sock_map_file 4715 ftp(7223): <- sock_map_fd 4732 ftp(7223): <- sys_socket 4775 ftp(7223): <- sys_socketcall
このサンプル出力には、以下の情報が含まれています。- スレッド (
thread_indent()
から文字列に追加) の最初のthread_indent()
呼び出しからの時間 (マイクロ秒単位)。 - 関数呼び出し (
thread_indent()
から文字列に追加) を実施したプロセス名 (およびその対応 ID)。 - 呼び出しがエントリー (
<-
) か終了 (->
) かを示す矢印。インデントがあることで、特定の関数呼び出しのエントリーとそれに対応する終了が一致しやすくなります。 - プロセスが呼び出した関数名。
- name
- 特定のシステムコールの名前を識別します。この変数は、イベント
syscall.system_call
を使用するプローブでのみ使用可能です。 - target()
stap script -x process ID
またはstap script -c command
と併用されます。プロセス ID またはコマンドの引数を取るスクリプトを指定したい場合、スクリプト内で参照先となる変数としてtarget()
を使用します。以下に例を示します。例3.7 targetexample.stp
probe syscall.* { if (pid() == target()) printf("%s/n", name) }
例3.7「targetexample.stp」 を引数-x process ID
で実行すると、(syscall.*
イベントで指定された) すべてのシステムコールを監視し、指定されたプロセスで実行された全システムコールの名前をプリントします。これは、特定のプロセスをターゲットとしたい場合に毎回if (pid() == process ID)
と指定することと同様の効果があります。ただし、target()
を使用すると、スクリプトの再利用が容易になり、スクリプトを実行するたびに引数としてプロセス ID を渡すだけで済みます (例:stap targetexample.stp -x process ID
)。
サポートされる SystemTap 関数の詳細情報は、
man stapfuncs
を参照してください。