程序的机器代码表示--函数调用

call和ret指令
如何访问栈帧、如何切换栈帧、如何传递参数和返回值

  1. call、ret指令作用:
    • call:1)将IP(即PC)旧值压栈保存(保存在函数的栈帧顶部);2)设置IP新值,无条件转移至被调用函数的第一条指令
    • ret:从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器
  2. 如何访问栈帧里的数据
    程序的机器代码表示--函数调用_第1张图片
    • 访问当前函数的局部变量:[ebp-4]、[ebp-8]...
    • 访问上一层函数传过来的参数:[ebp+8]、[ebp+12]...
  3. 如何切换栈帧(函数调用的时候)
    • 函数调用时发生的切换
      • call指令
        • 将IP旧值压栈保存:相当于push IP
        • 设置IP新值,无条件转移至被调用函数的第一条指令:相当于jmp add(add是调用的函数名)
      • 每个函数开头固定的例行处理(相当于执行指令enter):会导致每个栈帧底部(ebp),都保存着上一层栈帧的基址(ebp)
        • push ebp:保存上一层函数的栈帧基址(ebp旧值)
        • mov ebp,esp:设置当前函数的栈帧基址(ebp新值)
          程序的机器代码表示--函数调用_第2张图片
    • 函数返回时发生的切换
      • 每个函数ret前的固定例行处理(相当于执行指令leave
        • mov esp,ebp:让esp指向当前栈帧的底部
        • pop ebp :将esp所指元素出栈,并写入寄存器ebp
      • ret指令:从函数的栈顶找到IP旧值,将其出栈并恢复IP寄存器
    • 宏观看一下一个函数的整体汇编代码框架:
      程序的机器代码表示--函数调用_第3张图片
  4. 如何传递参数和返回值
    • 栈帧内包含的内容:
      • 栈帧最底部:上一层栈帧基址(ebp旧值) 【一定存在】
      • 栈帧底部:局部变量(离着本层函数的ebp近)【不一定存在】
      • 空闲区域:因为gcc编译器将每个栈帧大小设置为16B的整数倍(当前函数的栈帧除外)【不一定存在】
      • 中间:部分寄存器值(调用其他函数前,如果有必要,可将某些寄存器的值入栈保存,防止中间结果被破坏)【不一定存在】
      • 栈帧顶部:调用参数(离着下一层被调用函数的ebp近)【不一定存在】
      • 栈帧最顶部:当前函数的返回地址(函数调用时,call指令将IP寄存器值,即返回地址,压栈保存)【一定存在】
    • 如何传递参数:在call指令之前,将调用参数写入栈帧顶部区域
    • 如何传递返回值:在ret指令之前,将函数返回值写入eax寄存器

你可能感兴趣的:(计算机组成,计算机组成原理)