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 に投げておいたので、いずれ取り込んでもらえると良いなぁ。