ARM 過程調用標準 APCS 以及堆棧使用

APCS

ARM 過程調用標準 (ARM Procedure Call Standard)。除此之外,還有一種說法, ARM Application Procedure Call Standard (AAPCS)。

APCS 定義了:

  • 對寄存器使用的限制。
  • 使用棧的慣例。
  • 在函數調用之間傳遞/返回參數。
  • 可以被"回溯"的基於棧的結構的格式,用來提供從失敗點到程序入口函數的列表,包括函數參數。

有了這個約定,編譯器生成代碼時纔有參照,我們使用內聯彙編或者彙編編寫代碼時也纔有了參考,不至於各行其是,引起程序異常。

arm

默認的情況下,這些寄存器只是叫做 r0,r1,...,r14 等,在彙編器的支持下大寫也是可以接受的,而 APCS 對其起了不同的別名。
在這裏插入圖片描述

r0-r15 and R0-R15
a1-a4 (argument, result, or scratch registers, synonyms for r0 to r3)
v1-v8 (variable registers, r4 to r11)
sb and SB (static base, r9)
ip and IP (intra-procedure-call scratch register, r12)
sp and SP (stack pointer, r13)
lr and LR (link register, r14)
pc and PC (program counter, r15).

aarch641

接下來看下 aarch64 的函數調用過程標準。

int funcB(int, int);
int funcC(int, int);

int funcA(int a, int b) {
    int ret = funcB(a, b);
    return ret;
}

int funcB(int a, int b) {
    return funcC(a, b);
}

int funcC(int a, int b) {
    int c = a + b;
    return c;
}

int main(int argc, char *argv[])
{
        return 0;
}

A->B->C。從函數是否調用了其他函數的角度看,可分爲葉子函數、非葉子函數:
葉子函數:函數內部沒有調用其他函數了,例如上面的 funcC。
非葉子函數:函數內部還調用了其他的函數,例如上面的 funcA、funcB。

首先認識一下寄存器。

name number mark
fp x29 存着函數調用棧棧底,棧幀寄存器。
lr x30 存着函數返回地址
sp x31 存着函數調用棧棧頂,堆棧指針寄存器

以下是 O0 編譯的結果。

0000000000400524 <funcA>:
  400524:	a9bd7bfd 	stp	x29, x30, [sp, #-48]!
  400528:	910003fd 	mov	x29, sp
  40052c:	b9001fa0 	str	w0, [x29, #28]
  400530:	b9001ba1 	str	w1, [x29, #24]
  400534:	b9401ba1 	ldr	w1, [x29, #24]
  400538:	b9401fa0 	ldr	w0, [x29, #28]
  40053c:	94000005 	bl	400550 <funcB>		  // call the funcB, return address in the lr
  400540:	b9002fa0 	str	w0, [x29, #44]
  400544:	b9402fa0 	ldr	w0, [x29, #44]
  400548:	a8c37bfd 	ldp	x29, x30, [sp], #48
  40054c:	d65f03c0 	ret

0000000000400550 <funcB>:
  400550:	a9be7bfd 	stp	x29, x30, [sp, #-32]! // 保護現場,fp & lr. sp 下移 32 bytes
  400554:	910003fd 	mov	x29, sp				  // fp = sp
  400558:	b9001fa0 	str	w0, [x29, #28]		  // prepare the parameters
  40055c:	b9001ba1 	str	w1, [x29, #24]		  // Use fp to locate the para, why not sp ?
  400560:	b9401ba1 	ldr	w1, [x29, #24]
  400564:	b9401fa0 	ldr	w0, [x29, #28]
  400568:	94000003 	bl	400574 <funcC>		  // call the funcC
  40056c:	a8c27bfd 	ldp	x29, x30, [sp], #32	  // restore the fp & lr, and sp 上移動 32 bytes,回收棧空間
  400570:	d65f03c0 	ret

0000000000400574 <funcC>:
  400574:	d10083ff 	sub	sp, sp, #0x20		  // sp 下移 32 bytes。這裏沒有再存 lr & fp
  400578:	b9000fe0 	str	w0, [sp, #12]		  // 原因是 funcC 是葉子函數,裏面沒有 bl 指令等涉及更改 lr 的指令
  40057c:	b9000be1 	str	w1, [sp, #8]		  // 且下面的指令均使用 sp 去尋址,也沒有存 fp。
  400580:	b9400fe1 	ldr	w1, [sp, #12]
  400584:	b9400be0 	ldr	w0, [sp, #8]
  400588:	0b000020 	add	w0, w1, w0
  40058c:	b9001fe0 	str	w0, [sp, #28]
  400590:	b9401fe0 	ldr	w0, [sp, #28]
  400594:	910083ff 	add	sp, sp, #0x20		  // restore the sp, 回收棧空間
  400598:	d65f03c0 	ret

arm 處理器的棧是滿降序棧,由高地址往低地址增長,sp 總是指向在當前棧楨中使用的最低地址。aarch64 架構開闢棧空間是 16 字節的倍數,這是 aarch64 對棧對齊的要求,同時也許有助於充分利用 cache 的寬度,加快 CPU 對數據的訪問速度。


  1. https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch32-and-aarch64 ↩︎

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