ARM函数切换栈帧分析

1. 将lr寄存器和fp(r7)寄存器压栈,lr寄存器现在是main函数的返回地址,fp寄存器是上一个函数的栈帧基址。 2. 在栈中开辟一块空间用来存放局部变量和传参变量,将sp指…

func(int, int):
        push    {r7}
        sub     sp, sp, #20
        add     r7, sp, #0
        str     r0, [r7, #4]
        str     r1, [r7]
        ldr     r3, [r7, #4]
        ldr     r2, [r7]
        mul     r3, r2, r3
        ldr     r2, [r7]
        add     r3, r3, r2
        str     r3, [r7, #12]
        ldr     r2, [r7, #12]
        ldr     r3, [r7, #4]
        add     r3, r3, r2
        str     r3, [r7, #12]
        ldr     r3, [r7, #12]
        mov     r0, r3
        adds    r7, r7, #20
        mov     sp, r7
        ldr     r7, [sp], #4
        bx      lr
main:
        push    {r7, lr}
        sub     sp, sp, #16
        add     r7, sp, #0
        movs    r3, #1
        str     r3, [r7, #12]
        movs    r3, #2
        str     r3, [r7, #8]
        ldr     r1, [r7, #8]
        ldr     r0, [r7, #12]
        bl      func(int, int)
        str     r0, [r7, #4]
  1. 将lr寄存器和fp(r7)寄存器压栈,lr寄存器现在是main函数的返回地址,fp寄存器是上一个函数的栈帧基址。
  2. 在栈中开辟一块空间用来存放局部变量和传参变量,将sp指针的值赋给fp寄存器,作为main函数的栈帧基址。此处的fp寄存器,只保存局部变量,如果是传参变量则相当于是另一个函数的栈帧。
  3. 将局部变量存储到,以fp寄存器为基址的栈帧中。
  4. ARM规定默认传参放在r0,r1,r2,r3寄存器中,多于4个的参数会被压入栈中。
  5. 跳转到调用的函数。
  6. 完成函数调用。
  7. 恢复sp指针到函数调用前的位置,恢复fp寄存器,跳转到lr寄存器保存的位置。

Note

不同的编译器,不同的优化等级都会有不同的栈帧操作。

但是很多时候在caller中都不需要保存r0-r3,因为变量都直接存放在栈中了,r4-r11寄存器在简单逻辑中很少使用,具体如何操作都依赖于编译器的优化。

但是不管如何,在直接编写汇编代码时,还是需要根据AAPCS进行正确的寄存器保存。

总的来说,进行函数调用时,栈帧的操作大致为:

  • 保存fp栈帧基址和lr链接寄存器。
  • 局部变量保存到栈中。
  • 进行函数操作。
  • 还原fp寄存器。
  • 跳转回调用函数。