未来のいつか/hyoshiokの日記

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

rubyのスタックオーバフローに対処する。(その2)

sigsltstack(2)を利用して、SIGSEGVのシグナルハンドラを書いてみた。下記参照。
先に書いたとおりスタックオーバフローでのSIGSEGVの場合、スタックを使いきってしまうので、シグナルハンドラが動く余地もないので、綺麗に後処理ができない。そこで代替スタックを準備して、各種後処理ができるようにする。register_sigaltstack()で代替スタックをsigaltstack(2)で設定している。シグナルハンドラを設定する前に代替スタックの設定処理を呼ぶことにしている。

なお、register_sigaltstack()はBINARY_HACKを写経したものにエラー処理を若干追加したものである。

$ svn diff signal.c
Index: signal.c
===================================================================
--- signal.c	(revision 20086)
+++ signal.c	(working copy)
@@ -47,6 +47,10 @@
 # define NSIG (_SIGMAX + 1)      /* For QNX */
 #endif
 
+#ifdef SIGSEGV
+int is_altstack_defined = 0;
+#endif
+
 static const struct signals {
     const char *signm;
     int  signo;
@@ -410,6 +414,28 @@
 typedef RETSIGTYPE (*sighandler_t)(int);
 
 #ifdef POSIX_SIGNAL
+#define ALT_STACK_SIZE (4*1024)
+#ifdef SIGSEGV
+/* alternate stack for SIGSEGV */
+static void register_sigaltstack() {
+    stack_t newSS, oldSS;
+
+    if(is_altstack_defined)
+      return;
+
+    newSS.ss_sp = malloc(ALT_STACK_SIZE);
+    if(newSS.ss_sp == NULL)
+      /* should handle error */
+       rb_bug("register_sigaltstack. malloc error\n");
+    newSS.ss_size = ALT_STACK_SIZE;
+    newSS.ss_flags = 0;
+
+    if (sigaltstack(&newSS, &oldSS) < 0) 
+        rb_bug("register_sigaltstack. error\n");
+    is_altstack_defined = 1;
+}
+#endif
+
 static sighandler_t
 ruby_signal(int signum, sighandler_t handler)
 {
@@ -432,7 +458,12 @@
     if (signum == SIGCHLD && handler == SIG_IGN)
 	sigact.sa_flags |= SA_NOCLDWAIT;
 #endif
-    sigaction(signum, &sigact, &old);
+#ifdef SA_ONSTACK
+    if (signum == SIGSEGV)
+        sigact.sa_flags |= SA_ONSTACK;
+#endif
+    if (sigaction(signum, &sigact, &old) < 0)
+        rb_bug("sigaction error.\n");
     return old.sa_handler;
 }
 
@@ -663,6 +694,7 @@
 #ifdef SIGSEGV
       case SIGSEGV:
         func = sigsegv;
+        register_sigaltstack();
         break;
 #endif
 #ifdef SIGPIPE
@@ -1070,6 +1102,7 @@
     install_sighandler(SIGBUS, sigbus);
 #endif
 #ifdef SIGSEGV
+    register_sigaltstack();
     install_sighandler(SIGSEGV, sigsegv);
 #endif
     }

でもって、実行結果は下記だ。

$ ./ruby -e 'eval("1+" * 100000 + "1")'
-e:1: [BUG] Segmentation fault
ruby 1.9.0 (2008-11-01 revision 20086) [i686-linux]

-- control frame ----------
c:0004 p:---- s:0010 b:0010 l:000009 d:000009 CFUNC  :eval
c:0003 p:0017 s:0006 b:0006 l:000005 d:000005 TOP    -e:1
c:0002 p:---- s:0004 b:0004 l:000003 d:000003 FINISH :inherited
c:0001 p:0000 s:0002 b:0002 l:000001 d:000001 TOP    <dummy toplevel>:17
---------------------------
-e:1:in `eval': stack level too deep (SystemStackError)
	from -e:1:in `<main>'

どこで落ちているかrubyが教えてくれるので、デバッグのヒントになる。いきなり、"Segmentation fault"といって、コアダンプを吐くよりかは、若干ましになったかと思う。五十歩百歩かな。どうだろうか。

現状の落ち方。

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

味もそっけもない。

rubyの識者のご意見を待つ。