什么是ARM中的SP(堆栈)和LR?
LR是用于保存函数调用的返回地址的link register。
SP是堆栈指针。堆栈通常用于在函数调用中保存”automatic”变量和上下文/参数。从概念上讲,您可以将”stack”视为您”pile”您的数据的地方。您将”stacking”保留在一个数据之上,堆栈指针告诉您”high”的数据是”stack”。您可以从”stack”的”top”中删除数据并缩短。
从ARM架构参考:
SP, the Stack Pointer
Register R13 is used as a pointer to the active stack.
In Thumb code, most instructions cannot access SP. The only instructions that can access SP are those designed to use SP as a stack pointer. The use of SP for any purpose other than as a stack pointer is deprecated. Note Using SP for any purpose other than as a stack pointer is likely to break the requirements of operating systems, debuggers, and other software systems, causing them to malfunction.
LR, the Link Register
Register R14 is used to store the return address from a subroutine. At other times, LR can be used for other purposes.
When a BL or BLX instruction performs a subroutine call, LR is set to the subroutine return address. To perform a subroutine return, copy LR back to the program counter. This is typically done in one of two ways, after entering the subroutine with a BL or BLX instruction:
• Return with a BX LR instruction.
• On subroutine entry, store LR to the stack with an instruction of the form: PUSH {,LR} and use a matching instruction to return: POP {,PC} …
This link gives an example of a trivial subroutine.
次佳解决方案
SP是堆栈寄存器,用于键入r13的快捷方式。 LR是r14的快捷方式。 PC是程序计数器,用于输入r15的快捷键。
当执行一个调用,称为分支链接指令bl时,返回地址放在r14(链接寄存器)中。程序计数器pc更改为您要分支的地址。
传统的ARM内核中有几个堆栈指针(cortex-m系列是一个例外),当您打中断时,例如使用与前台运行时不同的堆栈,您不必更改代码,只需使用sp或r13正常情况下,硬件已经为您完成了开关,并在解码指令时使用正确的开关。
传统的ARM指令集(而不是Thump指令集)使您可以自由使用从低地址到更高地址的堆栈,或者从高地址到低地址的增长。编译器和大多数人将堆栈指针设置为高电平,并将其从高地址下降到较低的地址。例如,例如,您可以将ram从0x20000000设置为0x20008000,您可以设置链接描述文件来构建程序来运行/使用0x20000000,并将启动代码中的堆栈指针设置为0x20008000,至少要将系统/用户堆栈指针分开其他堆栈的内存,如果你需要/使用它们。
堆栈只是 memory 。处理器通常具有基于PC的特殊存储器读/写指令,一些是基于堆栈的。堆栈的最小值通常被称为push和pop,但不一定是(和传统的arm指令一样)。
如果你去http://github.com/lsasim我创建了一个教学处理器,并有一个汇编语言教程。在那里我会经历关于堆栈的讨论。它不是一个ARM处理器,但故事是一样的,它应该直接转换到你想要在ARM或大多数其他处理器上理解。
例如,您的程序中需要20个变量,但只有16个寄存器减去至少三个(sp,lr,pc),这些是特殊用途。你将不得不将一些变量保留在ram中。让我们说r5拥有一个你经常使用的变量,你不想保持在ram中,但是有一段代码,你真的需要另一个注册表来做某事,r5没有被使用,你可以保存r5堆栈以最小的努力,而您重用r5的其他东西,然后,很容易,恢复它。
传统(不一定都回到起初)ARM语法:
...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r15}
..
stm是存储多个,一次可以保存多个寄存器,直到所有这些都在一个指令中。
db表示之前递减,这是从高地址到较低地址的向下移动堆栈。
您可以使用r13或sp来指示堆栈指针。该特定指令不限于堆栈操作,可用于其他操作。
的!意味着在完成后用新地址更新r13寄存器,这里再次使用stm可以用于non-stack操作,因此您可能不想更改基地址寄存器,离开!在这种情况下。
然后在括号{}中列出要保存的寄存器,以逗号分隔。
ldmia是相反的,ldm表示加载多个。 ia表示递增,其余与stm相同
所以如果你的堆栈指针在0x20008000,当你打到stmdb指令看到,因为列表中有一个32位寄存器,它将在它使用它之前减少r13中的值,所以0x20007FFC然后它在存储器中写入r5到0x20007FFC,并保存值0x137FFC在r13。后来,假设你没有错误,当你得到ldmia指令r13有0x20007FFC在其中有一个单一的注册表在列表r5。所以它在0x20007FFC读取内存将该值放在r5中,ia表示增量后,0x20007FFC将一个寄存器大小增加到0x20008000,而!意味着将该号码写入r13以完成指令。
你为什么要使用堆栈而不是固定的内存位置?那么上面的美妙之处在于,当您运行该代码或0x20002000或其他任何代码仍然可以运行时,r13可以是0x20007654,如果您在循环中使用该代码,或者在循环中使用该代码,或者对于每个级别您递交的递归保存r5的新副本,您可能有30个保存的副本,具体取决于您在该循环中的位置。并且当它展开时,将所有副本放回所需的位置。单个固定内存位置不起作用。这将直接转换为C代码作为示例:
void myfun ( void ) { int somedata; }
在这样的C程序中,变量somedata存在于堆栈中,如果您递归调用myfun,则根据递归的深度,您将有多个副本的somedata值。此外,由于该变量仅在函数内部使用,并且不需要其他位置,那么您可能不想在程序的生命周期内为该变量刻录一定量的系统内存,只需要在该函数中使用这些字节,并释放该内存不在那个功能。这就是堆栈的用途。
在堆栈中找不到全局变量
回去…
说你想实现和调用这个函数,你会在调用myfun函数时有一些代码/函数。 myfun函数希望使用r5和r6,当它正在操作的东西,但它不想垃圾的任何人称它是使用r5和r6这样的持续时间的 myfun()你想要保存在堆栈上的这些寄存器。同样,如果您查看分支链接指令(b1)和链接寄存器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++等通常会有一个”calling convention”或应用程序接口(ABI和EABI是ARM定义的名称)。如果每个函数遵循调用约定,则将参数传递给在正确的寄存器或堆栈中被调用的函数。并且每个函数遵循规则,关于什么寄存器不必保留其内容和什么寄存器来保存内容,那么你可以使用函数调用函数调用函数,并执行递归和各种事情,只要堆栈不会太深,以至于它运行到用于全局变量和堆的内存中,所以您可以调用函数并从整个日期返回。 myfun的上述实现与编译器生成的内容非常相似。
ARM现在有很多核心和一些指令集,cortex-m系列的工作原理有所不同,只要没有一堆模式和不同的堆栈指针。并且在拇指模式下执行拇指指令时,您可以使用推送和弹出指令,这些指令不会让您自由使用任何类似stm的寄存器,它只使用r13(sp),而且您无法仅将所有寄存器保存在其特定子集中。流行的ARM组装人员允许您使用
push {r5,r6}
...
pop {r5,r6}
ARM代码以及拇指代码。对于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。稍后最后一个pop,sp为0x0FFC,读取的值为r1,然后堆栈指针的值为0x1000,在那里开始。
ARM ARM,ARM架构参考手册(infocenter.arm.com,参考手册,找到适用于ARMv5并下载的手册,这是ARM ARM与ARM和Thumb指令的传统ARM),包含ldm和stm ARM的伪代码关于这些如何使用的完整图片。同样,整本书都是关于ARM和如何编程的。在程序员模型章节前面将介绍所有模式下的所有寄存器等。
如果您正在编程ARM处理器,您应该首先确定(芯片厂商应该告诉您,ARM不会使芯片使芯片厂商的芯片成为芯片厂商的核心)。然后去arm站,找到那个家族的ARM ARM,找到特定内核的TRM(技术参考手册),包括修正版本(如果供应商提供的)(r2p0表示版本2.0(二点零,2p0)),甚至如果存在较新的转速,请使用与设计中使用的供应商所使用的手册。不是每个核心都支持每个指令或模式,TRM告诉您ARM ARM支持的模式和指令,总结了核心所处的整个处理器系列的功能。请注意,ARM7TDMI不是ARMv7,而是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,相同的交易超级定标器,多核,多吉赫兹。 cortex-m几乎没有制动100MHz的屏障,并且以千字节测量的内存,尽管如果你想要一个cortex-a的地方,它可能会运行一个电池几个月。
对于很长的帖子很抱歉,希望它是有用的。