ARM 栈类型
根据栈生长方向,ARM的栈可分为递增堆栈和递减堆栈。
- 递增堆栈:栈向高地址生长
- 递减堆栈:栈向低地址生长
根据栈顶指针的位置,又可分为满堆栈与空堆栈:
- 满堆栈:栈指针指向栈顶元素(即最后一个入栈的数据元素)
- 空堆栈:栈指针指向与栈顶元素相邻的一个可用书局单元时称为空栈
由此可以有四种数据栈:
空递增、空递减、满递增、满递减(sp为栈指针)
四种数据栈对应的Load/Store指令如下:
根据ATPCS规则,我们一般使用FD(FullDescending)类型的数据栈,经常使用的指令为STMFD和LDMFD。
下面用具体例子说明:
.section .text
.global _start
_start:
mov r0,#0
mov r1,#1
mov r2,#2
stmfd sp,{r0-r2}
mov r3,#3
mov r4,#4
mov r5,#5
stmfd sp!,{r3-r5}
ldmfd sp,{r3-r5}
ldmfd sp!,{r3-r5}
pi@raspberrypi:~ $ as test.s -o test.o
pi@raspberrypi:~ $ ld test.o -o test
使用GDB调试如下:
gef➤ b _start
Breakpoint 1 at 0x10054
gef➤ disassemble _start
Dump of assembler code for function _start:
0x00010054 <+0>: mov r0, #0
0x00010058 <+4>: mov r1, #1
0x0001005c <+8>: mov r2, #2
0x00010060 <+12>: stmdb sp, {r0, r1, r2}
0x00010064 <+16>: mov r3, #3
0x00010068 <+20>: mov r4, #4
0x0001006c <+24>: mov r5, #5
0x00010070 <+28>: push {r3, r4, r5}
0x00010074 <+32>: ldm sp, {r3, r4, r5}
0x00010078 <+36>: pop {r3, r4, r5}
End of assembler dump.
gef➤ r
gef➤ nexti 3
gef➤ p $sp
$2 = (void *) 0x7efff6b0
gef➤ x/10x $sp-12
0x7efff6a4: 0x00000000 0x00000000 0x00000000 0x00000001
0x7efff6b4: 0x7efff7d9 0x00000000 0x7efff7e7 0x7efff7f7
0x7efff6c4: 0x7efff806 0x7efff813
此时堆栈信息如下:
+-------------+ 低地址
|0x00000000 |
+-------------+
|0x00000000 |
+-------------+
|0x00000000 |
+-------------+
|0x00000001 | <- sp
+-------------+
|0x7efff7d9 |
+-------------+
|0x00000000 |
+-------------+ 高地址
执行完 stmfd sp,{r0-r2}后堆栈信息:
gef➤ next
+-------------+ 低地址
|0x00000000 |
+-------------+
|0x00000001 |
+-------------+
|0x00000002 |
+-------------+
|0x00000001 | <- sp
+-------------+
|0x7efff7d9 |
+-------------+
|0x00000000 |
+-------------+ 高地址
stmfd指令压栈顺序为r2,r1,r0且sp不变
与之对应ldmfd sp , {r3-r5},将栈上上数据传到寄存器,且sp不变
stmfd sp!,{r3-r5}指令执行后堆栈情况如下:
+-------------+ 低地址
|0x00000003 | <- sp
+-------------+
|0x00000004 |
+-------------+
|0x00000005 |
+-------------+
|0x00000001 |
+-------------+
|0x7efff7d9 |
+-------------+
|0x00000000 |
+-------------+ 高地址
stmfd sp!,{r3-r5}指令:每次入栈前sp加4,与x86 push指令作用相同。
与之对应ldmfd sp! , {r3-r5} 与x86 pop指令作用相同。
叶子函数与非叶子函数
如果一个函数A中不再调用其他任何函数,那么当前的函数A就是一个叶子函数,否则函数A就是一个非叶子函数。
ARM中用LR寄存器保存返回地址,被调函数执行完后通过LR返回调用函数。
当函数为非叶子函数时,因为后续会再次调用其它函数,LR寄存器的值会被改变,所以在进入非叶子函数时会将LR寄存器入栈。叶子函数则不会将LR寄存器入栈。
具体例子说明:
#include <stdio.h>
void noleaf(int a,int b){
int c;
c=a+b;
printf("%d",c);
}
int leaf(int a,int b){
return a+b;
}
void main()
{
int a=1,b=1;
noleaf(a,b);
leaf(a,b);
}
gcc subcall.c -o subcall
查看汇编代码,非叶子函数:
00010408 <noleaf>:
10408: e92d4800 push {fp, lr}
1040c: e28db004 add fp, sp, #4
10410: e24dd010 sub sp, sp, #16
10414: e50b0010 str r0, [fp, #-16]
10418: e50b1014 str r1, [fp, #-20] ; 0xffffffec
1041c: e51b2010 ldr r2, [fp, #-16]
10420: e51b3014 ldr r3, [fp, #-20] ; 0xffffffec
10424: e0823003 add r3, r2, r3
10428: e50b3008 str r3, [fp, #-8]
1042c: e51b1008 ldr r1, [fp, #-8]
10430: e59f000c ldr r0, [pc, #12] ; 10444 <noleaf+0x3c>
10434: ebffffab bl 102e8 <printf@plt>
10438: e1a00000 nop ; (mov r0, r0)
1043c: e24bd004 sub sp, fp, #4
10440: e8bd8800 pop {fp, pc}
10444: 00010528 .word 0x00010528
非子叶函数先将fp,与lr入栈。fp为栈底寄存器与x86的ebp作用相同,保存的值为main函数的栈底地址(oldfp),lr中则保存着函数返回main函数后需要执行的下一条指令。
1040c: e28db004 add fp, sp, #4
10410: e24dd010 sub sp, sp, #16
该指令设置新的栈底,开辟noleaf函数的栈空间
最后执行1041c: e8bd8800 pop {fp, pc}
恢复main函数fp,然后将pc寄存器置为栈上保存的lr值,执行main函数中需要执行的下一条指令。
叶子函数:
00010448 <leaf>:
10448: e52db004 push {fp} ; (str fp, [sp, #-4]!)
1044c: e28db000 add fp, sp, #0
10450: e24dd00c sub sp, sp, #12
10454: e50b0008 str r0, [fp, #-8]
10458: e50b100c str r1, [fp, #-12]
1045c: e51b2008 ldr r2, [fp, #-8]
10460: e51b300c ldr r3, [fp, #-12]
10464: e0823003 add r3, r2, r3
10468: e1a00003 mov r0, r3
1046c: e28bd000 add sp, fp, #0
10470: e49db004 pop {fp} ; (ldr fp, [sp], #4)
10474: e12fff1e bx lr
叶子函数不需要将lr入栈,最后使用bx lr直接跳转返回main函数执行下一条指令。
栈结构
在函数调用过程中ARM使用r0-r3四个寄存器传递参数,多余参数的直接放入堆栈中,函数返回值存放到r0。
进行函数调用时,根据被调函数是否为非叶子函数,判断是否将lr压栈。
上节非叶子函数,参数a=1,b=1,通过r0、r1传递,被调用时栈结构如下:
+-------------+ 低地址
|0x00000001 | <- sp,参数b
+-------------+
|0x00000001 | <- 参数a
+-------------+
|0x00000005 |
+-------------+
|0x00000002 | <-c 局部变量
+-------------+
| oldfp |
+-------------+
| lr | <- fp
+-------------+ 高地址
叶子函数被调用时的栈结构如下:
+-------------+ 低地址
|0x00000001 | <- sp,b
+-------------+
|0x00000001 | <-sp,参数b
+-------------+
|0x00000001 | <- 参数a
+-------------+
|xxxxxxxxxx |
+-------------+
| oldfp | <- fp
+-------------+ 高地址
10464: e0823003 add r3, r2, r3
10468: e1a00003 mov r0, r3
函数返回值保存到r0
网友评论