ARM中的SP(堆栈)和LR是什么?

时间:2011-11-23 03:27:01

标签: assembly stack arm

我一遍又一遍地阅读定义,我仍然没有得到ARM中的SP和LR?我理解PC(它显示下一条指令的地址),SP和LR可能是相似的,但我只是不知道它是什么。你能帮我吗?

编辑:如果您可以通过示例解释它,那就太棒了。

编辑终于找到了LR的用途,仍未获得SP的用途。

2 个答案:

答案 0 :(得分:81)

LR link register用于保存函数调用的返回地址。

SP是堆栈指针。堆栈通常用于在函数调用中保存“自动”变量和上下文/参数。从概念上讲,您可以将“堆栈”视为“堆积”数据的地方。您将一个数据“堆叠”在另一个上,堆栈指针告诉您“数据堆栈”的“高”程度。您可以从“堆栈”的“顶部”删除数据并将其缩短。

来自ARM体系结构参考:

  

SP,堆栈指针

     

寄存器R13用作指向活动堆栈的指针。

     

在Thumb代码中,大多数指令无法访问SP。唯一的   可以访问SP的指令是那些旨在使用SP作为的指令   堆栈指针。将SP用于除堆栈之外的任何其他目的   指针已弃用。注意将SP用于除以外的任何其他目的   堆栈指针很可能会打破操作的要求   系统,调试器和其他软件系统,导致它们   故障。

     

LR,链接注册

     

寄存器R14用于存储子程序的返回地址。在   其他时候,LR可以用于其他目的。

     

当BL或BLX指令执行子程序调用时,LR设置为   子程序返回地址。要执行子程序返回,请复制LR   回到程序柜台。这通常以两种中的一种来完成   用BL或BLX指令进入子程序后的方法:

     

•使用BX LR指令返回。

     

•在子程序输入上,将LR存储到   具有以下形式的指令的堆栈:PUSH {,LR}和   使用匹配的指令返回:POP {,PC} ...

This link gives an example of a trivial subroutine.

Here is an example of how registers are saved on the stack prior to a call and then popped back to restore their content.

答案 1 :(得分:42)

SP是堆栈寄存器,用于键入r13的快捷方式。 LR是链接寄存器r14的快捷方式。而PC是程序计数器,是输入r15的快捷方式。

当你执行一个叫做分支链接指令的调用bl时,返回地址被放在链接寄存器r14中。程序计数器pc更改为您要分支的地址。

传统ARM内核中有一些堆栈指针(cortex-m系列是一个例外)当你遇到一个中断时,例如你使用的堆栈不同于在前台运行时的堆栈,你不必改变你的代码只是正常使用sp或r13,硬件已为您完成切换,并在解码指令时使用正确的。

传统的ARM指令集(不是拇指)使您可以自由地使用堆栈,从较低地址到较高地址,或从高地址变为低地址。编译器和大多数人将堆栈指针设置为高电平,并使其从高地址变为低地址。例如,你可能有从0x20000000到0x20008000的ram你设置你的链接器脚本来构建你的程序来运行/使用0x20000000并在你的启动代码中将你的堆栈指针设置为0x20008000,至少是系统/用户堆栈指针,你必须分开其他堆栈的内存,如果你需要/使用它们。

Stack就是内存。处理器通常具有特殊的存储器读/写指令,这些指令是基于PC的,有些是基于堆栈的。最少的堆栈通常被命名为push和pop,但不必如此(与传统的arm指令一样)。

如果你去http://github.com/lsasim,我创建了一个教学处理器,并有一个汇编语言教程。在那里的某个地方,我会讨论堆栈。它不是一个手臂处理器,但故事应该直接转换为你想要在手臂或大多数其他处理器上理解的内容。

例如,假设您的程序中需要20个变量,但只有16个寄存器减去其中至少三个(sp,lr,pc)的特殊用途。你将不得不在ram中保留一些变量。让我们说r5拥有一个你经常使用的变量,你不想把它保留在ram中,但是有一段代码你真的需要另一个寄存器来做某事并且r5没有被使用,你可以保存r5当你将r5重新用于其他东西时,只需要很少的努力,然后,轻松地将其恢复。

传统(不是一直回到开头)手臂语法:

...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...

stm是存储多个,你可以一次保存多个寄存器,在一条指令中最多可以保存所有寄存器。

db表示之前递减,这是从高地址到低地址的向下移动堆栈。

您可以在此处使用r13或sp来指示堆栈指针。此特定指令不限于堆栈操作,可用于其他事项。

!表示完成后用新地址更新r13寄存器,此处stm可用于非堆栈操作,因此您可能不想更改基址寄存器,请保留!在那种情况下。

然后在括号{}中列出要保存的寄存器,逗号分隔。

ldmia是相反的,ldm意味着加载多个。 ia表示增量后,其余与stm相同

因此,当你点击stmdb指令时你的堆栈指针是0x20008000,因为列表中有一个32位寄存器,它会在它使用r13中的值之前递减,所以0x20007FFC然后它将r5写入内存中的0x20007FFC并且在r13中保存值0x20007FFC。稍后,假设您在执行ldmia指令时没有错误,r13中包含0x20007FFC,则列表r5中有一个寄存器。因此它在0x20007FFC读取内存将该值放入r5,ia表示增量,因此0x20007FFC将一个寄存器大小增加到0x20008000并且!意味着将该数字写入r13以完成指令。

为什么要使用堆栈而不仅仅是固定的内存位置?以上的优点是r13可以是任何地方,当你运行该代码或0x20002000或其他任何代码仍然起作用时,它可能是0x20007654,如果你在循环中使用该代码或使用递归它工作和每个级别更好你去保存r5的新副本的递归,你可能有30个保存的副本,具体取决于你在该循环中的位置。当它展开时,它会根据需要放回所有副本。使用单个固定内存位置无法正常工作。这直接转换为C代码作为示例:

void myfun ( void )
{
   int somedata;
}

在像这样的C程序中,变量some​​data存在于堆栈中,如果你以递归方式调用myfun,那么根据递归的深度,你会得到somedata值的多个副本。此外,由于该变量仅在函数内使用而在其他地方不需要,因此您可能不希望在程序的生命周期内为该变量烧制一定量的系统内存,在该函数中只需要那些字节并释放该内存时不在那个功能。这就是堆栈的用途。

在堆栈中找不到全局变量。

回去......

假设您想要实现并调用该函数,那么当您调用myfun函数时,您将拥有一些代码/函数。 myfun函数想要在运行某些东西时使用r5和r6,但它不想丢弃任何有人称之为使用r5和r6的东西,因为在myfun()期间你需要将这些寄存器保存在堆栈中。同样,如果查看分支链接指令(bl)和链接寄存器lr(r14),只有一个链接寄存器,如果从函数调用函数,则需要在每次调用时保存链接寄存器,否则无法返回

...
bl myfun
    <--- the return from my fun returns here
...


myfun:
stmdb sp!,{r5,r6,lr}
sub sp,#4 <--- make room for the somedata variable
...
some code here that uses r5 and r6
bl more_fun <-- this modifies lr, if we didnt save lr we wouldnt be able to return from myfun
   <---- more_fun() returns here
...
add sp,#4 <-- take back the stack memory we allocated for the somedata variable
ldmia sp!,{r5,r6,lr}
mov pc,lr <---- return to whomever called myfun.

所以希望你能看到堆栈使用和链接寄存器。其他处理器以不同的方式执行相同类型的事情。例如,some会将返回值放在堆栈上,当你执行return函数时,它会通过从堆栈中拉出一个值来知道返回的位置。编译器C / C ++等通常会有一个&#34;调用约定&#34;或应用程序接口(ABI和EABI是ARM定义的名称)。如果每个函数都遵循调用约定,则将传递给它的函数放入正确的寄存器或按照约定在堆栈中调用的函数。并且每个函数遵循规则,关于哪些寄存器不必保留其内容以及它有哪些寄存器来保存内容然后你可以有函数调用函数调用函数并做递归和各种事情,只要堆栈没有那么深,以至于它运行到用于全局变量和堆的内存等等,你可以调用函数并整天从它们返回。 myfun的上述实现与编译器生成的内容非常相似。

ARM现在拥有许多内核,而且只要没有一堆模式和不同的堆栈指针,cortex-m系列的一些指令集就会有所不同。在拇指模式下执行拇指指令时,您可以使用push和pop指令,这些指令不允许您自由使用任何寄存器,例如stm,它只使用r13(sp),并且您不能仅将所有寄存器保存为它们的特定子集。流行的手臂组装商允许你使用

push {r5,r6}
...
pop {r5,r6}

在手臂代码和拇指代码中。对于arm代码,它编码正确的stmdb和ldmia。 (在拇指模式下,你也没有选择使用db的时间和地点,之前递减,以及ia,之后递增)。

不,你绝对不必使用相同的寄存器,你也不必配对相同数量的寄存器。

push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}

假设在这些指令之间没有其他堆栈指针修改,如果你记得sp将递减12个字节用于推送,假设从0x1000到0x0FF4,r5将被写入0xFF4,r6到0xFF8和r7到0xFFC堆栈指针将更改为0x0FF4。第一个pop将取值0x0FF4并将其放入r2然后将值放在0x0FF8并将其放入r3中,堆栈指针获取值0x0FFC。在最后一次弹出后,sp是读取的0x0FFC,并且值放在r1中,然后堆栈指针得到值0x1000,它在那里开始。

ARM ARM,ARM架构参考手册(infocenter.arm.com,参考手册,找到一个用于ARMv5并下载它,这是传统的ARM ARM与ARM和拇指指令)包含ldm和stm的伪代码ARM提供了有关如何使用这些内容的完整图片。同样,整本书都是关于手臂以及如何编程的。在前面,程序员模型章节将引导您完成所有模式中的所有寄存器等。

如果您正在编程ARM处理器,您应该首先确定(芯片供应商应该告诉您,ARM不会制造芯片,而芯片供应商会将核心芯片放入芯片中)确切地说您拥有哪个核心。然后转到arm网站并找到该系列的ARM ARM,并找到特定内核的TRM(技术参考手册),包括修订版(如果供应商已提供)(r2p0表示修订版2.0(两点零,2p0)),甚至如果有更新的版本,请使用供应商在其设计中使用的手册。并非每个内核都支持每个指令或模式,TRM告诉您ARM ARM支持的模式和指令会为核心所在的整个处理器系列的功能提供覆盖。请注意,ARM7TDMI是ARMv4而不是ARMv7同样是ARM9不是ARMv9。 ARMvNUMBER是ARM7的系列名称,ARM11没有v是核心名称。较新的内核有像Cortex和mpcore这样的名称,而不是ARMNUMBER的东西,这减少了混乱。当然,他们不得不通过制作ARMv7-m(cortex-MNUMBER)和ARMv7-a(Cortex-ANUMBER)这些非常不同的系列来增加混乱,一个用于重载,台式机,笔记本电脑等,另一个是用于微控制器,咖啡机上的时钟和闪烁灯等等。谷歌beagleboard(Cortex-A)和stm32值线发现板(Cortex-M)来感受差异。甚至open-rd.org电路板使用超过千兆赫兹的多个核心或者来自nvidia的更新的tegra 2,相同的超级缩放器,多核心,多千兆赫兹。皮质m几乎没有制动100MHz的屏障并且以千字节为单位测量记忆,尽管如果你想要它可以运行一个电池几个月而不是皮质 - 而不是那么多。

抱歉很长的帖子,希望它有用。