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不同工作模式有不同的栈,定义栈的技巧是什么,避免定义相同的地址使用不同栈?
转自:http://blog.csdn.net/u011467781/article/details/39559737