Solaris 動的トレースガイド

サブルーチン copyin()copyinstr()

DTrace でのプロセスの扱い方は、従来のデバッガや監視ツールとは若干異なっています。従来のツールは、プロセスのスコープ内で実行されます。このため、プログラム変数のポインタの間接参照をユーザー側で行うことができます。これに対して、DTrace プローブは、Solaris カーネル内で実行されます。プロセス内で (プロセスの一部として) 実行されるわけではありません。プロセスデータにアクセスする場合、プローブは、サブルーチン copyin() または copyinstr() を使って、ユーザープロセスデータをカーネルのアドレス空間にコピーする必要があります。

たとえば、次のような write(2) システムコールがあるとします。

ssize_t write(int fd, const void *buf, size_t nbytes);

では、write(2) システムコールに渡される文字列の内容を出力するには、どうしたらよいでしょうか。以下は、正しくない D プログラムの例です。

syscall::write:entry
{
	printf("%s", stringof(arg1)); /* incorrect use of arg1 */
}

このスクリプトを実行すると、次のようなエラーメッセージが出力されます。


dtrace: error on enabled probe ID 1 (ID 37: syscall::write:entry): \
    invalid address (0x10038a000) in action #1

arg1 変数には、buf パラメータの値として、システムコールの実行プロセス内のメモリーを参照するアドレスが入ります。このアドレスの文字列を読み取るには、copyinstr() サブルーチンを使用し、その結果を printf() アクションで記録します。

syscall::write:entry
{
	printf("%s", copyinstr(arg1)); /* correct use of arg1 */

このスクリプトの出力から、write(2) システムコールに渡されるすべての文字列を確認できます。ただし、以下のような異常な出力が得られる場合もあります。


  0     37                      write:entry mada���

copyinstr() サブルーチンは、入力引数 (NULL で終わっている ASCII 文字列のユーザーアドレス) に対して機能します。しかし、write(2) システムコールに渡されるバッファーが、ASCII 文字列ではなくバイナリデータを参照していることがあります。呼び出し側が意図した文字列だけを出力するには、copyin() サブルーチンを使用します。このサブルーチンの第 2 引数にサイズを指定します。

syscall::write:entry
{
	printf("%s", stringof(copyin(arg1, arg2)));
}

stringof 演算子がないと、DTrace は、copyin() を使って取得したユーザーデータを正しく文字列に変換できません。しかし、copyinstr() を使用する場合は、stringof は不要になります。これは、copyinstr が常に string 型を返すからです。

エラーの回避

サブルーチン copyin()copyinstr() は、過去にアクセスしたことのないユーザーアドレスを読み取ることができません。アドレスが含まれているページが、過去にアクセスされてフォルトインされたことがないと、たとえそのアドレスが有効であっても、エラーが起きる可能性があります。次の例で考えてみてください。


# dtrace -n syscall::open:entry'{ trace(copyinstr(arg0)); }'
dtrace: description 'syscall::open:entry' matched 1 probe
CPU     ID                    FUNCTION:NAME
dtrace: error on enabled probe ID 2 (ID 50: syscall::open:entry): invalid address
(0x9af1b) in action #1 at DIF offset 52

この出力例では、アプリケーションが正常に機能していて、arg0 に指定されたアドレスも有効ですが、対応するプロセスがまだアクセスしたことがないページが参照されています。この問題を解決するには、カーネルまたはアプリケーションでデータが使用されるのを待ってからトレースを開始する必要があります。たとえば、システムコールが復帰して、copyinstr() が適用されるまで待ちます。次の例を参照してください。


# dtrace -n syscall::open:entry'{ self->file = arg0; }' \
-n syscall::open:return'{ trace(copyinstr(self->file)); self->file = 0; }'
dtrace: description 'syscall::open:entry' matched 1 probe
CPU     ID                    FUNCTION:NAME
  2     51                      open:return   /dev/null