我知道我需要在函数调用开始时推送链接寄存器,并在返回之前将该值弹出到Program Couter,以便执行可以从函数调用之前的位置开始执行。
我不明白为什么大多数人通过在push / pop中添加额外的寄存器来实现这一点。例如:
push {ip, lr}
...
pop {ip, pc}
例如,这是ARM中的Hello World,由official ARM blog提供:
.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"
问题1 :他们称之为“虚拟注册”的原因是什么?为什么不简单地推{lr}和pop {pc}?他们说这是保持堆栈8字节对齐,但不是堆栈4字节对齐?
问题2 :什么寄存器是“ip”(即r7或什么?)
答案 0 :(得分:6)
8字节对齐是符合AAPCS的对象之间互操作性的要求。
ARM有关于此主题的咨询说明:文章提到使用8字节对齐的两个原因
对齐错误或UNPREDICTABLE行为。 (硬件/体系结构相关原因 - LDRD / STRD可能导致对齐错误或在ARMv7以外的体系结构上显示UNPREDICTABLE行为)
申请失败。 (编译器 - 运行时假设差异,他们以va_start
和va_arg
为例)
当然这都是关于公共接口的,如果你创建一个没有额外链接的静态可执行文件,你可以将堆栈对齐为4个字节。
答案 1 :(得分:5)
他们称之为“虚拟注册”的原因是什么?为什么不简单地推{lr}和pop {pc}?他们说这是保持堆栈8字节对齐,但不是堆栈4字节对齐?
堆栈只需要4字节对齐;但如果数据总线是64位宽(就像在许多现代ARM上一样),将它保持在8字节对齐更有效。然后,例如,如果您调用需要的函数来堆叠两个寄存器,那么可以在一次64位写入而不是两次32位写入中完成。
更新:显然,这不仅仅是为了提高效率;正如评论中所述,这是官方程序调用标准的要求。
如果您的目标是较旧的32位ARM,那么额外的堆叠寄存器可能会略微降低性能。
什么寄存器是“ip”(即r7或什么?)
r12
。例如,请参阅过程调用标准使用的完整寄存器别名集here。
答案 2 :(得分:3)
因为您希望在执行功能后存储和恢复它们。
在函数entrence上,它保存ip
和lr
寄存器(名为prolog
)。
完成该功能后,它会分配两个(epilog
):
pc <- lr
ip <- old_ip
修改强>
寄存器r12
也称为IP
,用作过程内调用暂存寄存器,请参阅also。
惯例是被调用者函数可以更改ip,r0-r3
,因此您必须根据calling convention
<强> EDIT2:强> Why we might want the stack to be 8 aligned on ARM
如果堆栈不是八字节对齐,则可能使用LDRD和STRD(load and store doubleword) 导致对齐故障,具体取决于目标和配置 使用
注意that we have the same issue on X86和Mac OS we have 16 bytes alignment