関数呼び出し
パラメータは通常、が指すメモリの一部であるスタックに渡されますesp
。esp
オペレーティングシステムは、スタック用にメモリを予約し、プログラムに制御を渡す前に適切にセットアップする責任があります。
通常の関数呼び出しは次のようになります。
main:
push 456
push 123
call MyFunction
add esp, 8
ret
MyFunction:
; [esp+0] will hold the return address
; [esp+4] will hold the first parameter (123)
; [esp+8] will hold the second parameter (456)
;
; To return from here, we usually execute a 'ret' instruction,
; which is actually equivalent to:
;
; add esp, 4
; jmp [esp-4]
ret
呼び出し元の関数と呼び出されている関数の間には、レジスターの保持をどのように約束するかに関して、さまざまな責任があります。これらのルールは、呼び出し規約と呼ばれます。
上記の例では、cdecl呼び出し規約を使用しています。これは、パラメーターが逆の順序でスタックにプッシュされ、呼び出し関数が、esp
それらのパラメーターがスタックにプッシュされる前のポイントに復元する役割を果たします。それが何をするかadd esp, 8
です。
主な機能
通常、main
関数はアセンブリで記述し、オブジェクトファイルにアセンブルします。次に、このオブジェクトファイルをリンカに渡して実行可能ファイルを生成します。
リンカは、制御が関数に渡される前にスタックを適切に設定するスタートアップコードを生成する役割を果たしますmain
。これにより、関数は2つの引数(argc / argv)で呼び出されたかのように動作できます。つまり、main
関数は実際のエントリポイントではありませんが、argc / argv引数を設定した後、スタートアップコードはそこにジャンプします。
スタートアップコード
では、この「スタートアップコード」はどのように見えるのでしょうか。リンカは私たちのためにそれを生成しますが、ものがどのように機能するかを知ることは常に興味深いことです。
これはプラットフォーム固有ですが、Linuxでの典型的なケースについて説明します。この記事は、日付が付けられていますが、i386プログラムが起動したときのLinuxのスタックレイアウトについて説明しています。スタックは次のようになります。
esp+00h: argc
esp+04h: argv[0]
esp+08h: argv[1]
esp+1Ch: argv[2]
...
したがって、スタートアップコードは、スタックからargc / argv値を取得し、次のmain(...)
2つのパラメーターを使用して呼び出すことができます。
; This is very incomplete startup code, but it illustrates the point
mov eax, [esp] ; eax = argc
lea edx, [esp+0x04] ; edx = argv
; push argv, and argc onto the stack (note the reverse order)
push edx
push eax
call main
;
; When main returns, use its return value (eax)
; to set an exit status
;
...