ARM技术论坛
直播中

lalajie

8年用户 1228经验值
擅长:模拟技术 存储技术
私信 关注
[经验]

ARMV7指令集中堆栈指针SP的变化说明

首先说明一下,arm中函数调用不同的编译器可能差别很大,即使都是arm-linux的交叉编译器,也有差别,有的编译器把r7寄存器作为栈帧寄存器(fp),有的把r11寄存器作为栈帧指针(fp),例如arm-linux-gnueabihf-gcc用的r7和arm-linux-gnueabi-gcc用的r11,另外在函数执行开头的处理也不一样
1. arm-linux-gnueabihf-gcc编译器先给函数中变量分配栈空间,然后将fp和sp指向栈顶
2. arm-linux-gnueabi-gcc编译器先让fp和sp指向栈顶(栈底),然后再给函数中的变量分配栈空间,之后sp指向新的栈顶,而fp指向的老的栈顶变成了函数的栈底。
个人感觉第二种比较好理解,但是总而言之,每次函数开始执行都需要保存fp指针到栈空间,这里的被保存fp指针是上一个函数的数据(一般是栈底数据),然后立即将本函数的栈底数据保存到fp指针, 函数调用结束,根据fp的数据恢复sp指针,从而释放结束函数的栈空间。
这么看来,函数调用需要入栈的数据只有前一个函数的fp指针,然后跳转的时候会将跳转指令bl的下一条指令地址保存到 lr寄存器,方便函数调用结束后返回。当然如果函数入参很多,寄存器不够用,这些参数同样需要入栈。
下面贴出两种编译器的C代码以及反汇编:
下面我们看C程序:
int m = 8;
int fun(int a,int b)
{
    int c = 0;
    c = a + b;
    return c;
}
int main()
{
    int i = 4;
    int j = 5;
    m = fun(i, j);
    return 0;
}
使用的编译器是arm-linux-gnueabihf-gcc,针对的芯片是I.Mx6ull,对应的反汇编代码:
Disassembly of section .text:
00010094 :
   10094: b480      push {r7}
   10096: b085      sub sp, #20
   10098: af00      add r7, sp, #0
   1009a: 6078      str r0, [r7, #4]
   1009c: 6039      str r1, [r7, #0]
   1009e: 2300      movs r3, #0
   100a0: 60fb      str r3, [r7, #12]
   100a2: 687a      ldr r2, [r7, #4]
   100a4: 683b      ldr r3, [r7, #0]
   100a6: 4413      add r3, r2
   100a8: 60fb      str r3, [r7, #12]
   100aa: 68fb      ldr r3, [r7, #12]
   100ac: 4618      mov r0, r3
   100ae: 3714      adds r7, #20
   100b0: 46bd      mov sp, r7
   100b2: f85d 7b04 ldr.w r7, [sp], #4
   100b6: 4770      bx lr
000100b8
:
   100b8: b580      push {r7, lr}
   100ba: b082      sub sp, #8
   100bc: af00      add r7, sp, #0
   100be: 2304      movs r3, #4
   100c0: 607b      str r3, [r7, #4]
   100c2: 2305      movs r3, #5
   100c4: 603b      str r3, [r7, #0]
   100c6: 6839      ldr r1, [r7, #0]
   100c8: 6878      ldr r0, [r7, #4]
   100ca: f7ff ffe3 bl 10094
   100ce: 4602      mov r2, r0
   100d0: f240 03e4 movw r3, #228 ; 0xe4 全局变量m的地址
   100d4: f2c0 0302 movt r3, #2
   100d8: 601a      str r2, [r3, #0]
   100da: 2300      movs r3, #0
   100dc: 4618      mov r0, r3
   100de: 3708      adds r7, #8
   100e0: 46bd      mov sp, r7
   100e2: bd80      pop {r7, pc}
Disassembly of section .data:
000200e4 :
   200e4: 00000008 andeq r0, r0, r8 /* 这里00000008是全局变量m的值,被解读成andeq指令了 */
使用arm-linux-gnueabi-gcc编译器对应的反汇编代码:
00010400 :
   10400:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
   10404:       e28db000        add     fp, sp, #0
   10408:       e24dd014        sub     sp, sp, #20
   1040c:       e50b0010        str     r0, [fp, #-16]
   10410:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec
   10414:       e3a03000        mov     r3, #0
   10418:       e50b3008        str     r3, [fp, #-8]
   1041c:       e51b2010        ldr     r2, [fp, #-16]
   10420:       e51b3014        ldr     r3, [fp, #-20]  ; 0xffffffec
   10424:       e0823003        add     r3, r2, r3
   10428:       e50b3008        str     r3, [fp, #-8]
   1042c:       e51b3008        ldr     r3, [fp, #-8]
   10430:       e1a00003        mov     r0, r3
   10434:       e24bd000        sub     sp, fp, #0
   10438:       e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
   1043c:       e12fff1e        bx      lr
00010440
:
   10440:       e92d4800        push    {fp, lr}
   10444:       e28db004        add     fp, sp, #4
   10448:       e24dd008        sub     sp, sp, #8
   1044c:       e3a03004        mov     r3, #4
   10450:       e50b300c        str     r3, [fp, #-12]
   10454:       e3a03005        mov     r3, #5
   10458:       e50b3008        str     r3, [fp, #-8]
   1045c:       e51b1008        ldr     r1, [fp, #-8]
   10460:       e51b000c        ldr     r0, [fp, #-12]
   10464:       ebffffe5        bl      10400
   10468:       e1a02000        mov     r2, r0
   1046c:       e59f3010        ldr     r3, [pc, #16]   ; 10484 全局变量m的地址
   10470:       e5832000        str     r2, [r3]
   10474:       e3a03000        mov     r3, #0
   10478:       e1a00003        mov     r0, r3
   1047c:       e24bd004        sub     sp, fp, #4
   10480:       e8bd8800        pop     {fp, pc}
   10484:       00021024        andeq   r1, r2, r4, lsr #32
个人感觉参考第二份反汇编代码比较好理解堆栈的变化过程,其实函数调用就两个要点:
1. 要保存好函数自己的栈底地址,保证函数结束栈空间能够正常释放,但只有一个fp指针,所以使用fp指针存储本函数的栈底地址之前,要将上一个函数的栈底地址入栈也就是,现将fp入栈,然后再使用fp保存新的栈底指针。
2. 使用bl指令,保证函数调用结束能跳回。将bl指令下一条地址保存到lr寄存器中。

原作者:Lamar Davis

更多回帖

发帖
×
20
完善资料,
赚取积分