arm堆栈操作

发布时间:2012-9-17 18:48    发布者:a861388037
关键词: arm
arm堆栈操作
arm堆栈的组织结构是 满栈降 的形式,满栈即sp是要停留在最后一个进栈元素,降:就是堆栈的增长方向是从高地址向低地址发展。
arm对于堆栈的操作一般采用 LDMFD(pop)和STMFD (push) 两个命令。
以前困惑的就是STMFD 命令 对于操作数 是按照什么顺序压栈的
比如:STMFD sp!{R0-R5,LR} 进栈顺序是:
高地址(1方式)
LR
R5
R4
```````
R0   <-sp
低地址
还是:
高地址(2方式)
R0
R1
```
R5
LR <-sp
低地址
现在通过下表,可以轻松的解决这个问题:

寻址方式


说明


pop


=LDM


push


=STM


FA


递增满


LDMFA


LDMDA


STMFA


STMIB


FD


递减满


LDMFD


LDMIA


STMFD


STMDB


EA


递增空


LDMEA


LDMDB


STMEA


STMIA


ED


递减空


LDMED


LDMIB


STMED


STMDA

按照图表,可知 STMFD对应的是STMDB,根据arm指令手册,可知STMDB入栈顺序是(1方式)
而LDMFD对应的是LDMIA,这样这两个操作就可以成功配对:
以下是我在学习ARM指令中记录的关于堆栈方面的知识

1、寄存器 R13 在 ARM 指令中常用作堆栈指针
  2、对于 R13 寄存器来说,它对应6个不同的物理寄存器,其中的一个是用户模式与系统模式共用,另外5个物理寄存器对应于其他5种不同的运行模式。采用以下的记号来区分不同的物理寄存器: R13_ 其中,mode为以下几种模式之一:usr、fiq、irq、svc、abt、und。

  3、寄存器R13在ARM指令中常用作堆栈指针,但这只是一种习惯用法,用户也可使用其他的寄存器作为堆栈指针。而在Thumb指令集中,某些指令强制性的要求使用R13作为堆栈指针。由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行。

  4、有四种类型的堆栈:

  堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用一个称作堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。

  当堆栈指针指向最后压入堆栈的数据时,称为满堆栈(Full Stack),而当堆栈指针指向下一个将要放入数据的空位置时,称为空堆栈(Empty Stack)。

  同时,根据堆栈的生成方式,又可以分为递增堆栈(Ascending Stack)和递减堆栈(DecendingStack),当堆栈由低地址向高地址生成时,称为递增堆栈,当堆栈由高地址向低地址生成时,称为递减堆栈。这样就有四种类型的堆栈工作方式,ARM 微处理器支持这四种类型的堆栈工作方式,即:
       ◎ Full descending 满递减堆栈堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。 ARM-Thumb过程调用标准和ARMThumb C/C++ 编译器总是使用Full descending 类型堆栈。<这是什么原因呢?>

  ◎ Full ascending 满递增堆栈堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。

  ◎ Empty descending 空递减堆栈堆栈首部是低(这里是不是错了,应该是高地址吧)地址,堆栈向高地址增长。栈指针总是指向下一个将要放入数据的空位置。

  ◎ Empty ascending 空递增堆栈堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向下一个将要放入数据的空位置。

  5、操作堆栈的汇编指令堆栈类型 入栈指令 出栈指令 Full descending STMFD (STMDB) LDMFD (LDMIA) Full ascending STMFA (STMIB) LDMFA (LDMDA) Empty descending STMED (STMDA) LDMED (LDMIB) Empty ascending STMEA (STMIA) LDMEA (LDMDB)

  例子: STMFD r13!, {r0-r5} ; Push onto a Full Descending Stack LDMFD r13!, {r0-r5} ; Pop from a Full Descending Stack.
例子
1) 保护现场参数,不影响PC,嵌汇编的时候对之前的存参数的寄存器R0~R12保存
STMFD r13!, {r0-r7,LR}
LDMFD r13!, {r0-r7,PC}
2)  ARM汇编中lr(r14)寄存器的作用
lr(r14)的作用问题,这个lr一般来说有两个作用:
1.当使用bl或者blx跳转到子过程的时候,r14保存了返回地址,可以在调用过程结尾恢复。
2.异常中断发生时,这个异常模式特定的物理R14被设置成该异常模式将要返回的地址。

另外注意pc,在调试的时候显示的是当前指令地址,而用mov lr,pc的时候lr保存的是此指令向后数两条指令的地址,大家可以试一下用mov pc,pc,结果得到的是跳转两条指令,这个原因是由于arm的流水线造成的,预取两条指令的结果.
3.我们看到的LR值是上一个子程序调用保存的子程序返回地址,这个LR是要赋给PC的。
  嵌入式汇编要手动保存返回地址,进行现场保护。
  PC记录当前运行的地址。下一条回自己+4
  进入子程序,LR才自动更新为返回地址值,PC为程序运行地址
ARM汇编嵌套子程序
几个星期前阅读了(加)Carl Hamacher、 Zvonko Vranesic、 Safwat Zaky编写的《计算机组成》第五版中的ARM子程序调用的一些知识,启发很大,顺便将它整理了一下并加入了自己的理解。
子程序
1 通过寄存器传递参数
BL指令通常用于调用一个子程序。它和B指令的区别在于它将返回地址装载到R14中。由于子程序可能是嵌套的,因此LR的内容必须保存在子程序所使用的堆栈中。
下面的例子使用寄存器传递参数。调用者通过寄存器R1和R2分别将数组的大小和数组的首地址传递给子程序;子程序利用寄存器R0将和传递给调用者。该子程序使用了寄存器R3,必须将它和LR推入堆栈。
调用程序
LDR R1, N
LDR R2, POINTER
BL  LISTADD
STR R0, SUM
子程序
LISTADD  STMFD  R13!, {R3, R14}
MOV  R0, #0
LOOP LDR  R3, [R2], #4
ADD  R0, R0, R3
SUBS  R1, R1, #1
BGT  LOOP
LDMFD R13!, {R3, R15}
注:这里并没有遵守APCS(ARM过程调用标准),一般由调用者负责保存R0~R3,被调用者负责保存其他的寄存器以使调用返回后程序的状态不被破坏。
2 通过堆栈传递参数
调用程序
LDR R0, POINTER
STR R0, [R13, #-4]! ;将数组首地址推入堆栈
LDR R0, N
STR R0, [R13, #-4]! ;将元素个数N推入堆栈
BL  LISTADD
LDR R0, [R13, #4] ;将元素和装载到寄存器R0中
STR R0, SUM
ADD R13, R13, #8 ;恢复堆栈
子程序
LISTADD  STMFD  R13!, {R0-R3, R14}
LDR  R1, [R13, #20]
LDR  R2, [R13, #24]
MOV  R0, #0
LOOP LDR  R3, [R2], #4
ADD  R0, R0, R3
SUBS  R1, R1, #1
BGT  LOOP
STR R0, [R13,#24] ;把和推入堆栈的最深处
LDMFD R13!, {R0-R3, R15}

[R0]


[R1]


[R2]


[R3]


返回地址


N


POINTER/SUM

3 嵌套子程序
当子程序嵌套时,堆栈是用于处理返回地址的最合适的数据结构。当调用子程序时在堆栈上建立了完整的堆栈结构。应当注意当前子程序的堆栈帧指针所指向的空间中存储的是调用当前子程序的子程序的堆栈帧指针。调用者将子程序所需要的参数按照顺序推入堆栈。子程序首先保存工作寄存器、调用者的堆栈帧指针以及返回地址,然后它计算自己的堆栈帧指针的值(ADD  FP, SP, #16),并利用这个堆栈帧指针从堆栈帧中获取调用者传递给它的参数。在子程序完成它的任务之后,它也将返回值保存在堆栈中,此例保存在参数所在的内存单元。调用者和被调用者必须约定好参数的传递顺序和返回值保存位置。如果返回值比较多的话,调用者要为返回值预先在堆栈中保留合适的空间。
调用程序
2000 LDR   R0, PARAM2
STR   R0, [SP, #-4]! ;将参数推入堆栈
LDR   R0, PARAM1
STR  R0, [SP, #-4]!
BL   SUB1
2020 LDR  R0, [SP] ;保存SUB1的结果
STR  R0, RESULT
ADD  SP, SP, #8 ;恢复堆栈
子程序
2100 SUB1  STMFD  SP!, {R0-R3, FP,LR}
ADD  FP, SP, #16 ;计算帧指针
LDR  R0, [FP, #8] ;载入参数1
LDR  R1, [FP, #12] ;载入参数2
LDR  R2, PARAM3 ;载入参数3
STR  R2, [SP, #-4]! ;将参数3推入堆栈
BL  SUB2
2164 LDR  R2, [SP], #4 ;将SUB2的结果弹出并存储在R2中,并递增SP
STR  R3, [FP, #8] ;将结果推入堆栈
LDMFD  SP!, {R0-R3, FP, PC} ;恢复寄存器并返回
3000 SUB2  STMFD  SP!, {R0, R1, FP, LR}
ADD  FP, SP, #8 ;载入结构指针
LDR  R0, [FP, #8] ;载入参数
STR  R1, [FP, #8] ;将结果推入堆栈
LDMFD  SP!, {R0, R1, FP, PC}


[R0] from SUB1


[R1] from SUB1


[FP] from SUB1


2164/返回地址


param3/SUB2的结果最后保存在这里


[R0] from main


[R1] from main


[R2] from main


[R3] from main


[FP] from main


2020/返回地址


param1/SUB1的结果最后保存在这里


param2


原栈顶



3 子程序编译后都放在哪
嵌汇编子程序:会在编译后统一放到一个地方 系统调用__main()库函数,
后再进入main.c前的初始化C下面的堆栈命令前(这个堆栈是用来放置C里参数的)
   嵌汇编子程序:按定义顺序放在堆栈前,而C语言子程序,放在初始化堆栈堆栈命令后。

本文地址:https://www.eechina.com/thread-97735-1-1.html     【打印本页】

本站部分文章为转载或网友发布,目的在于传递和分享信息,并不代表本网赞同其观点和对其真实性负责;文章版权归原作者及原出处所有,如涉及作品内容、版权和其它问题,我们将根据著作权人的要求,第一时间更正或删除。
您需要登录后才可以发表评论 登录 | 立即注册

厂商推荐

  • Microchip视频专区
  • 5分钟详解定时器/计数器E和波形扩展!
  • 了解一下Microchip强大的PIC18-Q24 MCU系列
  • 为何选择集成电平转换?
  • 无线充电基础知识及应用培训教程2
  • 贸泽电子(Mouser)专区

相关视频

关于我们  -  服务条款  -  使用指南  -  站点地图  -  友情链接  -  联系我们
电子工程网 © 版权所有   京ICP备16069177号 | 京公网安备11010502021702
快速回复 返回顶部 返回列表