彷徨えるフジワラ

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

DTrace と関数宣言の名前無し引数と typedef 型

DTrace の ML に 以下のような投函が。

バグ 6494528 で困ってるんだけど、誰か修正した人居ねぇ?(意訳)

バグ 6494528 ってのは、typedef した型の配列を引数に持つ関数の宣言において、引数名記述を省略すると文法エラーとみなされる、というもの。つまり:

typedef struct foo_t {
    int f1;
};
extern int bar1(int a);
extern int bar2(int);
extern int bar3(int a[]);
extern int bar4(int[]);
extern int bar5(foot_t a);
extern int bar6(foot_t);
extern int bar7(foot_t a[]);
extern int bar8(foot_t[]);

上記の例だと、8つ目の宣言(bar8 関数)だけが DTrace で文法エラーとみなされてしまうらしい。

あ、書くまでも無いかもしれないけど、例によってこのエントリは長いです
とりあえず、dt_grammar.y での文法定義が引数リスト周りでポカってるんじゃなかろうか?という目星を付けつつ調査してみる。調査対象 dtrace に食わせるスクリプトは、以下のように単純化しておく(watch_typedef.d)。

typedef struct foo {
    int f1;
} foo_t;
int bar(foo_t[]);
BEGIN { exit(0); }

関数宣言で関数名が確定した際には、以下の文法定義における dt_decl_ident()@dt_decl.c が呼ばれるルートを通るので、これをキーに網を張ることに。

direct_declarator:
      DT_TOK_IDENT { $$ = dt_decl_ident($1); }
    | lparen declarator DT_TOK_RPAR { $$ = $2; }
    | direct_declarator array { dt_decl_array($2); }
    | direct_declarator function { dt_decl_func($1, $2); }
    ;

但し yacc ベースの処理は、字句を元に構文が確定するまでに一拍間が空いたりなど、一般的な逐次処理のようにトレース対象関数を絞り込むのが難しい。

そこで、bar 関数の宣言に関する解釈が始まってからの流れを把握するために、監視用の DTrace スクリプトは以下の様な感じのものを使用する(watch_dt_parse.d)。

BEGIN {
    self->traced = 0;
}
pid$target:libdtrace.so:dt_decl_ident:entry
/"bar" == copyinstr(arg0)/
{
    self->traced = 1;
}
pid$target:libdtrace.so::entry
/self->traced/
{
}
pid$target:libdtrace.so::return
/self->traced/
{
    printf("%d", arg1);
}
pid$target:libdtrace.so:yyparse:return,
pid$target:libdtrace.so:dt_set_errmsg:entry
{
    self->traced = 0;
}

網羅的なフロー把握のために、dt_decl_ident() が "bar" 関数の宣言で呼ばれた契機で、libdtrace.so 中の関数全てを監視する。但し、それ以後も監視を続けると正常実行時に出力が膨大になるので、構文解釈の区切りになる yyparse() からの復帰と、エラー時に呼ばれる dt_set_errmsg() の呼び出しごとに監視を打ち切る設定に。

先述した watch_typedef.d を DTrace が解釈する様子を、上記 watch_dt_parse.d を喰わせた DTrace で監視してみると:

$ export LD_PRELOAD_32=/usr/lib/libdtrace.so
$ dtrace -F -s watch_dt_parse.d -c '/usr/sbin/i86/dtrace -s watch_typedef.d'
dtrace: script 'watch_dt_parse.d' matched 1562 probes
.....
  0    <- id_or_type                          314
  0  <- yylex                                 314
  0  -> yyerror
  0    -> yyvwarn
  0      -> dt_errtag
  0      <- dt_errtag                         3535145676
  0      -> dt_set_errmsg
dtrace: failed to compile script watch_typedef.d: line 5: \
    syntax error near "foo_t"
dtrace: pid 8566 exited with status 1
$

比較のために、関数宣言部を DTrace のコンパイルが通るように "int bar(foo_t a[]);" としたもので実行してみると:

$ export LD_PRELOAD_32=/usr/lib/libdtrace.so
$ dtrace -F -s watch_dt_parse.d -c '/usr/sbin/i86/dtrace -s watch_typedef.d'
dtrace: script 'watch_dt_parse.d' matched 1562 probes
....
  0    <- id_or_type                          317
  0  <- yylex                                 317
  0  -> dt_decl_spec
....

あれ? 字句解析の yylex() が返す字句種別値が、エラー時だと 317(DT_TOK_IDENT) なのに、成功時は 314(DT_TOK_TNAME)になってるぞ?

該当する文法定義を見てみると(※ 処理部分の記述は省略):

type_specifier: 
      DT_KEY_VOID
    | DT_KEY_CHAR
    | DT_KEY_SHORT
    | DT_KEY_INT
    | DT_KEY_LONG
    | DT_KEY_FLOAT
    | DT_KEY_DOUBLE
    | DT_KEY_SIGNED
    | DT_KEY_UNSIGNED
    | DT_KEY_STRING
    | DT_TOK_TNAME /* 受け入れ可能なのは DT_TOK_TNAME のみ */
    | struct_or_union_specifier
    | enum_specifier
    ;

ははぁ、字句解析部が型名(DT_TOK_TNAME)である foo_t を識別子名(DT_TOK_IDENT)とみなしてしまうことで、構文解析が失敗してしまうのだな。

エラー時は何故に DT_TOK_IDENT と認識されてしまうかというと、字句解析の一環で実施される id_or_type()@dt_lex.l の以下の部分が原因の模様。

        switch (c0) {
        case '+':
        case '-':
                if ((c1 = input()) == c0)
                        ttok = DT_TOK_IDENT;
                unput(c1);
                break;
        case '=':
                if ((c1 = input()) != c0)
                        ttok = DT_TOK_IDENT;
                unput(c1);
                break;
        case '[':
                ttok = DT_TOK_IDENT;
                break;
        }

なるほどね。配列の開きブラケット('[')の手前にあるものは強制的に識別子名(DT_TOK_IDENT)とみなすわけか。

引数宣言の構文定義で配列が噛むケースでは、以下の様な direct_abstract_declarator(※ 処理部分の記述は省略)での「direct_abstract_declarator 無しの array」として解釈され得るので:

direct_abstract_declarator:
      lparen abstract_declarator DT_TOK_RPAR
    | direct_abstract_declarator array
    | array
    | direct_abstract_declarator function
    | function
    ;

仮に変数名を型名と誤認してしまっても、「変数名なし」の宣言と解釈可能なので、問題無さそうな感じ。あくまで C/C++ の宣言記述を解釈できれば良く、C/C++ コンパイラを作るわけではないので、多少ルーズであっても問題は無い筈だしね。

ということで、先の id_or_type() における '[' 判定部分を削除したソースから libdtrace.so をビルドし、エラーの出ていたスクリプトで動作確認。

$ export LD_PRELOAD_32=./usr/src/lib/libdtrace/i386/libdtrace.so.1
$ /usr/sbin/i86/dtrace -s watch_typedef.d
dtrace: script 'watch_typedef.d' matched 1 probe
CPU     ID                    FUNCTION:NAME
  0      1                           :BEGIN
$

おぉ!コンパイルエラーが出なくなった!

パラメータ指定形式とか構造体宣言とかで配列記述を使用したものを、思い付く範囲で何通りか試してみた限りでは、この修正で問題無さそう。

とりあえず bugzilla に投げておいたので、いずれ取り込んでもらえると良いなぁ。