【问题标题】:Assembly x86- How does stack pointer keep tack of correct location of local variables after return address is pushed into the stack?汇编x86-返回地址被推入堆栈后,堆栈指针如何跟踪局部变量的正确位置?
【发布时间】:2021-08-03 17:02:31
【问题描述】:

我目前正在阅读 CSAPP,我偶然发现了这个数字,但有些东西我就是想不通

在第1行,堆栈指针减16,两个8字节的数字分别存储在堆栈0和8字节相对堆栈指针,但在第7行,swap_add的返回地址被压入堆栈,所以堆栈现在应该是这样的:

我的问题是:为什么在第 8 行和第 9 行,堆栈指针仍然能够从偏移量 0 和 8 中检索到正确的值?据我了解,堆栈指针现在指向返回地址,因此为了取回值,它应该是 (%rsp)、%rsi 和 16(%rsp), %rdx,还是返回地址不入栈?请解释一下为什么会这样,谢谢

【问题讨论】:

  • 如果您注意到,在第 2 行,堆栈指针被调整为 16 个字节腾出空间。然后参数存储在堆栈中,相对于新的堆栈指​​针位置。第 8 行和第 9 行引用了新的堆栈指​​针位置(不是基指针,它在堆栈帧/函数调用期间是恒定的)。此外,您的图表具有误导性。返回地址将存储在rbp 而不是rsp 上方的 8 个字节。一些提示:使用 Intel 语法,因为它通常更清晰,并从高地址到低地址绘制堆栈图。
  • "在第 7 行,swap_add 的返回地址被压入堆栈。"它最终归结为使用的调用约定,如果没有更多细节我们无法知道。但是,查看此代码似乎可以安全地假设堆栈帧在 swap_add 函数调用之后恢复。也就是说,不用担心call指令弄乱了栈帧,因为函数返回后会恢复。
  • call swap_add 确实将返回地址压入堆栈。但随后它分支到swap_add 的代码,该代码未显示,但假定以执行ret 指令结束,该指令立即弹出该地址。因此,在这个调用约定中,从调用者的角度来看,您可以认为 call 指令对堆栈没有净影响。

标签: assembly x86


【解决方案1】:

似乎其他答案和 cmets 忽略了问题的重点,并给出了许多掩盖简单答案的细节:调用将返回地址压入堆栈,但是当调用返回时,return 会弹出返回地址,所以调用返回后,堆栈指针的值与调用前相同。

【讨论】:

    【解决方案2】:

    为什么在第 8 行和第 9 行,堆栈指针仍然能够检索到 偏移量 0 和 8 的值是否正确?

    因为堆栈指针只在这里修改过:

    subq $16, %rsp
    

    这里:

    addq $16, %rsp
    

    虽然您对swap_add 进行了函数调用,而且从技术上讲,它在内部调整了堆栈框架,但在该函数调用完成后,堆栈框架会恢复。因此,您应该只考虑单个堆栈帧,除非您想深入了解 swap_add 例程(其中的程序集已被省略,因此超出范围)。

    据我了解,堆栈指针现在指向返回 地址

    这是不正确的。在列出的程序集的开头和结尾处,返回地址为0(%rsp)。当到达ret 指令时,技术上会执行pop %rip,将新指令指针设置为返回地址。

    一些额外的说明:

    1. call 指令自动将下一条指令的地址(新函数的返回地址)压入堆栈。 ret 指令有效地撤消了这一点。
    2. 使用的调用约定决定了在函数调用之前和之后如何调整堆栈帧,以及执行调整的责任是谁。

    【讨论】:

    • 非常感谢,我没有注意到其他被调用的过程在返回时会恢复堆栈指针,我不知道%rbp是什么,因为我还没有读到那部分
    • @Mattmmmmm 可以访问相对于rsp 的返回地址,但在实践中不太常见。尽管在这个例子中似乎完全省略了rbp,并且堆栈帧总是相对于rsp。因此,在这个调用约定中,函数本身负责根据需要增加和缩小堆栈,只要将rsp 恢复为之前的值,返回地址将始终位于8(%rsp)
    • @Mattmmmmm:您的函数中的代码没有将 RBP 设置为带有push %rbp / mov %rsp, %rbp 的传统“帧指针”。这个答案关于 RBP 的 cmets 仅适用于执行此操作的调试构建编译器输出,或选择执行此操作的手写 asm。顺便说一句,您通常永远不会显式访问返回地址,只能通过 call 推送它并 ret 弹出它。当然,隐式堆栈访问与 RSP 相关 (felixcloutier.com/x86/ret)。 ret 是我们拼写pop %rip 的方式。 RBP 仅由 leave 隐式使用(以及慢速 enter 指令)。
    • @PeterCordes 我并不是在暗示需要直接在8(%rsp) 访问返回地址。相反,我希望根据所使用的调用约定来理解这一点,函数本身负责增加和缩小堆栈帧,只要返回地址在函数末尾的8(%rsp), ret / pop %rip 将适当地将控制权转移回函数调用者。此外,您建议 rbp cmets 仅适用于调试版本。对于什么编译器? x86_64 gcc 11.1 使用 rbp 和 rsp 生成带有堆栈帧的程序集。
    • @h0r53: 来自-fstack-protector-strong 的堆栈金丝雀就像如果你有一个额外的int64_t 局部变量,编译器在保存的返回地址(godbolt.org/z/1ohd148ez)的正下方分配空间,或低于调用保留的寄存器保存槽(如果有)。或者至少高于任何当地人;如果有未使用的堆栈空间进行对齐,它可能在金丝雀上方。 (godbolt.org/z/aK7548e1K 显示 -O1 -fno-omit-frame-pointer -fstack-protector-strong 引用相对于 RBP 的金丝雀)。您可以自己在 Godbolt 上使用代码或选项。
    猜你喜欢
    • 2014-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-23
    • 1970-01-01
    • 2011-02-02
    • 2011-02-13
    相关资源
    最近更新 更多