函数调用大致包括以下几个步骤:
1.参数入栈 比如有三个参数
push arg3
push arg2
push arg1
2.返回地址入栈 代码去跳转
call target_addr
call指令 相当于
push EIP
jmp target_addr
就是把将当前指令地址压入栈中,跳转到函数入口处。
此时esp指向 返回地址
3.栈帧调整
push ebp
mov ebp,esp
sub esp,xxx
将当前栈帧的基址ebp入栈,此时esp指向了当前栈帧的基址esp,然后
mov ebp,esp
sub esp,xxx
调高栈底,申请新的栈空间。
所以在进入函数后,ebp指向的是前栈帧的基址,ebp+4 指向的是返回地址 ebp+8 指向的是arg1
栈结构如下:
xx———————当前esp指向这里 这里假设申请了12个字节
xx
xx
前栈帧的ebp———当前ebp指向指向这里
返回地址eip
arg1
arg2
arg3
函数的返回过程:
1.弹出当前的栈帧,恢复栈平衡
2.跳转eip
1.add esp,xxx 降低栈顶
2.
pop ebp 将当前的esp指向的 前栈帧的ebp 恢复到ebp中, 也就是恢复栈基址
retn
retn的功能:弹出栈顶元素 到eip中,恢复调用前的代码区。