• 函数调用


    在汇编语言中需要调用函数时要call这个函数名,函数的执行过程如下:

    准备执行

      在主程序中每次调用函数时,先依次把各参数以相反的顺序入栈;
      然后call func_name, 这里call要做两件事: 一是把函数的返回地址入栈,二是让指令执行指针%eip指向函数开始处。


    开始执行

       现在函数要开始执行了,但它执行函数代码前还要做一点小事,首先把原来的基地址寄存器%ebp值入栈(基址寻址?看汇编),因为在程序执行中%ebp要另作它用, 接着堆栈指针%esp的值复制给%ebp, 此后在函数执行中%ebp一直保持不变,可以由此寻址获得函数参数。

     pushl %ebp  (值入栈,占据4个位
     movl %esp, %ebp   (ebp esp又再指向一个新位置,但并不一定在栈中装入了数据

        下面开始执行函数代码了。函数先要把它的局部变量保存在栈中,这很简单。比如要保存一个long型数据,只要把%esp指针向下移动4个字节(因为栈增长方向是由高地址到低地址),再根据%esp把该数据移入. 下面是保存两个局部变量long后的堆栈内容:
    Parameter #N <--- N*4+4(%ebp)
    ...
    Parameter 2 <--- 12(%ebp)
    Parameter 1 <--- 8(%ebp)
    Return Address <--- 4(%ebp)
    Old %ebp <--- (%ebp)
    Local Variable 1 <--- -4(%ebp)
    Local Variable 2 <--- -8(%ebp) and (%esp)

    从上可以看出通过%ebp基地址寻址(基址寻址?看汇编)可以访问所有的函数参数和局部变量. 当然也可以不用
    %ebp而用其它的寄存器进行同样的基地址寻址。但对于x86结构使用%ebp寄存器可能会更
    快一点。

    执行结束:

    现在函数执行要结束了,在它返回之前,还要做下面几件事:
    1. 把函数的返回值存放在通用寄存器%eax中,供外部使用
    2. 把%esp指向函数开始执行的位置, 即movl %ebp,%esp
    3. 在函数返回ret之前,要还原ebx, 即popl %ebp

    movl %ebp, %esp
    popl %ebp
    ret

    调用函数的过程,再看一下函数是如何退出的。观察mainf不难发现,退出函数使用的是如下指令

    leave
    ret

    leave指令相当于如下指令:

    movl	%ebp, %esp
    popl	%ebp
       
    • 第一条语句是将esp重置到ebp,可以理解为清空当前函数所使用的栈
    •  
    • 第二条语句是将栈顶值赋值给ebp,并弹出,栈顶值是什么呢?通过上面的分析不难发现,此时的栈顶值实际上是前一个函数的栈基地址,所以第二条语句的意思就是把ebp恢复到前一个函数的栈基地址

    接着ret就是相当于,恢复指令指向:

    popl %eip

    总结

    最后,通过这个例子,总结一下函数调用的过程:

    由main函数进入f函数:

    1. 当前栈基地址压栈(当前栈基地址实际上是前一个函数(main)的栈基地址)  pushl %ebp  ;  movl %esp, %ebp ;

    调用f函数:

    1. 参数从右到左进栈(实参)
    2. 下一条指令地址进栈

    退出函数:

    1. 栈顶esp归位,回到本函数的ebp     movl %ebp, %esp;
    2. 基地址回退到上一个函数的基地址     popl %ebp  (弹值,该处ebp的值与1、中ebp的值不同,由此可以推测形参并不在栈中
    3. eip退回到上一个函数即将要执行的那条语句的地址上   ret
  • 相关阅读:
    java 8
    内存溢出VS内存泄漏
    dubbo zk 分布式服务项目搭建与配置
    转发 VS 重定向
    过滤器
    Synchronized
    java 泛型
    spring 整合 mongo
    泛型
    反虚拟机
  • 原文地址:https://www.cnblogs.com/wgang171412/p/5153883.html
Copyright © 2020-2023  润新知