armv7指令集 C函數調用 堆棧指針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 <fun>:
   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 <main>:
   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 <fun>
   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 <m>:
   200e4:	00000008 	andeq	r0, r0, r8 /* 這裏00000008是全局變量m的值,被解讀成andeq指令了 */

使用arm-linux-gnueabi-gcc編譯器對應的反彙編代碼(參考自https://blog.csdn.net/melody157398/article/details/104454219,個人感覺此文章給的圖可能有點不對,但是反彙編代碼應該沒問題):

00010400 <fun>:
   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 <main>:
   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 <fun>
   10468:       e1a02000        mov     r2, r0
   1046c:       e59f3010        ldr     r3, [pc, #16]   ; 10484 <main+0x44> 全局變量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寄存器中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章