生成机器码.o文件,使用objdump - d -M intel hello_func.o来看汇编代码
栈内存由于历史原因看作是从高地址往低地址扩张所以栈底为高地址,栈顶为低地址。
rbp存储的时当前栈帧的基地址,栈底地址。
rsp存储的是栈顶地址,rip存储的是下一条指令的地址。
在main函数之前就存在start函数,所以在main函数之前就存在着栈帧。图中的栈帧为了方便显示地址用四个字节表示,每块内存占两字节。
rip永远指向下一条指令的地址。在压栈后rsp会下移2字节
pop:将rbp的值设置为 rbp所指向内存里的值,rsp退一格元素
leave:move rsp rbp(rsp的地址设置为rbp地址,就是将rsp和rbp值一致)
main函数栈帧进行的操作:
main函数开始时进行操作:
1.push rbp :将栈底指针压栈(rsp值 - 2字节)原因:rbp需要存储新的地址,记住上一个栈帧的基地址,后续需要恢复。
2.mov rbp rsp,将rsp的值赋值给rbp,此时rbp下移。rsp rbp指向一起。
main函数进行的操作:
.......(暂不讨论)
main函数开始调用add函数时的操作:
3.call 64 <main+0x25>先将rip的地址压入栈中。后续add函数调用完回到main函数的下一条地址
等待add函数调用完成回到此位置再进行下面的操作
add函数栈帧进行的操作:
add函数开始时进行操作:
1.push rbp :将栈底指针压栈(rsp值 - 2字节)原因:rbp需要存储新的地址,记住上一个栈帧的基地址,后续需要恢复。
2.mov rbp rsp,将rsp的值赋值给rbp,此时rbp下移。rsp rbp指向一起。
add函数执行时进行操作:
......
add函数调用mov进行操作:
3.call 32 <add+0x1d>先将rip的地址压入栈中(下一条指令的地址)。后续add函数调用完回到main函数的下一条地址
等待mov结束后返回到此位置继续其他操作
add函数结束时的操作
4.leave ,leave操作相当于 mov rsp rbp,将rbp和rsp指到一起,再pop rbp:将rbp的值设为rbp所指地址里的值,因为rbp指向的值,为上一次rbp的基地址,(这也就是为什么在函数开始时需要push rbp) ,然后rsp向上一格,rsp此时指向的main函数的栈顶地址。
5.将rip的值设为rsp指向的地址,此时指向的是main函数再调用时存储的下一条指令。
回到main函数的函数栈帧内
mov函数栈帧进行的操作:
mov函数开始时进行操作:
1.push rbp :将栈底指针压栈(rsp值 - 2字节)原因:rbp需要存储新的地址,记住上一个栈帧的基地址,后续需要恢复。
2.mov rbp rsp,将rsp的值赋值给rbp,此时rbp下移。rsp rbp指向一起。
mov函数执行时进行操作:
......此时再进行其他压栈操作时,rsp的值不用向下移,此时rbp rsp在同一位置,因为后续没有函数需要栈帧,这会导致在mov结束时不用进行leave操作。
mov函数结束时的操作!!!!!
3.pop rbp ,将rbp当前指向的值设为rbp的值,因为rbp指向的值为上一次rbp的栈底地址,这也是为何函数开始要push rbp的原因。将rsp回退一格,回退后,rsp指向的是add函数的栈底。
4.ret ,将rip的值设为rsp指向的地址,此时指向的是add函数再调用时存储的下一条指令。
执行完ret后程序返回到add函数的栈帧内。