彷徨えるフジワラ

年がら年中さまよってます

DTrace で const 指定が無視される件 - その1

DTrace でユーザ定義プロバイダを作成する際に、プローブ引数の型宣言を const 修飾した場合:

provider foo {
    probe bar(const char *a);
};

このプロバイダ定義を元に、C/C++ 向けヘッダファイルを生成すると:

extern void __dtrace_foo___bar(char *);

こんな感じで、const 修飾が外れてしまうので、例えばこのプローブを const 指定付きの char* 変数で呼び出そうとすると、コンパイラに文句を言われてしまう。

キャストで強制的に型変換するようなマクロを間に挟んでも良いのだけれど、そうすると、うっかり型違いの引数を渡した場合のコンパイル時チェックが機能しなくなってしまうわけで、折角コンパイラを使用しているのに意味無くねぇ?という感じ。

というわけで、再び DTrace をデバッグすることに。なんか最近 DTrace のデバッグばかりやっている気がする....
まずは const キーワードに対して構文解析においてどのような処理が行われているか調べてみると:

type_qualifier:
      DT_KEY_CONST { $$ = dt_decl_attr(DT_DA_CONST); }
    | DT_KEY_RESTRICT { $$ = dt_decl_attr(DT_DA_RESTRICT); }
    | DT_KEY_VOLATILE { $$ = dt_decl_attr(DT_DA_VOLATILE); }
    ;

ということで、DT_DA_CONST に対する処理を探してみることに。.... あれ?何もやってない。あー、何もやってないなら、確かに現在の挙動も納得だわなぁ....。

気を取り直して、他に dt_decl_attr() を呼び出している箇所を調べてみると、singed/unsigned 修飾とかもこれを使っているみたい。

じゃぁ、そっちはちゃんと処理されているの?ってことで、"const char*" ではなく "unsigned char*" を引数にしたプローブ定義からヘッダを生成してみると .... お、こっちはちゃんと unsigned 付きの関数が宣言されている。っつーことで、unsigned 修飾の際の処理と同じような感じにすれば良さそう。

で、unsigned 修飾の際の処理は?というと、dt_decl_type()@dt_decl.c において:

    case CTF_K_FLOAT:
        if (ddp->dd_attr & DT_DA_SIGNED)
                (void) strcat(name, "signed ");
        if (ddp->dd_attr & DT_DA_UNSIGNED)
                (void) strcat(name, "unsigned ");
        if (ddp->dd_attr & DT_DA_SHORT)
                (void) strcat(name, "short ");
        if (ddp->dd_attr & DT_DA_LONG)
                (void) strcat(name, "long ");
        if (ddp->dd_attr & DT_DA_LONGLONG)
                (void) strcat(name, "long long ");
        if (ddp->dd_attr == 0 && ddp->dd_name == NULL)
                (void) strcat(name, "int");
        break;

こんな感じで、名前用バッファ name に文字列を形成したなら、これでもって dt_type_lookup()@dt_parser.c を呼び出すことで型情報を設定している模様。

じゃぁ、ここに DT_DA_CONST の場合の strcat(name, "const ") を足せば宜しかろう、ということで早速改造版で動作確認してみる .... 駄目だ orz

あれ〜?何でだ?ということで、デバッグ用の prinf() 埋め込みをしつつ解析する事暫し。

いつものように dtrace で解析しないのは、フローとかメモリイメージがあまりにも漠然とし過ぎていて、採取ポイントを絞り込めないから。こんな状況だとデバッガもいまいち役に立たないし ....

で、わかったことは、dt_type_lookup() に "const char" を渡しても、"char" で引いた型情報しか設定してくれない、ということ。dt_type_lookup() は空白区切りの最後の要素しか見てくれてない。それじゃぁ、駄目に決まっている。

煮詰まってしまったので、仕切りなおして別な方向から切り込んでみることに。

ヘッダファイルに引数の型情報を書き出しているのは dt_header_decl()@dt_program.c で、じゃぁ、どこから引数の型情報を得ているのかというと、dt_node_t 構造体の dn_type フィールドを使って ctf_type_name() に文字列化させているらしい。

じゃぁ、その dn_type がどこで設定されているかというと、何箇所かで設定しているのだけれど、結局 dt_decl_type() から得た値を代入しているみたい。最初の試行と繋がった! > dt_decl_type()

繋がりはしたものの、はて、どうしたものか?と途方に暮れること暫し。今度は CTF(Compact C Type Format) 側から攻めてみることに。

DTrace は型とか識別子の定義に関する管理を完全に CTF 機能に頼りきっているので、そもそも CTF 側で const 修飾を認識できなければ話にならない筈。CTF のヘッダ(/usr/include/sys/ctf.h)を見てみると:

#define CTF_K_CONST     12      /* ctt_type is base type */

ctf_type_name() でも CTF_K_CONST に対して "const" 文字列を生成しているので、どうやら CTF の機能から見た場合は心配は無さそう。

他に CTF_K_CONST に関して何か特別なことをしてないか CTF のソースを調べてみると:

ctf_id_t
ctf_add_const(ctf_file_t *fp, uint_t flag, ctf_id_t ref)
{
        return (ctf_add_reftype(fp, flag, ref, CTF_K_CONST));
}

どうやら、指定された型 ref に対する const 付きの型を新たに追加する機能らしい。

あぁ、ならば const 付きの型に関して dt_decl_type() は:

  1. dt_type_lookup() で型情報を得る
  2. 得た型情報で ctf_add_const() を呼び出して、const 付きの型情報を新たに生成
  3. 呼び出し元には const 付きの型情報の識別氏を返却

という具合にすれば良いのではなかろうか?

ということで、早速修正版で実験....お!上手く行った! > "const char*" 引数

動作確認は成功したものの、ctf_add_const() が呼び出している ctf_add_reftype() は const 修飾型が登録済みか否かの判定をしていないので、同じ型に関する複数回の const 修飾は都度新規型情報を生成する羽目になるから、資源消費的にはちょっとイケてない感じ。

でも、CTF 側でも「指定された型に対して、const 修飾付きの型の有無を調べる」的な機能は無さそう。「指定された型が const 修飾付きなら、const 修飾無しの型を取得する」機能なら有るんだけどなぁ。

っつーことで、新規作成を抑止しようとするなら、「const 付き型一覧の管理機能」を新たに作成するか、「定義済み型一覧を走査して探す」ぐらいしか手が無いことを考えると、都度新規で手を打つしか無いのかも。まぁ、仕方ないか。