SystemTap
提供: Wikinote
ついに伝家の宝刀 (でも諸刃の剣) を抜く時が来たようだ…。
目次
参考 URL
- SystemTap 公式ページ。Documentation と Wiki を見ればとりあえず事足りる。
覚え書き
基本形
文法は、awk に似ている。begin/end はオプション。 詳細は man stap を参照のこと。
#!/usr/bin/stap -v function hoge() { ... } probe begin { ... } probe kernel.function("sys_*") { printf("%s[%d] called %s()\n", execname(), pid(), probefunc()) } probe end { ... }
プローブポイント
詳細は、man stapprobes を参照のこと。 SystemTap のバージョンにより使えるものがかなり違うので注意すること。
- begin/end/error
- 名前の通り。
- timer.[ jiffies | s | ms | us | ns | hz ](N)
- インターバル指定。例えば、timer.s(10) で 10 秒ごとに動作する。
- syscall.*[.return]
- システムコール関数。特別に以下の変数を利用可能。
- argstr — 引き数の値のリスト
- name — 関数名
- retstr — 返り値 (.return のみ)
- kernel.function("PATTERN")[.return]
- 任意のカーネル関数。ワイルドカード (*, ?, [ ]) による指定も可能。以下ように変数へのアクセスが可能。
- $var — ソース中の変数にアクセス
- $var->field
- $return — 返り値 (.return のみ)
- $var[N]
- $$vars — sprintf("parm1=%x ... var1=%x ...", parm1, ..., var1, ...) と等価 (変数の文字列展開)
- $$locals — ローカル変数を文字列展開
- $$perms — 引き数を文字列展開
- $$return — 返り値を文字列展開 (.return のみ)
- kernel.statement("PATTERN")
- カーネルの任意の位置。以下のような指定が可能。
- kernel.function("*@kernel/timer.c:123")
- kernel.statement("sys_adjtimex@kernel/time.c+3")
- module("MPATTERN").function("PATTERN")
- module("MPATTERN").statement("PATTERN")
- モジュール内の関数などを指定する。例えば、module("ext3").function("ext3_invalidatepage") など。
組み込み関数
使えそうなものを挙げておく。詳細は man stapfuncs を参照のこと。
- cpu() — 現在の CPU 番号
- execname() — 現在のプロセス名
- exit() — SystemTap セッションを終了
- pid() — 現在のプロセスの PID
- gettimeofday_[ s | ms | us | ns ]() — UNIX epoch からの経過時間
- ctime() — ctime(gettimeofday_s()) で現在の時刻を文字列に変換
- pp() — プローブポイントの情報
- probefunc() — プローブした関数名
- probemod() — プローブしたモジュール名
- print_backtrace() — コールスタックを表示する
- print_regs() — レジスタ情報を表示する
出力例
print_regs() と print_backtrace() のサンプル
ソースコード <toggledisplay>
#!/usr/bin/stap -v global count = 0 probe kernel.function("de_put") { printf("%s[%d] called %s\n", execname(), pid(), probefunc()) printf("print_regs():\n") print_regs() printf("print_backtrace():\n") print_backtrace() count++ if (count == 10) { exit() } }
</toggledisplay> この例では 10 回分出力されるが、1 つ抜き出すとこんな感じ。
gzip[7858] called de_put print_regs(): EIP: c049ea7e ESP: c049ed89 EAX: f7ffbd40 EBX: f7ffbd40 ECX: 00000000 EDX: 00000000 ESI: f4376b78 EDI: f3dea380 EBP: f4376b78 DS: 007b ES: 007b CR0: 8005003b CR2: 08061000 CR3: 0b5104c0 CR4: 000006f0 print_backtrace(): 0xc049ea7e : de_put+0x1/0x51 [kernel] 0xc049ed89 : proc_delete_inode+0x59/0x64 [kernel] 0xc0487055 : generic_delete_inode+0xa5/0x10f [kernel] 0xc0486af9 : iput+0x64/0x66 [kernel] 0xc0485ae9 : dput+0xd5/0xed [kernel] 0xc047218a : __fput+0x13f/0x167 [kernel] 0xc046fad5 : filp_close+0x4e/0x54 [kernel] (inexact) 0xc0470d01 : sys_close+0x71/0xa8 [kernel] (inexact) 0xc0404f17 : sys_sigreturn+0x1f8/0xf2d [kernel] (inexact)
カーネル変数にアクセス
stap コマンドを -g オプション付きで実行すると、Guru モードというあやしげなモードになり、C 言語を埋め込めるようになる。 しかし、ネットで調べてもあまりドキュメントがなく使い方がよくわからなかったのだが、/usr/share/systemtap/tapset に C 言語を使ったコードが多数あるので、これらを参考にしてみた。
例えば、jiffies なんかの値も確認できるようになる。
#!/usr/bin/stap -vg ★g が必要 %{ #include <linux/jiffies.h> %} function get_jiffies() %{ THIS->__retvalue = jiffies; %} probe timer.s(1) { printf("jiffies = %d\n", get_jiffies()) }
実行すると・・・
[root@lab stap]# ./jiffies.stp Pass 1: parsed user script and 52 library script(s) in 340usr/10sys/352real ms. Pass 2: analyzed script: 1 probe(s), 1 function(s), 1 embed(s), 0 global(s) in 10usr/0sys/6real ms. Pass 3: using cached /root/.systemtap/cache/51/stap_51d9dc13ad57fe556d3fd725f4c74545_656.c Pass 4: using cached /root/.systemtap/cache/51/stap_51d9dc13ad57fe556d3fd725f4c74545_656.ko Pass 5: starting run. jiffies = 1561628 jiffies = 1562628 jiffies = 1563628 jiffies = 1564628 jiffies = 1565627 Pass 5: run completed in 0usr/20sys/5730real ms.
CentOS 5 なので、HZ=1000 で間違いない。すごい。
Tips
プローブポイントを確認
-l オプションでプローブポイントの一覧を出力できる。
[root@lab ~]# stap -v -l 'kernel.function("add_to_*")' Pass 1: parsed user script and 45 library script(s) in 300usr/10sys/318real ms. kernel.function("add_to_page_cache@mm/filemap.c:439") kernel.function("add_to_page_cache_lru@mm/filemap.c:462") kernel.function("add_to_swap@mm/swap_state.c:147") kernel.function("add_to_swap_cache@mm/swap_state.c:99") Pass 2: analyzed script: 4 probe(s), 0 function(s), 0 embed(s), 0 global(s) in 290usr/300sys/591real ms.
RHEL 4 の stap コマンドには -l オプションがない。 この場合は、-p2 オプションでプローブポイント確認後に動作を止めればよい。
[root@centos47 stap]# stap -e 'probe kernel.function("add_to_*"){}' -p2 # probes kernel.function("add_to_page_cache@mm/filemap.c:343") /* pc=0x59caf */ /* <- kernel.function("add_to_*") */ kernel.function("add_to_page_cache_lru@mm/filemap.c:367") /* pc=0x59d61 */ /* <- kernel.function("add_to_*") */ kernel.function("add_to_swap@mm/swap_state.c:142") /* pc=0x712b2 */ /* <- kernel.function("add_to_*") */ kernel.function("add_to_swap_cache@mm/swap_state.c:95") /* pc=0x71635 */ /* <- kernel.function("add_to_*") */
参考:http://d.hatena.ne.jp/mhiramat/20081004
注意事項
-
probe kernel.function("*")
(すべてのカーネル関数をフック) をやってはいけない。この中で何か出力しようものなら、あっという間にシステムがストールしてしまう (実証済み…)。何も出力しなければ or screen 上で実行しなければ問題ないのかもしれないが、もう怖くてできない。。