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 對數據的訪問速度。
https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch32-and-aarch64 ↩︎