Cのコード
void f(){
printf("a\n");
}
int main(){
f();
return 0;
}
アセンブリコード
コンパイラはicc 14.0.3
コンパイラオプションは -O0 -g
0000000000400514 <f>:
400594: 55 push %rbp
400595: 48 89 e5 mov %rsp,%rbp
400598: 48 83 ec 10 sub $0x10,%rsp
40059c: b8 3c 08 40 00 mov $0x40083c,%eax
4005a1: 48 89 c7 mov %rax,%rdi
4005a4: b8 00 00 00 00 mov $0x0,%eax
4005a9: e8 ba fe ff ff callq 400468 <printf@plt>
4005ae: 89 45 f0 mov %eax,-0x10(%rbp)
4005b1: c9 leaveq
4005b2: c3 retq
4005b3: 90 nop
0000000000400554 <main>:
400554: 55 push %rbp
400555: 48 89 e5 mov %rsp,%rbp
400558: b8 00 00 00 00 mov $0x0,%eax
40055d: e8 b2 ff ff ff callq 400514 <f>
400562: b8 00 00 00 00 mov $0x0,%eax
400567: c9 leaveq
400568: c3 retq
動作を理解していく。
まずmainから
400558: b8 00 00 00 00 mov $0x0,%eax
40055d: e8 b2 ff ff ff callq 400514 <f>
eaxレジスタは戻り値を渡すのに使われるらしい。
でも400558で初期化するのは無駄な気もする。
そして、callqで400514 fに飛ぶ
400514: 55 push %rbp
400515: 48 89 e5 mov %rsp,%rbp
400518: 48 83 ec 10 sub $0x10,%rsp
callerのベースポインタを退避して、callerのスタックの若いほうにcalleeのスタックを積む
40059c: b8 3c 08 40 00 mov $0x40083c,%eax
4005a1: 48 89 c7 mov %rax,%rdi
4005a4: b8 00 00 00 00 mov $0x0,%eax
.rodataセクションに埋め込んである"a\n\0"のアドレスをeaxに入れる。
raxを引数用レジスタrdiに入れる。
引数渡し用のレジスタはrdi/rsi/rdx/rcx/r8/r9の順に使われる。
これも直接rdiにアドレスを書き込めば良いような気がする。
やはりeaxを初期化する。
4005a9: e8 ba fe ff ff callq 400468 <printf@plt>
4005ae: 89 45 f0 mov %eax,-0x10(%rbp)
printfを読んで返り値をレジスタに入れる。
4005b1: c9 leaveq
4005b2: c3 retq
rbp,rspをcallee側で戻して返る。
-O2を付けたら、かなり大胆に変わったので、次はそれを読む。