#include <stdio.h>
int test(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10) {
return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10;
}
int main() {
printf("%d", test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
return 0;
}
000000000040052d <test>:
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 89 7d fc mov %edi,-0x4(%rbp)
400534: 89 75 f8 mov %esi,-0x8(%rbp)
400537: 89 55 f4 mov %edx,-0xc(%rbp)
40053a: 89 4d f0 mov %ecx,-0x10(%rbp)
40053d: 44 89 45 ec mov %r8d,-0x14(%rbp)
400541: 44 89 4d e8 mov %r9d,-0x18(%rbp)
400545: 8b 45 f8 mov -0x8(%rbp),%eax
400548: 8b 55 fc mov -0x4(%rbp),%edx
40054b: 01 c2 add %eax,%edx
40054d: 8b 45 f4 mov -0xc(%rbp),%eax
400550: 01 c2 add %eax,%edx
400552: 8b 45 f0 mov -0x10(%rbp),%eax
400555: 01 c2 add %eax,%edx
400557: 8b 45 ec mov -0x14(%rbp),%eax
40055a: 01 c2 add %eax,%edx
40055c: 8b 45 e8 mov -0x18(%rbp),%eax
40055f: 01 c2 add %eax,%edx
400561: 8b 45 10 mov 0x10(%rbp),%eax
400564: 01 c2 add %eax,%edx
400566: 8b 45 18 mov 0x18(%rbp),%eax
400569: 01 c2 add %eax,%edx
40056b: 8b 45 20 mov 0x20(%rbp),%eax
40056e: 01 c2 add %eax,%edx
400570: 8b 45 28 mov 0x28(%rbp),%eax
400573: 01 d0 add %edx,%eax
400575: 5d pop %rbp
400576: c3 retq
0000000000400577 <main>:
400577: 55 push %rbp
400578: 48 89 e5 mov %rsp,%rbp
40057b: 48 83 ec 20 sub $0x20,%rsp
40057f: c7 44 24 18 0a 00 00 movl $0xa,0x18(%rsp)
400586: 00
400587: c7 44 24 10 09 00 00 movl $0x9,0x10(%rsp)
40058e: 00
40058f: c7 44 24 08 08 00 00 movl $0x8,0x8(%rsp)
400596: 00
400597: c7 04 24 07 00 00 00 movl $0x7,(%rsp)
40059e: 41 b9 06 00 00 00 mov $0x6,%r9d
4005a4: 41 b8 05 00 00 00 mov $0x5,%r8d
4005aa: b9 04 00 00 00 mov $0x4,%ecx
4005af: ba 03 00 00 00 mov $0x3,%edx
4005b4: be 02 00 00 00 mov $0x2,%esi
4005b9: bf 01 00 00 00 mov $0x1,%edi
4005be: e8 6a ff ff ff callq 40052d <test>
4005c3: 89 c6 mov %eax,%esi
4005c5: bf 70 06 40 00 mov $0x400670,%edi
4005ca: b8 00 00 00 00 mov $0x0,%eax
4005cf: e8 3c fe ff ff callq 400410 <printf@plt>
4005d4: b8 00 00 00 00 mov $0x0,%eax
4005d9: c9 leaveq
4005da: c3 retq
4005db: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
关于函数的调用栈,https://www.cnblogs.com/clover-toeic/p/3755401.html 这篇文章写得挺好。
push %rbp
把 caller(相对于当前函数的调用函数)的函数栈的栈底的内存地址(存在 rbp 寄存器中)压栈(写入到栈上),注意会占用8字节的内存长度。
因为C程序的入口函数是_start
,然后再经过一系列初始化函数,才到main
,所以这里main
里面也需要执行push %rbp
,又比如test
函数里面的push %rbp
就是main
函数的%rbp
里记录的地址。
栈是由高位向低位生长,所以此时是在调用函数的rsp
基础上向低位移动,又因为x86-64机器上,地址占8字节,所以push %rbp
的背后还有一个隐含的操作就是sub $0x8 %rsp
(如果是32机器就是向下移动4字节)。
mov %rsp,%rbp
把 caller 的栈顶的内存地址(存在 rsp 寄存器中)赋值到 rbp 寄存器。
sub $0x20,%rsp
把rsp
再减去0x20
,也就是地址再向下移动32个单元(字节),用于main
函数局部变量和参数的存储。这里预留少空间,在编译期就计算好了。当前实验是10个参数,如果我们改为13个参数,则会是sub $0x50,%rsp
,所以生成的汇编代码,在运行之前就已经确定了rsp
偏移量。
movl $0xa,0x18(%rsp)
movl $0x9,0x10(%rsp)
movl $0x8,0x8(%rsp)
movl $0x7,(%rsp)
参数的压栈是从右向左的,最后面的参数最先处理,我们知道寄存器里存储函数参数的只有6个,所以还需要使用栈上的空间来存储其他参数。把0xa
放在rsp+0x18
的地址上,然后把0x9
放在rsp+0x10
的地址上,以此类推。
mov $0x6,%r9d
mov $0x5,%r8d
mov $0x4,%ecx
mov $0x3,%edx
mov $0x2,%esi
mov $0x1,%edi
把参数从右向左进行处理,剩余的6个则有专门的寄存器来存储。
callq 40052d <test>
调用test
函数,背后隐含的一个操作是把调用test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
下一条指令mov %eax,%esi
的地址压栈,这样rsp
的地址会向下偏移8字节。
mov %eax,%esi
把test
的返回值,放入esi
作为printf
的第二个参数
mov $0x400670,%edi
把 0x400670
作为第一个参数传入printf
,0x400670
则是%d
字符串常量,在只读取的地址
具体的之前在 https://mengkang.net/1383.html 的实验中做过说明
leaveq
这个指令是函数开头的push %rbp
和mov %rsp,%rbp
的逆操作。把rbp
赋值给rsp
,然后把rsp+0x8
retq
retq
指令,它是callq
指令的逆操作。在上面执行leaveq
之后,再把0x8(rsp)
里保存的调用函数的下一行代码的地址取出来赋值给rip
(rip
程序计数器,负责从哪里开始执行代码),然后再次rsp+0x8
,就从test
恢复到main
里面了。
test
函数的操作就比较简单了,就是把寄存器的值压栈,然后相加计算,返回。