Resolved: Curious x86 call instruction

by syoyo

the problem has been resolved.
call is used to get current PC(program counter, stored in eip register) address to access global symbol.

[Ja]

不思議な call 命令 ですが、幾人かの方からメールでご指導をいただけました.
ありがとうございます.

このような知りたい、答えたいをつなぐポータルみたいな仕組みがほしいなぁと常日頃思います.
Ask reddit みたいな感じかな.
たとえば私だったら GI だったらほぼなんでも答えられるので、
GI がよくわかんなくて困っているひとがそこで質問してくれれば、すぐさま見つけていつでも答えてあげられるし.
(そのやりとりをさらに他人が参考に見て知識が広がることにもなる)

さて、本題に戻って、call は PC(プログラムカウンタ)を取得するために利用されていました.
x86 では直接 PC(eip レジスタ)を読み出せません.
そこで call 命令で、スタックに call 命令のアドレス(に加えて call 命令の引数の値)をプッシュしてジャンプします.
ジャンプ先で pop ecx とかすれば、ecx レジスタに PC が入っているというわけ.
そして、大域変数へのアクセスのように、相対座標でしかアドレッシングできないような用途のための、
ベースアドレスとして使われます
(普通はバイナリは PIC なコードで、大域変数(bss, data)とプログラム(text)はメモリの自由なところに貼付けられることになるので、絶対座標ではアドレッシングできない).

gcc では、この call & pop で eip を得ることと同等のことを行う
__i686.get_pc_thunk という関数が用意されていて、場合によってはこれを呼ぶようになっている.
どのような基準で、この関数を呼び出すようになるのかそれとも call & pop を直接吐くのかは不明.
(-fPIC とかのオプションなのかなぁ)

ちなみに、今回の call は相対アドレスを引数にとる call 命令です.
Intel の x86 命令セットマニュアルを見ると、call は call でも絶対アドレスを取るものとかあって、
同じ call 命令でもやる内容が違うのでややこしい.

これを gcc -O2 -c でコンパイルします.ダンプしてみるとこんな感じ.

ecx には call & pop 後に 0x5 が入っているので,

movl 0x0000002b(%ecx),%eax

0x5 + 0x2b = 0x30 = data セクションの 0x40400000 = 3.0f = 変数 b

を eax にロードに、

movl %eax,0x0000002f(%ecx)

0x5 + 0x2f = 0x34 = data セクションの 0x40000000 = 2.0f = 変数 a

にストアになります.

実行バイナリを作って、gdb でも確認してみます.

ecx に、 call のアドレス 0x1fd0 に call の引数 0x5 を加えた 0x1fd5 が入っているのが確認できます.

しかしまだ不思議な点が.

プログラムカウンタ(eip)が基本的に不可視(明示的にアクセスできない)というのは x86 に限ったことではないのですが、
書き込みできるのはまずいとして、読み込みくらいならできてもいいと思うのですよね.
なんでできないのだろう…

さすがに x86 野郎もおかしいと思ったのか(?)、 この問題は x86-64 では改善されて、
プログラムカウンタレジスタをベースアドレスとして指定できるようになっています.
http://www.x86-64.org/documentation/assembly.html

Advertisements