前回は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 プロバイダを使用する場合、entryreturn |
上記の各要素の組み合わせによって特定される
<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な)次回予告