落書きノート

ふと自分が気になった事を書いてます

関数呼び出し i386編

i386がなんとなく気になったので記事に書いてみました。以下のプログラムをi386で逆アセンブルします。

int call_complex1(){
   return return_arg1(0xfe) + 1;
}

以下が生成されたコードです。

00fe150d <call_complex1>:
  fe150d:    68 fe 00 00 00         push   $0xfe
  fe1512:    e8 26 ff ff ff         call   fe143d <return_arg1>
  fe1517:    83 c4 04               add    $0x4,%esp
  fe151a:    40                     inc    %eax
  fe151b:    c3                       ret 

call fe143d <return_arg1>という部分があります。推測するとこれがreturn_arg1()の呼び出しだと思われますが、その前後にはH8と同様、数命令しか無いようです。fe150dでは「0xfe」という値を扱っていますので、これがおそらく引数の準備です。またfe151aの「inc %eax」は戻り値を加算している(i386では戻り値はEAXレジスタで渡されます)ようなので、恐らくreturn_arg1()の戻り値を加算してcall_complex1()の戻り値を生成しているのだと思われます。その後はret命令で関数から返るだけです。

ただ、先頭ではpush命令によりスタックが利用されていますし、call命令の直後ではadd命令によってスタックポインタが加算されているようです。つまりスタックが何らかの用途に利用されているようなので、H8とほとんど同じ、というわけではなさそうです。

push命令では、スタック上に0xfeという定数値を積んでいます。具体的に言うと、スタックポインタを減算し、そこに0xfeという値を書き込みます。i386では、引数はスタックに積んで渡すという動作になっていました。このため、関数の呼び出し前に引数をスタックに積んでいるようです。call命令の後のadd命令は、引数渡しに利用したスタックを解放するための加算のように思います。

i386も戻り先アドレスはスタックに自動的に積まれるようです。つまりH8と同様に、call命令を呼ぶと指定したアドレスにジャンプするのと同時にスタックポインタが減算され、そこに戻り先アドレスが格納されます。ret命令を呼ぶとスタックポインタの指す先の値(つまり、戻り先アドレス)を飛び先としてジャンプし、スタックポインタが加算されます。RISC系プロセッサのようなプロローグ・エピローグの処理がないのは、H8で説明した理由と同じです。

異なるのは引数の渡し方です。i386では引数はスタックに積んで渡しています。H8は引数はレジスタ渡しで戻り先アドレスはスタックに積まれるという動作でしたが、i386は完全なスタックベースの動作になっているわけです。