一ヶ月以上前の記事だけど、前回のシステムコールするとかいう記事。あれで大嘘を書いていたことに気付いた。
引数を4つpushしないとシステムコールできないと書いていたけど、実は全然関係ないことが分かった。
では、何が必要かというと、
関数呼び出しの時のespの指すアドレスは16の倍数アドレスでないといけない
たったこれだけのルールだった。
つまり、第一引数(スタックトップにくる)は16の倍数アドレスにないといけないってこと。
下での_mainに入ったところからたどってみる(32bitコードで考えてます)。
1、まず、_mainに入るのも関数呼び出しなので、call _mainの直前ではespは16の倍数(16x と表す)にある。
2、次に、call命令が実行されると、戻りアドレス(call実行時のeipの値)がスタックトップにpushされる。この瞬間、esp = 16x - 4
3、_mainに入ったら、まずはスタックフレームをつくるために、ebpをスタックトップにpushする。このpushされるebpの値は、一つ前(つまり_mainを呼び出した関数)のスタックフレームのベースポインタを表す。このとき、esp = 16x-8
4、下のコードだと、その次にsubl $8,%espとしているので、espを8バイト上に押し上げる。つまり、この8バイト分のメモリを静的変数向けなどに確保。これでesp=16x-16
5、この後、pushl $0を3回、その後pushl $5を1回してる。これによって積まれるのは4バイト×4=16バイト分。つまり、ここまで終わったときにはesp = 16x-32 = 16(x - 2)。つまり、call命令の時のセグメント違反をしない条件が揃っているから関数コールできる。
もっと少ないメモリ容量でやろうと思ったら、
subl $8, %espをsubl $12, %espに変えて、pushl $5だけして関数コールとか、
subl $8,%espを消して、pushl $0で4バイトだけ埋めて、pushl $5とした後関数コール。
これでうまくいく。うーん、パディングって難しいなぁ。