pid プロバイダでは、プロセス内の任意の命令をトレースできます。大半のプロバイダとは異なり、pid プローブは、D プログラム内のプローブ記述で、オンデマンドで作成されます。そのため、ユーザー自身で pid プローブを有効にしないかぎり、dtrace -l を実行しても何も出力されません。
pid プロバイダのもっとも単純な操作モードは、fbt プロバイダにとってのユーザー空間に似ています。以下は、ある関数の開始 (entry) と終了 (return) をすべてトレースするプログラム例です。$1 マクロ変数 (コマンド行の最初のオペランド) には、トレース対象のプロセスのプロセス ID が入ります。$2 マクロ変数 (コマンド行の 2 番目のオペランド) には、すべての関数呼び出しをトレースする関数の名前が入ります。
pid$1::$2:entry { self->trace = 1; } pid$1::$2:return /self->trace/ { self->trace = 0; } pid$1:::entry, pid$1:::return /self->trace/ { }
このスクリプトを userfunc.d という名前のファイルに保存し、chmod で実行可能ファイルに変更します。このスクリプトからは、次のような結果が出力されます。
# ./userfunc.d 15032 execute dtrace: script './userfunc.d' matched 11594 probes 0 -> execute 0 -> execute 0 -> Dfix 0 <- Dfix 0 -> s_strsave 0 -> malloc 0 <- malloc 0 <- s_strsave 0 -> set 0 -> malloc 0 <- malloc 0 <- set 0 -> set1 0 -> tglob 0 <- tglob 0 <- set1 0 -> setq 0 -> s_strcmp 0 <- s_strcmp ... |
pid プロバイダは、すでに実行中のプロセスに対してしか使用できません。$target マクロ変数 (第 15 章スクリプトの作成を参照) と、dtrace コマンドの -c オプションと-p オプションを使って、プロセスを作成し、取り込みます。さらに、DTrace を使って、これらのプロセスを計測します。たとえば、次の D スクリプトでは、特定の従属プロセスによって実行される libc の関数呼び出しの内訳がわかります。
pid$target:libc.so::entry { @[probefunc] = count(); }
このスクリプトを libc.d という名前のファイルに保存し、次のコマンドを実行すると、date(1) コマンドによって実行されるこの種の呼び出しの内訳がわかります。
# dtrace -s libc.d -c date dtrace: script 'libc.d' matched 2476 probes Fri Jul 30 14:08:54 PDT 2004 dtrace: pid 109196 has exited pthread_rwlock_unlock 1 _fflush_u 1 rwlock_lock 1 rw_write_held 1 strftime 1 _close 1 _read 1 __open 1 _open 1 strstr 1 load_zoneinfo 1 ... _ti_bind_guard 47 _ti_bind_clear 94 |
pid プロバイダでは、任意のユーザー関数内の任意の命令をトレースできます。pid プロバイダは、必要に応じて、関数内の各命令に対して 1 つずつプローブを作成します。各プローブの名前は、関数内の対応する命令のオフセット (16 進整数) になります。たとえば、PID 123 のプロセス内にあるモジュール bar.so の関数 foo で、オフセット 0x1c にある命令に関連したプローブを有効にしたい場合は、次のコマンドを使用します。
# dtrace -n pid123:bar.so:foo:1c |
関数 foo 内のプローブを、各命令用のプローブも含めてすべて有効にするには、次のコマンドを使用します。
# dtrace -n pid123:bar.so:foo: |
このコマンドは、ユーザーアプリケーションのデバッグと解析にたいへん役立ちます。発生頻度の低いエラーは、再現性が低いため、簡単にはデバッグできません。また、エラーの発生後、ようやく問題を突き止めたときには、すでにコードパスを再現できなくなっている場合もあります。以下の例のように、pid プロバイダと投機トレース (第 13 章投機トレースを参照) を組み合わせて、関数内のすべての命令を個別にトレースすると、この問題を解決できます。
pid$1::$2:entry { self->spec = speculation(); speculate(self->spec); printf("%x %x %x %x %x", arg0, arg1, arg2, arg3, arg4); } pid$1::$2: /self->spec/ { speculate(self->spec); } pid$1::$2:return /self->spec && arg1 == 0/ { discard(self->spec); self->spec = 0; } pid$1::$2:return /self->spec && arg1 != 0/ { commit(self->spec); self->spec = 0; }
errorpath.d を実行すると、次のような結果が出力されます。
# ./errorpath.d 100461 _chdir dtrace: script './errorpath.d' matched 19 probes CPU ID FUNCTION:NAME 0 25253 _chdir:entry 81e08 6d140 ffbfcb20 656c73 0 0 25253 _chdir:entry 0 25269 _chdir:0 0 25270 _chdir:4 0 25271 _chdir:8 0 25272 _chdir:c 0 25273 _chdir:10 0 25274 _chdir:14 0 25275 _chdir:18 0 25276 _chdir:1c 0 25277 _chdir:20 0 25278 _chdir:24 0 25279 _chdir:28 0 25280 _chdir:2c 0 25268 _chdir:return |