SystemTapの使い方(User-Space Probing)
- 1 User-Space Probingとは?
- 2 検証環境
- 3 debuginfoパッケージのインストール
- 4 ユーザプログラムにプローブを設定する方法
- 5 httpdにプローブを設定する方法
- 6 動作中のhttpdプロセスにプローブを設定する方法
- 7 ライブラリ関数にプローブを設定する方法
- Z 参考情報
1 User-Space Probingとは?
SystemTapはカーネルだけでなく、ユーザプログラムに対しても使うことができます。ここでは、ユーザプログラムに対するSystemTapの使い方を説明します。
他にもSystemTapの記事を作成したので参考にしてください。
SystemTapの使い方 - hana_shinのLinux技術ブログ
2 検証環境
AlmaLinuxの版数は以下のとおりです。
[root@server ~]# cat /etc/redhat-release AlmaLinux release 8.6 (Sky Tiger)
カーネル版数は以下のとおりです。
[root@server ~]# uname -r 4.18.0-372.9.1.el8.x86_64
systemtapパッケージをインストールします。
[root@server ~]# dnf -y install systemtap
stapコマンドの版数を確認します。
[root@server ~]# stap -V Systemtap translator/driver (version 4.7/0.186/0.187, rpm 4.7-1.el8) Copyright (C) 2005-2022 Red Hat, Inc. and others This is free software; see the source for copying conditions. tested kernel versions: 2.6.32 ... 5.18-rc3 enabled features: AVAHI BOOST_STRING_REF DYNINST BPF JAVA PYTHON3 LIBRPM LIBSQLITE3 LIBVIRT LIBXML2 NLS NSS READLINE MONITOR_LIBS
3 debuginfoパッケージのインストール
kernel-debuginfoパッケージをインストールするため、リポジトリを追加します。
[root@server ~]# dnf config-manager --add-repo=https://repo.almalinux.org/vault/8.6/BaseOS/debug/x86_64/
kernel-debuginfoパッケージをインストールします。
[root@server ~]# dnf install kernel-debuginfo-4.18.0-372.9.1.el8
kernel-develパッケージをインストールするため、リポジトリを追加します。
[root@server ~]# dnf config-manager --add-repo=https://repo.almalinux.org/vault/8.6/BaseOS/x86_64/os/
kernel-develパッケージをインストールします。
[root@server ~]# dnf install kernel-devel-4.18.0-372.9.1.el8.x86_64
4 ユーザプログラムにプローブを設定する方法
4.1 テストプログラムの作成
テストプログラム(以降TP)を作成します。
[root@server ~]# 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 ret;
}-gオプションを付けて、TPをコンパイルします。
[root@server ~]# gcc -Wall -g -o tp tp.c
実行ファイルを確認します。
[root@server ~]# ls -l tp* -rwxr-xr-x. 1 root root 21080 1月 17 20:26 tp -rw-r--r--. 1 root root 516 1月 17 20:25 tp.c
4.2 関数の入り口にプローブを設定する方法
func1()の入り口に設定したプローブが実行されると、pp()を実行するスクリプトを作成します。
[root@server ~]# cat tp.stp
#!/usr/bin/stap
probe process("/root/tp").function("func1")
{
printf("pp=%s\n", pp())
}スクリプトを実行します。
[root@server ~]# stap -vg tp.stp Pass 1: parsed user script and 485 library scripts using 295516virt/96596res/18164shr/80232data kb, in 340usr/100sys/455real ms. Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 297100virt/99344res/19144shr/81816data kb, in 10usr/0sys/15real ms. Pass 3: using cached /root/.systemtap/cache/9a/stap_9aac9b9924135443eec26a77b23d2146_1168.c Pass 4: using cached /root/.systemtap/cache/9a/stap_9aac9b9924135443eec26a77b23d2146_1168.ko Pass 5: starting run.
もう1つターミナルを開いてTPを実行します。
[root@server ~]# ./tp
SystemTapの実行結果を確認すると、プローブの情報(func1()がtp.cファイルの6行目に定義されている)が表示されていることがわかります。
[root@server ~]# stap -vg tp.stp
-snip-
pp=process("/root/tp").function("func1@/root/tp.c:6")
pp=process("/root/tp").function("func1@/root/tp.c:6")ファイルを確認すると、tp.cの6行目にfunc1()が定義されていることがわかります。
[root@server ~]# less -N /root/tp.c
-snip-
5
6 int func1(int x)
7 {
4.3 関数から復帰する場所にプローブを設定する方法
func2()から復帰する場所に設定したプローブが実行されると、pp()の実行とfunc2()の戻り値を表示するスクリプトを作成します。
[root@server ~]# cat tp.stp
#!/usr/bin/stap
probe process("/root/tp").function("func2").return
{
printf("pp=%s, ret=%d\n", pp(), $return)
}スクリプトを実行します。
[root@server ~]# stap -vg tp.stp
もう1つターミナルを開いてTPを実行します。
[root@server ~]# ./tp
SystemTapの実行結果を確認すると、プローブの情報(func2()がtp.cファイルの16行目に定義されている)とfunc2()の戻り値(2)が表示されていることがわかります。
[root@server ~]# stap -vg tp.stp
-snip-
pp=process("/root/tp").function("func2@/root/tp.c:16").return, ret=2
pp=process("/root/tp").function("func2@/root/tp.c:16").return, ret=2
4.4 ファイルの指定した場所にプローブを設定する方法
プローブを設定する場所を確認するため、lessコマンドでtp.cを参照します。ここでは、tp.cの10行目にプローブを設定します。
[root@server ~]# less -N /root/tp.c
-snip-
9
10 fd = open("/tmp/test1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
11 printf("func1\n");tp.cの10行目に設定したプローブが実行されると、pp()を実行するスクリプトを作成します。
[root@server ~]# cat tp.stp
#!/usr/bin/stap
probe process("/root/tp").statement("func1@/root/tp.c:10")
{
printf("pp=%s\n", pp())
}スクリプトを実行します。
[root@server ~]# stap -vg tp.stp
もう1つターミナルを開いてTPを実行します。
[root@server ~]# ./tp
SystemTapの実行結果を確認すると、プローブの情報(設定したプローブがtp.cの10行目)が表示されていることがわかります。
[root@server ~]# stap -vg tp.stp
-snip-
pp=process("/root/tp").statement("func1@/root/tp.c:10")
pp=process("/root/tp").statement("func1@/root/tp.c:10")
5 httpdにプローブを設定する方法
5.1 debuginfoパッケージのインストール
httpdパッケージと同じ版数のhttpd-debuginfoパッケージをインストールするため、まず、httpdパッケージの版数を確認します。
[root@server ~]# rpm -qa|grep httpd almalinux-logos-httpd-84.5-1.el8.noarch httpd-tools-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64 httpd-filesystem-2.4.37-51.module_el8.7.0+3281+01e58653.noarch httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64
appstream-debuginfoリポジトリを有効にします。
[root@server ~]# dnf config-manager --enable appstream-debuginfo
httpd-debuginfoパッケージをインストールします。
[root@server ~]# dnf install httpd-debuginfo-2.4.37-51.module_el8.7.0+3281+01e58653
httpd-debuginfoパッケージの版数を確認します。
[root@server ~]# rpm -qa|grep httpd httpd-debuginfo-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64 almalinux-logos-httpd-84.5-1.el8.noarch httpd-tools-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64 httpd-filesystem-2.4.37-51.module_el8.7.0+3281+01e58653.noarch httpd-debugsource-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64 httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64
5.2 httpdのmain()にプローブを設定する方法
whichコマンドを実行して、SystemTapスクリプトに記述するhttpdのパスを確認します。
[root@server ~]# which httpd /usr/sbin/httpd
httpdプログラムのmain()入り口にプローブを設定します。そして、プローブが実行されると、pp()を実行するスクリプトを作成します。
[root@server ~]# cat httpd.stp
#!/usr/bin/stap
probe process("/usr/sbin/httpd").function("main")
{
printf("pp=%s\n", pp())
}スクリプトを実行します。
[root@server ~]# stap -vg httpd.stp Pass 1: parsed user script and 485 library scripts using 295520virt/96560res/18132shr/80236data kb, in 330usr/60sys/394real ms. Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 300668virt/103140res/19312shr/85384data kb, in 50usr/0sys/51real ms. Pass 3: using cached /root/.systemtap/cache/47/stap_47ccd673e95239f6187c55d0d22ba59f_1332.c Pass 4: using cached /root/.systemtap/cache/47/stap_47ccd673e95239f6187c55d0d22ba59f_1332.ko Pass 5: starting run.
httpdサービスを起動します。
[root@server ~]# systemctl start httpd
SystemTapの実行結果を確認すると、プローブの情報(main()がmain.cファイルの466行目に定義されている)が表示されていることがわかります。
[root@server ~]# stap -vg httpd.stp
-snip-
pp=process("/usr/sbin/httpd").function("main@/usr/src/debug/httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64/server/main.c:466")httpdのソースファイルを確認すると、main()がmain.cファイルの466行目に定義されていることがわかります。
[root@server ~]# less -N /usr/src/debug/httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64/server/main.c
-snip-
465
466 int main(int argc, const char * const argv[])
467 {
6 動作中のhttpdプロセスにプローブを設定する方法
動作中のhttpdプロセスにプローブを設定する方法について説明します。psコマンドを実行して、プローブを設定するプロセスのPIDを確認します。ここでは、PID=10066 のhttpdに対してプローブポイントを設定してみます。
[root@server ~]# ps -C httpd
PID TTY TIME CMD
10066 ? 00:00:00 httpd
10067 ? 00:00:00 httpd
10068 ? 00:00:00 httpd
10069 ? 00:00:00 httpd
10070 ? 00:00:00 httpdプローブを設定する場所を確認するため、lessコマンドでmpm_common.cを参照します。mpm_common.cのap_wait_or_timeout()関数入り口にプローブを設定します。
[root@server ~]# less -N /usr/src/debug/httpd-2.4.37-51.module_el8.7.0+3281+01e58653.x86_64/server/mpm_common.c
-snip-
177 AP_DECLARE(void) ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode,
178 apr_proc_t *ret, apr_pool_t *p,
179 server_rec *s)
180 {ap_wait_or_timeout()入り口にプローブを設定します。そして、プローブが実行されるとap_wait_or_timeoutI()への引数の値を表示するスクリプトを作成します。
[root@server ~]# cat httpd.stp
#!/usr/bin/stap
probe process(10066).function("ap_wait_or_timeout")
{
printf("%s\n", $$parms)
}SystemTapの実行結果を確認すると、ap_wait_or_timeout()への引数の値が表示されていることがわかります。
[root@server ~]# stap -vg httpd.stp Pass 1: parsed user script and 485 library scripts using 295516virt/96620res/18192shr/80232data kb, in 260usr/330sys/613real ms. Pass 2: analyzed script: 1 probe, 5 functions, 0 embeds, 0 globals using 300664virt/103216res/19372shr/85380data kb, in 60usr/0sys/75real ms. Pass 3: using cached /root/.systemtap/cache/ff/stap_ffe11129161d317f17c1f7beecc71453_1899.c Pass 4: using cached /root/.systemtap/cache/ff/stap_ffe11129161d317f17c1f7beecc71453_1899.ko Pass 5: starting run. status=0x7ffc2c5e3898 exitcode=0x7ffc2c5e389c ret=0x7ffc2c5e38a0 p=0x5651d4e4ba18 s=0x5651d4e74cc0 status=0x7ffc2c5e3898 exitcode=0x7ffc2c5e389c ret=0x7ffc2c5e38a0 p=0x5651d4e4ba18 s=0x5651d4e74cc0 -snip-
7 ライブラリ関数にプローブを設定する方法
glibc等の共有ライブラリ関数にプローブを設定する方法について説明します。
7.1 debuginfoパッケージのインストール
[root@server ~]# dnf install glibc-debuginfo-2.28-189.1.el8.x86_64
7.2 事前準備
mallocを呼び出すテストプログラム(以降TP)を作成します。
[root@server ~]# cat tp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *ptr;
for(;;) {
printf("malloc\n");
ptr = malloc(100);
free(ptr);
sleep(2);
}
return 0;
}TPをコンパイルします。
[root@server ~]# gcc -Wall -g -o tp tp.c
lddコマンドを実行してTPがリンクする共有ライブラリのパスを確認します。
[root@server ~]# ldd tp
linux-vdso.so.1 (0x00007ffef25ac000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff6b2060000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff6b2425000)
Z 参考情報
私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ
SystemTapの記事作成で参考にしたページは以下になります。
SystemTap Examples
SystemTap ビギナーズガイド Red Hat Enterprise Linux 7 | Red Hat Customer Portal
https://sourceware.org/systemtap/tutorial.pdf
https://lrita.github.io/images/posts/systemtap/langref.pdf

