前回は、
今回は、
引数と戻り値
引数
関数の呼び出し元は、
- 自身の局所変数の値
- 自身に指定された引数
- 他の関数の呼び出し結果
- 上記の値から導出された値
(例: 四則演算/ 構造体参照等)
一方で
そのため、
movl $1, 0(%esp)
movl $15, 4(%esp)
呼び出し先の関数は、
movl 8(%ebp), %eax # eax には 1 が格納
movl 12(%ebp), %eax # eax には 15 が格納
格納時のESPレジスタに対する即値指定と、
- 関数復帰の際の呼び出し元アドレス格納領域
(+4) - 旧EBP値の格納領域
(+4)
上記の分だけスタック上に領域が確保されているためです。
関数が呼び出された際のスタックは以下のような構成となっています。

戻り値
関数引数が複数の値を扱う必要があったのに対して、
Intel x86アーキテクチャで関数呼び出しを実現する場合、
movl $1, %eax # 戻り値は 1
leave
ret
再帰呼び出し
関数呼び出しの例として、
int get_crossing_depth(int lower, int upper){
if((upper -= 2) <= (lower += 3)){
return 1;
}
else{
return get_crossing_depth(lower, upper) + 1;
}
}
.... depth = get_crossing_depth(1, 15);
上記のCプログラムをアセンブラで実現すると、
.text
.align 4
.global get_crossing_depth
get_crossing_depth:
enter $8, $0 # 再帰呼び出し引数(4x2)の領域を
# スタック上に確保
subl $2, 12(%ebp) # upper -= 2
addl $3, 8(%ebp) # lower += 3
movl 12(%ebp), %eax
movl 8(%ebp), %edx
cmpl %eax, %edx # upper と lower の比較
jl recursively
# upper <= lower なので再帰呼び出し無し
movl $1, %eax # 戻り値設定
leave
ret
recursively:
# upper > lower なので再帰呼び出し有り
movl 8(%ebp), %eax
movl %eax, 0(%esp) # lower 引数格納
movl 12(%ebp), %eax
movl %eax, 4(%esp) # upper 引数格納
call get_crossing_depth
addl $1, %eax # 戻り値設定
# 再帰呼び出しの戻り値に +1
leave
ret
.global entry_point
entry_point:
int3
addl $8, %esp # 引数領域確保
movl $1, (%esp) # lower 引数格納
movl $15, 4(%esp) # upper 引数格納
call get_crossing_depth
.global end_of_program
end_of_program:
int3
再帰呼び出しを行う get_
では、enter
命令の時点で4バイト×2=8バイト分をスタック上に確保していますenter
の詳細は前回の説明を参照)。
また、entry_
側でも、
ESP/
低レイヤから見た関数呼び出し
Application Programming Interface
その一方で Application Binary Interface
本稿ではこれまで、
しかしその一方で、
以上のように、
たとえば、
これは、
先の再帰呼び出し実装の例からもわかるように、
引数の数が固定=呼び出し先で解放可能な関数の呼び出しであっても、
その一方で、
これは先ほどの "cdecl" の逆で、
これらの呼出規約は、
すべてを自前の関数で構成するのであれば、
おわりに
全6回
ソフトウェアの世界は、
アセンブラレベルからのボトムアップ的な視点を身につけることで、
この連載が、