未来のいつか/hyoshiokの日記

hyoshiokの日々思うことをあれやこれや

rubyのバグ、gdb豆知識(その3)

以下でSEGVする

$ ./ruby -e 'eval("1+" * 100000 + "1")'
Segmentation fault

参照: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/37007

さっそくgdbでおってみる。

(gdb) run  -e 'eval("1+" * 100000 + "1")'
Starting program: /home/hyoshiok/work/ruby_trunk/ruby/ruby -e 'eval("1+" * 100000 + "1")'
[Thread debugging using libthread_db enabled]
[New Thread 0xb7d3c6b0 (LWP 8295)]
[New Thread 0xb7f10b90 (LWP 8296)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xb7d3c6b0 (LWP 8295)]
iseq_compile_each (iseq=0x824ab18, ret=0xbf2253a0, node=0x871d27c, poped=0) at compile.c:2883

となる。

どこで呼ばれているかというと、

(gdb) bt 5
#0  iseq_compile_each (iseq=0x824ab18, ret=0xbf2253a0, node=0x871d27c, poped=0) at compile.c:2883
#1  0x08108088 in iseq_compile_each (iseq=0x824ab18, ret=0xbf2255f0, node=0x871d240, poped=0) at compile.c:3954
#2  0x08108088 in iseq_compile_each (iseq=0x824ab18, ret=0xbf225840, node=0x871d204, poped=0) at compile.c:3954
#3  0x08108088 in iseq_compile_each (iseq=0x824ab18, ret=0xbf225a90, node=0x871d1c8, poped=0) at compile.c:3954
#4  0x08108088 in iseq_compile_each (iseq=0x824ab18, ret=0xbf225ce0, node=0x871d18c, poped=0) at compile.c:3954
(More stack frames follow...)

スタックフレームを一つあがって(up)、ソースを見る(list)。

(gdb) up
#1  0x08108088 in iseq_compile_each (iseq=0x824ab18, ret=0xbf2255f0, node=0x871d240, poped=0) at compile.c:3954
(gdb) list
3949		    }
3950		}
3951	#endif
3952		/* reciever */
3953		if (type == NODE_CALL) {
3954		    COMPILE(recv, "recv", node->nd_recv);
3955		}
3956		else if (type == NODE_FCALL || type == NODE_VCALL) {
3957		    ADD_CALL_RECEIVER(recv, nd_line(node));
3958		}

3954行のCOMPILE(recv, "recv", node->nd_recv);というところから呼ばれているのがわかる。
COMPILEというのはマクロで定義は下記のとおりである。

/* compile node */
#define COMPILE(anchor, desc, node) \
  (debug_compile("== " desc "\n", \
                 iseq_compile_each(iseq, anchor, node, 0)))

さて、SEGVしたのは、再帰的にiseq_compile_each()をずんずん呼んでいって、スタックオーバフローしたからである。さきほどのコードで確実に再現するので(わたしの環境はUbuntu 8.04)、gdbを利用していろいろ調べることが容易にできる。

gdbの場合、signalを掴まえると、設定によって、その場所で停止する。info signalでその設定をみることができる。

(gdb) info signal
Signal        Stop	Print	Pass to program	Description

SIGHUP        Yes	Yes	Yes		Hangup
SIGINT        Yes	Yes	No		Interrupt
SIGQUIT       Yes	Yes	Yes		Quit
SIGILL        Yes	Yes	Yes		Illegal instruction
SIGTRAP       Yes	Yes	No		Trace/breakpoint trap
SIGABRT       Yes	Yes	Yes		Aborted
SIGEMT        Yes	Yes	Yes		Emulation trap
SIGFPE        Yes	Yes	Yes		Arithmetic exception
SIGKILL       Yes	Yes	Yes		Killed
SIGBUS        Yes	Yes	Yes		Bus error
SIGSEGV       Yes	Yes	Yes		Segmentation fault
SIGSYS        Yes	Yes	Yes		Bad system call
SIGPIPE       Yes	Yes	Yes		Broken pipe
SIGALRM       No	No	Yes		Alarm clock
SIGTERM       Yes	Yes	Yes		Terminated
SIGURG        No	No	Yes		Urgent I/O condition
SIGSTOP       Yes	Yes	Yes		Stopped (signal)
SIGTSTP       Yes	Yes	Yes		Stopped (user)
SIGCONT       Yes	Yes	Yes		Continued
SIGCHLD       No	No	Yes		Child status changed
...

SIGSEGVは停止して表示する設定になっている。通常SEGVは不正なアクセスや、スタックオーバフローなどで発生するので、ソースコードをみて対策をとればいい。

一般的にいってsignalを捕獲したら、それようのシグナルハンドラを用意して、何がしかの作業をすればいい。rubyではスタックオーバフローには対処していないようなので、新規にそれを実装するのは、ちょっとめんどうかもしれない。

BINARY HACKSのHACK #76(pp. 291-300)、"sigaltstackでスタックオーバフローに対処する"が参考になりそうだ。

Binary Hacks ―ハッカー秘伝のテクニック100選

Binary Hacks ―ハッカー秘伝のテクニック100選