__dyld_stub_binding_helper_interface で落ちてた件

状況としては、「yajit がコンパイルしたコードが yajit 内の関数を呼んだ後で libruby の関数を呼んだ場合にのみ」に落ちるというもので、 gdb で落としてみると、

Program received signal EXC_BAD_INSTRUCTION, Illegal instruction/operand.
0x8fe13184 in __dyld_stub_binding_helper_interface ()

とか言っていた。いかにも late binding 関係の問題っぽいので、 yajit が呼ぶ関数を、

typeof(rb_ary_new2)* RB_ARY_NEW2 = &rb_ary_new2;
typeof(rb_funcall2)* RB_FUNCALL2 = &rb_funcall2;
...

とかいう感じで強制的に bind してしまう、っていう雑な解決でとりあえずゴマかしたのだった。でもまぁ「yajit がコンパイルしたコードが yajit 内の関数を呼んだ後で libruby の関数を呼んでその関数がまた libruby のコードを呼んだ」ような落ちるケースがでてきてしまって、マジメに追うことになった。

とりあえず dyld のコードは Apple が配ってくれてる。とてもとてもありがたい。ただなんか OSX 10.5 のヤツと 10.4 のヤツのコードはだいぶ違うみたいだった。 10.5 では別の理由で動かなかったりするかもなー…という。

http://www.opensource.apple.com/darwinsource/

とりあえず落ち方はよく見ると bad instruction とか言ってるので相当ヘンだ。逆アセしてみたところ、

(gdb) disassemble $eip $eip+1
Dump of assembler code from 0x8fe13184 to 0x8fe13185:
0x8fe13184 <__dyld_stub_binding_helper_interface+18>:   movdqa %xmm0,32(%esp)

SSEのアラインメントかよ! という。該当個所のコードを読んでみると…

    .globl _stub_binding_helper_interface
_stub_binding_helper_interface:
	pushl		%ebp
	movl		%esp,%ebp
	subl		$STACK_SIZE,%esp		# at this point stack is 16-byte aligned because two meta-parameters where pushed
	movl		%eax,EAX_SAVE(%esp)		# save registers that might be used as parameters
	movl		%ecx,ECX_SAVE(%esp)
	movl		%edx,EDX_SAVE(%esp)
	movdqa		%xmm0,XMMM0_SAVE(%esp)    # ここで落ちた
	movdqa		%xmm1,XMMM1_SAVE(%esp)
	movdqa		%xmm2,XMMM2_SAVE(%esp)
	movdqa		%xmm3,XMMM3_SAVE(%esp)

まぁ要はとりあえずレジスタの値保存しておいてから bind する関数を呼ぼうって腹で、スタックにレジスタを保存してるんだと思う。んで stack pointer を見ると、

(gdb) p $esp
$1 = (void *) 0xbfffddfc

当然ながらアラインされてない。これはまぁ、 yajit が好き勝手にスタックを使ってて関数呼ぶ時のスタックの状態なんか気にしてないのがまずくて、たぶんアラインしなきゃいけないんだろう。

とりあえずてっとり早い解決としては export DYLD_BIND_AT_LAUNCH=1 とかしてから起動する手があって、これをしてやれば最初に全シンボルが bind されるので問題ない…んだけど、そんなことを強要するのは負けた気がする。

スタックの位置調整ってインラインアセンブリとかでは書きにくいしいちいち全関数に調整そのコード埋めるのもめんどくさいし、 __attribute__( (aligned(?))) をつけた変数配置するってのはスタック変数の計算がもっとめんどくさいし…と、どうしたもんかなーと思いました。でまぁなんか GCC の __attribute__ にあるんじゃないかと探してみたら、 __attribute__( (force_align_arg_pointer)) というのがありました。これを JIT コードが呼び出す yajit 内の関数につけてみたところ、解決したみたいです。

予想通りちょっと面白い感じでした。

ちなみにこの attribute をつけるとこんなコードを関数の頭につけるみたいです。

        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)

たぶん ECX に ESP つっこんで SP 変わりに使ってるんだと思います。で ESP はアラインしとく、と。

なんか適当にぐぐってみると、 force_align_arg_pointer は汎用性無いし遅いしよくないとかで、改善の提案があるみたいでした。

http://archives.free.net.ph/message/20080117.230310.aa614225.ja.html

あと force_align_arg_pointer 無しで同じことをやるハックをされてる方がいました。面白いなー。

http://kikyou.info/diary/?200610

って見たことあると思ったら吉里吉里の方か…どうりで。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h