栈的初始化及栈帧概念解析--国嵌第三季--专题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不同工作模式有不同的栈,定义栈的技巧是什么,避免定义相同的地址使用不同栈?

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值