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 を使わず自前で書くことにしよう。