関数呼び出しにおける基本的な情報採取に関しては、
今回は、
マルチスレッド
マルチスレッド化によって性能や応答性の向上を実現するケースは、
特に近年は、
マルチスレッド稼動時の関数フロー
マルチスレッドで稼働中のプロセスから DTraceで関数フローを採取した場合、
各スレッドで、
void*
running(void* arg)
{
int loops = (int)arg;
int i;
for(i = 0 ; i
ループ内で呼び出している関数 f3()
については、
この処理を複数のスレッドで実行する際の関数フローを採取してみましょう。関数フロー採取には、watch_
スクリプトを使用します。
リスト2 関数フロー採取スクリプト
(watch_
)
pid$target:$1::entry,
pid$target:$1::return
{
}
5つのスレッドを並走させて採取された関数フローの例を、
CPU FUNCTION
0 -> _start
0 -> __fsr
0 <- __fsr
0 -> main
0 -> running
0 -> running
0 -> running
0 -> running
0 -> running
0 -> f3
0 -> f1
0 <- f1
0 -> f2
0 -> f1
0 <- f1
0 <- f2
0 <- f3
0 -> f3
0 -> f1
0 <- f1
0 -> f2
....
どの関数フローがどのスレッドで実行されたものか判別がつきませんね。
関数フローが複雑な場合や、
スレッド識別情報の使用
マルチスレッド稼動時の DTrace 出力の問題点は、
しかし裏を返せば、
Dスクリプトで使用可能な組み込み変数には、tid
変数があります。リンク先の説明にもあるように、pthread_
呼び出し結果と同等の値を得ることができます。
それでは、tid
を併用して関数フローを採取してみましょう。
pid$target:$1::entry,
pid$target:$1::return
{
printf("tid=%d", tid);
}
上記Dスクリプトは、entry
)return
)tid
の値を表示することで、
CPU FUNCTION
0 -> _start tid=1
0 -> __fsr tid=1
0 <- __fsr tid=1
0 -> main tid=1
0 -> running tid=2
0 -> running tid=3
0 -> running tid=4
0 -> running tid=5
0 -> running tid=6
0 -> f3 tid=2
0 -> f1 tid=2
0 <- f1 tid=2
0 -> f2 tid=2
0 -> f1 tid=2
0 <- f1 tid=2
0 <- f2 tid=2
0 <- f3 tid=2
0 -> f3 tid=3
0 -> f1 tid=3
0 <- f1 tid=3
0 -> f2 tid=3
....
スレッド識別情報を表示することで、
子プロセス
マルチスレッド化と同様に、
fork()
時のトレース採取
これまで説明に使用してきたDスクリプトでは、pid
プロバイダに対してプロセスIDを明示的に指定してきました。
しかし、fork()
システムコールで生成された子プロセスでは、
つまり、
新規に生成された子プロセスから情報採取を行う簡単な方法は、dtrace
の適用を行う、
たとえば、fork()
により子プロセスを生成するプログラムshow_
を仮定します
show_mp
)int
main(int argc,
const char* argv[])
{
pid_t pid;
int loops = atoi(argv[1]);
if(0 == (pid = fork())){ /* 子プロセス */
running(loops);
}
else if(0
それではデバッガを使って、
以降の実行例は、dbx
を使用したものです。
show_mp
の実行$ dbx show_mp .... (dbx) dbxenv follow_fork_mode child ※ 子プロセスの追跡を指定 (dbx) run 10 ※ 実行開始 Running: show_mp 10 (process id 12161) detaching from process 12161 ※ 親プロセスの切り離し Attached to process 12162 ※ 子プロセスとの接続 stopped in __forkx at 0xd2af207b ※ fork() 完了時点で停止 0xd2af207b: __forkx+0x000b: popl %ecx (dbx)
"Attached to process .....
" 部分に、fork()
により生成された子プロセスのプロセスIDdbx
の cont
コマンドで子プロセスの実行を再開する前に、dtrace
コマンドを適用して、
ただし、dtrace
コマンドの-p
オプションによる採取対象プロセス ID 指定を行うと、
-p
指定付きdtrace
コマンドの実行$ dtrace -p 12162 .... dtrace: failed to grab pid 12162: process is traced $
デバッガの制御下にあるプロセスに対してdtrace
コマンドを適用する場合は、-p
オプションを使用しないようにしてください。
なお、dbx
をコマンドラインから使用した場合、dtrace
コマンド適用を行うことはできません。
このような場合には、dtrace
を適用する、
truss
コマンド出力の加工などによるfork()
戻り値=子プロセスのプロセスIDの抽出- 与えられたプロセスIDに対する
dtrace
コマンドの適用
ただしこの方法の場合、truss
コマンドによるfork()
実行の検出から、dtrace
適用までに時間差が生じますので、
exec()
時のトレース採取
厳密には、exec()
fork()
した子プロセス側で実施されるのが一般的ですので、
たとえば、exec()
するプログラムshow_
を仮定します。
exec()
の実行show_exec
)int
main(int argc,
char* argv[])
{
execv(argv[1], argv + 1);
/* execv() 成功時は、ここには到達しません */
return 1;
}
exec()
に関する注意点を説明するに当たり、
まずはデバッガを使用して、show_
プログラムのmain
関数冒頭を逆アセンブルしてみましょう。
main
冒頭の逆アセンブル結果~その1$ dbx show_exec .... (dbx) dis main 0x08050af0: main : pushl %ebp 0x08050af1: main+0x0001: movl %esp,%ebp 0x08050af3: main+0x0003: subl $0x00000004,%esp 0x08050af6: main+0x0006: movl 0x0000000c(%ebp),%edx 0x08050af9: main+0x0009: movl 0x0000000c(%ebp),%eax 0x08050afc: main+0x000c: addl $0x00000004,%eax ....
上記の結果を踏まえて、
main
冒頭にブレークポイントを設定- プログラムを実行 ~
main
で停止 main
のブレークポイントを無効化
dbx
での実行例を以下に示します。
show_exec
の実行~その1(dbx) stop in main ※ ブレークポイント設定 (2) stop in main (dbx) run /usr/bin/ls -l /tmp ※ 実行開始 Running: show_exec /usr/bin/ls -l /tmp (process id 12493) stopped in main at 0x08050af0 ※ ブレークポイントで停止 0x08050af0: main : pushl %ebp (dbx) status *(2) stop in main (dbx) handler -disable 2 ※ ブレークポイントの無効化 (dbx) status *[2] stop in main -disable
この段階でデバッグ中のプロセスdtrace
コマンドを適用し、
dtrace
コマンドによる採取が開始されたなら、
main
冒頭の逆アセンブル結果~その2(dbx) dis main 0x08050af0: main : int $0x3 0x08050af1: main+0x0001: movl %esp,%ebp 0x08050af3: main+0x0003: subl $0x00000004,%esp 0x08050af6: main+0x0006: movl 0x0000000c(%ebp),%edx 0x08050af9: main+0x0009: movl 0x0000000c(%ebp),%eax 0x08050afc: main+0x000c: addl $0x00000004,%eax ....
dtrace
コマンドの適用前までは"pushl %ebp
"であった関数冒頭部分に、int $0x3
" 命令が埋め込まれています。
上記の結果からわかるように、pid
プロバイダを使用したDスクリプトを実行する際には、
一方で、exec()
が実施された際にも、
ということは、exec()
の実施によって全て上書きされてしまいますから、exec()
後のプロセスからは情報採取ができないことを意味します。
つまり、exec()
後のプロセスから情報採取をしようとするなら、
dbx
を使用している場合であれば、exec()
が完了して新たなプログラムの実行が開始された段階で、dbx
のcont
コマンドを使用してください)。
show_exec
の実行~その2(dbx) run /usr/bin/ls -l /tmp ※ 実行開始 Running: show_exec /usr/bin/ls -l /tmp (process id 12493) dbx: process 12493 about to exec("/usr/bin/ls") dbx: program "/usr/bin/ls" just exec'ed dbx: to go back to the original program use "debug $oprog" Reading ls stopped in main at 0x080520ac ※ exec() 完了時点で停止 0x080520ac: main : pushl %ebp (dbx)
この時点で改めて dtrace
コマンドを対象プロセスexec()
後のプロセスからも情報採取を行うことができます。
なお、dbx
の適用対象プロセスがexec()
を実施した場合、dbx
はそれ以後のデバッグ対象をexec()
されたプログラムに変更します/usr/
")。
exec()
前の段階から再度実行し直したい場合は、debug $oprog
" によってdbx
に通知してください。
共有ライブラリ
今時のプログラムは共有ライブラリ
主要な機能の大半が共有ライブラリによって実現されている、
共有ライブラリの問題
リスト8に示すスクリプトwatch_
を使用して、/usr/
コマンド実行時のlibc.
リスト8 関数フロー採取のDスクリプト
(watch_
)
pid$target:$1::entry,
pid$target:$1::return
{
}
以下に実行例を示します。
$ dtrace -F \ -s watch_flow_whole.d \ -c '/usr/sbin/zpool status' \ libc.so dtrace: script 'watch_flow_whole.d' matched 5815 probes CPU FUNCTION 0 -> __tls_static_mods 0 -> lmalloc 0 -> getbucketnum 0 <- getbucketnum 0 -> initial_allocation 0 -> __systemcall 0 <- __systemcall 0 <- initial_allocation 0 <- lmalloc 0 -> tls_modinfo_alloc ....
dtrace
コマンドに渡している引数"libc.
" は、watch_
でのプローブ記述のモジュール指定部分に記述された"$1
"と置換されます。
第2回での文法の説明の際には割愛しましたが、
それではlibc.zpool
が参照している共有ライブラリの一覧を取得します。
zpool
参照先共有ライブラリの取得$ ldd /usr/sbin/zpool libzfs.so.1 => /lib/libzfs.so.1 libnvpair.so.1 => /lib/libnvpair.so.1 libdevid.so.1 => /lib/libdevid.so.1 ..... $
先ほどはlibc.
$ dtrace -F \ -s watch_flow_whole.d \ -c '/usr/sbin/zpool status' \ libzfs.so dtrace: failed to compile script watch_flow_whole.d: line 1: \ probe description pid12634:libzfs.so::entry \ does not match any probes $
どうやら思ったようには実行されません。
実はこの問題は、
通常のC/
プロセスが起動された段階で、
しかし libzfs.
そのため、
共有ライブラリ中の関数フローの採取
対象とする共有ライブラリからの情報採取ができないのは、
- 共有ライブラリがマッピングされるまで待ってから採取を開始する
- プロセス起動時点で共有ライブラリをマッピングさせる
前者の方法は、dtrace
コマンドの適用を開始する、
後者の方法は、LD_
環境変数
説明とは言うものの、LD_
環境変数に指定した状態でdtrace
コマンドを起動する、
$ export LD_PRELOAD_32=/lib/libzfs.so.1 $ dtrace -F \ -s watch_flow_whole.d \ -c '/usr/sbin/zpool status' \ libzfs.so dtrace: script 'watch_flow_whole.d' matched 828 probes CPU FUNCTION 0 -> zfs_iscsi_init 0 <- zfs_iscsi_init 0 -> _zfs_init_libshare 0 <- _zfs_init_libshare 0 -> libzfs_init 0 -> zfs_prop_init 0 -> register_index 0 -> register_impl 0 -> zprop_get_proptable ....
今度は期待通りに共有ライブラリlibzfs.
の関数フローを採取することができました。
採取用スクリプトのプローブ記述を複数列挙すれば、libzfs.
中の関数の呼び出し元となるzpool
コマンド中の関数フローと併せて採取することもできます。
pid$target:zpool::entry,
pid$target:zpool::return,
pid$target:libzfs.so::entry,
pid$target:libzfs.so::return
{
}
ちなみに、dtrace
実行にpfexec
を併用している場合、LD_
を設定した状況でpfexec
を実行すると、LD_
設定が無視されます。
LD_PRELOAD_32
の権限問題$ export LD_PRELOAD_32=/lib/libzfs.so.1 $ pfexec dtrace -F \ -s watch_flow_whole.d \ -c '/usr/sbin/zpool status' \ libzfs.so ld.so.1: pfexec: warning: /lib/libzfs.so.1: open failed: \ illegal insecure pathname ....
LD_
の設定は、
次回予告
次回は、