setjmp用の__jmpbuf の スタックポインタがエンコードされている件
継続ライブラリを書くのに、setjmp を使えば(ある程度)可搬性が維持できると思い実験していたら見事にはまった。
setjmp が使う jmp_buf にはスタックポインタやら汎用レジスタが保存されていて、それらを書き換えて longjmp してあげれば好きな状態に復帰できる。(当然 jmp_buf はアーキテクチャ毎に構造が違う)
jmp_buf に保存されている esp / eip が自分で取得したものと違っていてはまりまくった。
jmpbuf[0].__jmpbuf[3] // ebp jmpbuf[0].__jmpbuf[4] // esp jmpbuf[0].__jmpbuf[5] // eip
のように取り出せるはずなのだけど
saved ebp = bfd0c6a8 saved esp = 40dac680 // 本当の値は bfd0c680 saved eip = f70e874a // 本当の値は 804874a
なんかおかしい。
最初は __jmpbuf のインデックスを間違えたかと思ったけど、そんなことはなかった。
よくよくソースを深追いしていくと
glibc-2.4/sysdeps/i386/Setjmp.S
/* Save registers. */ movl %ebx, (JB_BX*4)(%eax) movl %esi, (JB_SI*4)(%eax) movl %edi, (JB_DI*4)(%eax) leal JMPBUF(%esp), %ecx /* Save SP as it will be after we return. */ #ifdef PTR_MANGLE PTR_MANGLE (%ecx) #endif movl %ecx, (JB_SP*4)(%eax) movl PCOFF(%esp), %ecx /* Save PC we are returning to now. */ #ifdef PTR_MANGLE PTR_MANGLE (%ecx) #endif
うあぁ PTR_MANGLE 超怪しい。
glibc-2.4/sysdeps//unix/sysv/linux/i386/sysdep.h にあった。
うーん。これで何のためにやっているんだろう?とググってみたら
長生きするポインタ(特に関数ポインタ)は悪用されやすいので、値を素のまま格納しないほうがよい」
あ。これ読んだことあったな(ぉ。
これの影響をうけて動かなくなるライブラリとかあるだろうな。
原因が分かってすっきりしたので setjmp を使わず自前で書くことにしよう。