From: Masaki Fukushima Date: 1999-08-26T15:25:01+09:00 Subject: [ruby-dev:7757] [BUG] rb_gc() stack_end ? 福嶋です。 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 , 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 : pushl %ebp 0x8063011 : movl %esp,%ebp 0x8063013 : subl $0xa0,%esp # 0xa0 == 160 バイト 0x8063019 : pushl %esi # ここで port を push 0x806301a : pushl %ebx 0x806301b : movl $0x0,0x80bc2c8 0x8063025 : movl $0x0,0x80bc2c4 0x806302f : cmpl $0x0,0x80c9284 0x8063036 : jne 0x8063123 0x806303c : movl $0x1,0x80c9284 0x8063046 : movl 0x80c9f68,%esi 0x806304c : testl %esi,%esi 0x806304e : je 0x8063060 0x8063050 : pushl %esi 0x8063051 : call 0x8062fe0 0x8063056 : addl $0x4,%esp 0x8063059 : movl 0x18(%esi),%esi 0x806305c : testl %esi,%esi 0x806305e : jne 0x8063050 0x8063060 : movl 0x80c9f68,%esi 0x8063066 : testl %esi,%esi 0x8063068 : je 0x806308f ---Type to continue, or q 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), --- 福嶋正機