#1 グルモード(guru)とは?
グルモードを使うと、systemtapのスクリプトにC言語のソースコードを埋め込むことができます。
systemtapスクリプトだけでは実現できないことができるようになります。
なお、systemtapのインストール、基本的な使い方は、ここ(SystemTapの使い方)を参照してください。
#2 環境
VMware Workstation 14 Playerで作成した仮想マシンを使用しました。
[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
[root@server ~]# uname -r
3.10.0-957.el7.x86_64
#3 基本的な使い方
C言語の部分は%{
と%}
で囲みます。
関数への引数は、STAP_ARG_
と引数を連結した形で使用します。
以下の例では、引数x
とy
をSTAP_ARG_x
とSTAP_ARG_y
というかたちで使用しています。
また、関数の戻り値は、STAP_RETVALUEを使用しています。
[root@server stap]# cat ping.stp
#!/usr/bin/stap
private function add_param:long(x:long, y:long) %{
int sum;
sum = STAP_ARG_x + STAP_ARG_y;
STAP_RETVALUE = sum;
%}
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if( execname() == "ping") {
sum = add_param(100,200);
printf("pp=%s,sum=%d\n",pp(),sum);
}
}
guruモードを使うので、-g
オプションを付けて、systemtapを実行します。
[root@server stap]# stap -vg ping.stp
デフォルトGWに対してpingを1回実行しました。
なお、pingの使い方は、ここ(pingコマンドの使い方)を参照してください。
[root@server stap]# ping -c 1 192.168.3.1
guruモードとして定義した関数の戻り値300が出力されていることがわかります。
[root@server stap]# stap -vg ping.stp
-snip-
pp=kernel.function("ip_output@net/ipv4/ip_output.c:348"),sum=300
#4 syslogにメッセージを出力する方法
プローブ(ip_output関数)を実行したら、syslogにメッセージを出力してみます。
なお、下記スクリプト中のprintkは、カーネルがsyslogに
メッセージを出力するときに使う関数です。
[root@server stap]# cat ping.stp
#!/usr/bin/stap
private function output_log() %{
printk(KERN_ERR "Hello");
%}
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if(execname() == "ping") {
output_log();
printf("pp=%s\n",pp());
}
}
syslogへのログ出力を確認するため、journalctlコマンドを実行します。
[root@server ~]# journalctl -f
guruモードを使うので、-g
オプションを付けて、systemtapを実行します。
[root@server stap]# stap -vg ping.stp
デフォルトGWに対してpingを1回実行しました。
[root@server stap]# ping -c 1 192.168.3.1
syslogにメッセージが出力されたことがわかります。
[root@server ~]# journalctl -f
9月 27 19:21:36 server kernel: Hello
#5 syslogにjiffiesを出力する方法
jiffiesは、カーネルが使用する変数です。
カーネルが起動してからの相対時間を表します。1ミリ秒ごとにカウントアップされます。
ここでは、プローブを実行したら、その時のjiffiesの値をsyslogに出力してみます。
[root@server stap]# cat ping.stp
#!/usr/bin/stap
private function output_log() %{
printk(KERN_ERR "jiffies=%ld\n", jiffies);
%}
probe kernel.function("ip_output@net/ipv4/ip_output.c")
{
if(execname() == "ping") {
output_log();
printf("pp=%s\n",pp());
}
}
syslogへのログ出力を確認するため、journalctlコマンドを実行します。
[root@server ~]# journalctl -f
guruモードを使うので、-g
オプションを付けて、systemtapを実行します。
[root@server stap]# stap -vg ping.stp
デフォルトGWに対してpingを1回実行しました。
[root@server stap]# ping -c 1 192.168.3.1
syslogにjiffiesの値が出力されたことがわかります。
プローブを実行したときのjiffiesの値が4299765913であることがわかります。
[root@server stap]# stap -vg ping.stp
-snip-
9月 27 19:39:16 server kernel: jiffies=4299765913
#6 文字列を返す方法
pingを実行すると、ICMP echoパケットが送信されます。
ICMP echoパケットのルーティング処理が実行されると、
sk_buff構造体が示すnet_device構造体のnameメンバに、
ICMP echoパケットを出力するデバイス名が設定されます。
以下の例では、nameメンバにeth0が設定されています。
sk_buff構造体
skb-> +----------+ net_device構造体
| *dev | --------> +---------------+ <- dev
| | | name(eth0) |
| | | |
| | +---------------+
| |
| |
| | icmp echoパケット
| | --------> +---------------+
+----------+ | |
| |
| |
+---------------+
ip_finish_output関数を実行した際、ICMP echoパケットがどのデバイスから
送信されるのかを確認するスクリプトを以下に示します。
なお、この例は、わざわざグルモードを使わなくても、実現可能です。
デバイス名の文字列を返す関数の使い方を確認したかったので、
グルモードを使ってみました。
なお、下記スクリプト中のIFNAMSIZは16と定義されています。
[root@server stap]# cat ping.stp
#!/usr/bin/stap
%{
#include <linux/netdevice.h>
%}
private function show_dev:string(skb:long) %{
struct sk_buff *skb;
struct net_device *dev;
skb = (struct sk_buff *)STAP_ARG_skb;
dev = skb->dev;
snprintf(STAP_RETVALUE, IFNAMSIZ, "%s", dev->name);
%}
probe kernel.function("ip_finish_output@net/ipv4/ip_output.c")
{
if(execname() == "ping") {
name = show_dev($skb);
printf("pp=%s,dev_name=%s\n",pp(),name);
}
}
guruモードを使うので、-g
オプションを付けて、systemtapを実行します。
[root@server stap]# stap -vg ping.stp
デフォルトGWに対してpingを1回実行しました。
[root@server stap]# ping -c 1 192.168.3.1
eth0という名前のデバイスが出力されていることがわかります。
[root@server stap]# stap -vg ping.stp
-snip-
pp=kernel.function("ip_finish_output@net/ipv4/ip_output.c:267"),dev_name=eth0
#7 統計情報をカウントアップする方法
カーネル内で使用している統計情報を任意の場所でカウントアップしてみます。
どのような事にやくに立つのか不明ですが。
ここでは、icmp_rcv関数が実行されたら、ICMP_MIB_INERRORSという統計情報を
カウントアップしてみます。
ICMP_MIB_INERRORSという統計情報は、本来、以下のようにエラー発生時に
カウントアップするためのものですが、icmp_rcv関数が呼び出されたら、
ICMP_MIB_INERRORSをカウントアップしてみます。
/*
* Deal with incoming ICMP packets.
*/
int icmp_rcv(struct sk_buff *skb)
{
-中略-
error:
ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
goto drop;
}
icmp_rcv関数が呼ばれたら、統計情報をカウントアップするスクリプトです。
[root@server stap]# cat ping.stp
#!/usr/bin/stap
%{
#include <net/ip.h>
#include <net/icmp.h>
#include <linux/skbuff.h>
%}
private function inc_stat(tmp:long) %{
struct sk_buff *tmp,*skb;
struct rtable *rt;
struct net *net;
skb = (struct sk_buff *)STAP_ARG_tmp;
rt = skb_rtable(skb);
net = dev_net(rt->dst.dev);
ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
%}
probe kernel.function("icmp_rcv@net/ipv4/icmp.c")
{
inc_stat($skb);
printf("pp=%s\n",pp());
}
クライアントからサーバにpingを実行すると、統計情報がカウントアップするのがわかります。
なお、nstatの使い方は、ここ(nstatコマンドの使い方)を参照してください。
[root@server ipv4]# nstat -a|grep IcmpInErrors
IcmpInErrors 1 0.0
[root@server ipv4]# nstat -a|grep IcmpInErrors
IcmpInErrors 2 0.0
[root@server ipv4]# nstat -a|grep IcmpInErrors
IcmpInErrors 3 0.0
#Z 参考情報
SystemTap Language Reference