Assembly ARM:为什么我需要在函数调用时按下/弹出两个寄存器?

Assembly ARM:为什么我需要在函数调用时按下/弹出两个寄存器?,assembly,arm,Assembly,Arm,我知道我需要在函数调用开始时推送链接寄存器,并在返回之前将该值弹出到程序计数器,以便执行可以从函数调用之前的位置进行 我不明白的是,为什么大多数人会在push/pop中添加一个额外的寄存器。例如: push {ip, lr} ... pop {ip, pc} 例如,以下是ARM中的Hello World,由提供: 问题1:他们称之为“虚拟寄存器”的原因是什么?为什么不直接推{lr}和弹出{pc}?他们说这是为了保持堆栈8字节对齐,但堆栈4字节不是对齐的吗 问题2:什么寄存器是“ip”(即r7或

我知道我需要在函数调用开始时推送链接寄存器,并在返回之前将该值弹出到程序计数器,以便执行可以从函数调用之前的位置进行

我不明白的是,为什么大多数人会在push/pop中添加一个额外的寄存器。例如:

push {ip, lr}
...
pop {ip, pc}
例如,以下是ARM中的Hello World,由提供:

问题1:他们称之为“虚拟寄存器”的原因是什么?为什么不直接推{lr}和弹出{pc}?他们说这是为了保持堆栈8字节对齐,但堆栈4字节不是对齐的吗


问题2:什么寄存器是“ip”(即r7或什么?

,因为您希望在执行功能后存储和恢复它们。 在函数entrence上,它保存
ip
lr
寄存器(名为
prolog
)。 完成函数后,它将分配两个(
epilog
):

pc
他们称之为“虚拟寄存器”的原因是什么?为什么不直接推{lr}和弹出{pc}?他们说这是为了保持堆栈8字节对齐,但堆栈4字节不是对齐的吗

堆栈只需要4字节对齐;但是,如果数据总线是64位宽的(就像许多现代武器上一样),则将其保持在8字节对齐会更有效。然后,例如,如果调用一个需要堆叠两个寄存器的函数,则可以在一次64位写入而不是两次32位写入中完成

更新:显然这不仅仅是为了效率;如评论中所述,这是官方程序调用标准的要求

如果您针对的是较旧的32位ARM,那么额外的堆叠寄存器可能会略微降低性能

什么寄存器是“ip”(即r7或什么?)


r12
。例如,有关过程调用标准使用的完整寄存器别名集,请参见。

8字节对齐是符合AAPC的对象之间互操作性的要求

ARM有一份关于此主题的咨询说明:

本文提到了使用8字节对齐的两个原因

  • 对齐错误或不可预测的行为。(硬件/体系结构相关原因-LDRD/STD可能导致校准故障或在除ARMv7以外的体系结构上显示不可预测的行为)

  • 应用程序失败。(编译器-运行时假设差异,以
    va_start
    va_arg
    为例)


当然,这都是关于公共接口的,如果您制作一个静态可执行文件,而不需要额外的链接,那么您可以以4字节对齐堆栈。

我知道它可以做到这一点。我的问题是为什么大多数人在push/pop时使用两个寄存器。为什么不简单地推送{lr}和弹出{pc}呢?既然该语言允许你推送{list of registers},并且是一条汇编指令,假设你想存储
r0-r15
,你可以用32位代码长度或15*32位代码长度来做,那更好呢?你不明白我的问题。我重新编辑了它,请检查。@DanielS:原因是,ARM EABI指定堆栈保持64位对齐,其他WEISE ldrd/STD无法在堆栈上使用。此外,到目前为止,我看到的大多数实现都能够在32位的同时进行64位宽的内存访问,如果地址是64位对齐的。在这种情况下,添加ip(或任何其他寄存器)只需使代码不必显式地(通过add和sub)对齐即可。如果代码只推送/pop lr/pc,则printf的堆栈将不再对齐,并且在调用ldrd时可能会崩溃。@Masta79:为什么不添加注释作为答案?这是正确的解释,现有的解释没有一个是完整的。我链接到一篇ARM博客文章,他们推荐这种双注册模式。请查看,那里有一些代码。不鼓励使用链接,因为链接可能不会持续到问题的长度(和/或他们只是删除问题,因为它使用链接而不是在此处进行讨论)。啊,因此链接回答了您的问题。你可以自己发布答案。结束这个问题。除了一个虚拟寄存器(ip)来保持堆栈8字节对齐,但是它是如何工作的?据我所知,堆栈是4字节对齐的。事实上,当我不使用虚拟寄存器时,它工作得很好。所以我的问题仍然悬而未决。这个答案是误导和危险的。8字节对齐是所有符合EABI的代码的一项要求,不在所有外部边界上维护8字节对齐可能会导致运行时失败——更糟糕的是,在某些处理器上执行的编译器的某些版本上构建8字节对齐可能会导致运行时失败。只需响应@unixsmurf的响应即可。AAPCS的5.2.1.2规定“SP mod 8=0。堆栈必须双字对齐。”用于公共接口。除非你知道自己在做什么,否则你真的想一直这样做。ARM上有一篇知识文章。@unixsmurf:对不起,我对过程调用标准的了解有点过时了;我不知道现在8字节对齐是一项要求。我想我最好停止回答有关ARM的问题。我更新了答案以反映这一点;希望它现在可以接受,但不幸的是,只要它被接受,我就不能删除它。值得一提的是:store 2寄存器用例非常常见,以至于在armv8中,它删除了
push
pop
,有专用的push对和pop对指令
stp
ldp
.syntax unified

    @ --------------------------------
    .global main
main:
    @ Stack the return address (lr) in addition to a dummy register (ip) to
    @ keep the stack 8-byte aligned.
    push    {ip, lr}

    @ Load the argument and perform the call. This is like 'printf("...")' in C.
    ldr     r0, =message
    bl      printf

    @ Exit from 'main'. This is like 'return 0' in C.
    mov     r0, #0      @ Return 0.
    @ Pop the dummy ip to reverse our alignment fix, and pop the original lr
    @ value directly into pc — the Program Counter — to return.
    pop     {ip, pc}

    @ --------------------------------
    @ Data for the printf calls. The GNU assembler's ".asciz" directive
    @ automatically adds a NULL character termination.
message:
    .asciz  "Hello, world.\n"
pc <- lr

ip <- old_ip