福嶋です。

rb_file_open_internal() 内の MakeOpenFile(の中のxmalloc)の所で GC
が発生すると、少し後で Segmentation fault で落ちるという現象に遭
遇しました。

微妙なタイミングなので、簡単に再現することはできませんが、以下の
ような修正をおこなって、

% ./ruby -de 'open("ruby").read'

とやることで、一応再現できました。

--- io.c.orig	Thu Aug 26 13:28:58 1999
+++ io.c	Thu Aug 26 14:03:42 1999
@@ -1261,6 +1261,7 @@
     OpenFile *fptr;
     NEWOBJ(port, struct RFile);
     OBJSETUP(port, klass, T_FILE);
+    if (ruby_debug && fname[0] == 'r') xmalloc(777);
     MakeOpenFile(port, fptr);
 
     fptr->mode = rb_io_mode_flags(mode);
--- gc.c	Thu Aug 26 13:54:29 1999
+++ gc.c.orig	Thu Aug 26 13:23:15 1999
@@ -65,7 +65,6 @@
     }
     if (size == 0) size = 1;
     malloc_memories += size;
-    if (size == 777) rb_gc();
     if (malloc_memories > GC_MALLOC_LIMIT && alloc_objects > GC_NEWOBJ_LIMIT) {
 	rb_gc();
     }

GCの際に port が指しているオブジェクトが回収されてしまうのが原因
でした。

環境は
  ruby  8/25時点の cvs
  Debian slink
  gcc version 2.7.2.3
  CFLAGS = -g -O2 -I. -I.. -I${prefix}/include

port に volatile を付ければ解決する問題かとも思ったのですが、調べ
てみたところ、以下のようなことが分かりました。結論から言うと、
「port の値を格納しているレジスタの値が stack_end の更に上にpush
されていた」ということです。

以下、上記の再現方法を使ってデバッグした時の出力です。

(gdb) b rb_gc
Breakpoint 1 at 0x806301b: file ../gc.c, line 898.
(gdb) run
Starting program: /home/fukusima/src/ruby/cvs/ruby/Linux/ruby -de 'open("ruby").read'

Breakpoint 1, rb_gc () at ../gc.c:898
(gdb) where
#0  rb_gc () at ../gc.c:898
#1  0x8061c93 in xmalloc (size=777) at ../gc.c:68
#2  0x8067bd5 in rb_file_open_internal (klass=1074974284, 
    fname=0x80f11c8 "ruby", mode=0x80acaf7 "r") at ../io.c:1264
#3  0x8068203 in rb_file_s_open (argc=1, argv=0xbffff078, klass=1074974284)
    at ../io.c:1570
#4  0x806828a in rb_f_open (argc=1, argv=0xbffff078) at ../io.c:1592
#5  0x8057cc5 in call_cfunc (func=0x8068240 <rb_f_open>, recv=1075011284, 
    len=-1, argc=1, argv=0xbffff078) at ../eval.c:3674
#6  0x805826f in rb_call0 (klass=1075014804, recv=1075011284, id=6441, argc=1, 
    argv=0xbffff078, body=0x4012e6c0, nosuper=1) at ../eval.c:3813
#7  0x80589a1 in rb_call (klass=1075014804, recv=1075011284, mid=6441, argc=1, 
    argv=0xbffff078, scope=1) at ../eval.c:4007
#8  0x8053f5c in rb_eval (self=1075011284, node=0x40129f1c) at ../eval.c:2183
#9  0x8053b3b in rb_eval (self=1075011284, node=0x40129f08) at ../eval.c:2161
#10 0x8050e40 in eval_node (self=1075011284) at ../eval.c:987
#11 0x8050f4e in ruby_run () at ../eval.c:1023
#12 0x804fa7c in main (argc=3, argv=0xbffffa24, envp=0xbffffa34)
    at ../main.c:39
(gdb) up 2
#2  0x8067bd5 in rb_file_open_internal (klass=1074974284, 
    fname=0x80f11c8 "ruby", mode=0x80acaf7 "r") at ../io.c:1264
(gdb) p port
$1 = (struct RFile *) 0x40129eb8
(gdb) down 2
#0  rb_gc () at ../gc.c:898
(gdb) p &stack_end
$2 = (long unsigned int *) 0xbfffedbc
(gdb) x/8 &stack_end - 4
0xbfffedac:	0x00000000	0x00000000	0x00000309	0x40129eb8
0xbfffedbc:	0x00000000	0x00000000	0x00000000	0x00000000

このように、port の値が stack_end の上に積まれています。このため
GC でうまくマークされなかったようです。

rb_gc を disassemble することで、確にそのようなコードが生成されて
いることが確認できました。

(gdb) list 890,900
890	void
891	rb_gc()
892	{
893	    struct gc_list *list;
894	    struct FRAME *frame;
895	    jmp_buf save_regs_gc_mark;
896	    VALUE stack_end;
897	
898	    alloc_objects = 0;
899	    malloc_memories = 0;
900	
(gdb) p &list
Address requested for identifier "list" which is in a register.
(gdb) p &frame
Address requested for identifier "frame" which is in a register.
(gdb) p &save_regs_gc_mark
$3 = (jmp_buf *) 0xbfffedc0
(gdb) p &stack_end
$4 = (long unsigned int *) 0xbfffedbc
(gdb) p sizeof(save_regs_gc_mark) + sizeof(stack_end)
$5 = 160

ローカル変数の領域は 160 バイトあることがわかります。
なお、xmalloc, rb_file_open_internal を disassmeble することで、
rb_gc() が呼び出された時点では esi レジスタに port の値が格納され
ていることが分かっています。(xmalloc は esi を使っていません)

(gdb) disassemble rb_gc
Dump of assembler code for function rb_gc:
0x8063010 <rb_gc>:	pushl  %ebp
0x8063011 <rb_gc+1>:	movl   %esp,%ebp
0x8063013 <rb_gc+3>:	subl   $0xa0,%esp # 0xa0 == 160 バイト
0x8063019 <rb_gc+9>:	pushl  %esi       # ここで port を push
0x806301a <rb_gc+10>:	pushl  %ebx
0x806301b <rb_gc+11>:	movl   $0x0,0x80bc2c8
0x8063025 <rb_gc+21>:	movl   $0x0,0x80bc2c4
0x806302f <rb_gc+31>:	cmpl   $0x0,0x80c9284
0x8063036 <rb_gc+38>:	jne    0x8063123 <rb_gc+275>
0x806303c <rb_gc+44>:	movl   $0x1,0x80c9284
0x8063046 <rb_gc+54>:	movl   0x80c9f68,%esi
0x806304c <rb_gc+60>:	testl  %esi,%esi
0x806304e <rb_gc+62>:	je     0x8063060 <rb_gc+80>
0x8063050 <rb_gc+64>:	pushl  %esi
0x8063051 <rb_gc+65>:	call   0x8062fe0 <rb_gc_mark_frame>
0x8063056 <rb_gc+70>:	addl   $0x4,%esp
0x8063059 <rb_gc+73>:	movl   0x18(%esi),%esi
0x806305c <rb_gc+76>:	testl  %esi,%esi
0x806305e <rb_gc+78>:	jne    0x8063050 <rb_gc+64>
0x8063060 <rb_gc+80>:	movl   0x80c9f68,%esi
0x8063066 <rb_gc+86>:	testl  %esi,%esi
0x8063068 <rb_gc+88>:	je     0x806308f <rb_gc+127>
---Type <return> to continue, or q <return> to quit---q
Quit


以下のパッチで、ローカル変数の上にpushされたレジスタが指すオブジェ
クトもマークできました。


--- gc.c.orig Thu Aug 26 13:23:15 1999 +++ gc.c Thu Aug 26 15:06:11 1999 @@ -892,7 +892,9 @@ struct gc_list *list; struct FRAME *frame; jmp_buf save_regs_gc_mark; +#ifdef C_ALLOCA VALUE stack_end; +#endif alloc_objects = 0; malloc_memories = 0; @@ -925,7 +927,11 @@ /* This assumes that all registers are saved into the jmp_buf */ setjmp(save_regs_gc_mark); mark_locations_array((VALUE*)&save_regs_gc_mark, sizeof(save_regs_gc_mark) / sizeof(VALUE *)); +#ifdef C_ALLOCA rb_gc_mark_locations(rb_gc_stack_start, (VALUE*)&stack_end); +#else + rb_gc_mark_locations(rb_gc_stack_start, (VALUE*)alloca(1)); +#endif #if defined(THINK_C) || defined(__human68k__) #ifndef __human68k__ mark_locations_array((VALUE*)((char*)save_regs_gc_mark+2),
--- 福嶋正機