関数呼び出しにおける基本的な情報採取に関しては、
今回は、
マルチスレッド
マルチスレッド化によって性能や応答性の向上を実現するケースは、
特に近年は、
マルチスレッド稼動時の関数フロー
マルチスレッドで稼働中のプロセスから 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_の設定は、
次回予告
次回は、