前回はDTraceを用いて関数呼び出しフローを採取しました。今回は、
引数値の採取
関数呼び出しにおいて採取したい情報の筆頭は、
引数値の表示
まずは、multiply()
を持つプログラムshow_
multiply()
)int
multiply(int x, int y)
{
return (x * y);
}
この関数が呼び出された際の引数を表示するDスクリプトは、show_
およびmultiply
部分を適宜書き換えてください)。
pid$target:show_args:multiply:entry
{
printf("multiply(%d, %d)\n", arg0, arg1);
}
上記Dスクリプトの各要素は、pid$target:show_
" および"entry
"部分に関しては、
multiply |
この位置には情報採取対象の関数名を記述します。 この例では、 multiply 関数が採取対象となります。
|
---|---|
printf() |
C 標準ライブラリの printf()
関数に相当する処理を行う DTraceアクションを呼び出します。標準Cライブラリで既定されているフォーマット指定は概ね使用可能です。 |
arg0 , arg1 |
(この例では) 最初の引数が arg0 、arg1 、arg2 ...で参照可能です。範囲外の引数を参照した場合の値は不定となります。 |
dtrace
コマンドの起動方法は前回と同様です。
$ dtrace -s watch_arg_val.d \
-q \
-c './show_args 13 17'
multiply(13, 17) ← 採取結果
$
今回は関数フローの必要が無いので、-F
指定を省略しています。
また -q
指定により、printf()
アクションによる)
D スクリプトの文法詳細
これまでは、
D スクリプトは、
<Probe-Description> [, ....]
{
[<Action> ....]
}
<Action>
部分は、printf()
のような処理を、
<Probe-Description>
は以下の形式で構成されます。
<Probe-Description>
の構成<Provider>:<ProbeModule>:<ProbeFunc>:<ProbeName>
各要素は以下の意味を持ちます。
<Probe-Description>
の構成要素<Provider> |
情報採取機能の種類を指定します。この機能種別を DTrace ではプロバイダ ユーザプログラムからの情報採取の場合は、 pid プロバイダの使用がメインになると思いますが、 |
---|---|
<ProbeModule> |
情報採取対象のバイナリファイル名を指定します。 現状は採取対象コマンド名と同一とみなして構いません |
<ProbeFunc> |
情報採取対象となる関数名を指定します。 |
<ProbeName> |
どの時点で情報を採取するかを指定します。pid プロバイダを使用する場合、entry return |
上記の各要素の組み合わせによって特定される
<ProbeModule>
と<ProbeFunc>
が採取対象となる関数<Provider>
と<ProbeName>
が採取対象となる情報
<Probe-Description>
中の各要素は省略可能で、
ただし、pid
プロバイダを使用してユーザプログラムから情報採取する場合、<ProbeFunc>
ぐらいだと思ってください
<Action>
が実行される際の採取対象プローブに関する<Probe-Description>
中の各要素は、probeprov
、probemod
、probefunc
および probename
という組み込み変数を使って参照することができます。
そのため、<Probe-Description>
はカンマで区切って複数列挙できることと、<Action>
の対象となっている関数名をprobefunc
参照できることを利用して:
<Action>
共有例pid$target:show_args:add:entry,
pid$target:show_args:subtract:entry,
pid$target:show_args:multiply:entry,
pid$target:show_args:divide:entry
{
printf("%s(%d, %d)\n", probefunc, arg0, arg1);
}
上記のように、<Action>
を、<Probe-Description>
で共有しつつ、
上記以外の組み込み変数に関しては、
引数文字列の採取
文字列表示に関する制限
関数呼び出しにおける引数値表示の次は、
show_
コマンドは、main()
関数のargv[0]
引数、showname
を呼び出すものと仮定します。
showname
)static void
showname(const char* name)
{
.....
}
先ほどの例ですでに、%s
" フォーマットとprobefunc
組み込み変数を用いたprintf()
による文字列表示を行いましたので、showname()
関数呼び出しにおける文字列引数を表示してみましょう。
pid$target:show_args:showname:entry
{
printf("(%s)\n", arg0);
}
しかし、dtrace
コマンドを実行してみると……
$ dtrace -s watch_arg_val.d \ -q \ -c './show_args' dtrace: failed to compile script watch_args_str_bad.d: line 3: \ printf( ) argument #2 is incompatible with conversion #1 prototype: conversion: %s prototype: char [] or string (or use stringof) argument: int64_t $
何やらエラーが表示されてしまいました。
まず第1の問題は、int64_
)%s
"が期待する型と一致していない点にあります。
もうひとつの問題は、
実はDTraceは、
「文字列」
- 妥当な長さであるか不明
- 本当に "\0" で終端しているか不明
- 当該メモリ領域が使用可能であるか不明
といった点でprintf("%s")
による出力が直接は実施できないように、
先の例で使用した probefunc
がprintf("%s")
で表示できたのは、probefunc
の値が
stringof
サブルーチンstring
型オブジェクトを返却します。
pid$target:show_args:showname:entry
{
printf("(%s)\n", stringof(arg0));
}
それではエラーメッセージの指示に従い、stringof
を使ったDスクリプトを実行してみると……
$ dtrace -s watch_arg_val.d \ -q \ -c './show_args' dtrace: error on enabled probe ID 1 \ (ID 60308: pid11310:show_args:showname:entry): \ invalid address (0x8047d40) in action #1 $
またもやエラーが出てしまいました
実はこのエラーも、
表示しようとしている文字列の格納先arg0
の値)invalid address
")
文字列引数の表示
前述したように、
安心してください。以下のようなDスクリプトにより、
pid$target:show_args:showname:entry
{
printf("show_args(%s)", copyinstr(arg0));
}
copyinstr
サブルーチンは、
- 文字列データを、
ユーザ空間からカーネル空間に複製 (有限長) string
(安全な文字列)型オブジェクトに変換
上記のDスクリプトを実行してみると……
$ dtrace -s watch_arg_val.d \
-q \
-c './show_args'
showname(./show_args) ← 採取結果
$
今度は無事に文字列引数の内容を採取することができました。
もしも、
(1) 固定長領域に格納されていて、
(2) 必ずしも "\0" 終端していない文字列を扱う場合は、
以下の方法で文字列表示が可能です。
pid$target:show_args:showname:entry
{
printf("show_args(%s)", stringof(copyin(arg0), 64));
}
上記のDスクリプトでは、copyin
によりユーザ空間からカーネル空間に64バイト分のデータが転送され、stringof
により当該データがstring
型へと変換されます
メモリ内容の採取
先の採取例では、
そこで、 先述したように、 そこで、 このDスクリプトによって、 上記の D スクリプトにおけるアクション部分は、 それではここで初めて出てきた Dスクリプトでは、 上記のDスクリプト なお、 これまでに説明してきた手法を組み合わせれば、 後は関数の戻り値を採取できれば、 以下のDスクリプトは、 何らかのデータが格納されている領域を指すアドレスが戻り値になっていて、 なお、 関数フローにおける基本的な情報採取について、 次回は、checksum
を想定します。なお、checksum
)int
checksum(const char* buf)
{
int val = 0;
int i;
int length = 32;
for(int i = 0 ; i < length ; i += 1){
val = (val << 1) ^ buf[i];
}
return val;
}
buf
が指しているユーザ空間のメモリ内容を一旦カーネル空間にコピーしてから、pid$target:show_args:checksum:entry
{
this->iobuf = alloca(32);
copyinto(arg0, 32, this->iobuf);
tracemem(this->iobuf, 32);
}
buf
の指す領域は以下のような形式で表示されます。$ dtrace -s watch_arg_mem.d \
-q \
-c './show_args'
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 80 09 05 08 34 00 00 00 ............4...
$
iobuf
を確保alloca
サブルーチンによって)iobuf
変数にバッファ領域を参照させるchecksum
関数の引数buf
の指すメモリ領域iobuf
の指す領域copyinto
サブルーチンによって)iobuf
の指す領域32バイト分をtracemem
アクションによって)copyinto
やtracemem
などは、this
というキーワードは何でしょうか?this->VariableName
と記述することで、iobuf
変数を使用していることになります。tracemem
に指定するデータ長は、戻り値の採取
show_
中の全ての関数に対して、pid$target:show_args::return
{
printf("%s()=0x%p", probefunc, arg1);
}
pid
プロバイダでentry
プローブを指定するDスクリプトでは、arg0
やarg1
は関数引数の参照に使用しました。しかし、return
プローブを指定するDスクリプトでは、
arg0
: 関数の戻り先アドレスarg1
: 関数の戻り値copyinstr
やcopyinto
+ tracemem
などを使用する必要があります。
arg1
の値は、
void
な)次回予告