1. 程式人生 > >棧的初始化及棧幀概念解析--國嵌第三季--專題10 課程1

棧的初始化及棧幀概念解析--國嵌第三季--專題10 課程1

1、棧:FILO先進後出的資料結構 棧底是第一個進棧的資料的位置(壓箱 底)  棧頂是最後一個進棧的資料位置 2、根據SP指標指向的位置,棧可分為 滿棧和空棧  滿棧:當sp指標總是指向最後壓入堆疊 的資料(ARM採用滿棧)   空棧:當堆疊指標SP總是指向下一個將 要放入資料的空位置。   3、根據SP指標移動的方向,可分為升 棧和降棧  升棧:隨資料的入棧,SP由低地址--> 高地址 
降棧:隨資料的入棧,SP由高地址--> 低地址(ARM採用降棧)   4、棧幀:儲存在使用者棧上的(當然核心棧同樣適用)每一次函式呼叫涉及的相關資訊的記錄單元 ; 棧幀(stack frame)就是一個函式所
使用的那部分棧,所有函式的棧幀串起來就組成了一個完整的棧。 棧幀的兩個邊界分別有FP(R11)和SP(R13)L來限定。                  棧幀
棧的作用: 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不同工作模式有不同的棧,定義棧的技巧是什麼,避免定義相同的地址使用不同棧?