- 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