ARM中的SP(堆栈)和LR是什么?
我一遍又一遍地阅读定义,我仍然不明白ARM中的SP和LR是什么? 我了解PC(它显示下一个指令的地址),SP和LR可能是相似的,但我只是不明白它是什么。 你可以帮我吗?
编辑:如果你可以用例子来解释,那将是非常棒的。
编辑:终于搞清楚什么是LR,仍然没有得到什么SP的。
LR是链接寄存器,用于保存函数调用的返回地址。
SP是堆栈指针。 堆栈通常用于在函数调用中保存“自动”variables和上下文/参数。 从概念上讲,您可以将“堆栈”视为“堆积”数据的地方。 你保持“堆叠”一个数据的另一个,堆栈指针告诉你“高”你的“堆栈”的数据是。 您可以从“堆栈”的“顶部”删除数据并缩短。
从ARM体系结构参考:
SP,堆栈指针
寄存器R13用作指向活动堆栈的指针。
在Thumb代码中,大多数指令都不能访问SP。 唯一可以访问SP的指令是那些被devise为使用SP作为堆栈指针的指令。 不推荐使用SP作为堆栈指针以外的任何用途。 注意将SP用于堆栈指针以外的任何用途可能会破坏操作系统,debugging器和其他软件系统的要求,导致其发生故障。
LR,链接寄存器
寄存器R14用于存储子程序的返回地址。 在其他时候,LR可以用于其他目的。
当BL或BLX指令执行子程序调用时,LR被设置为子程序返回地址。 要执行子程序返回,请将LR复制回程序计数器。 在通过BL或BLX指令进入子程序之后,通常通过以下两种方式之一来完成:
•用BX LR指令返回。
•在子程序入口处,将LR存储到堆栈,并使用指令forms:PUSH {,LR}并使用匹配的指令返回:POP {,PC} …
这个链接给出了一个简单子例程的例子。
下面是一个如何在通话之前将寄存器保存在堆栈中然后popup以恢复其内容的示例。
SP是堆栈登记键入r13的快捷方式。 LR是链接注册r14的快捷方式。 而PC是程序计数器打字r15的捷径。
当你执行一个叫做分支链接指令的调用bl时,返回地址被放在链接寄存器r14中。 程序计数器PC被改变为你要分支的地址。
在传统的ARM内核中,有一些堆栈指针(cortex-m系列是个例外),例如当你使用与前台运行不同的堆栈时,你不必更改代码就可以使用sp或r13,因为硬件为您完成了开关,并在解码指令时使用正确的开关。
传统的ARM指令集(不是拇指)使您可以自由使用从低地址到高地址的堆栈或从高地址到低地址的堆栈。 编译器和大多数人将堆栈指针设置为高,并且从高地址向低地址增长。 例如,也许你有从0x20000000到0x20008000的内存设置你的链接脚本来build立你的程序运行/使用0x20000000和你的堆栈指针设置为0x20008000在你的启动代码,至less系统/用户堆栈指针,你必须划分其他堆栈的内存,如果你需要/使用它们。
堆栈只是内存。 处理器通常具有特殊的存储器读/写指令,这些指令是基于PC的,并且一些是基于堆栈的。 最less的堆栈通常被命名为push和pop,但不一定是(如传统的arm指令)。
如果你去http://github.com/lsasim我创build了一个教学处理器,并有一个汇编语言教程。; 在那里的某个地方,我经历了关于堆栈的讨论。 这不是一个arm处理器,但故事是一样的,它应该直接转化为你想要了解的arm或大多数其他处理器。
举个例子,在程序中你需要20个variables,但是只有16个寄存器减去至less三个variables(sp,lr,pc),这些variables是特殊用途的。 你将不得不保留一些你的variables在内存中。 让我们说r5拥有一个你经常使用的variables,你不想保留它的内存,但是有一段代码,你真的需要另一个寄存器去做一些事情,而r5没有被使用,你可以保存r5当你重复使用r5做其他事情时,尽可能减less堆栈,然后再轻松地恢复它。
传统(并不是所有的方式回到开始)arm语法:
... stmdb r13!,{r5} ...temporarily use r5 for something else... ldmia r13!,{r15} ...
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可以在任何地方它可能是0x20007654当你运行该代码或0x20002000或任何和代码仍然function,甚至更好,如果你使用该代码在一个循环或recursion它的作品和每个级别recursion,你去保存一个新的r5副本,你可能有30个保存的副本,取决于你在哪个循环。 并展开后,将所有副本按需要放回原处。 与一个固定的内存位置,不工作。 这直接转换为C代码为例:
void myfun ( void ) { int somedata; }
在像这样的C程序中,variablessomedata位于堆栈上,如果recursion地调用myfun,那么根据recursion的深度,someData将具有多个值的副本。 此外,因为该variables只用于函数内部,并且在其他地方不需要,所以您可能不想在程序的整个生命周期内为该variables烧制大量的系统内存,只需要那些在该函数中的字节并释放该内存不在这个function。 这是一个堆栈用于。
全局variables不会在堆栈中find
回去…
假设你想实现并调用这个函数,当你调用myfun函数的时候,你会得到一些代码/函数。 myfun函数在使用r5和r6的时候想要使用r5和r6,但是它不想在rf和r6中使用r5和r6来保存这些寄存器。 同样,如果您查看分支链接指令(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.
所以希望你可以看到堆栈使用情况和链接注册。 其他处理器以不同的方式做同样的事情。 例如有些会将返回值放在堆栈上,而当您执行返回函数时,它会通过从堆栈中拉出一个值来知道该返回的位置。 编译器C / C ++等通常具有“调用约定”或应用程序接口(ABI和EABI是ARM定义的名称)。 如果每个函数遵循调用约定,则将parameter passing给在正确的寄存器或堆栈中按照约定调用的函数。 而且每个函数遵循的规则是什么寄存器它不必保存的内容和什么注册它有保存的内容然后你可以有函数调用函数调用函数,做recursion和各种事情,只要堆栈不会太深,以至于用于全局和堆的内存等,你可以调用函数并整天返回。 myfun的上述实现与您将看到的编译器生成的非常相似。
现在ARM有很多内核,只要没有一堆模式和不同的堆栈指针,cortex-m系列的工作方式有点不同。 而当在拇指模式下执行拇指指令时,您使用push和pop指令,这些指令不会让您像使用任何寄存器那样自由使用r13(sp),并且不能只保存所有寄存器的特定子集。 stream行的arm组装器允许你使用
push {r5,r6} ... pop {r5,r6}
在arm代码以及拇指代码。 对于arm代码,它编码正确的stmdb和ldmia。 (在拇指模式下,你也没有select使用db的时间和地点,之前递减和ia递增之后)。
不,你绝对不必使用相同的寄存器,你不必配对相同数量的寄存器。
push {r5,r6,r7} ... pop {r2,r3} ... pop {r1}
假设在这些指令之间没有其他的堆栈指针修改,如果你记得sp将要递减12个字节,推送可以说从0x1000到0x0FF4,r5将被写入0xFF4,r6到0xFF8,r7到0xFFC的堆栈指针将变为0x0FF4。 第一个popup窗口将取值为0x0FF4,并把它放在r2,然后在0x0FF8的值,并把它放在r3栈指针得到值0x0FFC。 后来的最后一个popup窗口,sp是0x0FFC读取和值放置在r1中,堆栈指针然后获取值0x1000,它开始。
ARM ARM,ARM体系结构参考手册(infocenter.arm.com,参考手册,find一个用于ARMv5并下载它,这是ARM的ARM和ARM的传统ARM指令),包含用于ldm和stm的伪代码ARM fortructions for关于这些如何使用的完整图片。 同样好,整本书是关于arm,如何编程。 前面的程序员模型章将带你通过所有模式的所有寄存器等。
如果你正在编程一个ARM处理器,你应该首先确定(芯片供应商应该告诉你,ARM不会制造芯片,芯片厂商把他们的芯片放在哪个核心上),你究竟是哪个核心。 然后进入arm网站,find该ARM系列的ARM ARM,如果供应商提供了(r2p0表示版本2.0(两点零,2p0)),则find特定核心的TRM(技术参考手册)如果有更新的版本,请使用供应商在devise中使用的手册。 并非每个内核都支持每种指令或模式,但TRM告诉您支持的模式和指令。ARM ARM为整个内核处理器系列的function提供了一个覆盖范围。请注意,ARM7TDMI是ARMv4不是ARMv7 ARM9不是ARMv9。 ARMvNUMBER是系列名称ARM7,ARM11没有av是核心名称。 较新的内核有像Cortex和mpcore这样的名称,而不是ARMNUMBER的东西,这减less了混淆。 当然,他们不得不通过制造一个ARMv7-m(cortex-MNUMBER)和ARMv7-a(Cortex-ANUMBER)这两个非常不同的系列,一个是重负载,台式机,笔记本电脑等等。微控制器,咖啡机上的时钟和闪烁灯以及类似的东西。 谷歌Beagleboard(Cortex-A)和stm32价值线发现板(Cortex-M)来感受差异。 或者甚至是使用多核以上千兆赫以上的open-rd.org板,或者是来自nvidia的更新的tegra 2,同样的交易超级缩放器,多核,多千兆赫。 一个cortex-m几乎可以制动100MHz的屏障,并以千字节为单位测量内存,尽pipe如果你想把它放在一个皮质的地方,可能需要几个月时间。
抱歉,很长的post,希望它是有用的。