zoukankan      html  css  js  c++  java
  • 汇编语言中的函数调用

    C语言从原则上来说,只能在函数内执行代码。
    所以任何 text 段都对应有自己的帧栈。
    本文主要谈一下 call leave ret 三条与函数调用紧密相关的指令。

    call 指令

    call 的不同形式

    call Label 所谓直接跳转
    call *operand 所谓间接跳转

    080483f7 <caller>:
      804840c:   e8 dc ff ff ff          call   80483ed <callee>

    上边代码段中 caller 中 call 80483ed <callee> 就是直接跳转

    call 之前的准备

    080483f7 <caller>:
      80483fa:   83 ec 08                sub    $0x8,%esp
      80483fd:   c7 44 24 04 1c a0 04    movl   $0x804a01c,0x4(%esp)
      8048404:   08
      8048405:   c7 04 24 01 00 00 00    movl   $0x1,(%esp)

    gcc ABI约定被调函数的参数保存在调用者的栈帧(frame)上,所以 caller 需要将 callee 的参数放在自己的栈帧上。这个过程分两步完成。

    • 开栈。
      将栈指针向下(栈由高位向下扩展)移动 8 bytes。这是因为两个参数一个是指针类型,一个是整数类型,均需要 4 bytes 来存储。事实上由于对齐的要求,即使参数类型小于 4 bytes 编译器还是会为其分配 4 bytes 的栈空间,
    • 反向保存参数。
      gcc ABI规定,反向保存参数,故栈顶保存最后一个参数。如果参数类型大于 4 bytes,IA32 需要用两条 movl 指令来传递参数。
      值得注意的是,ABI只规定了参数在栈上存储的空间顺序,并没有规定参数压入栈中的时间顺序

    call 干了什么

    存储返回地址。
    call 指令将 (%eip) 对应指令之后的那条指令的起始地址放在栈上,也就是把 %eip + n 放在 (%esp),其中 n 为 (%eip) 中指令的长度。然后跳转到 call 的操作数所指的地址。

    call之后发生了什么

    080483ed <callee>:
      80483ed:   55                      push   %ebp   // sub   $0x4,%esp    
                                                       // mov   %ebp,(%esp)
      80483ee:   89 e5                   mov    %esp,%ebp
    
      80483f0:   83 ec 2c                sub    $0x2c,%esp
    
      8048405:   c7 45 e8 01 00 00 00    movl   $0x1,-0x18(%ebp)
      804840c:   c7 45 ec 02 00 00 00    movl   $0x2,-0x14(%ebp)
      8048413:   c7 45 f0 03 00 00 00    movl   $0x3,-0x10(%ebp)
      804841a:   c7 45 f4 04 00 00 00    movl   $0x4,-0xc(%ebp)
      8048421:   c7 45 f8 05 00 00 00    movl   $0x5,-0x8(%ebp)
      8048428:   c7 45 fc 06 00 00 00    movl   $0x6,-0x4(%ebp)
    • 切换栈帧。
      被调函数首先将旧的栈底指针 %ebp 压到自己的栈帧上,然后以其地址(而非内容)作为自己的栈底指针的内容,此时新的栈帧已经形成了,由于 %esp == %ebp,故新的栈帧暂时没有使用栈内存。
    • 开栈。
      当局部变量数量太大时,编译器会选择将局部变量放在栈帧上。gcc的ABI约定,函数栈帧的大小必须 16 bytes 对齐,所以sub指令所减去的16进制数以c结尾(栈帧上已经有上一帧 %ebp ) 。
    • 初始化局部变量。
      这里对局部变量的初始化是以栈底指针为基准的,此处值得注意的是 (%ebp) 中存储的是上一帧的 %ebp

    leave 指令

    8048411:   c9                      leave

    leave 所做的工作是还原上一帧的栈底指针与栈顶指针,等效于

    mov  %ebp,%esp // 把栈顶指针置为本帧的栈底(同时也是存储上一帧栈底指针内容的地址),
    popl %ebp      // 还原上一帧的栈底指针,此时 %esp 指向返回地址

    ret 指令

    8048412:   c3                      ret

    ret 所做的工作是弹出栈顶的返回地址,并跳转到此地址。此时 %esp 指向调用函数所存储的被调函数的最后一个参数。

    杂记

    一个完整的栈帧上会有什么?
    从底到顶依次是:

    1. 上一帧的 `%ebp` 
    1.  ABI 约定被调用者保存(如果有)的调用者的三个寄存器的内容 `%ebx` `%esi` `%edi`
    
    2. 局部变量
    2. 对齐空白
    
    3. ABI 约定调用者保存(如果有)的自己的三个寄存器的内容 `%eax` `%edx` `%ecx`
    3. 所调用的函数的参数
    3. 返回地址(本帧的 %esp所指,下一帧的 0x4(%ebp))
  • 相关阅读:
    CCS
    CCS
    CCS
    CCS
    CCS
    CCS
    CCS
    CCS
    Java之内部类
    Java之回调模式和ThreadLocal
  • 原文地址:https://www.cnblogs.com/guochaoxxl/p/11217092.html
Copyright © 2011-2022 走看看