> 文档中心 > 【ARM汇编的堆栈问题】压栈运用|堆栈类型测试

【ARM汇编的堆栈问题】压栈运用|堆栈类型测试


【ARM汇编的堆栈问题】


☀️作者简介:大家好我是IM汤姆凯特,大家可以叫我汤姆
🐋个人主页:IM汤姆凯特的CSDN博客
🎁系列专栏:【ARM嵌入式基础】
🌱每日一句:
“假如你花7美元买了一张电影票,你怀疑这个电影是否值7美元。看了半个小时后,你最担心的事被证实了:影片糟透了。你应该离开影院吗?在做这个决定时,你应当忽视那7美元。它是沉没成本,无论你离开影院与否,钱都不会再收回。” —— ——斯蒂格利茨(2001年诺贝尔经济学奖获得者)


今天来带大家了解ARM汇编的堆栈问题

  1. ARM汇编中压栈能解决什么问题?

  2. ARM微处理器有哪几种类型的堆栈工作方式呢?

  3. 堆栈类型又如何检验呢?

写在前面:为什么必须要讲ARM的堆栈问题,因为如果没有堆栈的运用很多问题是没有办法解决的,之前有文章讲到,只能用寄存器R0~R3来传递参数,因此当你需要传递多个参数的时候就会收到限制,该怎么解决?这里就引入了压栈和出栈概念的运用。

看完前面写的文章,大家肯定学会了如何从内存获取地址、如何读取数据、如何调用scanf和printf,但只能输入和输出三个数据,今就带大家学习一下如何运用栈输入输出4个数据甚至更多。

怎么一次性输出多个数据呢?

其实很简单,只需要我们在ARM汇编中栈的运用。用于传递参数的寄存器只有四个R0~R3,也称为易失性寄存器,当一次需要传递的参数较多时,我们可以先把数据压到栈中,这样我们把R0~R3的数据读取完毕后,就会优先读取栈顶中的数据依次类推,如果不压入栈中,自然就没有办法读取到。

💫没有接触栈概念时的思路(从内存中读取地址——将内容传给寄存器——调用printf显示)

源代码:

.data    str:.asciz " %5d\n %5d\n %5d\n %5d\n"    a:.word 1    b:.word 2    c:.word 3    d:.word 4.text.globl mainmain:    stmfd sp!,{lr}    ldr r0,=a    ldr r1,[r0]    ldr r0,=b    ldr r2,[r0]    ldr r0,=c     ldr r3,[r0]    ldr r0,=d     ldr r4,[r0]    ldr r0,=str    bl printf    mov r0, #0    ldmfd sp!,{lr}mov pc,lr.end

运行结果:

【ARM汇编的堆栈问题】压栈运用|堆栈类型测试

如果这一部分代码还是看不明白的话,那就请小可爱自行看前面写的文章了哦。【ARM嵌入式基础】

我们发现第四个数据并不是我们想要的数据,这里为什么会出错呢?就是因为我们调用printf也好scanf也好只能用R0~R3中的数据,R4中的数据取不到的。


如何压入栈中,输出正确结果呢?

⭐️我们知道了为什么出错,下面就来看看怎么不出错

从上面例子中来看,想输出内存中“d”的值,就要把获取到“d”数据的寄存器压到栈中,让调用printf的时候能找到它。我们看,r4其实已经通过取地址和间接寻址获取到了“d”的数据,那么只需要把r4压到栈中就可以。

压栈的指令在这篇文章中其实已经提到【ARM汇编模板】

//压栈 push {r4}//出栈 pop {r4}

💖这里由于我们调用了printf,调用之后实际上已经把r4出栈,所以不用加pop {r4}

但是当没有运用函数让他出栈时要手动出栈,因为我们一定要遵循堆栈平衡原则,我们来看下一

压栈后的源代码:

.data    str:.asciz " %5d\n %5d\n %5d\n %5d\n"    a:.word 1    b:.word 2    c:.word 3    d:.word 4.text.globl mainmain:    mov r4,#0    stmfd sp!,{lr}    ldr r0,=a    ldr r1,[r0]    ldr r0,=b    ldr r2,[r0]    ldr r0,=c     ldr r3,[r0]    ldr r0,=d     ldr r4,[r0]    push {r4}    ldr r0,=str    bl printf    mov r0, #0    ldmfd sp!,{lr}mov pc,lr.end

运行结果:

【ARM汇编的堆栈问题】压栈运用|堆栈类型测试

👍没错,就是这么简单!大家一定要动手试一下哦


ARM微处理器有哪几种堆栈工作方式呢?

既然提到了栈的运用,就把它给讲明白

堆栈是一种数据结构,按照先进后出的工作方式,使用一个称为堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶

当堆栈指针指向最后压入堆栈的数据时,称为满堆栈,而当堆栈指针指向下一个将要放入数据的空位置时,称为空堆栈。同时,当堆栈由低地址向高地址生成时,称为递增堆栈,当堆栈由高地址向低地址生成时,称为递减堆栈。这样就有4种类型的堆栈工作方式,ARM微处理器支持这四种类型的堆栈工作方式。

1. 满递增堆栈(FA):堆栈指针指向最后压入的数据,且由低地址向高地址生成。
2. 满递减堆栈(FD):堆栈指针指向最后压入的数据,且由高地址向低地址生成。
3. 空递增堆栈(EA):堆栈指针指向下一个将要放入数据的空位置,且由低地址向高地址生成。
4. 空递增堆栈(ED):堆栈指针指向下一个将要放入数据的空位置,且由高地址向低地址生成。

一共就这四种类型,下面我们可以验证一下ARM默认的是哪种堆栈类型


怎么来检测ARM堆栈的类型?

我们了解完堆栈的类型,发现有两个变量:满|空堆栈类型、递增|递减堆栈类型。

这样就得到了一个思路:

1. 通过测试栈顶的内容是不是空,来判断是满堆栈类型还是空堆栈类型 2. 通过测试栈顶的地址是增加还是减少,来判断是递增类型还是递减类型

直接上代码:

.data    fmt:.asciz "\n r4=%d sp=%d lr=%d\n".text.globl mainmain:    push {lr}    ldr r0,=fmt mov r1,r4 mov r2,sp mov r3,lr    bl printf    push {r4}    ldr r0,=fmt ldr r1,[sp] mov r2,sp mov r3,lr    bl printf    mov r0, #0    ldmfd sp!,{lr}    mov pc, lr.end

给大家分开讲解一下:

①把r4中的内容给到r1,把栈顶的内容给到r2,把链接寄存器的内容给r3,然后调用printf输出

②这里的sp是R13寄存器(堆栈指针寄存器)的别名,sp指向栈顶

③ 这里的lr是R14寄存器(链接寄存器)的别名,想通过他是否改变来验证调用printf跳转一次后,寄存器的值是否会发生变化

    ldr r0,=fmt mov r1,r4 mov r2,sp mov r3,lr    bl printf

①先把r4压到栈中

②把栈顶的内容给r1然后输出,目的是为了把r4压到栈中后,比较栈顶和上面r4中的内容,查看r4是不是放到了栈顶,从而来判断是不是满堆栈

③ 再把栈顶的地址给r2输出,目的是为了看现在的地址和上面没有压栈时的地址变化情况,从而判断是递增还是递减

查看运行结果:

【ARM汇编的堆栈问题】压栈运用|堆栈类型测试

得出结论:

  1. 压栈后栈顶的内容和原本r4的内容一直,说明r4到了栈顶,所以是满堆栈。
  2. 查看两次栈顶地址的值,发现减小了,所以是递减堆栈。
  3. 综上所述:ARM的默认堆栈类型就是(FD)满递减堆栈

🎁总结:

  • 可以通过入栈和出栈的方式输入或输入3个以上的数据

  • ARM支持4中堆栈类型,默认是(FD)满递减堆栈,可以通过其他方式来改变你所需的堆栈类型

  • 要学会通过动手实践来验证你的猜想是否正确,这里我们学会了验证ARM堆栈类型的方法


本期就结束了,如果对您有帮助,点赞+评论支持一下博主再走吧 还没有关注汤姆的朋友,点个关注每天学一点汇编 下期预告: ARM汇编数据的批量加载

在这里插入图片描述

China香烟网