[ruby-dev:28263] Re: FUNCTION_CALL_MAY_RETURN_TWICE
From:
Tanaka Akira <akr@...17n.org>
Date:
2006-01-21 18:52:22 UTC
List:
ruby-dev #28263
In article <yge8xt9y17d.wl%ume@mahoroba.org>,
Hajimu UMEMOTO <ume@mahoroba.org> writes:
> --enable-pthread なんですね。
はい。FUNCTION_CALL_MAY_RETURN_TWICE が使われるのは
主に --enable-pthread のときです。
(例外としては 1.8 かつ IA64 の場合があります)
> さっきダメだったのは --enable-pthread でしたが、試しに指定せずにやっ
> てみたら、無事作れて、make test も成功しました。
> FreeBSD の pthread 回りの秘孔を突いた?
AMD でなくても FreeBSD 5.4 で -mpentium4 としたら再現したので追いかけました。
コンパイルが compiling Win32API で止まるというところからテス
トケースを作ると次のようになります。
% cat z.rb
begin
raise StandardError
rescue StandardError
p $!
end
p :end
これは次のように #<StandardError: StandardError> と :end の
2行が表示されるのが適切な動作です。
% ruby z.rb
#<StandardError: StandardError>
:end
しかし、FreeBSD 5.4 で --enable-pthread とすると :end とだけ
表示して終わります。
% ./miniruby z.rb
:end
getcontext の直後と setcontext の直前で報告させるようにする
と、奇妙な挙動が観察されます。
Index: eval.c
===================================================================
RCS file: /src/ruby/eval.c,v
retrieving revision 1.871
diff -u -p -r1.871 eval.c
--- eval.c 18 Jan 2006 15:00:58 -0000 1.871
+++ eval.c 21 Jan 2006 17:51:23 -0000
@@ -94,6 +94,7 @@ rb_jump_context(env, val)
int val;
{
env->status = val;
+ fprintf(stderr, "before setcontext: %p %d\n", &env->context, env->status), \
setcontext(&env->context);
abort(); /* ensure noreturn */
}
@@ -175,6 +176,7 @@ int function_call_may_return_twice_false
(just_before_setjmp), \
FUNCTION_CALL_MAY_RETURN_TWICE, \
getcontext(&(j)->context), \
+ fprintf(stderr, " after getcontext: %p %d\n", &(j)->context, (j)->status), \
FUNCTION_CALL_MAY_RETURN_TWICE, \
(j)->status)
#else
% ./miniruby z.rb
after getcontext: 0xbfbfe7d0 0
after getcontext: 0xbfbfe7e0 0
after getcontext: 0xbfbfe7c0 0
after getcontext: 0xbfbfd7f0 0
before setcontext: 0xbfbfd7f0 6
:end
after getcontext: 0xbfbfe7a0 0
after getcontext: 0xbfbfe450 0
ひとつだけある setcontext が raise による大域脱出に対応しま
すが、その setcontext とその直後の getcontext の引数の
ucontext_t * と status の値が一致していません。
| % gdb miniruby
| GNU gdb 6.1.1 [FreeBSD]
| Copyright 2004 Free Software Foundation, Inc.
| GDB is free software, covered by the GNU General Public License, and you are
| welcome to change it and/or distribute copies of it under certain conditions.
| Type "show copying" to see the conditions.
| There is absolutely no warranty for GDB. Type "show warranty" for details.
| This GDB was configured as "i386-marcel-freebsd"...
まず gdb 上で、再現することを確認します。
| (gdb) run z.rb
| Starting program: /pub/akr/ruby/19/ruby/miniruby z.rb
| after getcontext: 0xbfbfe7a0 0
| after getcontext: 0xbfbfe7b0 0
| after getcontext: 0xbfbfe790 0
| after getcontext: 0xbfbfd7c0 0
| before setcontext: 0xbfbfd7c0 6
| :end
| after getcontext: 0xbfbfe770 0
| after getcontext: 0xbfbfe420 0
|
| Program exited normally.
よくわからないんですが、libc の symbol を読んでくれなくて
getcontext, setcontext に breakpoint を設定できなかったので
明示的に読ませます。
| (gdb) break main
| Breakpoint 1 at 0x8054eb2: file main.c, line 40.
| (gdb) run z.rb
| Starting program: /pub/akr/ruby/19/ruby/miniruby z.rb
|
| Breakpoint 1, main (argc=0, argv=0x0, envp=0xbfbfeb2c) at main.c:40
| 40 RUBY_INIT_STACK
| (gdb) info sharedlibrary
| From To Syms Read Shared Object Library
| 0x28142ec0 0x2815b418 No /usr/lib/libpthread.so.1
| 0x28162e88 0x28166cc8 No /lib/libcrypt.so.2
| 0x2817cb00 0x2818cc40 No /lib/libm.so.3
| 0x281b2780 0x2824b870 No /lib/libc.so.5
| 0x281159d8 0x2812d0a9 No /libexec/ld-elf.so.1
| (gdb) sharedlibrary libc.so
| Reading symbols from /lib/libc.so.5...done.
| Loaded symbols for /lib/libc.so.5
getcontext, setcontext に break point を設定します。
| (gdb) break getcontext
| Breakpoint 2 at 0x281ba224
| (gdb) break setcontext
| Breakpoint 3 at 0x281df7f4
4回目の getcontext が setcontext で使われるものなのでそこま
で進めます。
| (gdb) c
| Continuing.
|
| Breakpoint 2, 0x281ba224 in getcontext () from /lib/libc.so.5
| (gdb)
| Continuing.
| after getcontext: 0xbfbfe7a0 0
|
| Breakpoint 2, 0x281ba224 in getcontext () from /lib/libc.so.5
| (gdb)
| Continuing.
| after getcontext: 0xbfbfe7b0 0
|
| Breakpoint 2, 0x281ba224 in getcontext () from /lib/libc.so.5
| (gdb)
| Continuing.
| after getcontext: 0xbfbfe790 0
|
| Breakpoint 2, 0x281ba224 in getcontext () from /lib/libc.so.5
問題の getcontext は NODE_RESCUE の EXEC_TAG から呼ばれてい
ます。
| (gdb) up
| #1 0x0805ff7d in rb_eval (self=135678472, n=0xbfbfd7c0) at eval.c:3107
| 3107 if ((state = EXEC_TAG()) == 0) {
| (gdb) l
| 3102 {
| 3103 volatile VALUE e_info = ruby_errinfo;
| 3104 volatile int rescuing = 0;
| 3105
| 3106 PUSH_TAG(PROT_NONE);
| 3107 if ((state = EXEC_TAG()) == 0) {
| 3108 retry_entry:
| 3109 result = rb_eval(self, node->nd_head);
| 3110 }
| 3111 else if (rescuing) {
| (gdb) down
| #0 0x281ba224 in getcontext () from /lib/libc.so.5
getcontext の中身を調べます。
ソースは http://www.FreeBSD.org/cgi/cvsweb.cgi/src/lib/libc/i386/sys/getcontext.S?rev=1.1&content-type=text/x-cvsweb-markup
ですが、getcontext の大半はカーネルで実装されていますが、
return address を ECX に入れておいて、setcontext から帰って
きた時にはすでに存在しない getcontext の stack frame にアク
セスせずに return するというところがユーザレベルで実装されて
います。
| (gdb) disassemble
| Dump of assembler code for function getcontext:
| 0x281ba224 <getcontext+0>: mov (%esp),%ecx
| 0x281ba227 <getcontext+3>: mov $0x1a5,%eax
| 0x281ba22c <getcontext+8>: int $0x80
| 0x281ba22e <getcontext+10>: jb 0x281ba235 <getcontext+17>
| 0x281ba230 <getcontext+12>: add $0x4,%esp
| 0x281ba233 <getcontext+15>: jmp *%ecx
| 0x281ba235 <getcontext+17>: push %ebx
| 0x281ba236 <getcontext+18>: call 0x281ba23b <getcontext+23>
| 0x281ba23b <getcontext+23>: pop %ebx
| 0x281ba23c <getcontext+24>: add $0x9f8d1,%ebx
| 0x281ba242 <getcontext+30>: jmp 0x281afe5c <_init+4284>
| 0x281ba247 <getcontext+35>: nop
| End of assembler dump.
命令ごとにステップ実行していきます。
| (gdb) display/i $pc
| 1: x/i $pc 0x281ba224 <getcontext>: mov (%esp),%ecx
| (gdb) si
| 0x281ba227 in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba227 <getcontext+3>: mov $0x1a5,%eax
| (gdb)
| 0x281ba22c in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba22c <getcontext+8>: int $0x80
システムコールを行う前に、ここの getcontext での保存先の
prot_tag と register の内容を調べます。
| (gdb) p prot_tag
| $1 = (struct tag *) 0xbfbfd7c0
| (gdb) p *prot_tag
| $2 = {buf = {{context = {uc_sigmask = {__bits = {0, 0, 0, 0}}, uc_mcontext = {
| mc_onstack = 0, mc_gs = 0, mc_fs = 0, mc_es = 0, mc_ds = 0,
| mc_edi = 0, mc_esi = 0, mc_ebp = 0, mc_isp = 0, mc_ebx = 0,
| mc_edx = 0, mc_ecx = 0, mc_eax = 0, mc_trapno = 0, mc_err = 0,
| mc_eip = 0, mc_cs = 0, mc_eflags = 0, mc_esp = 0, mc_ss = 0,
| mc_len = 0, mc_fpformat = 0, mc_ownedfp = 0, mc_spare1 = {0},
| mc_fpstate = {0 <repeats 128 times>}, mc_spare2 = {0, 0, 0, 0, 0, 0,
| 0, 0}}, uc_link = 0x0, uc_stack = {ss_sp = 0x0, ss_size = 0,
| ss_flags = 0}, uc_flags = 0, __spare__ = {0, 0, 0, 0}},
| status = 0}}, frame = 0x811a4e0, iter = 0xbfbfe760, tag = 0, retval = 4,
| scope = 0x8165d2c, dst = 0, prev = 0xbfbfe790, blkid = 0}
| (gdb) info registers
| eax 0x1a5 421
| ecx 0x805ff7d 134610813
| edx 0xbfbfd7c0 -1077946432
| ebx 0x8119cf4 135372020
| esp 0xbfbfcffc 0xbfbfcffc
| ebp 0xbfbfe738 0xbfbfe738
| esi 0x811100c 135335948
| edi 0xbfbfe760 -1077942432
| eip 0x281ba22c 0x281ba22c
| eflags 0x207 519
| cs 0x1f 31
| ss 0x2f 47
| ds 0x2f 47
| es 0x2f 47
| fs 0x2f 47
| gs 0x97 151
システムコールを行います。
| (gdb) si
| 0x281ba22e in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba22e <getcontext+10>: jb 0x281ba235 <getcontext+17>
システムコールから出てきた後で prot_tag と register の内容を
調べると、prot_tag にいろいろと保存されています。
| (gdb) p prot_tag
| $3 = (struct tag *) 0xbfbfd7c0
| (gdb) p *prot_tag
| $4 = {buf = {{context = {uc_sigmask = {__bits = {0, 0, 0, 0}}, uc_mcontext = {
| mc_onstack = 0, mc_gs = 151, mc_fs = 47, mc_es = 47, mc_ds = 47,
| mc_edi = -1077942432, mc_esi = 135335948, mc_ebp = -1077942472,
| mc_isp = -866099852, mc_ebx = 135372020, mc_edx = 0,
| mc_ecx = 134610813, mc_eax = 0, mc_trapno = -866100612,
| mc_err = 582, mc_eip = 672899630, mc_cs = 31, mc_eflags = 775,
| mc_esp = -1077948420, mc_ss = 47, mc_len = 640, mc_fpformat = 65538,
| mc_ownedfp = 131074, mc_spare1 = {-1050295680}, mc_fpstate = {
| 2101887, 66912256, 134785089, 31, 135619620, 47, 8064, 65535,
| 0 <repeats 25 times>, -822083584, 16380, 0, -1564784640,
| -1376234408, 16384, 0, 0, 1087476736, 0 <repeats 86 times>},
| mc_spare2 = {-1041346560, -1041346560, 582, 0, -866100040,
| -1067356287, 0, -1041346560}}, uc_link = 0x0, uc_stack = {
| ss_sp = 0x0, ss_size = 0, ss_flags = 0}, uc_flags = 0, __spare__ = {
| 0, 0, 0, 0}}, status = 0}}, frame = 0x811a4e0, iter = 0xbfbfe760,
| tag = 0, retval = 4, scope = 0x8165d2c, dst = 0, prev = 0xbfbfe790,
| blkid = 0}
| (gdb) info registers
| eax 0x0 0
| ecx 0x805ff7d 134610813
| edx 0xbfbfd7c0 -1077946432
| ebx 0x8119cf4 135372020
| esp 0xbfbfcffc 0xbfbfcffc
| ebp 0xbfbfe738 0xbfbfe738
| esi 0x811100c 135335948
| edi 0xbfbfe760 -1077942432
| eip 0x281ba22e 0x281ba22e
| eflags 0x206 518
| cs 0x1f 31
| ss 0x2f 47
| ds 0x2f 47
| es 0x2f 47
| fs 0x2f 47
| gs 0x97 151
ここで eflags が奇数なので carry flag (eflags の最下位ビット)
は 0 です。
http://www.int80h.org/bsdasm/
によれば、FreeBSD では carry flag で system call の失敗を表
すので、system call が成功したことを意味します。
getcontext の残りを進めると、carry flag は 0 なので jb では
分岐せず、ECX へ jump するコードに進みます。
| (gdb) si
| 0x281ba230 in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba230 <getcontext+12>: add $0x4,%esp
| (gdb)
| 0x281ba233 in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba233 <getcontext+15>: jmp *%ecx
ECX へ jump すると、rb_eval 内の呼出元へ戻ります。
| (gdb)
| 0x0805ff7d in rb_eval (self=135678472, n=0xbfbfd7c0) at eval.c:3107
| 3107 if ((state = EXEC_TAG()) == 0) {
| 1: x/i $pc 0x805ff7d <rb_eval+10919>: mov 0xc894(%ebx),%edx
setcontext まで進めます。
| (gdb) c
| Continuing.
| after getcontext: 0xbfbfd7c0 0
| before setcontext: 0xbfbfd7c0 6
|
| Breakpoint 3, 0x281df7f4 in setcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281df7f4 <setcontext>: mov $0x1a6,%eax
setcontext が呼び出された時点での prot_tag を調べると、上の
getcontext で得たものがそのまま入っています。
| (gdb) p prot_tag
| $5 = (struct tag *) 0xbfbfd7c0
| (gdb) p *prot_tag
| $6 = {buf = {{context = {uc_sigmask = {__bits = {0, 0, 0, 0}}, uc_mcontext = {
| mc_onstack = 0, mc_gs = 151, mc_fs = 47, mc_es = 47, mc_ds = 47,
| mc_edi = -1077942432, mc_esi = 135335948, mc_ebp = -1077942472,
| mc_isp = -866099852, mc_ebx = 135372020, mc_edx = 0,
| mc_ecx = 134610813, mc_eax = 0, mc_trapno = -866100612,
| mc_err = 582, mc_eip = 672899630, mc_cs = 31, mc_eflags = 775,
| mc_esp = -1077948420, mc_ss = 47, mc_len = 640, mc_fpformat = 65538,
| mc_ownedfp = 131074, mc_spare1 = {-1050295680}, mc_fpstate = {
| 2101887, 66912256, 134785089, 31, 135619620, 47, 8064, 65535,
| 0 <repeats 25 times>, -822083584, 16380, 0, -1564784640,
| -1376234408, 16384, 0, 0, 1087476736, 0 <repeats 86 times>},
| mc_spare2 = {-1041346560, -1041346560, 582, 0, -866100040,
| -1067356287, 0, -1041346560}}, uc_link = 0x0, uc_stack = {
| ss_sp = 0x0, ss_size = 0, ss_flags = 0}, uc_flags = 0, __spare__ = {
| 0, 0, 0, 0}}, status = 6}}, frame = 0x811a4e0, iter = 0xbfbfe760,
| tag = 0, retval = 4, scope = 0x8165d2c, dst = 0, prev = 0xbfbfe790,
| blkid = 0}
setcontext の中身を調べてみると、これもシステムコールで実装
されています。
| (gdb) disassemble
| Dump of assembler code for function setcontext:
| 0x281df7f4 <setcontext+0>: mov $0x1a6,%eax
| 0x281df7f9 <setcontext+5>: int $0x80
| 0x281df7fb <setcontext+7>: jb 0x281df7e0 <swapcontext+12>
| 0x281df7fd <setcontext+9>: ret
| 0x281df7fe <setcontext+10>: nop
| 0x281df7ff <setcontext+11>: nop
| 0x281df800 <setcontext+12>: push %ebx
| 0x281df801 <setcontext+13>: call 0x281df806 <setcontext+18>
| 0x281df806 <setcontext+18>: pop %ebx
| 0x281df807 <setcontext+19>: add $0x7a306,%ebx
| 0x281df80d <setcontext+25>: jmp 0x281afe5c <_init+4284>
| 0x281df812 <setcontext+30>: mov %esi,%esi
| End of assembler dump.
システムコールの直前まで進めます。
| (gdb) si
| 0x281df7f9 in setcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281df7f9 <setcontext+5>: int $0x80
システムコールを実行すると、getcontext 内のシステムコール直
後に制御が移ります。
| (gdb)
| 0x281ba22e in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba22e <getcontext+10>: jb 0x281ba235 <getcontext+17>
この時点での register の内容を調べると、eflags が奇数なので
システムコールが失敗していることになります。
| (gdb) info registers
| eax 0x0 0
| ecx 0x805ff7d 134610813
| edx 0x0 0
| ebx 0x8119cf4 135372020
| esp 0xbfbfcffc 0xbfbfcffc
| ebp 0xbfbfe738 0xbfbfe738
| esi 0x811100c 135335948
| edi 0xbfbfe760 -1077942432
| eip 0x281ba22e 0x281ba22e
| eflags 0x207 519
| cs 0x1f 31
| ss 0x2f 47
| ds 0x2f 47
| es 0x2f 47
| fs 0x2f 47
| gs 0x97 151
進めると jb で分岐し、先ほどとは違うところにいきます。
| (gdb) si
| 0x281ba235 in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba235 <getcontext+17>: push %ebx
| (gdb)
| 0x281ba236 in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba236 <getcontext+18>: call 0x281ba23b <getcontext+23>
| (gdb)
| 0x281ba23b in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba23b <getcontext+23>: pop %ebx
| (gdb)
| 0x281ba23c in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba23c <getcontext+24>: add $0x9f8d1,%ebx
| (gdb)
| 0x281ba242 in getcontext () from /lib/libc.so.5
| 1: x/i $pc 0x281ba242 <getcontext+30>: jmp 0x281afe5c <_init+4284>
| (gdb)
| 0x281afe5c in ?? () from /lib/libc.so.5
| 1: x/i $pc 0x281afe5c <_init+4284>: jmp *0x434(%ebx)
| (gdb)
| 0x281ed990 in .cerror () from /lib/libc.so.5
| 1: x/i $pc 0x281ed990 <.cerror>: push %eax
| (gdb)
| 0x281ed991 in .cerror () from /lib/libc.so.5
| 1: x/i $pc 0x281ed991 <.cerror+1>: call 0x281b0b5c <_init+7612>
| (gdb)
| 0x281b0b5c in ?? () from /lib/libc.so.5
| 1: x/i $pc 0x281b0b5c <_init+7612>: jmp *0x774(%ebx)
| (gdb)
| 0x2815ae14 in ?? () from /usr/lib/libpthread.so.1
| 1: x/i $pc 0x2815ae14: push %ebp
| (gdb)
| 0x2815ae15 in ?? () from /usr/lib/libpthread.so.1
| 1: x/i $pc 0x2815ae15: mov %esp,%ebp
| (gdb)
| 0x2815ae17 in ?? () from /usr/lib/libpthread.so.1
| 1: x/i $pc 0x2815ae17: push %ebx
| (gdb) bt
| #0 0x2815ae17 in ?? () from /usr/lib/libpthread.so.1
| #1 0xbfbfe738 in ?? ()
| #2 0x281ed996 in .cerror () from /lib/libc.so.5
| #3 0x08119cf4 in __JCR_LIST__ ()
| #4 0x080606f8 in rb_eval (self=0, n=0x0) at eval.c:3133
| Previous frame inner to this frame (corrupt stack?)
というわけで、setcontext から getcontext に移った時に carry
flag が 1 になっているのが非常に怪しいです。
p $eflags = 518 などとしてデバッガから無理矢理 0 にして動か
すと、意図どおりに動くようです。
というわけで、carry flag が 1 になっているのがなぜか、という
話なんですが、さて?
--
[田中 哲][たなか あきら][Tanaka Akira]