ARM汇编【5】:STACK AND FUNCTIONS

       在这一部分中,我们将研究称为堆栈的进程的一个特殊内存区域。本章介绍了Stack的用途和相关操作。此外,我们还将介绍ARM中函数的实现、类型和差异。

STACK      

    一般来说,堆栈是程序/进程中的一个内存区域。这部分内存是在创建进程时分配的。我们使用Stack存储临时数据,如某些函数的局部变量、帮助我们在函数之间转换的环境变量等。我们使用PUSH和POP指令与堆栈交互。正如第4部分:内存指令中所解释的:加载和存储PUSH和POP是其他一些与内存相关的指令的别名,而不是真正的指令,但出于简单的原因,我们使用PUSH和POP。

    在我们研究一个实际的例子之前,我们必须知道Stack可以用各种方式实现。首先,当我们说Stack增长时,我们的意思是一个项目(32位数据)被放在Stack上。堆栈可以向上(当堆栈以降序实现时)或向下(当堆栈按升序实现时)增长。下一条(32位)信息将被放入的实际位置由堆栈指针定义,或者准确地说,由存储在SP寄存器中的存储器地址定义。在这里,地址可以再次指向堆栈中的当前(最后)项或该项的下一个可用内存插槽。如果SP当前指向堆栈中的最后一个项目(全堆栈实现),则SP将减少(如果是降序堆栈)或增加(如果是升序堆栈),然后该项目才会放在堆栈中。如果SP当前指向堆栈中的下一个空插槽,则会先放置数据,然后SP才会减少(降序堆栈)或增加(升序堆栈)。

 作为不同Stack实现的总结,我们可以使用下表,该表描述了在不同情况下使用的存储多条/加载多条指令。

 在我们的示例中,我们将使用全降序堆栈。让我们快速了解一个简单的练习,它处理这样一个堆栈,它就是堆栈指针。

/* azeria@labs:~$ as stack.s -o stack.o && gcc stack.o -o stack && gdb stack */
.global main

main:
     mov   r0, #2  /* set up r0 */
     push  {r0}    /* save r0 onto the stack */
     mov   r0, #3  /* overwrite r0 */
     pop   {r0}    /* restore r0 to it's initial state */
     bx    lr      /* finish the program */

  一开始,堆栈指针指向地址0xbefff6f8(在您的情况下可能不同),它表示堆栈中的最后一项。此时,我们看到它存储了一些值(同样,在您的情况下,值可能不同):

gef> x/1x $sp
0xbefff6f8: 0xb6fc7000

     在执行第一条(MOV)指令后,堆栈方面没有任何变化。当我们执行PUSH指令时,会发生以下情况:首先,SP的值减少4(4字节=32位)。然后,R0的内容被存储到SP指定的新地址。当我们现在检查SP引用的更新的内存位置时,我们看到整数2的32位值存储在该位置:

gef> x/x $sp
0xbefff6f4: 0x00000002

   我们示例中的指令(MOV r0,#3)用于模拟r0的损坏。然后,我们使用POP来恢复以前保存的R0值。因此,当POP被执行时,会发生以下情况:首先,从SP中地址当前指向的存储位置(0xbefff6f4)读取32位数据。然后,SP寄存器的值增加4(再次变为0xbefff6 f8)。作为结果,寄存器R0包含整数值2。

gef> info registers r0
r0       0x2          2

    (请注意,下面的gif显示的堆栈顶部有较低的地址,底部有较高的地址,而不是像第一幅不同堆栈变体的插图中那样相反。这样做的原因是让它看起来像你在GDB中看到的堆栈视图)

 

 

     我们将看到函数利用Stack来保存局部变量、保留寄存器状态等。为了保持一切有序,函数使用Stack Frames,这是堆栈中专用于特定函数的本地化内存部分。在函数的序言中创建了一个堆栈框架(下一节将对此进行详细介绍)。帧指针(FP)被设置到堆栈帧的底部,然后为堆栈帧分配堆栈缓冲区。堆栈帧(从底部开始)通常包含返回地址(前一个LR)、前一个帧指针、需要保留的任何寄存器、函数参数(如果函数接受4个以上)、局部变量等。虽然堆栈帧的实际内容可能有所不同,但前面概述的内容是最常见的。最后,堆栈帧在函数的尾声中被销毁。

以下是堆栈中堆栈帧的抽象说明:

作为Stack Frame可视化的一个快速示例,让我们使用以下代码:

/* azeria@labs:~$ gcc func.c -o func && gdb func */
int main()
{
 int res = 0;
 int a = 1;
 int b = 2;
 res = max(a, b);
 return res;
}

int max(int a,int b)
{
 do_nothing();
 if(a<b)
 {
 return b;
 }
 else
 {
 return a;
 }
}
int do_nothing()
{
 return 0;
}

 在下面的屏幕截图中,我们可以通过GDB调试器的视角看到堆栈帧的简单说明

   我们可以在上图中看到,目前我们即将离开函数max(见底部反汇编中的箭头)。在这种状态下,FP(R11)指向0xbefff254,它是堆栈帧的底部。堆栈上的此地址(绿色地址)存储0x00010418,它是返回地址(以前的LR)。在此之上4个字节(在0xbefff250处),我们有一个值0xbefff26c,它是前一个帧指针的地址。地址0xbefff24c和0xbefff248处的0x1和0x2是在执行函数max期间使用的局部变量。因此,我们刚刚分析的堆栈帧只有LR、FP和两个局部变量。

现在看下,程序代码、寄存器和栈在运行时的变化:

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水火汪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值