アセンブリ言語を始めてみる
アセンブリ言語*1を始めてみることにした。
OCamlとLispはちょいとお休みで、しばらくはバイナリ方面をやってみることにした。手始めにインラインアセンブラについて勉強してみているところ。
思考タレ流しに近いので、以前やってたこととあんまり変わりないことをグダグダと書いてます。
とりあえず適当な本*2を買って読んでみたのだけど、まず -4(%esp) とかって記述が良く理解できなかった。なんでいきなり引いてるの?
しばらく他に意識を移して、ネット資料を見てみたところ唐突に「あ、これスタックフレーム上でのアドレスってことか!」と気づいた。
そうと分かれば引数についてもほぼ同様だと分かる。関数の引数は 8(%ebs) などとなってるけど、この離れ方はなんなのかというと、 0(%ebs) が前のフレームへのポインタで 4(%ebs) が戻り先アドレス、とかそんな感じだったはず。あー、分かってきたかも知れない。
関数を呼び出すときにすることを順番に想定してみる。
- 引数を逆順にスタックにプッシュする
- すると、低位アドレスから高位アドレスへ順番に引数が並ぶ
- プログラムで戻るアドレスをプッシュする
- このへんはまだ読んでないけど後々出てくるはず。これってPC退避かも?知らないけど。
- 前のスタックポインタをスタックにプッシュする
- push %ebs かな。
- 新しいベースポインタを作る
- movl %esp, %ebs で、新しいフレームのベースポインタ作りをする
- ローカル変数をとりあえずプッシュしてフレームポインタを動かしておく
- こっちは正順で並べる。(なんで引数は逆転させてるんだろ。意味なくね?)
とかやれば、まあ準備は出来てるんじゃないだろうか。
インラインアセンブラを勉強しているはずが、いつのまにかスタックポインタの勉強になってる。
本当かどうか、簡単に実験で確認。
#include <stdio.h> int func(int arg1, int arg2, int arg3){ int loc1, loc2, loc3; printf("arg1 address : %x\n" "arg2 address : %x\n" "arg3 address : %x\n" "loc1 address : %x\n" "loc2 address : %x\n" "loc3 address : %x\n" ,&arg1, &arg2, &arg3, &loc1, &loc2, &loc3 ); return loc2; } int main(void){ func(1,2,3); }
の出力結果は
arg1 address : bfbcfb50
arg2 address : bfbcfb54
arg3 address : bfbcfb58
loc1 address : bfbcfb44
loc2 address : bfbcfb40
loc3 address : bfbcfb3c
このようになるので、
引数は大きい方へと順に積まれているのに対して、ローカル変数は小さい方(=スタックの伸びる方向)に順に積まれていることが分かるね。
続いて、引数とローカル変数との間にあるスキマを確認する。
#include <stdio.h> int func(int arg1, int arg2, int arg3){ int loc1, loc2, loc3; printf("%x : loc3\n" "%x : loc2\n" "%x : loc1\n" "%x : something @ %x\n" "%x : something @ %x\n" "%x : something @ %x\n" "%x : something @ %x\n" "%x : something @ %x\n" "%x : arg1\n" "%x : arg2\n" "%x : arg3\n" ,&loc3, &loc2, &loc1 ,&loc1+1,*(&loc1 + 1) ,&loc1+2,*(&loc1 + 2) ,&loc1+3,*(&loc1 + 3) ,&loc1+4,*(&loc1 + 4) ,&loc1+5,*(&loc1 + 5) ,&arg1, &arg2, &arg3); return loc2; } int main(void){ printf("%x : main location\n",main); func(1,2,3); }
出力は、
804844f : main location
bf9b8910 : loc3
bf9b8914 : loc2
bf9b8918 : loc1
bf9b891c : something @ b7f16ff4
bf9b8920 : something @ b7f47ce0
bf9b8924 : something @ 0
bf9b8928 : something @ bf9b8948
bf9b892c : something @ 8048490
bf9b8930 : arg1
bf9b8934 : arg2
bf9b8938 : arg3
このようになる。
ローカル変数と引数との間には、何やらヨクワカラナイモノが入っているけど、ひとまずこれを評価してみる。
下から四行目のアドレス、つまり引数のプッシュが終わった直後のアドレスの中身は 8048490 。これはmain関数の位置である 804844f にほど近いので戻りアドレスだろうと予想できる。
その上のアドレスの中身は bf9b8948 となっているけど、この値は他と比較して現在いるスタック領域に近い。よってこれは前のフレームへのポインタと見られる。
そしてその上の三つの値。これが何なのかはいまのところわかんない。引数を増やしても変わらず在りつづけるし、その値の参照している先の値を見ても意味のある値に見えないし、文字列でもないし、printfのアドレスでもないし。困ったもんだ。
結局、前にprintfで遊んだときとほとんど変わらないな。だめじゃんか。スタックポインタやベースレジスタとは仲良くなれてる気がするけど気のせいだよ。
参考URL [ http://www.mars.sannet.ne.jp/sci10/on_gcc_asm.html ]