From: "H.Yamamoto" Date: 2005-02-01T17:06:30+09:00 Subject: [ruby-dev:25609] Re: some problems on ext/tk/sample/**/*.rb 山本です。 >問題がありましたらご連絡ください. あれから調べていたのですが、なぜ namespace が壊れるのか ようやくわかりました。結論から言うと、Tcl のメニューを 押したとき ruby 側のコールバックが呼ばれますが、そこで Kernel.exit を呼ぶと、現在の実装では Tcl の環境をまたいで longjmp が行われてしまいます。そのため、フレームとして 登録されていたスタックな CallFrame が Pop されず残って しまい、フレームの破壊につながったと考えられます。 // 再現手順 tcltklib.c(ip_ruby_cmd) の 1815行目 rb_raise(rb_eSystemExit, RSTRING(res)->ptr); にブレークポイントを置くと、コールスタックは以下のようになります。 ip_ruby_cmd(void * 0x00000000, Tcl_Interp * 0x02443548, int 4, Tcl_Obj * const * 0x0240e134) line 1816 EvalObjv(Tcl_Interp * 0x02443548, int 4, Tcl_Obj * const * 0x0240e134, char * 0x08fc03f8, int 31, int 0) line 932 + 25 bytes Tcl_EvalEx(Tcl_Interp * 0x02443548, char * 0x08fc03f8, int 31, int 262144) line 1393 + 30 bytes Tcl_EvalObjEx(Tcl_Interp * 0x02443548, Tcl_Obj * 0x02501fa8, int 262144) line 2580 + 21 bytes Tcl_EvalObjCmd(void * 0x00000000, Tcl_Interp * 0x02443548, int 3, Tcl_Obj * const * 0x0244b560) line 624 + 18 bytes TclExecuteByteCode(Tcl_Interp * 0x02443548, ByteCode * 0x024756a0) line 874 + 25 bytes Tcl_EvalObjEx(Tcl_Interp * 0x02443548, Tcl_Obj * 0x02434e28, int 0) line 2733 + 13 bytes TclObjInterpProc(void * 0x0249ebf0, Tcl_Interp * 0x02443548, int 2, Tcl_Obj * const * 0x0244b558) line 1001 + 21 bytes TclExecuteByteCode(Tcl_Interp * 0x02443548, ByteCode * 0x024757a0) line 874 + 25 bytes Tcl_EvalObjEx(Tcl_Interp * 0x02443548, Tcl_Obj * 0x025027a0, int 131072) line 2733 + 13 bytes TkInvokeMenu(Tcl_Interp * 0x02443548, TkMenu * 0x024754a0, int 0) line 1119 + 28 bytes TkWinHandleMenuEvent(HWND__ * * 0x0240ec34, unsigned int * 0x0240ec38, unsigned int * 0x0240ec3c, long * 0x0240ec40, long * 0x0240ec28) line 989 + 23 bytes TkWinMenuProc(HWND__ * 0x000f05d8, unsigned int 273, unsigned int 1, long 0) line 861 + 25 bytes USER32! 77e0a420() USER32! 77de4605() USER32! 77de5b77() Tcl_DoOneEvent(int -3) line 889 + 9 bytes lib_eventloop_core() line 698 + 10 bytes lib_eventloop_main() line 804 + 16 bytes rb_ensure(unsigned long (void)* 0x02dc1040 lib_eventloop_main(void), unsigned long 2, unsigned long (void)* 0x02dc16f0 lib_eventloop_ensure(void), unsigned long 0) line 5233 + 7 bytes lib_eventloop_launcher() line 840 + 23 bytes lib_mainloop() line 860 + 9 bytes rb_call0() line 5565 + 157 bytes rb_call(unsigned long 46032112, unsigned long 46032568, unsigned long 9897, int 1, const unsigned long * 0x0240f06c, int 0) line 5787 + 40 bytes rb_eval(unsigned long 45992824, RNode * 0x02a96df8) line 3205 + 212 bytes rb_call0() line 5694 + 13 bytes rb_call(unsigned long 45959416, unsigned long 45992824, unsigned long 9897, int 0, const unsigned long * 0x00000000, int 0) line 5787 + 40 bytes rb_eval(unsigned long 44864088, RNode * 0x02ab6f88) line 3205 + 212 bytes eval_node() line 1296 + 13 bytes ruby_exec_internal() line 1476 + 18 bytes ruby_exec() line 1495 ruby_run() line 1511 + 5 bytes main() line 40 RUBY! mainCRTStartup + 227 bytes KERNEL32! 77e7893d() 次に longjmp して setjmp に戻ることになりますが、このままではデバッガの ステップ実行が setjmp に移ってくれません。そこで、下のようなパッチを 当て、dummy1 にブレークポイントを置くことで setjmp の直後から再開できる ようにします。 Index: eval.c =================================================================== RCS file: /src/ruby/eval.c,v retrieving revision 1.748 diff -u -w -b -p -r1.748 eval.c --- eval.c 5 Jan 2005 03:49:50 -0000 1.748 +++ eval.c 1 Feb 2005 07:20:04 -0000 @@ -925,7 +925,13 @@ static struct tag *prot_tag; #define PROT_YIELD INT2FIX(3) /* 7 */ #define PROT_TOP INT2FIX(4) /* 9 */ -#define EXEC_TAG() (FLUSH_REGISTER_WINDOWS, setjmp(prot_tag->buf)) +static void dummy1(void) +{ +} + +static int dummy2; + +#define EXEC_TAG() (FLUSH_REGISTER_WINDOWS, dummy2 = setjmp(prot_tag->buf), dummy1(), dummy2) #define JUMP_TAG(st) do { \ ruby_frame = prot_tag->frame; \ 例外発生後、コールスタックは次のようになります。 dummy1() line 929 rb_ensure(unsigned long (void)* 0x02dc1040 lib_eventloop_main(void), unsigned long 2, unsigned long (void)* 0x02dc16f0 lib_eventloop_ensure(void), unsigned long 0) line 5232 + 27 bytes lib_eventloop_launcher() line 840 + 23 bytes lib_mainloop() line 860 + 9 bytes rb_call0() line 5565 + 157 bytes rb_call(unsigned long 46032112, unsigned long 46032568, unsigned long 9897, int 1, const unsigned long * 0x0240f06c, int 0) line 5787 + 40 bytes rb_eval(unsigned long 45992824, RNode * 0x02a96df8) line 3205 + 212 bytes rb_call0() line 5694 + 13 bytes rb_call(unsigned long 45959416, unsigned long 45992824, unsigned long 9897, int 0, const unsigned long * 0x00000000, int 0) line 5787 + 40 bytes rb_eval(unsigned long 44864088, RNode * 0x02ab6f88) line 3205 + 212 bytes eval_node() line 1296 + 13 bytes ruby_exec_internal() line 1476 + 18 bytes ruby_exec() line 1495 ruby_run() line 1511 + 5 bytes main() line 40 RUBY! mainCRTStartup + 227 bytes KERNEL32! 77e7893d() Tclの環境を一気に飛び越してしまっている事がわかります。Tclではスタック変数を PushCallFrame することが行われているので、不正なスタック領域がフレームチェーン に残ってしまうことになります。そこは解放されたスタックなので、以後の自動変数の 定義で利用され、値が変更されていくことになります。 ruby_eval 中に ruby_stop してもいいのかわかりませんが、とりあえず下のように すると落ちなくなりました。本当は Tcl_SetResult などを使って、Tcl の環境を 安全に通過した後、Tcl_GetResult を見て lib_eventloop_launcher で exit(2) すべきなのかもしれません。(試してないので不可能かもしれませんが) Index: tcltklib.c =================================================================== RCS file: /src/ruby/ext/tk/tcltklib.c,v retrieving revision 1.1 diff -u -w -b -p -r1.1 tcltklib.c --- tcltklib.c 25 Jan 2005 14:31:44 -0000 1.1 +++ tcltklib.c 1 Feb 2005 06:54:25 -0000 @@ -1813,7 +1813,7 @@ ip_ruby_cmd(clientData, interp, argc, ar rb_thread_critical = thr_crit_bup; - rb_raise(rb_eSystemExit, RSTRING(res)->ptr); + ruby_stop(0); } else if (rb_obj_is_kind_of(res, eLocalJumpError)) { VALUE reason = rb_ivar_get(res, ID_at_reason); また、以下の変更は、本来のバグを隠してしまうので revert すべきだと思います。 Mon Jan 31 13:13:35 2005 Hidetoshi NAGAI * ext/tk/tcltklib.c: add invalid namespace check * ext/tk/lib/multi-tk.rb: add invalid_namespace? method * ext/tk/lib/remote-tk.rb: ditto # tcltk8.3.3 と ruby(29 Jan 2005 15:00) を VisaulC++ でデバッグビルドした環境で # テストしました。 # しかし、C++Builderのデバッガはなぜか「そこにある」デバッグ情報 tds を呼んでくれず、 # てこずりました。C++Builder1 を入れてバイナリにデバッグ情報を直接埋め込む tlink32 # を使ったりしましたが、ruby をビルドできず、結局 VisualC++ のデバッガに助けられました。