棧的初始化及棧幀概念解析--國嵌第三季--專題10 課程1
阿新 • • 發佈:2019-01-25
1、棧:FILO先進後出的資料結構
棧底是第一個進棧的資料的位置(壓箱 底)
棧頂是最後一個進棧的資料位置
2、根據SP指標指向的位置,棧可分為 滿棧和空棧
滿棧:當sp指標總是指向最後壓入堆疊 的資料(ARM採用滿棧)
空棧:當堆疊指標SP總是指向下一個將 要放入資料的空位置。
3、根據SP指標移動的方向,可分為升 棧和降棧
升棧:隨資料的入棧,SP由低地址--> 高地址
降棧:隨資料的入棧,SP由高地址--> 低地址(ARM採用降棧) 4、棧幀:儲存在使用者棧上的(當然核心棧同樣適用)每一次函式呼叫涉及的相關資訊的記錄單元 ; 棧幀(stack frame)就是一個函式所 使用的那部分棧,所有函式的棧幀串起來就組成了一個完整的棧。
棧幀的兩個邊界分別有FP(R11)和SP(R13)L來限定。
棧幀
棧的作用: 1)儲存區域性變數 分析程式碼:
反彙編之後的程式碼;
2)儲存函式的引數 分析程式碼:
注:C中,若函式的引數小於等於4個,則用通用暫存器儲存其引數值,多於4個的引數儲存在棧中 3)儲存暫存器的值 分析程式碼:
反彙編之後的程式碼;
初始化棧:即對SP指標賦予一個記憶體地址(統一標準:2440、6410、210) 在記憶體的64MB位置即ldr sp, =0x34000000(2440) ldr sp, =0x54000000(6410) ldr sp, =0x24000000(210) 由上可知ARM採用滿棧(指向剛入棧的資料)、降棧(由高地址向低地址入棧) 問題:因為ARM不同工作模式有不同的棧,定義棧的技巧是什麼,避免定義相同的地址使用不同棧?
降棧:隨資料的入棧,SP由高地址--> 低地址(ARM採用降棧) 4、棧幀:儲存在使用者棧上的(當然核心棧同樣適用)每一次函式呼叫涉及的相關資訊的記錄單元 ; 棧幀(stack frame)就是一個函式所
棧的作用: 1)儲存區域性變數 分析程式碼:
#include <stdio.h>
int main()
{
int a;
a++;
return a;
}</span>
反彙編之後的程式碼;
stack: file format elf32-littlearm Disassembly of section .text: 00000000 <main>: #include <stdio.h> int main() { 0: e52db004 push {fp} ; (str fp, [sp, #-4]!) @將棧幀底部指標FP壓入棧中;建立屬於main函式的棧幀。 4: e28db000 add fp, sp, #0 ; 0x0 @fp指標為函式棧幀的底部, 8: e24dd00c sub sp, sp, #12 ; 0xc @sp指標為棧幀的頂部,同時為棧的棧頂。 int a; a++; c: e51b3008 ldr r3, [fp, #-8] @由此三句可知變數a在棧幀中執行了加法操作,及棧幀具有儲存區域性變數的作用 10: e2833001 add r3, r3, #1 ; 0x1 14: e50b3008 str r3, [fp, #-8] return a; 18: e51b3008 ldr r3, [fp, #-8] } </span>
2)儲存函式的引數 分析程式碼:
<span style="font-size:18px;">#include <stdio.h> void func1(int a,int b,int c,int d,int e,int f) { int k; k=e+f; } int main() { func1(1,2,3,4,5,6); return 0; } 反彙編之後的程式碼; void func1(int a,int b,int c,int d,int e,int f) @多於4個引數 { 0: e52db004 push {fp} ; (str fp, [sp, #-4]!)@儲存main函式的棧幀底部指標FP 4: e28db000 add fp, sp, #0 ; 0x0 8: e24dd01c sub sp, sp, #28 ; 0x1c @由棧幀頂部指標SP建立一片棧幀儲存子函式的前四個引數 c: e50b0010 str r0, [fp, #-16] @ a 10: e50b1014 str r1, [fp, #-20] @ b 14: e50b2018 str r2, [fp, #-24] @ c 18: e50b301c str r3, [fp, #-28] @ d int k; k=e+f; 1c: e59b3004 ldr r3, [fp, #4] @在子函式的棧幀中實現第五個引數與第六個引數的運算 20: e59b2008 ldr r2, [fp, #8] @由ldr r2, [fp, #8]知引數儲存在main函式的棧幀中,並運算 24: e0833002 add r3, r3, r2 @以子函式的棧幀底部指標(fp)做參考座標實現對引數的查詢 28: e50b3008 str r3, [fp, #-8] } 2c: e28bd000 add sp, fp, #0 ; 0x0 30: e8bd0800 pop {fp} 34: e12fff1e bx lr 00000038 <main>: int main() { 38: e92d4800 push {fp, lr} @由於呼叫子函式,先儲存main函式的棧幀底部指標FP和返回地址LR(當前PC指標的下一地址) 3c: e28db004 add fp, sp, #4 ; 0x4 @可知先壓入FP,後壓入lr.把此時子函式(被呼叫者)的棧幀底部指標FP指向儲存在子函式棧幀的main函式(呼叫者)的棧幀底部指標FP 40: e24dd008 sub sp, sp, #8 ; 0x8 @建立棧 func1(1,2,3,4,5,6); 44: e3a03005 mov r3, #5 ; 0x5 48: e58d3000 str r3, [sp] 4c: e3a03006 mov r3, #6 ; 0x6 50: e58d3004 str r3, [sp, #4] 54: e3a00001 mov r0, #1 ; 0x1 @用通用暫存器儲存前四個引數的值 58: e3a01002 mov r1, #2 ; 0x2 5c: e3a02003 mov r2, #3 ; 0x3 60: e3a03004 mov r3, #4 ; 0x4 64: ebfffffe bl 0 <func1> return 0; 68: e3a03000 mov r3, #0 ; 0x0 } 6c: e1a00003 mov r0, r3 70: e24bd004 sub sp, fp, #4 ; 0x4 74: e8bd4800 pop {fp, lr} 78: e12fff1e bx lr</span>
注:C中,若函式的引數小於等於4個,則用通用暫存器儲存其引數值,多於4個的引數儲存在棧中 3)儲存暫存器的值 分析程式碼:
<span style="font-size:18px;">include <stdio.h>
void func2(int a,int b)
{
int k;
k=a+b;
}
void func1(int a,int b)
{
int c;
func2(3,4);
c=a+b;
}
int main()
{
func1(1,2);
return 0;
}</span>
反彙編之後的程式碼;
<span style="font-size:18px;">void func2(int a,int b)
{
0: e52db004 push {fp} ; (str fp, [sp, #-4]!)
4: e28db000 add fp, sp, #0 ; 0x0
8: e24dd014 sub sp, sp, #20 ; 0x14
c: e50b0010 str r0, [fp, #-16] @儲存暫存器的值
10: e50b1014 str r1, [fp, #-20]
int k;
k=a+b;
14: e51b3010 ldr r3, [fp, #-16]
18: e51b2014 ldr r2, [fp, #-20]
1c: e0833002 add r3, r3, r2
20: e50b3008 str r3, [fp, #-8]
}
24: e28bd000 add sp, fp, #0 ; 0x0
28: e8bd0800 pop {fp}
2c: e12fff1e bx lr
00000030 <func1>:
void func1(int a,int b)
{
30: e92d4800 push {fp, lr}
34: e28db004 add fp, sp, #4 ; 0x4
38: e24dd010 sub sp, sp, #16 ; 0x10
3c: e50b0010 str r0, [fp, #-16] @程式碼44行呼叫func2函式後,又使用r0\r1儲存引數,所以此時將r0\r1暫存器的
40: e50b1014 str r1, [fp, #-20] @值放入棧中
int c;
func2(3,4);
44: e3a00003 mov r0, #3 ; 0x3
48: e3a01004 mov r1, #4 ; 0x4
4c: ebfffffe bl 0 <func2>
c=a+b;
50: e51b3010 ldr r3, [fp, #-16]
54: e51b2014 ldr r2, [fp, #-20]
58: e0833002 add r3, r3, r2
5c: e50b3008 str r3, [fp, #-8]
}
60: e24bd004 sub sp, fp, #4 ; 0x4
64: e8bd4800 pop {fp, lr}
68: e12fff1e bx lr
0000006c <main>:
int main()
{
6c: e92d4800 push {fp, lr}
70: e28db004 add fp, sp, #4 ; 0x4
func1(1,2);
74: e3a00001 mov r0, #1 ; 0x1
78: e3a01002 mov r1, #2 ; 0x2
7c: ebfffffe bl 30 <func1>
return 0;
80: e3a03000 mov r3, #0 ; 0x0
}
84: e1a00003 mov r0, r3
88: e24bd004 sub sp, fp, #4 ; 0x4
8c: e8bd4800 pop {fp, lr}
90: e12fff1e bx lr</span>
初始化棧:即對SP指標賦予一個記憶體地址(統一標準:2440、6410、210) 在記憶體的64MB位置即ldr sp, =0x34000000(2440) ldr sp, =0x54000000(6410) ldr sp, =0x24000000(210) 由上可知ARM採用滿棧(指向剛入棧的資料)、降棧(由高地址向低地址入棧) 問題:因為ARM不同工作模式有不同的棧,定義棧的技巧是什麼,避免定義相同的地址使用不同棧?