SystemTapの使い方（User-Space Probing)

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

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



#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の使い方は[ここ](https://qiita.com/hana_shin/items/9b265b4f9a51f98d0f4d)を参照ください。
カーネルに対する使い方と同じです。

```
[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は、アプリとカーネルの関数に同時にプローブを設定することができます。
カーネルの関数にプローブを設定する方法は[ここ](https://qiita.com/hana_shin/items/9b265b4f9a51f98d0f4d)を参照ください。


##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 サンプルプログラム

```console:ソース
[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  }
```

```console:コンパイル
[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 実行結果

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

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

```console:関数の戻り値を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の使い方](https://qiita.com/hana_shin/items/9b265b4f9a51f98d0f4d)

[SystemTap Beginners Guide](https://sourceware.org/systemtap/SystemTap_Beginners_Guide/userspace-probing.html)
[SystemTap](https://wiki.archlinux.jp/index.php/SystemTap)
[How to do user-space probing using debug info in systemtap](https://stackoverflow.com/questions/36683832/how-to-do-user-space-probing-using-debug-info-in-systemtap)
[SystemTap Beginners Guide 4章のメモ](http://d.hatena.ne.jp/mmitou/20120724/1343151224)
[Understanding malloc behavior using Systemtap userspace probes](https://developers.redhat.com/blog/2014/10/02/understanding-malloc-behavior-using-systemtap-userspace-probes/)
[Systemtap](https://wiki.ubuntu.com/Kernel/Systemtap)
[systemtap and Ruby 2.1 (on Ubuntu)](http://asquera.de/blog/2014-01-26/stap-and-ruby-2/)
[Using Markers](https://sourceware.org/systemtap/wiki/UsingMarkers)
[SystemTap Language Reference](https://sourceware.org/systemtap/langref.pdf)
[SystemTap SystemTap](http://d3s.mff.cuni.cz/teaching/crash_dump_analysis/slides/11-systemtap.pdf)

[Instrumenting CPython with DTrace and SystemTap](https://docs.python.org/3/howto/instrumentation.html)
[SystemTap 3.1 has been released](https://lwn.net/Articles/714880/)

[Python3サンプルコード集(その1)](http://www.segmentation-fault.xyz/entry/2017/08/11/160312)
[systemtap-cheat-sheet(github)](https://github.com/calio/systemtap-cheat-sheet)
[SystemTap Language Reference](https://sourceware.org/systemtap/langref.pdf)