3.14 控制转移

控制转移指令 callret 实现函数调用和返回的流程跳转,同时维护栈帧的正确链接。

call 指令

call 指令执行两个操作:

  1. 压入返回地址:将下一条指令的地址(call 指令的下一条指令)压入栈
  2. 跳转:跳转到被调函数的起始地址

直接调用

call    func                # 调用函数 func
# 返回后从这里继续执行

间接调用

call    *%rax               # 以 %rax 中的值作为函数地址调用
call    *(%rdi)             # 从内存中读取函数地址并调用

call 的等价操作

# call func 等价于:
pushq   $return_addr        # 压入返回地址
jmp     func                 # 跳转到函数
 
return_addr:                 # call 之后的指令地址
    # ...

ret 指令

ret 指令执行与 call 相反的操作:

  1. 弹出返回地址:从栈顶弹出地址
  2. 跳转:跳转到该地址继续执行

ret 的等价操作

# ret 等价于:
popq    %rip                # 弹出栈顶值到指令指针(实际无法直接访问 %rip)

%rip(指令指针寄存器)保存当前正在执行指令的地址,程序员不能直接修改。ret 通过将弹出值加载到 %rip 实现跳转。

PC 在 call/ret 中的变化

调用过程

执行 call func 之前:
    %rip = 0x400100 (call 指令地址)
    %rsp = 0x2000

执行 call func:
    [1] 计算返回地址:0x400105 (下一条指令地址)
    [2] %rsp = 0x1FF8 (压栈前减 8)
    [3] 内存[0x1FF8] = 0x400105 (保存返回地址)
    [4] %rip = 0x400200 (func 的地址,跳转)

执行后:
    %rip = 0x400200 (func 的第一条指令)
    %rsp = 0x1FF8
    栈:[0x1FF8] = 0x400105 (返回地址)

返回过程

执行 ret 之前(在 func 内部):
    %rip = 0x400250 (func 的 ret 指令)
    %rsp = 0x1FF8
    栈:[0x1FF8] = 0x400105 (返回地址)

执行 ret:
    [1] %rip = 内存[0x1FF8] = 0x400105 (弹出返回地址)
    [2] %rsp = 0x2000 (弹出后加 8)

执行后:
    %rip = 0x400105 (返回到 call 的下一条指令)
    %rsp = 0x2000

完整的调用-返回周期

# 调用者代码
    movq    $10, %rdi          # 设置参数
    call    my_function        # 1. call: 压入返回地址并跳转
.Lreturn:                      # ← 返回地址指向这里
    movq    %rax, %rbx         # 5. 使用返回值
 
# 被调函数
my_function:
    pushq   %rbp               # 2. 函数序言
    movq    %rsp, %rbp
    # ... 函数体 ...
    movq    %rbp, %rsp
    popq    %rbp               # 3. 函数尾声
    ret                        # 4. ret: 弹出返回地址并跳转
时间轴:
调用者环境 → call → 被调函数环境 → ret → 调用者环境

栈帧的嵌套

main:
    call    A                   # main → A
 
A:
    # ... A 的函数体 ...
    call    B                   # A → B
    # ... 返回到 A ...
    ret                         # A → main
 
B:
    # ... B 的函数体 ...
    ret                         # B → A

栈的变化:

main 调用 A 后:
栈顶 → [返回地址到 main] ← %rsp

A 调用 B 后:
栈顶 → [返回地址到 A]
        [返回地址到 main]
        ...

B 执行 ret 后:
栈顶 → [返回地址到 main] ← %rsp
        ...

A 执行 ret 后:
栈顶 → 最初的状态

call/ret 与条件码

callret 不修改 。这意味着在函数调用前后,条件码的状态保持不变(除非被调函数内部的指令修改了它们)。

相对于其他跳转指令

指令影响栈用途是否支持间接
jmp无条件跳转是 (jmp *%rax)
call是(压入返回地址)函数调用是 (call *%rax)
ret是(弹出返回地址)函数返回不适用
je/jne/...条件跳转

相关笔记

    • call/ret 对栈的影响
    • 完整的栈帧管理
    • 与其他跳转指令的对比

链接到