[ruby-dev:25609] Re: some problems on ext/tk/sample/**/*.rb
From:
"H.Yamamoto" <ocean@...2.ccsnet.ne.jp>
Date:
2005-02-01 08:06:30 UTC
List:
ruby-dev #25609
山本です。
>問題がありましたらご連絡ください.
あれから調べていたのですが、なぜ 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 <nagai@ai.kyutech.ac.jp>
* 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++ のデバッガに助けられました。