以一段簡單的函數調用開始,看看程序的上下文是如何切換的。
工具:Keil5
平台:Cortex-M7
1. 簡單函數調用
1 int func(int a, int b, int c, int d, int e, int f) 2 { 3 int buf[10], i; 4 5 buf[0] = 0; 6 i = a + b + c + d + e + f; 7 8 if (i > 0) 9 { 10 buf[0] = 1; 11 } 12 return i + buf[0]; 13 } 14 15 int main() 16 { 17 int value = func(5, 6, 7, 8, 9, 10); 18 19 return value; 20 }
編譯成功后仿真,看看匯編里做了啥:
1. 程序執行到C代碼的17行 時調用子函數,准備切換下文,首先將func()的傳參從右向左掃描,依次暫存在寄存器中(見下列匯編 1-7行)。由於傳參數多達6個,而寄存器R0-R3不夠用(備注1),只好將末尾的兩個傳參壓入棧中(棧指針會偏移8個字節,備注2)。
1 0x080002E8 200A MOVS r0,#0x0A 2 0x080002EA 2109 MOVS r1,#0x09 3 0x080002EC 2308 MOVS r3,#0x08 4 0x080002EE 2207 MOVS r2,#0x07 5 0x080002F0 E9CD1000 STRD r1,r0,[sp,#0] 6 0x080002F4 2106 MOVS r1,#0x06 7 0x080002F6 2005 MOVS r0,#0x05 8 0x080002F8 F7FFFFE1 BL.W func (0x080002BE)
2. 進入func函數內,首先將上文的R4-R7寄存器及 lr 鏈接地址壓入棧中(備注3),然后繼續准備下文。
首先為數組buf開辟內存空間,大小為sp棧指針偏移0x28=40字節。
接下來要將傳參准備好,由於有兩個傳參在棧中,需要透過sp指針取出,見下列匯編 第7行,此處對sp的指針偏移了0x3C=60字節,剛好就是(0x28=40 + (R4-R7)*4 =16 + lr*4=4)的長度,偏移回了傳參壓棧時的位置。
匯編9-10行要對buf[0]賦0操作,由於buf剛在棧中分配完,且棧的生長方向向下,因此sp此時指向的地址為buf[10]的最低位,即buf[0],STR指令的sp偏移即為0x00.
繼續往下看,r1-r4,r6-r7保存了6個傳參值,求和的結果最終存儲在r5中
后續對sp及r0、r5的操作大同小異,不再贅述。
1 57: { 2 58: int buf[10], i; 3 59: 4 0x080002BE B5F0 PUSH {r4-r7,lr} 5 0x080002C0 B08A SUB sp,sp,#0x28 6 0x080002C2 4604 MOV r4,r0 7 0x080002C4 E9DD670F LDRD r6,r7,[sp,#0x3C] 8 60: buf[0] = 0; 9 0x080002C8 2000 MOVS r0,#0x00 10 0x080002CA 9000 STR r0,[sp,#0x00] 11 61: i = a + b + c + d + e + f; 12 62: 13 0x080002CC 1860 ADDS r0,r4,r1 14 0x080002CE 4410 ADD r0,r0,r2 15 0x080002D0 4418 ADD r0,r0,r3 16 0x080002D2 4430 ADD r0,r0,r6 17 0x080002D4 19C5 ADDS r5,r0,r7 18 63: if (i > 0) 19 64: { 20 0x080002D6 2D00 CMP r5,#0x00 21 0x080002D8 DD01 BLE 0x080002DE 22 65: buf[0] = 1; 23 66: } 24 0x080002DA 2001 MOVS r0,#0x01 25 0x080002DC 9000 STR r0,[sp,#0x00] 26 67: return i + buf[0]; 27 0x080002DE 9800 LDR r0,[sp,#0x00] 28 0x080002E0 4428 ADD r0,r0,r5 29 68: }
思考
備注1. 為什么傳參只用到r0-r3寄存器?
經搜索,在《The ARM-THUMB Procedure Call Standard》手冊上對寄存器的使用做了如下限定:
備注2. 什么時候sp指針會移動?
特殊的指令在執行時SP指針會移動,例如:PUSH/POP LDR/STR 等。
備注3. r4-r10保持上文的信息,每次入棧的寄存器數為何不一樣?
根據本文例子1/2的觀察看,不是每次都會將所有的臨時寄存器r4-r11入棧,而是要看上文環境所使用到的寄存器個數而定。
2. 函數遞歸調用
若將func() 函數改造為遞歸調用,則棧的情況又如何呢?
1 int func(int a, int b, int c, int d, int e, int f) 2 { 3 int buf[10], i; 4 5 buf[0] = 0; 6 i = a + b + c + d + e + f; 7 8 if (i > 0) 9 { 10 buf[0] = func(a-1, b-1, c-1, d-1, e-1, f-1); 11 } 12 return i + buf[0]; 13 } 14 15 int main() 16 { 17 int value = func(5, 6, 7, 8, 9, 10); 18 19 return value; 20 }
與章節1相比,只是改動了第10行,編譯下看看效果。
1. 首先是傳參的掃描處理,跟章節1完全一樣
1 0x0800030C 200A MOVS r0,#0x0A 2 0x0800030E 2109 MOVS r1,#0x09 3 0x08000310 2308 MOVS r3,#0x08 4 0x08000312 2207 MOVS r2,#0x07 5 0x08000314 E9CD1000 STRD r1,r0,[sp,#0] 6 0x08000318 2106 MOVS r1,#0x06 7 0x0800031A 2005 MOVS r0,#0x05 8 0x0800031C F7FFFFCF BL.W func (0x080002BE)
2. 進入函數內,首先不同的是壓棧的指針數,由之前的 r4-r7 增加到 r4-r10,
sp指針的偏移也由0x28增加到0x30,多出8個字節,
匯編第6行-20行為buf[0]及i的初始化,執行過程沒有變化,除了采用了不同的寄存器組
在if語句中,改成遞歸后的變化為:因為傳參眾多,此時寄存器r0-r10都被占用(r10存着 i的臨時值),這就是為什么在遞歸調用本函數之前,入棧的寄存器增加到r10
此外還記得章節1中寄存器不足,需要壓棧兩個傳參嗎,這兩個傳參剛好放在sp多分配出來的8個字節空間里,其它的傳參依然還是在r0-r3中。
最終遞歸調用的上下文環境與第一次調用時保持了一致。
1 58: { 2 59: int buf[10], i; 3 60: 4 0x080002BE E92D47F0 PUSH {r4-r10,lr} 5 0x080002C2 B08C SUB sp,sp,#0x30 6 0x080002C4 4604 MOV r4,r0 7 0x080002C6 460D MOV r5,r1 8 0x080002C8 4616 MOV r6,r2 9 0x080002CA 461F MOV r7,r3 10 0x080002CC E9DD8914 LDRD r8,r9,[sp,#0x50] 11 61: buf[0] = 0; 12 0x080002D0 2000 MOVS r0,#0x00 13 0x080002D2 9002 STR r0,[sp,#0x08] 14 62: i = a + b + c + d + e + f; 15 63: 16 0x080002D4 1960 ADDS r0,r4,r5 17 0x080002D6 4430 ADD r0,r0,r6 18 0x080002D8 4438 ADD r0,r0,r7 19 0x080002DA 4440 ADD r0,r0,r8 20 0x080002DC EB000A09 ADD r10,r0,r9 21 64: if (i > 0) 22 65: { 23 0x080002E0 F1BA0F00 CMP r10,#0x00 24 0x080002E4 DD0C BLE 0x08000300 25 66: buf[0] = func(a-1, b-1, c-1, d-1, e-1, f-1); 26 67: } 27 0x080002E6 F1A90101 SUB r1,r9,#0x01 28 0x080002EA F1A80001 SUB r0,r8,#0x01 29 0x080002EE 1E7B SUBS r3,r7,#1 30 0x080002F0 1E72 SUBS r2,r6,#1 31 0x080002F2 E9CD0100 STRD r0,r1,[sp,#0] 32 0x080002F6 1E69 SUBS r1,r5,#1 33 0x080002F8 1E60 SUBS r0,r4,#1 34 0x080002FA F7FFFFE0 BL.W func (0x080002BE) 35 0x080002FE 9002 STR r0,[sp,#0x08] 36 68: return i + buf[0]; 37 0x08000300 9802 LDR r0,[sp,#0x08] 38 0x08000302 4450 ADD r0,r0,r10 39 69: }