前回は、
今回は、
なお、pid
プロバイダを使用する場合にも利用可能です。
可変長領域ダンプの採取
まずは、
採取対象の準備
まずはリスト1に示す独自プロバイダuserio
を定義するものと仮定します。
userio.d
)provider userio
{
probe readin(void* buf, size_t len);
probe writeout(void* buf, size_t len);
};
userio
プロバイダが提供するプローブは、readin
プローブなら実際のI/writeout
プローブなら実際のI/
プロバイダを定義したなら、
- ヘッダフィルの生成
- 採取対象ソースファイル
( read_
)n_write. c の作成 - 採取対象ソースファイルのコンパイル
( read_
の生成)n_write. o dtrace
コマンドによるリンク前処理( userio.
の生成)o - 採取対象実行可能バイナリのリンク
( read_
の生成)n_write
作業手順例を図1に示します。
$ dtrace -s userio.d -h (1) ※read_n_write.cの作成(2) $ cc -c read_n_write.c (3) $ dtrace -s userio.d -G read_n_write.o (4) $ cc -o read_n_write read_n_write.o userio.o (5) $
tracemem()
の問題点
第2回でデータ領域内容の採取に使用したDスクリプトをリスト2に再掲します。
pid$target:show_args:checksum:entry
{
this->iobuf = alloca(32);
copyinto(arg0, 32, this->iobuf);
tracemem(this->iobuf, 32);
}
tracemem()
は、
また、tracemem()
を繰り返す、
しかし、
数キロバイトまでの対応で割り切ることができるのであれば、tracemem()
による固定長のダンプを、
可変長領域ダンプの実現
さて、
ここでは、
- スクリプト中の各節は、
記述順序に実行される - 述語
(=前提条件) 記述には、 改変を伴う式を記述することができる
繰り返し処理の大枠の構造を、
inline uintptr_t width = 128;
/* 節 (1) */
userio$target:$1::
{ self->offset = - width; }
/* 節 (2) */
userio$target:$1::
/(self->offset += width) < arg1/
{ ※ self->offset は 0 }
/* 節 (3) */
userio$target:$1::
/(self->offset += width) < arg1/
{ ※ self->offset は 128 }
/* 節 (4) */
userio$target:$1::
/(self->offset += width) < arg1/
{ ※ self->offset は 256 }
:
(以下、同じ記述の繰り返し)
:
冒頭の"inline int width = 128;
"は、
第1節で"- width
"、self->offset
" 変数は、self->offset += width
"記述により、
つまり、
この方法による擬似的な繰り返しが、self->offset
"変数の値が有効値(この例ではarg1
)を越えた後も、
なお、self->offset
" の値をわざわざ負の値に初期化しているのは、self->offset
"が0の場合の処理を特別扱いしないことで、
実際に可変長領域をダンプするDスクリプトをリスト4に示します。
inline uintptr_t width = 128;
userio$target:$1::
{
printf("addr=0x%p", arg1);
self->offset = - width;
}
userio$target:$1::
/(self->offset += width) < arg1/
{
self->buf = alloca(width);
copyinto(arg0 + self->offset, width, self->buf);
printf("+0x%p -", self->offset);
tracemem(self->buf, width);
}
:
(以下、同じ記述の繰り返し)
:
このスクリプトによる実行例を図 2 に示します。
$ dtrace -s watch_io.d \ -c './read_n_write ofile 400 300' \ read_n_write \ < ./read_n_write dtrace: script 'watch_io.d' matched 10 probes CPU ID FUNCTION:NAME 0 60346 main:readin addr=0x8062a10 0 60346 main:readin +0x0 - 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 0: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............ 10: 02 00 03 00 01 00 00 00 cc 10 05 08 34 00 00 00 ............4... 20: 48 38 00 00 00 00 00 00 34 00 20 00 06 00 28 00 H8......4. ...(. 30: 26 00 25 00 06 00 00 00 34 00 00 00 34 00 05 08 &.%.....4...4... 40: 00 00 00 00 c0 00 00 00 c0 00 00 00 05 00 00 00 ................ 50: 00 00 00 00 03 00 00 00 f4 00 00 00 00 00 00 00 ................ 60: 00 00 00 00 11 00 00 00 00 00 00 00 04 00 00 00 ................ 70: 00 00 00 00 fd ff ff 6f 08 01 00 00 08 01 05 08 .......o........ 0 60346 main:readin +0x80 - 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 0: 00 00 00 00 10 00 00 00 00 00 00 00 04 00 00 00 ................ 10: 00 00 00 00 01 00 00 00 00 00 00 00 00 00 05 08 ................ 20: 00 00 00 00 a9 23 00 00 a9 23 00 00 05 00 00 00 .....#...#...... 30: 00 00 01 00 01 00 00 00 ac 23 00 00 ac 23 06 08 .........#...#.. 40: 00 00 00 00 6c 02 00 00 58 06 00 00 07 00 00 00 ....l...X....... 50: 00 00 01 00 02 00 00 00 40 24 00 00 40 24 06 08 ........@$..@$.. 60: 00 00 00 00 58 01 00 00 00 00 00 00 07 00 00 00 ....X........... 70: 00 00 00 00 2f 75 73 72 2f 6c 69 62 2f 6c 64 2e ..../usr/lib/ld. ....(以下略)
この例では、readin
プローブを契機に、
なお、copyinto()
サブルーチンに指定するサイズ情報は、
copyinto(arg0 + self->offset,
(arg1 - self->offset < width
? arg1 - self->offset
: width),
self->buf);
この記述により、width
よりも小さい場合には、copyinto()
アクションによるカーネル空間への取り込み対象となります。
copyinto()
アクションによるユーザ空間からカーネル空間へのコピーの際に、
サイズ指定をリスト5のように記述することで、
なお、a ? b : c
"形式の"条件式"
間接参照先の取得
間接参照先の内容を知りたいケースの最も典型的な例は、main()
関数のargv
引数から文字列を取得したい、
DTrace動作原理に由来する制限
第2回で触れたように、
そのため、copyinstr()
サブルーチンを使用する必要がありました。
間接参照先の情報を取得する場合も、
例として使用するmain()
関数のargv
引数の場合、char*
" の配列」
つまり argv
の値を直接使用した参照ができないのは勿論、argv
の値を元にchar*
" の配列」copyinto
しても、char*
"」、
間接参照先の取得
main()
関数のargv[0]
が参照する文字列の取得を例に、
リスト 6 argv[0]
文字列取得スクリプト
(watch_
)
self uintptr_t* buf;
pid$target:$1:main:entry
{
self->buf = alloca(sizeof(uintptr_t));
copyinto(arg1, sizeof(uintptr_t), self->buf);
printf("argv[0]='%s'", copyinstr(*(self->buf)));
}
copyinto()
サブルーチンでカーネル空間に取り込んだアドレス情報self->buf
に格納)copyinstr()
サブルーチンを実施することで、argv[0]
に相当)
Dスクリプト冒頭における"self uintptr_
" 記述は、self->buf
の型を宣言しておくことで、
DTraceは概ね自動的に型を判定してくれますが、
それでは実際に動かしてみましょう。
argv[0]
文字列の採取$ dtrace -s watch_argv0.d \ -32 \ -c '/usr/bin/true' \ true dtrace: script 'watch_argv0.d' matched 1 probe dtrace: pid 911 has exited CPU ID FUNCTION:NAME 0 60346 main:entry argv[0]='/usr/bin/true' $
採取対象に
先ほどの実行例 実はこのオプションは、 間接参照先の取得を行う場合、 Dスクリプトにおけるポインタ値のビット幅は、 そして、 そこで、 なお、 次回は、/usr/
コマンドを用いているのは、ポインタ値ビット幅の問題
dtrace
コマンド実行の際に、-32
"オプションを使用しました。sizeof(intptr_
" といった式を元に算出されますが、sizeof(intptr_
" 値と、sizeof(intptr_
" 値が異なります。-32
"オプションによって指定するわけです。isainfo -b
"などによりカーネルの動作モードを確認することをお勧めします。可変長間接参照への応用
main()
関数のargv[]
引数から任意の要素文字列を取得するような、argv[]
引数の任意個表示self uintptr_t* argv;
pid$target:$1:main:entry
{
self->index = -1;
}
pid$target:$1:main:entry
/(self->index += 1) < arg0/
{
self->argv = alloca(sizeof(uintptr_t));
copyinto(arg1 + (sizeof(uintptr_t) * self->index),
sizeof(uintptr_t),
self->argv);
printf("argv[%d]='%s'",
self->index, copyinstr(*(self->argv)));
}
:
(以下、同じ記述の繰り返し)
:
argv
引数(=arg1
)に対してsizeof(uintptr_
をself->index
倍した値を加算することで、argv[self->index]
に相当するアドレス値を算出しているため、copyinto()
サブルーチンを使用する箇所が、次回予告