LoginSignup
8

More than 1 year has passed since last update.

SystemTapの使い方(User-Space Probing)

Last updated at Posted at 2017-12-09

以下に移行予定です。
https://hana-shin.hatenablog.com/entry/2023/01/20/202125

#0 はじめに
systemtapはカーネルだけでなく、ユーザプログラムに対しても使うことができます。
ここでは、ユーザプログラムに対するsystemtapの使い方を説明します。
なお、カーネルに対する使い方はここ(SystemTapの使い方)を参照ください。
どちらも、使い方は同じです。

#1 環境
VMware Workstation 12 Player上のゲストマシンを使っています。

[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)

[root@server ~]# uname -r
3.10.0-514.el7.x86_64

#2 事前準備
カーネル版数(3.10.0-514.el7)と同じ版数のkernel-debuginfoをインストールします。

yumキャッシュをクリアする。
[root@master ~]# yum clean all

kernel-debuginfoをインストールするため、base-debuginfoリポジトリを有効にする。
[root@master ~]# yum --disablerepo=* --enablerepo=base-debuginfo -y install kernel-debuginfo-3.10.0-514.el7

kernel-develもインストールする。
[root@master tools]# yum install kernel-devel-3.10.0-514.el7.x86_64

インスールしたパッケージ版数を確認する。カーネル版数と同じ版数のkernel-debuginfoがインストールされたことがわかる。
[root@master ~]# rpm -qa|grep kernel-debuginfo
kernel-debuginfo-3.10.0-514.el7.x86_64
kernel-debuginfo-common-x86_64-3.10.0-514.el7.x86_64

systemtapをインストールする。
[root@master tools]# yum install systemtap
[root@master tools]# yum install systemtap-runtime

#3 自作プログラムに対するsystemtapの使い方
##3.1 自作プログラムの作成
実行ファイルを作成する時、-gオプションを付けてコンパイルする必要があります。

[root@server stap]# vi tp.c
[root@server stap]# cat tp.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int func1(int x)
{
  int fd;

  fd = open("/tmp/test1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  printf("func1\n");
  close(fd);
  return 1;
}

int func2(int x)
{
  int fd;

  fd = open("/tmp/test2.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  printf("func2\n");
  close(fd);

  return 2;
}

int main(int argc, char *argv[])
{
  int ret;

  for(;;) {
    ret = func1(10);
    sleep(2);

    ret = func2(20);
    sleep(2);
  }
  return 0;
}

-gオプションを付けて、コンパイルします。警告がでますが(retが未使用)、気にしないでください。
[root@server stap]# gcc -Wall -g -o tp tp.c
[root@server stap]# ls
tp  tp.c

3.2 process.functionの使い方

###3.2.1 サンプルスクリプト
テスト用のスクリプトを作成します。func1()とfunc2()の引数と戻り値を表示します。
なお、pid(),pp()等のtapsetの使い方はここを参照ください。
カーネルに対する使い方と同じです。

[root@server stap]# pwd
/root/stap

スクリプトを作成する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe process("/root/stap/tp").function("func1").call
{
  printf("pid=%d,procname=%s,pp=%s, arg=%d\n", pid(), execname(), pp(), $x)
}

probe process("/root/stap/tp").function("func2").call
{
  printf("pid=%d,procname=%s,pp=%s, arg=%d\n", pid(), execname(), pp(), $x)
}

probe process("/root/stap/tp").function("func1").return
{
  printf("pid=%d,procname=%s,pp=%s,ret=%d\n", pid(), execname(), pp(), $return)
}

probe process("/root/stap/tp").function("func2").return
{
  printf("pid=%d,procname=%s,pp=%s,ret=%d\n", pid(), execname(), pp(), $return)
}

###3.2.2 実行結果

テストプログラムを実行する。
[root@server stap]# ./tp
func1
func2
-以下、略-

もう1つターミナルをオープンして、スクリプトを実行します。各関数への引数、戻り値を確認することができます。
[root@server stap]# stap -v tp.stp
-中略-
pid=11812,procname=tp,pp=process("/root/stap/tp").function("func1@/root/stap/tp.c:6").call, arg=10
pid=11812,procname=tp,pp=process("/root/stap/tp").function("func1@/root/stap/tp.c:6").return,ret=1
pid=11812,procname=tp,pp=process("/root/stap/tp").function("func2@/root/stap/tp.c:16").call, arg=20
pid=11812,procname=tp,pp=process("/root/stap/tp").function("func2@/root/stap/tp.c:16").return,ret=2

##3.3 process.statementの使い方
関数内の任意の場所にプローブポイントを設定します。

###3.3.1 サンプルスクリプト

[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe process("/root/stap/tp").statement("func1@/root/stap/tp.c:12")
{
  printf("procname=%s,pp=%s\n", execname(), pp())
}

###3.3.2 実行結果

テストプログラムを実行する。
[root@server stap]# ./tp
func1
func2
-以下、略-

スクリプトを実行する。
[root@server stap]# stap -v tp.stp
-中略-
procname=tp,pp=process("/root/stap/tp").statement("func1@/root/stap/tp.c:12")
procname=tp,pp=process("/root/stap/tp").statement("func1@/root/stap/tp.c:12")
procname=tp,pp=process("/root/stap/tp").statement("func1@/root/stap/tp.c:12")

3.4 process.syscallの使い方

システムコールにプローブポイントを設定します。

3.4.1 サンプルスクリプト

テストプログラムがopen,closeシステムコールを実行したときの第一引数を表示してみます。
openの場合、ファイルのパス名、closeの場合、ファイルディスクリプタの番号になります。
全部で6個の引数を表示することができます。第一引数は$arg1,第二引数は$arg2と記述します。

[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe process("/root/stap/tp").syscall
{
  if( execname() == "tp"){
    # open systemcall
    if( $syscall == 2) {
      printf("open(%s)\n", kernel_string($arg1))
    }
    # close systemcall
    else if ( $syscall == 3) {
      printf("close(%d)\n", $arg1)
    }
  }
}

###3.4.2 実行結果

テストプログラムを実行する。
[root@server stap]# ./tp
func1
func2
-以下、略-

スクリプトを実行する。openとcloseの第一引数が表示されれていることがわかる。
[root@server stap]# stap -v tp.stp
-中略-
open(/tmp/test2.txt)
close(3)
open(/tmp/test1.txt)
close(3)

なお、システムコールの番号は、ausyscall コマンドを使って確認することができます。
[root@server stap]# ausyscall --exact open
2
[root@server stap]# ausyscall --exact close
3

##3.5 process.library.functionの使い方
ここでは、ライブラリ関数にプローブポイントを設定する方法について説明します。

###3.5.1 glibcのdebuginfoパッケージのインストール

glibcのパッケージを確認する。
[root@server stap]# rpm -qa|grep glibc
glibc-common-2.17-157.el7.x86_64
glibc-2.17-157.el7.x86_64
glibc-devel-2.17-157.el7.x86_64
glibc-headers-2.17-157.el7.x86_64

debuginfoパッケージをインストールする。
[root@server stap]# yum --disablerepo=* --enablerepo="base-debuginfo" install glibc-debuginfo-2.17-157.el7

glibcのパッケージを確認する。debuginfoパッケージ(★)がインストールされたことがわかる。
[root@server stap]# rpm -qa|grep glibc
glibc-common-2.17-157.el7.x86_64
glibc-2.17-157.el7.x86_64
glibc-devel-2.17-157.el7.x86_64
glibc-debuginfo-2.17-157.el7.x86_64 ★
glibc-headers-2.17-157.el7.x86_64
glibc-debuginfo-common-2.17-157.el7.x86_64 ★

###3.5.2 作成するテストプログラム

ライブラリ関数(malloc)を呼び出すテストプログラムを作成する。
[root@server stap]# cat tp.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  int ret;
  char *ptr;

  for(;;) {
    printf("malloc\n");
    ptr = malloc(100);
    free(ptr);
    sleep(2);
  }
  return 0;
}

作業ディレクトリを確認する。
[root@server stap]# pwd
/root/stap

コンパイルする。
[root@server stap]# gcc -g -o tp tp.c
[root@server stap]# ls
tp tp.c

###3.5.3 サンプルスクリプト

tpにリンクするライブラリ関数を調べる。libc.so.6内の関数にプローブを設定する。
[root@server stap]# ldd tp
        linux-vdso.so.1 =>  (0x00007ffd55063000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f1bfe41d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f1bfe7ea000)

スクリプトを作成する。
[root@server stap]# cat lib.stp
#!/usr/bin/stap

probe process("/root/stap/tp").library("/lib64/libc.so.6").function("malloc").call
{
  printf("=>%s(%s)\n", pp(), $$parms);
}

probe process("/root/stap/tp").library("/lib64/libc.so.6").function("malloc").return
{
  printf("<=%s:%s\n", pp(), $$return);
}

###3.5.4 実行結果

テストプログラムを実行する。
[root@server stap]# ./tp

systemtapを実行する。malloc関数が呼び出されていることがわかる。
関数の引数は獲得するメモリサイズ(100byte)、戻り値は獲得したメモリ領域の先頭アドレス(0xafe010)であることがわかる。
[root@server stap]#  stap -v lib.stp
-中略-
=>process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").call(bytes=0x64)
=>process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").call(bytes=0x64)
<=process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").return:return=0xafe010
<=process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").return:return=0xafe010
=>process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").call(bytes=0x64)
<=process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").return:return=0xafe010
=>process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").call(bytes=0x64)
<=process("/usr/lib64/libc-2.17.so").function("__libc_malloc@/usr/src/debug/glibc-2.17-c758a686/malloc/malloc.c:2881").return:return=0xafe010

3.6 バックトレースの表示方法

スクリプトを作成する。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
global x
probe process("/root/stap/tp").function("func1").call
{
  if(x==0) {
    print_usyms(ubacktrace())
    exit()
  }
  x++
}

テストプログラムを実行する。
[root@server stap]# ./tp
func1
func2
-以下、略-

スクリプトを実行する。_start -> __libc_start_main -> main -> func1と呼ばれていることがわかる。
[root@server stap]# stap -v tp.stp --ldd
-中略-
 0x400588 : func1+0xb/0x1c [/root/stap/tp]
 0x4005ce : main+0x19/0x4b [/root/stap/tp]
 0x7f3809b17b35 : __libc_start_main+0xf5/0x1c0 [/usr/lib64/libc-2.17.so]
 0x4004b9 : _start+0x29/0x30 [/root/stap/tp]
Pass 5: run completed in 30usr/400sys/4110real ms.

##3.7 $$の使い方
$$parmsは関数の全ての引数、$$returnは関数の戻り値を表示することができる。

3.7.1 サンプルスクリプト

[root@server stap]# cat tp.stp
#!/usr/bin/stap

probe process("/root/stap/tp").function("*").call
{
  printf("=>%s(%s)\n", probefunc(), $$parms);
}

probe process("/root/stap/tp").function("*").return
{
  printf("<=%s:%s\n", probefunc(), $$return);
}

3.7.2 実行結果

テストプログラムを実行する。
[root@server stap]# ./tp
func1
func2
-以下、略-

スクリプトを実行する。関数への引数や戻り値が確認できる。
func1()の引数は10,func2()の引数は20、戻り値はそれぞれ、1,2であることがわかる。
[root@server stap]# stap -v tp.stp
-中略-
=>_start()
=>__libc_csu_init()
=>_init()
<=__libc_csu_init:
=>frame_dummy()
=>register_tm_clones()
<=__libc_csu_init:
<=__libc_csu_init:
<=0x7fadb0bf9ac5:
=>main(argc=0x1 argv=0x7fffbb9f69f8)
=>func1(x=0xa)
<=main:return=0x1
=>func2(x=0x14)
<=main:return=0x2
-以下、略-

#4 コンパイル済アプリケーションに対するsystemtapの使い方(その1)
ここでは、仮想マシンにインストール済のhttpdに対してsystemtapを実行してみます。
httpdは-gオプションでコンパイルされている必要があります。

##4.1 httpdのdebuginfoパッケージのインストール

httpdのパッケージを確認する。
[root@server stap]# rpm -qa|grep httpd
httpd-2.4.6-67.el7.centos.6.x86_64
httpd-tools-2.4.6-67.el7.centos.6.x86_64

httpdのdebuginfoパッケージをインストールする。
[root@server stap]# yum --disablerepo=* --enablerepo="base-debuginfo" -y install httpd-debuginfo-2.4.6-67.el7.centos.6.x86_64

httpdのパッケージを確認する。debuginfoパッケージ(★)がインストールされてことがわかる。
[root@server stap]# rpm -qa|grep httpd
httpd-2.4.6-67.el7.centos.6.x86_64
httpd-debuginfo-2.4.6-67.el7.centos.6.x86_64 ★
httpd-tools-2.4.6-67.el7.centos.6.x86_64

##4.2 全ての関数にプローブポイントを設定する方法
ここでは、httpdの全ての関数にプローブポイントを設定してみます。

httpdのパス名を調べる。/usr/sbin/httpdであることがわかる。
[root@server stap]# which httpd
/usr/sbin/httpd

スクリプトを作成する。httpdプロセスの全関数に対してプローブポイントを設定する。
全ての関数を指定する場合、function("*")と書きます。
[root@server stap]# vi http.stp
[root@server stap]# cat http.stp
#!/usr/bin/stap
probe process("/usr/sbin/httpd").function("*")
{
  printf("procname=%s,pp=%s\n", execname(), pp())
}

スクリプトを実行する。ap_wait_or_timeout(),ap_run_monitor()等が呼ばれていることがわかる。
[root@server stap]# stap -v http.stp
-中略-
procname=httpd,pp=process("/usr/sbin/httpd").function("ap_wait_or_timeout@/usr/src/debug/httpd-2.4.6/server/mpm_common.c:173")
procname=httpd,pp=process("/usr/sbin/httpd").function("ap_wait_or_timeout@/usr/src/debug/httpd-2.4.6/server/mpm_common.c:173")
procname=httpd,pp=process("/usr/sbin/httpd").function("ap_run_monitor@/usr/src/debug/httpd-2.4.6/server/mpm_common.c:91")
procname=httpd,pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_process@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:603")
procname=httpd,pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:570")

ap_wait_or_timeout()のソースコードを確認する。
ファイル(mpm_common.c)先頭から173行目のap_wait_or_timeout()にプローブポイントが設定されていることがわかる。
[root@server httpd-2.4.6]# less -N server/mpm_common.c
-中略-
    173 AP_DECLARE(void) ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode,
    174                                     apr_proc_t *ret, apr_pool_t *p,
    175                                     server_rec *s)
    176 {
    177     apr_status_t rv;
    178
    179     ++wait_or_timeout_counter;
    180     if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) {
    181         wait_or_timeout_counter = 0;
    182         ap_run_monitor(p, s);
    183     }
    184
    185     rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p);
    186     if (APR_STATUS_IS_EINTR(rv)) {
    187         ret->pid = -1;
    188         return;
    189     }
    190
    191     if (APR_STATUS_IS_CHILD_DONE(rv)) {
    192         return;
    193     }
    194
    195     apr_sleep(apr_time_from_sec(1));
    196     ret->pid = -1;
    197     return;
    198 }

##4.3 特定の関数にプローブポイントを設定する方法

スクリプトを作成する。ap_get_scoreboard_worker_from_indexes()にプローブポイントを設定する。
[root@server stap]# cat http.stp
#!/usr/bin/stap
probe process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes")
{
  printf("pp=%s, %s\n", pp(), $$parms)
}

スクリプトを実行する。ap_get_scoreboard_worker_from_indexes()が呼ばれていることがわかる。
引数xとyの値も確認することができる。
[root@server stap]# stap -v http.stp
-中略-
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:570"), x=0x0 y=0x0
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:570"), x=0x1 y=0x0
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:570"), x=0x2 y=0x0
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:570"), x=0x3 y=0x0

ap_get_scoreboard_worker_from_indexes()のソースを確認する。
[root@server httpd-2.4.6]# less -N server/scoreboard.c
    570 AP_DECLARE(worker_score *) ap_get_scoreboard_worker_from_indexes(int x, int y)
    571 {
    572     if (((x < 0) || (x >= server_limit)) ||
    573         ((y < 0) || (y >= thread_limit))) {
    574         return(NULL); /* Out of range */
    575     }
    576     return &ap_scoreboard_image->servers[x][y];
    577 }

#5 コンパイル済アプリケーションに対するsystemtapの使い方(その2)
こんどは、ssコマンドに対してsystemtapを使ってみます。やり方は、httpdのときと全く同じです。
なお、ssコマンドについてはssコマンドの使い方を参照ください。

##5.1 ssコマンドのdebuginfoパッケージのインストール

ssコマンドのパス名を調べる。
[root@server ~]# which ss
/usr/sbin/ss

ssコマンドが含まれているrpmパッケージを調べる。iprouteパッケージであることがわかる。
[root@server ~]# rpm -qf /usr/sbin/ss
iproute-3.10.0-74.el7.x86_64

iproute-debuginfoパッケージをインストールする。
iprouteパッケージと同じ版数のものをインストールする必要があります。
[root@server ~]# yum --disablerepo=* --enablerepo="base-debuginfo" install iproute-debuginfo-3.10.0-74.el7

パッケージを確認する。iproute-debuginfo(★)がインストールされたことがわかる。
[root@server ~]# rpm -qa|grep iproute
iproute-3.10.0-74.el7.x86_64
iproute-debuginfo-3.10.0-74.el7.x86_64 ★

##5.2 ssコマンドの全関数にプローブを設定する。

スクリプトを作成する。関数の入り口にプローブを設定し、関数の引数を表示する内容となっています。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe process("/usr/sbin/ss").function("*").call
{
  printf("pp=%s,%s\n", pp(), $$parms)
}

##5.3 実行結果

スクリプトを実行する。
[root@server stap]# stap -v tp.stp

ssコマンドを実行する。リスニングソケットだけを表示してみる。
[root@server ~]# ss -lt4

ssコマンドのプローブポイントの情報が表示されていることがわかる。
[root@server stap]# stap -v tp.stp
-中略-
pp=process("/usr/sbin/ss").function("_start").call,
pp=process("/usr/sbin/ss").function("__libc_csu_init").call,
pp=process("/usr/sbin/ss").function("_init").call,
pp=process("/usr/sbin/ss").function("frame_dummy").call,
pp=process("/usr/sbin/ss").function("register_tm_clones").call,
pp=process("/usr/sbin/ss").function("main@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:3444").call,argc=0x2 argv=0x7fff67faf018
pp=process("/usr/sbin/ss").function("ssfilter_parse@/usr/src/debug/iproute-3.10.0-55.el7/misc/ssfilter.y:274").call,f=0x61bdf0 argc=0x0 argv=0x7fff67faf028 fp=0x0
pp=process("/usr/sbin/ss").function("yyparse@/usr/src/debug/iproute-3.10.0-55.el7/misc/ssfilter.c:1161").call,
pp=process("/usr/sbin/ss").function("tcp_show@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2312").call,socktype=0x6 f=0x61bde0
pp=process("/usr/sbin/ss").function("inet_show_netlink@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2223").call,dump_fp=0x0 protocol=0x6 f=0x61bde0
pp=process("/usr/sbin/ss").function("rtnl_open_byproto@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:46").call,rth=0x7fff67faece0 subscriptions=0x0 protocol=0x4
pp=process("/usr/sbin/ss").function("rtnl_dump_filter_nc@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:301").call,rth=0x7fff67faece0 filter=0x406c40 arg1=0x7fff67faeca0 nc_flags=0x0
pp=process("/usr/sbin/ss").function("rtnl_dump_filter_l@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:194").call,rth=0x7fff67faece0 arg=0x7fff67faec10
pp=process("/usr/sbin/ss").function("show_one_inet_sock@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2208").call,addr=0x7fff67fa6b60 h=0x7fff67fa6bc0 arg=0x7fff67faeca0
pp=process("/usr/sbin/ss").function("inet_show_sock@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2009").call,nlh=0x7fff67fa6bc0 f=0x0 protocol=0x6
pp=process("/usr/sbin/ss").function("parse_rtattr@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:726").call,tb=0x7fff67fa6880 max=0xb rta=0x7fff67fa6c18 len=0x8
pp=process("/usr/sbin/ss").function("parse_rtattr_flags@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:731").call,tb=0x7fff67fa6880 max=0xb rta=0x7fff67fa6c18 len=0x8 flags=0x0
pp=process("/usr/sbin/ss").function("inet_stats_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:1575").call,s=0x7fff67fa68e0 protocol=0x6
pp=process("/usr/sbin/ss").function("sock_state_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:794").call,s=0x7fff67fa68e0 sock_name=0x4101a4
pp=process("/usr/sbin/ss").function("inet_addr_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:1013").call,a=0x7fff67fa68f0 port=0x6f ifindex=0x0
pp=process("/usr/sbin/ss").function("generic_proc_open@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:294").call,env=0x40fe42 name=0x411448
pp=process("/usr/sbin/ss").function("sock_addr_print_width@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:813").call,addr_len=0x12 addr=0x7fff67fa6410 delim=0x40fd45 port_len=0x14 port=0x25bd250 ifname=?
pp=process("/usr/sbin/ss").function("inet_addr_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:1013").call,a=0x7fff67fa6918 port=0x0 ifindex=0x0
pp=process("/usr/sbin/ss").function("sock_addr_print_width@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:813").call,addr_len=0x12 addr=0x7fff67fa6410 delim=0x40fd45 port_len=0x14 port=0x61bd20 ifname=?
pp=process("/usr/sbin/ss").function("show_one_inet_sock@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2208").call,addr=0x7fff67fa6b60 h=0x7fff67fa6c20 arg=0x7fff67faeca0
pp=process("/usr/sbin/ss").function("inet_show_sock@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2009").call,nlh=0x7fff67fa6c20 f=0x0 protocol=0x6
pp=process("/usr/sbin/ss").function("parse_rtattr@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:726").call,tb=0x7fff67fa6880 max=0xb rta=0x7fff67fa6c78 len=0x8
pp=process("/usr/sbin/ss").function("parse_rtattr_flags@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:731").call,tb=0x7fff67fa6880 max=0xb rta=0x7fff67fa6c78 len=0x8 flags=0x0
pp=process("/usr/sbin/ss").function("inet_stats_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:1575").call,s=0x7fff67fa68e0 protocol=0x6
pp=process("/usr/sbin/ss").function("sock_state_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:794").call,s=0x7fff67fa68e0 sock_name=0x4101a4
pp=process("/usr/sbin/ss").function("inet_addr_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:1013").call,a=0x7fff67fa68f0 port=0x16 ifindex=0x0
pp=process("/usr/sbin/ss").function("sock_addr_print_width@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:813").call,addr_len=0x12 addr=0x7fff67fa6410 delim=0x40fd45 port_len=0x14 port=0x25bd2a0 ifname=?
pp=process("/usr/sbin/ss").function("inet_addr_print@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:1013").call,a=0x7fff67fa6918 port=0x0 ifindex=0x0
pp=process("/usr/sbin/ss").function("sock_addr_print_width@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:813").call,addr_len=0x12 addr=0x7fff67fa6410 delim=0x40fd45 port_len=0x14 port=0x61bd20 ifname=?
pp=process("/usr/sbin/ss").function("rtnl_dump_filter_nc@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:301").call,rth=0x7fff67faece0 filter=0x406c40 arg1=0x7fff67faeca0 nc_flags=0x0
pp=process("/usr/sbin/ss").function("rtnl_dump_filter_l@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:194").call,rth=0x7fff67faece0 arg=0x7fff67faec10
pp=process("/usr/sbin/ss").function("show_one_inet_sock@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2208").call,addr=0x7fff67fa6b60 h=0x7fff67fa6bc0 arg=0x7fff67faeca0
pp=process("/usr/sbin/ss").function("show_one_inet_sock@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2208").call,addr=0x7fff67fa6b60 h=0x7fff67fa6c28 arg=0x7fff67faeca0
pp=process("/usr/sbin/ss").function("show_one_inet_sock@/usr/src/debug/iproute-3.10.0-55.el7/misc/ss.c:2208").call,addr=0x7fff67fa6b60 h=0x7fff67fa6c90 arg=0x7fff67faeca0
pp=process("/usr/sbin/ss").function("rtnl_close@/usr/src/debug/iproute-3.10.0-55.el7/lib/libnetlink.c:38").call,rth=0x7fff67faece0
pp=process("/usr/sbin/ss").function("__do_global_dtors_aux").call,
pp=process("/usr/sbin/ss").function("deregister_tm_clones").call,
pp=process("/usr/sbin/ss").function("_fini").call,

#6 コンパイル済アプリケーションに対するsystemtapの使い方(その3)
ここでは、実行ファイルのパス名ではなく、PIDを指定する方法について説明します。
PIDとして指定できるプロセスは、ユーザモードで動作するプロセスだけです。
カーネルスレッドのプロセスに対しては実行できません。

httpdのPIDを調べる。
[root@server stap]# ps -C httpd
   PID TTY          TIME CMD
  1393 ?        00:00:02 httpd
  1405 ?        00:00:00 httpd
  1406 ?        00:00:00 httpd
  1408 ?        00:00:00 httpd
  1409 ?        00:00:00 httpd
  1410 ?        00:00:00 httpd

スクリプトを作成する。PID=1393のhttpdに対してプローブポイントを設定してみます。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe process(1393).function("*").call
{
  printf("pp=%s,%s\n", pp(), $$parms)
}

[root@server stap]# stap -v tp.stp
-中略-
pp=process("/usr/sbin/httpd").function("ap_wait_or_timeout@/usr/src/debug/httpd-2.4.6/server/mpm_common.c:173").call,status=0x7fffa2a33688 exitcode=0x7fffa2a3368c ret=0x7fffa2a33690 p=0x7f7ed46d9138 s=0x7f7ed4700310
pp=process("/usr/sbin/httpd").function("ap_run_monitor@/usr/src/debug/httpd-2.4.6/server/mpm_common.c:91").call,p=0x7f7ed46d9138 s=0x7f7ed4700310
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_process@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:603").call,x=0x0
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:570").call,x=0x0 y=0x0
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_process@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:603").call,x=0x1
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_worker_from_indexes@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:570").call,x=0x1 y=0x0
pp=process("/usr/sbin/httpd").function("ap_get_scoreboard_process@/usr/src/debug/httpd-2.4.6/server/scoreboard.c:603").call,x=0x2

#7 アプリとカーネルの関数に同時にプローブを設定する方法
systemtapは、アプリとカーネルの関数に同時にプローブを設定することができます。
カーネルの関数にプローブを設定する方法はここを参照ください。

##7.1 作成するスクリプト

[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
probe process("/root/stap/tp").syscall
{
  if( execname() == "tp"){
    # open systemcall
    if( $syscall == 2) {
      printf("pp=%s,open(%s)\n", pp(),kernel_string($arg1))
    }
    # close systemcall
    else if ( $syscall == 3) {
      printf("pp=%s,close(%d)\n", pp(),$arg1)
    }
  }
}
probe syscall.open
{
  if (execname() == "tp")
  {
    printf("pp=%s,filename=%s\n", pp(), kernel_string($filename))
  }
}

probe syscall.close
{
  if (execname() == "tp")
  {
    printf("pp=%s,fd=%d\n", pp(), $fd)
  }
}

##7.2 実行結果

スクリプトを実行する。アプリケーションからのシステムコール呼び出しと、カーネル関数が呼び出されていることがわかる。
[root@server stap]# ./tp
malloc
malloc


[root@server stap]# stap -v tp.stp
-中略-
pp=process("/root/stap/tp").syscall,open(/etc/ld.so.cache)
pp=kernel.function("SyS_open@fs/open.c:1041").call,filename=/etc/ld.so.cache
pp=process("/root/stap/tp").syscall,close(3)
pp=kernel.function("SyS_close@fs/open.c:1102").call,fd=3
pp=process("/root/stap/tp").syscall,open(/lib64/libc.so.6)
pp=kernel.function("SyS_open@fs/open.c:1041").call,filename=/lib64/libc.so.6
pp=process("/root/stap/tp").syscall,close(3)
pp=kernel.function("SyS_close@fs/open.c:1102").call,fd=3

#8 プローブポイントに指定できる関数の調べ方(-L)

httpdの実行ファイルのパスを調べる。/usr/sbin/httpdであることがわかる。
[root@server ~]# which httpd
/usr/sbin/httpd

[root@server ~]# stap -L 'process("/usr/sbin/httpd").function("*")'
process("/usr/sbin/httpd").function("__do_global_dtors_aux")
process("/usr/sbin/httpd").function("__libc_csu_fini")
process("/usr/sbin/httpd").function("__libc_csu_init")
process("/usr/sbin/httpd").function("_fini")
process("/usr/sbin/httpd").function("_init")
process("/usr/sbin/httpd").function("_start")
process("/usr/sbin/httpd").function("abort_on_oom")
process("/usr/sbin/httpd").function("add_any_filter")
process("/usr/sbin/httpd").function("add_any_filter_handle")
process("/usr/sbin/httpd").function("add_name_vhost_config.isra.1")
process("/usr/sbin/httpd").function("add_optional_notes.isra.0")
process("/usr/sbin/httpd").function("add_vary.isra.1")
process("/usr/sbin/httpd").function("ap_abort_on_oom")
process("/usr/sbin/httpd").function("ap_add_cgi_vars")
-以下、略-

#9 値を変更する方法(グローバル変数の変更)

##9.1 サンプルプログラム

[root@server stap]# pwd
/root/stap
[root@server stap]# vi tp.c
[root@server stap]# cat tp.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int a=10;

int func1(int x)
{
  int fd;

  fd = open("/tmp/test1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
  printf("func1,a=%d\n",a);
  close(fd);
  return 1;
}

int main(int argc, char *argv[])
{
  int ret;

  for(;;) {
    ret = func1(10);
    sleep(2);
  }
  return 0;
}

テストプログラムをコンパイルする。
[root@server stap]# gcc -g -Wall -o tp tp.c
[root@server stap]# ls tp*
tp  tp.c

##9.2 サンプルスクリプト
グローバル変数aの値を10から500に変更します。

スクリプトを作成する。
グローバル変数の値を表示、グローバル変数の値を10から500に変更したら、スクリプトを終了します。
[root@server stap]# vi tp.stp
[root@server stap]# cat tp.stp
#!/usr/bin/stap
global count=0

probe process("/root/stap/tp").function("*").call
{
 if(count==0){
  printf("a=%d\n",@var("a"))
  @var("a")=500
  printf("a=%d\n",@var("a"))
  exit()
 }
}

##9.3 実行結果

テストプログラムを実行する。
[root@server stap]# ./tp
func1,a=10
func1,a=10

もう1つターミナルを開く。スクリプトを実行する。
[root@server stap]# stap -vg tp.stp
a=10
a=500

テストプログラムのグローバル変数が10から500に変更されたことがわかる。
[root@server stap]# ./tp
func1,a=10
func1,a=10
func1,a=500
func1,a=500
func1,a=500

#10 値を変更する方法(ローカル変数の変更)

##10.1 サンプルプログラム

ソース
[root@centos74 systemtap]# pwd
/root/systemtap

[root@centos74 systemtap]# cat -n tp.c
     1  #include <stdio.h>
     2  #include <stdlib.h>
     3  #include <unistd.h>
     4
     5  int func(void)
     6  {
     7    return 1;
     8  }
     9
    10  int main(int argc, char *argv[])
    11  {
    12    int ret;
    13
    14    while(1) {
    15      ret = func();
    16      if(ret == 1)
    17        printf("ret=%d\n",ret);
    18      else if(ret == 2)
    19        printf("ret=%d\n",ret);
    20      else
    21       printf("ret=%d\n",ret);
    22      sleep(2);
    23    }
    24    return ret;
    25  }
コンパイル
[root@centos74 systemtap]# gcc -Wall -g -o tp tp.c
[root@centos74 systemtap]# ls tp*
tp  tp.c

##10.2 サンプルスクリプト

[root@centos74 systemtap]# pwd
/root/systemtap

サンプルプログラムの16行目で変数(ret)の値を変更する。$1はスクリプトへの引数を表します。
[root@centos74 systemtap]# cat tp.stp
#!/usr/bin/stap
probe process("/root/systemtap/tp").statement("main@/root/systemtap/tp.c:16")
{
  @var("ret") = $1;
}

##10.3 実行結果

関数の戻り値を5に変更
スクリプトを実行する。
[root@centos74 systemtap]# stap -vg tp.stp 5

テストプログラムを実行する。関数funcの戻り値が5にかわったことがわかる。
[root@centos74 systemtap]# ./tp
ret=5
ret=5
関数の戻り値を10に変更
スクリプトを実行する。
[root@centos74 systemtap]# stap -vg tp.stp 10

テストプログラムを実行する。関数funcの戻り値が10にかわったことがわかる。
[root@centos74 systemtap]# ./tp
ret=10
ret=10

#Y メモ

/* in-scope variable var */
$var
/* alternative syntax for $varname */
@var("varname")
/* the global (either file local or external) variable varname defined when the file src/file.c was compiled */
@var("varname@src/file.c")
/* traverses a structure’s field */
$var->field
@var("var@file.c")->field
/* indexes into an array */
$var[N]
@var("var@file.c")[N]
/* get the address of a variable as a long */
&$var
&@var("var@file.c")
/* provide the address of a particular field or an element in an array */
&var->field
&@var("var@file.c")[N]
/* a string that only includes the values of all basic type values of fields of the variable structure type but not any nested complex type values */
$var$
/* a string that also includes all values of nested data types */
@var("var")$$


[root@server stap]# cat mod.stp
#!/usr/bin/stap

probe begin
{
  printf("gc_thresh=%d\n",@var("xfrm4_dst_ops_template@net/ipv4/xfrm4_policy.c")->gc_thresh)
  @var("xfrm4_dst_ops_template@net/ipv4/xfrm4_policy.c")->gc_thresh=1024
}

probe end
{
  printf("gc_thresh=%d\n",@var("xfrm4_dst_ops_template@net/ipv4/xfrm4_policy.c")->gc_thresh)
}

#X 参考情報
SystemTapの使い方

SystemTap Beginners Guide
SystemTap
How to do user-space probing using debug info in systemtap
SystemTap Beginners Guide 4章のメモ
Understanding malloc behavior using Systemtap userspace probes
Systemtap
systemtap and Ruby 2.1 (on Ubuntu)
Using Markers
SystemTap Language Reference
SystemTap SystemTap

Instrumenting CPython with DTrace and SystemTap
SystemTap 3.1 has been released

Python3サンプルコード集(その1)
systemtap-cheat-sheet(github)
SystemTap Language Reference

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8