V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
amiwrong123
V2EX  ›  程序员

x86 栈的使用有 rbp 和 rsp,只设计 rsp 可不可以?为什么?

  •  
  •   amiwrong123 · 2024-06-01 10:49:51 +08:00 · 2346 次点击
    这是一个创建于 390 天前的主题,其中的信息可能已经有所发展或是发生改变。

    https://zhuanlan.zhihu.com/p/656740329

    在 x86-64 架构中,rbp 和 rsp 寄存器分别是栈帧基址指针( Base Pointer )和栈指针( Stack Pointer )。

    看完了这篇文章,完全搞懂了函数调用过程中,rbp 和 rsp 的使用情况。

    但是还是有一点不太理解,就是假如 CPU 只设计 rsp 可以吗?从我简单来看,好像也是可以够用的啊。我总结一下,无外乎是这些:

    • 函数使用过程中,减 rsp ,用来放局部变量
    • 调用函数瞬间,硬件自动减 rsp ,用来放 return Address
    • 下一个函数,继续减 rsp ,用来放局部变量
    • 以此类推

    返回过程:

    • 如果是“减 rsp ,用来放局部变量”,那么直接加 rsp ,就回收了栈空间。
    • 如果是“硬件自动减 rsp ,用来放 return Address”,那么 pop 到 pc 即可( x86 里,ret 指令就是这个效果)

    那从上面分析来看,只有 rsp ,好像也能完成函数调用的工作啊?

    20 条回复    2024-06-02 15:23:15 +08:00
    shawnsh
        1
    shawnsh  
       2024-06-01 10:52:24 +08:00 via Android
    怎么取参数
    ThirdFlame
        2
    ThirdFlame  
       2024-06-01 10:54:19 +08:00
    RSP 可以移动
    RBP 是不动的

    你去取变量肯定是靠 RBP+偏移来取啊。 靠 RSP 那你还得设计个地方去记录 RSP 的变化,还不如直接设计一个 RBP 呢
    amiwrong123
        3
    amiwrong123  
    OP
       2024-06-01 10:54:43 +08:00
    @shawnsh #1
    你是指,函数调用传参,不是通过寄存器传递;而是通过栈来传递参数的情况呗?
    luxor
        4
    luxor  
       2024-06-01 10:58:23 +08:00
    gcc: -fomit-frame-pointer msvc: /Oy
    shawnsh
        5
    shawnsh  
       2024-06-01 11:00:54 +08:00 via Android
    @amiwrong123 寄存器才能放多少东西,多次的函数调用不用栈内存保存,你用啥
    amiwrong123
        6
    amiwrong123  
    OP
       2024-06-01 11:02:06 +08:00
    @shawnsh #1
    @ThirdFlame #2
    我好像懂你俩的意思了,比如一个函数的汇编里面:
    sub $0x10, %rsp
    之后取局部变量时,都是用-0x10(%rsp)来取 局部变量的。

    是这个意思吧?
    amiwrong123
        7
    amiwrong123  
    OP
       2024-06-01 11:04:13 +08:00
    @shawnsh #5
    嗯,确实。
    很多时候,可能必须用栈 来传递参数。这个时候,只有 rbp 记录了 上一个函数的栈帧基地址(刚进入下一个函数时),如果没有 rbp 就不好办了还。
    amiwrong123
        8
    amiwrong123  
    OP
       2024-06-01 11:12:34 +08:00
    @amiwrong123 #6
    这里写了,比如一个函数的汇编里面:
    sub $0x30, %rsp
    之后取局部变量时,都是用-0x10(%rbp)、-0x20(%rbp)、-0x30(%rbp)来取 各个局部变量的。
    amiwrong123
        9
    amiwrong123  
    OP
       2024-06-01 11:12:49 +08:00
    这里写错了
    vituralfuture
        10
    vituralfuture  
       2024-06-01 11:14:22 +08:00 via Android   ❤️ 1
    不要 rbp 是可行的,只不过追溯函数调用栈变得困难

    不要 rbp 的时候,编译器知道每个函数分配的栈帧大小,但是没有保存起来,例如函数返回时需要恢复 rbp ,而 rbp 就保存在当前 rbp 指向的地址,这时 rsp 减去编译器知道的栈帧大小就能得到 rbp

    call 指令会将 pc(指向下一条指令)和 rbp 压栈,这样子程序能够恢复栈帧,并回到函数调用发生的位置的下一条指令,如果要追溯函数调用栈,只需要拿到 rbp-8 指向的返回地址。读取 rbp 可以使用内联汇编。这就需要知道 rbp 的值,而这个值编译器知道却没有保存

    gcc 有个参数-fomit-frame-pointer ,就是省略了 rbp 的使用,但不难以调试程序
    adoal
        11
    adoal  
       2024-06-01 12:45:28 +08:00
    别说是没有 BP 可以,就算没有 SP 也不是不可以的
    adoal
        12
    adoal  
       2024-06-01 12:58:47 +08:00   ❤️ 1
    从正常的 C 编译成 X86 汇编的代码来看,为了维护栈帧而对 BP 的所有操作,其实都是显式的,所以实际上就是个普通寄存器,只不过出于约定,用 BP 来做这事就是了,没有 BP ,也可以拿其它寄存器用。

    SP 就比 BP 特殊了,call/ret/push/pop 等指令要隐式操作 SP……但实质上无非就是移动 SP 和读写数值而已,这些指令的设计是因为对应的操作太常见了,所以就为之做了优化,设计出专用指令,但从指令集完整性的角度完全可以编译器拆开做,SP 的功能也可以由任意通用寄存器来承担,在很多 RISC 设计里就是约定到某个通用寄存器。只有 IP (一般叫 PC 的更多)才是无论如何也不可能通用化的。
    ysc3839
        13
    ysc3839  
       2024-06-01 15:14:52 +08:00
    x86 bp 只是叫 base pointer 吧,跟 stack 无关,用户想怎么使用都可以。sp 是真的 stack pointer ,push pop 等操作会影响 sp 。
    amiwrong123
        14
    amiwrong123  
    OP
       2024-06-01 15:52:57 +08:00
    @vituralfuture #10
    谢谢。这就是我想要的答案。
    也就是说,默认编译出来的汇编,每个函数都会去使用 rbp 的。
    但是如果加了-fomit-frame-pointer 参数,那么每个函数就不会去使用 rbp 了。

    所以结论就是:函数调用过程中,可以不使用 rbp 。
    amiwrong123
        15
    amiwrong123  
    OP
       2024-06-01 15:54:23 +08:00
    @adoal #12
    嗯嗯,你的主要意思是:
    如果没有 BP ,那么用其他 通用寄存器来当 BP 来用,也是一样的。

    在 RISC 里,甚至 SP 都可以用 其他通用寄存器 来代替。
    amiwrong123
        16
    amiwrong123  
    OP
       2024-06-01 19:18:12 +08:00
    @vituralfuture #10
    不过 1 楼那个问题,要是 函数调用时参数是通过栈来传递参数的话(不然可以直接通过寄存器传参),没有 rbp 的话,会不会有点不好办。

    不过我想,有 rbp 的话(在刚进入函数时),就是 rbp 减一个数来 获得传参;如果没有 rbp ,那就通过 rsp 加一个数 来获得传参。好像一样能解决问题。
    PTLin
        17
    PTLin  
       2024-06-01 20:34:55 +08:00
    rbp 主要就是为了 backtrack ,假如完全不需要这个功能就可以取消 push rbp,mov rbp rsp 什么的,在 Linux 内核中完全不需要 ftrace 什么的功能也可以编译成不用 rbp 的。
    sMil3
        18
    sMil3  
       2024-06-01 21:10:00 +08:00 via Android
    mips 就是按 sp 找局部变量吧
    iceheart
        19
    iceheart  
       2024-06-02 04:19:06 +08:00 via Android
    rbp 是链表指针,
    这个链表就是 callstack 链表
    kingly
        20
    kingly  
       2024-06-02 15:23:15 +08:00
    基础不行真的不建议思考这些问题,没啥用,纯浪费精力。你调试的时候看堆栈回溯,没有 rbp 你看个屁啊,栈底上面保存了返回地址,除此之外还有很多其他小用处,比如优化了寻找速度,这都是用处。建议基础没砸实之前不要去产生过多疑问,浪费人生。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5705 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 02:37 · PVG 10:37 · LAX 19:37 · JFK 22:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.