名词、概念介绍
当传递的参数较少时,还是可以用寄存器来传参的,但是一旦传递的参数多了,就没办法了。
主程序将入口参数压入堆栈,子程序从堆栈中取出参数;出口参数通常不使用堆栈传递。
高级语言进行函数调用时提供的参数实质上也是用堆栈传递的,高级语言还利用堆栈创建局部变量。
保存参数和局部变量的堆栈区域称为堆栈帧,在函数调用时建立、返回后消失。
esp通常用来储存栈顶,ebp通常用来储存栈底
引入例子
没有例子肯定学不来,所以我用书本上的一个简单的例子来演示一下,实际上书本上的解释已经挺清楚了,不过还是可以更加细致的了解这个过程
用C语言先把大概的代码写一下
void print(int *arr,int len){
printf("%d%d",*arr,len);
}
int main(){
int arr[5] = {5,4,3,2,1};
print(arr,5);
}
就是简单的调用两个参数
先给出汇编代码
main proc
push offset arr ;把指针入栈
push lengthof arr ;把长度入栈
call print
call writeint
main endp
这边要知道,call也涉及到栈
-
使用call指令的时候,会把下一句指令的eip存入栈中
相当于 push eip 然后跳转到函数那边去
下一步就是比较难理解的地方了
print proc
push ebp
mov ebp,esp
这边寄存器EBP已经指向当前这个函数栈的栈底了,栈里面的ebp就是存的主函数main的栈底,容我介绍网上看来的几个概念:
- 栈平衡:为了防止缓冲区攻击,程序在结束时需要判断栈平衡,也就是要使ESP==EBP
- 临时变量:像我们在写c语言的时候,总是在函数中用到临时变量,这些临时变量在函数开始时产生,在函数结束时销毁,那他们具体是存在哪里的呢?答案就是存在ebp和esp之间的空间中,这也是为什么临时变量开的数组不能很大,一旦很大就会报错栈溢出(stackOverflow)。
- 我的这个例子里面没有运用到临时变量,但是平时我们看到的
add esp,10H
这样的操作其实就是在开辟临时变量区,开了16个字节的空间。
- 我的这个例子里面没有运用到临时变量,但是平时我们看到的
- 栈帧:在一个函数调用的过程中,ebp和esp之间的空间就叫做一个栈帧,在函数调用时建立、返回后消失。
接下来是做保护寄存器的操作,我们担心寄存器中有存有不能丢失的变量,但是我们有希望能用到那个寄存器,这时候就要把寄存器push到栈里,最后再把原来的值pop出来
push edx
push ecx
mov edx,[edp+8] ; edx <-- len
mov ecx,[edp+12]; ecx <-- *arr
; 输出操作
mov eax,edx
call writeint
mov eax,[ecx*4]
call writeint
; end of the method
pop ecx
pop edx
这边应该比较好理解,一个不好理解的地方应该是关于取参数的,因为栈是从高位向低位push的,所以是要通过ebp+4*n来取出参数。
到了这边函数要结束了,所以要取出主函数的栈底位置
pop ebp
ret 8
print endp
在 pop ebp 之后情况是这样的
还记得之前call print
时压入栈的eip吗,这个数据是指向call后面一句代码的地址,所以在ret 8
的时候,其实做了两个操作
- pop eip
- sub esp,8 ; 这是为了保持栈平衡,同时两个参数也销毁了
最后就是这样啦,函数调用完毕~~
这就是通过堆栈传递函数参数的过程了,实际上并不复杂,理解了这个过程,将会很有利于未来对高级语言的深入理解!