Red Hat Training

A Red Hat training course is available for Red Hat Enterprise Linux

3.2.2. Systemtap ハンドラー/ボディー

以下のサンプルスクリプトを見てみましょう。

例3.4 helloworld.stp

probe begin
{
  printf ("hello world\n")
  exit ()
}
例3.4「helloworld.stp」 では、begin イベント (セッションの開始) が { } で囲まれているハンドラーを始動させます。これは単に hello world を出力して改行し、終了するものです。
注記
SystemTap スクリプトの実行は、exit() 関数が実行されるまで継続されます。スクリプトの実行を停止したい場合は、手動で Ctrl+C と入力すると中断できます。

printf ( ) ステートメント

printf() ステートメントは、データを出力するための最も単純な関数の一つです。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()probe の関数を以下の書式で出力します。
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() 呼び出しからの時間 (マイクロ秒単位)。
  • 関数呼び出しを行ったプロセス名 (およびそれに対応する ID)。
  • 呼び出しがエントリー (<-) か終了 (->) かを示す矢印。インデントがあることで、特定の関数呼び出しのエントリーとそれに対応する終了が一致しやすくなります。
  • プロセスが呼び出した関数名。
name
特定のシステムコールの名前を識別します。この変数は、イベント syscall.system_call を使用するプローブでのみ使用できます。
target()
次の 2 つのコマンドのいずれかと組み合わせて使用します。
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 関数の詳細情報は、stapfuncs(3) を参照してください。