esp
call执行跳入子程序以后栈顶寄存器,一直指向栈顶
ebp
call执行过跳入子程序以后,一直指向栈底
看下面例子模拟一个有2个参数的函数.例如函数参数1是1, 参数2是2
push 2 //参数2
push 1 //参数1
call 子程序地址
//子程序开始以后的栈的内容操作如下图红框出是call的下一条语句的地址,这个地址会被首先压入栈顶,如下图
压入栈顶之后如下图
上面图栈顶显示是:"返回到"这个就是外面call的下一条语句的地址 ,这样当子程序结束的时候可以通过retn语句的时候 找到call下面的语句,.因为retn语句,等于pop eip,然后cpu就会执行eip里的语句
上一层ebp和本层ebp的保存方法
首先要明白为什么要保存上一层ebp
ebp是用来保存本层call的栈底位置的寄存器,每次进入call之后,会保存本层call子程序的栈底,加入不保存上一层的ebp,那么本层call执行结束以后。ebp因为保存了本层的栈底,就改变了,上一层的栈底在进入本层call之前是被保存到ebp里面的,但是结束本层靠以后就被改变了,那么回到上一层,通过ebp就找不到栈底了。所以上一层call的ebp必须在本层靠进来的时候就保存,保存方法就是在一进来call就push ebp, 当在返回之前的上一句,再pop ebp,这样上一层的ebp就被保存下来了,在call 内的子程序一般如下格式保存ebp .注意下图中的mov ebp, esp,是保存本层的栈底。因为一进来本层的call,压入之前ebp以后,栈顶往上移动一个元素,esp - 4,然后现在开始的栈顶被认为也是栈底。
push ebp //子程序外侧的ebp入栈,目的是保存外面的ebp,因为外面也有call,外层的call的栈底保存在ebp里面,出站
mov ebp,esp //因为此时的esp是栈顶,同时也是栈底,所以用ebp栈底寄存器保存栈底
一直到遇到retnebp都不会变化,因为ebp就是用来保存本层call的栈底的
pop ebp
retn
保存本层和上一层ebp的过程在 od中的截图如下:
下图是retn时候,还原上一层ebp的执行结果截图:pop ebp以后,就把最开始进来就保存的上一层ebp还原了
call 函数的参数在栈中的位置
看下面图是cpu执行到call里面,然后执行完push ebp mov ebp,esp之后栈里面的情况
以内给本层ebp复制以后,ebp在本层不在改变,所以,call的参数位置也是固定的,位置如下:
ebp+4是返回到
ebp+8是参数1
ebp+c 是参数2
ebp +10 参数3
总结是 ebp+ 8是第一个参数,之后每次+4,按照十六进制表示
子程序里面的push是局部变量,如下图的2处push esi
下面图是 在栈空间通过sub esp改变了栈顶以后再分配局部变量,下面图让 sub esp 28,栈顶向上移动28再往栈顶push局部变量
总结函数参数和局部变量
esp在子程序头部
- esp + 4, + 8 + c都是 函数参数,是在call外push进栈的.
- esp - 4, - 8 等,只要是减的,可能是局部变量,不是局部变量的情况是,在申请局部变量之前,执行了push操作,让esp减少了.或者执行了 sub esp ,4等 操作,让esp减少了.
od中查看 栈空间的返回到子程序的情况
- 在返回到子程序的上面,就是局部变量
- 在返回到子程序的下面,是call外传进来的参数
如下图: