X86 32位汇编利用堆栈传递函数参数的过程

名词、概念介绍

当传递的参数较少时,还是可以用寄存器来传参的,但是一旦传递的参数多了,就没办法了。

主程序将入口参数压入堆栈,子程序从堆栈中取出参数;出口参数通常不使用堆栈传递。

高级语言进行函数调用时提供的参数实质上也是用堆栈传递的,高级语言还利用堆栈创建局部变量。

保存参数和局部变量的堆栈区域称为堆栈帧,在函数调用时建立、返回后消失

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 ;把长度入栈

X86 32位汇编利用堆栈传递函数参数的过程_第1张图片

	call print
	call writeint
main endp

这边要知道,call也涉及到栈

  • 使用call指令的时候,会把下一句指令的eip存入栈中

    相当于 push eip 然后跳转到函数那边去
    X86 32位汇编利用堆栈传递函数参数的过程_第2张图片

下一步就是比较难理解的地方了

print proc
	push ebp
	mov ebp,esp

X86 32位汇编利用堆栈传递函数参数的过程_第3张图片

这边寄存器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

X86 32位汇编利用堆栈传递函数参数的过程_第4张图片

这边应该比较好理解,一个不好理解的地方应该是关于取参数的,因为栈是从高位向低位push的,所以是要通过ebp+4*n来取出参数。

到了这边函数要结束了,所以要取出主函数的栈底位置

	pop ebp
	ret 8
print endp

在 pop ebp 之后情况是这样的

X86 32位汇编利用堆栈传递函数参数的过程_第5张图片

还记得之前call print时压入栈的eip吗,这个数据是指向call后面一句代码的地址,所以在ret 8的时候,其实做了两个操作

  • pop eip
  • sub esp,8 ; 这是为了保持栈平衡,同时两个参数也销毁了

X86 32位汇编利用堆栈传递函数参数的过程_第6张图片

最后就是这样啦,函数调用完毕~~

这就是通过堆栈传递函数参数的过程了,实际上并不复杂,理解了这个过程,将会很有利于未来对高级语言的深入理解!

你可能感兴趣的:(ASM,汇编,数据结构)