网络安全

ARM栈结构

2020-12-08  本文已影响0人  骑猪满天飞

ARM 栈类型

根据栈生长方向,ARM的栈可分为递增堆栈和递减堆栈。

根据栈顶指针的位置,又可分为满堆栈与空堆栈:

由此可以有四种数据栈:
空递增、空递减、满递增、满递减(sp为栈指针)


stacktype.PNG

四种数据栈对应的Load/Store指令如下:


loadstore.PNG

根据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
上一篇 下一篇

猜你喜欢

热点阅读