systemtap probe各種めも

systemtapスクリプトを書くときのめもです。カーネル、ライブラリ、実行ファイルの3パターンで。

tapsetsにある定義済みの関数を使う場合。ここではvm.kmallocvm.kfreeを使ってます。

#!/usr/bin/env stap

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

function print_detail(funcname, caller_function, bytes_req, ptr) {
        printf("call %s: %s-%d caller(%s): %d bytes : addr 0x%x¥n", funcname, execname(), pid(), caller_function,  bytes_req, ptr)
}

probe vm.kmalloc {
        print_detail(name, caller_function, bytes_req, ptr)
}

probe vm.kfree {
        print_detail(name, caller_function, -1, ptr)
}

probe end {
        printf("Done¥n")
}

vm.kmalloc、vm.kfreeともに引数を受け取るけど、これは自作の関数を定義する時と違って明示的に書かない。たとえば、kmalloc()の戻り値となるアドレスはptrという変数でアクセスできる。実行するとこんな感じでログが流れる。

start
call kfree: rcuop/1-20 caller(0xffffffff8133f952): -1 bytes : addr 0xffff88003c123aa0
call kfree: rcuop/1-20 caller(0xffffffff8133f952): -1 bytes : addr 0xffff88003c123520
call kfree: rcuop/0-10 caller(0xffffffff8133f952): -1 bytes : addr 0xffff88003c1232a0
call kmalloc: kworker/0:3-26949 caller(0xffffffff81508259): 96 bytes : addr 0xffff880040408600
call kmalloc: kworker/0:3-26949 caller(0xffffffff8137fe47): 152 bytes : addr 0xffff880043a489c0
call kfree: swapper/0-0 caller(0xffffffff813800db): -1 bytes : addr 0xffff880043a489c0
call kfree: kworker/0:3-26949 caller(0xffffffff815082b9): -1 bytes : addr 0xffff880040408600

今度はカーネル空間ではなくて、ユーザ空間でのデバッグ。libpthreadのpthread_createが呼ばれた時にプロセス名とpidを出力する。

#!/usr/bin/env stap

probe process("/usr/lib64/libpthread.so.0").mark("pthread_create") {
        printf("%s - %d ¥n", execname(), pid())
}

process()の引数はそのままの意味だけど、mark()を使うにはライブラリ・プログラム側でprobe pointを用意しておく必要がある。これは後述します。

次に普通のプログラムに対してsystemtapを実行する場合。バイナリのパスが/home/masami/helloで、helloという関数に対してはfunctionをfoobarという関数に対してはmarkを使用してます。

#!/usr/bin/env stap

probe process("/home/masami/hello").mark("foobar") {
        printf("foobar(): %s - %d argument value is %d¥n", execname(), pid(), $arg1)
}

probe process("/home/masami/hello").function("hello") {
        printf("hello(): %s - %d ¥n", execname(), pid())
}

実行するとこうなります。こちらはバイナリを実行した側。

masami@miko:~$ ./hello 1 2 3
hello, world
argc is 4

systemtapを実行している側。foobarのところで4という値が出ていてfoobar()の引数のargcの値が出ているのがわかると思います。

root@miko:/home/masami# stap -v hello.stp
Pass 1: parsed user script and 111 library script(s) using 171016virt/37596res/5080shr/32736data kb, in 130usr/20sys/148real ms.
Pass 2: analyzed script: 2 probe(s), 3 function(s), 3 embed(s), 0 global(s) using 172924virt/41072res/6572shr/34636data kb, in 30usr/1180sys/1213real ms.
Pass 3: translated to C into "/tmp/stapx1rsZ3/stap_802319b489e2eb7a5b517c5322ee0836_3262_src.c" using 172924virt/41220res/6700shr/34636data kb, in 40usr/1150sys/1188real ms.
Pass 4: compiled C into "stap_802319b489e2eb7a5b517c5322ee0836_3262.ko" in 2540usr/1140sys/3484real ms.
Pass 5: starting run.
hello(): hello - 27811
foobar(): hello - 27811 argument value is 4
^CPass 5: run completed in 10usr/90sys/6707real ms.

このsystemtapスクリプトで動かしたバイナリのソースはこれです。

#include <stdio.h>
#include <sys/sdt.h>

#define NOINLINE __attribute__((noinline))

NOINLINE static void foobar(int argc)
{
        STAP_PROBE1(hello, foobar, argc);
        printf("argc is %d¥n", argc);
}

NOINLINE static void hello(void)
{
        printf("hello, world¥n");
}

int main(int argc, char **argv)
{
        hello();
        foobar(argc);
        return 0;
}

ここでmarkを使うにはプログラム側で準備が必要と先に説明した部分が出てきます。STAP_PROBE1(hello, foobar, argc);とやっているSTAP_PROBE1マクロが.mark()と連動している部分です。マクロ名の1というのは引数の数で、今回はargcを渡しています。 この引数に対してsystemtapスクリプトからは$arg1としてアクセスしています。 STAP_PROBEマクロを使用するためにはsys/sdt.hが必要です。このファイルはsystemtapのパッケージからインストールすれば/usr/include/sys/sdt.hとして置かれている思います。自分でビルドした場合は--prefixで指定した場所のinclude/sys以下ですね。 ちなみに、hello.cはデバッグオプションは無しでコンパイルしてます(たんにgcc hello.c -o helloだけ)。

エクストリームプログラミング

エクストリームプログラミング