Linux汇编教程11:函数与栈

Linux汇编教程1

开发一个程序,把所有的功能代码都在一块,会让程序变得难以维护。为了协助小组中的其他成员分工合作,我们需要把程序划分成互相独立的模块。一个模块问题不会牵连整个项目。一个程序有数千个函数构成,每一个函数实现一个功能,现在,我们开始学习函数部分。
一个函数有下面一个部分

函数名 —— 函数的名称作为一个标签,代表函数代码的起始位置。
函数参数 —— 函数参数是现实给函数处理的数据项
局部变量 —— 局部变量是函数进行处理时使用的数据存储区,但函数返回是,变量的空间会被舍弃回收。函数中的局部变量,程序中的任何其他函数无法直接利用。
静态变量 —— 静态变量是函数处理时用到的数据存储区,使用后的空间不会被舍弃。
全局变量 —— 全局变量是函数之外的管理数据的存储区。
返回值 —— 返回一些数据或信息给调用了函数的部分。
返回地址 —— 返回地址不能直接在函数中使用。返回地址是但函数结束后接着执行代码的位置。call指令会为你处理返回地址,ret指令负责按照地址返回调用函数的地方。

下面一个部分,我们来讲栈。

计算机的栈位于内存地址的最顶端。是用后进先出的方式,我们通过pushl指令来入栈,popl来出栈。我们的栈寄存器%esp包含指向当前栈顶的指针。当我们使用pushl入栈是,%esp中指针的值会减去4,当使用popl出栈的时候,%esp中指针的值会加4。
popl:

将栈顶数据弹出到某个内存位置或寄存器,相当于依次执行 movl (%esp), R/M和addl $4, %esp两条指令

pushl:

将某个值入栈,相当于一次执行subl $4, %esp和 movl I/R/M, (%esp)两条指令。
栈对于函数的局部变量、参数、返回地址的实现十分重要。在执行函数之前,会将函数的所有参数按逆序压入栈内。接着call指令会实现两个作用:把下一条指令的地址(也就是返回地址)压入栈中;修改%eip中的指令指针以指向函数起始处。接着开始执行之前,栈中的分布类似下面:
返回地址 << (%esp)
参数1
参数2
……
参数n
函数的参数被压入栈中,最后入栈的是返回地址。现在%esp里的指针指向返回地址。接下来函数本身还有一些操作:由于我们需要使用基址寄存器%ebp进行对栈中数据的索引访问,所以我们需要把%ebp中原有的数据做一个保留。首先,通过pushl %ebp指令把%ebp入栈。%ebp是一个特殊的寄存器,用于访问函数和局部变量;接着,执行movl %esp, %ebp指令,让%ebp指针的值和现在的%esp一样。现在的栈看起来是这样:

原来的(%ebp) <<< (%esp) 和 (%ebp)
返回地址 <<< 4(%ebp)
参数1 <<< 8(%ebp)
参数2 <<< 12(%ebp)
……
参数n <<< (n+1)*4(%ebp)

现在就可以利用%ebp进行基址索引了,接下来,函数为需要的局部变量开辟空间,我们只要移动栈指针就好了。如果我们需要3个字的内存,执行subl $12, %esp指令,这样就有空间存储变量,当函数返回时,这个堆栈也就消失了,这些变量也就没有了。现在在栈中的情况是这样:

局部变量3 <<< -12(%ebp) 和 (%esp)
局部变量2 <<< -8(%ebp)
局部变量1 <<< -4(%ebp)
原来的(%ebp) <<< (%ebp)
返回地址 <<< 4(%ebp)
参数1 <<< 8(%ebp)
参数2 <<< 12(%ebp)
……
参数n <<< (n+1)*4(%ebp)
当一个函数执行结束后,函数还会进行三个步骤

将返回值存储到%eax
移除当前栈帧,并使调用代码的栈帧恢复
将控制权交还给调用它的程序。利用ret指令

所以要让函数返回,需要使用下面的指令:

movl %ebp, %esp
popl %ebp
ret

让%esp中是值指向旧的%ebp中的值保存的地方,在把就的旧的%ebp中的值弹出到%ebp中,现在%esp的值就指向了返回地址,在使用ret,将栈顶的值弹出,并将指令指针寄存器%eip设置为这个弹出值。

版权声明

Moriarty_221为本文的CSDN博客

如未注明,均为原创,转载请注明出处

转载请注明:coskimo » Linux汇编教程11:函数与栈

版权所有 © 科斯基摩 | 本网站采用cc by-nc-sa 3.0协议进行授权

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章