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

apeeds0o  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(264)

我一遍又一遍地阅读定义,但我仍然不明白ARM中的SP和LR是什么?我理解PC(它显示下一条指令的地址),SP和LR可能是相似的,但我就是不明白它是什么。您能帮助我吗?

**编辑:**如果你能用例子解释一下,那就太棒了。
**编辑:**终于弄清楚LR的用途,但仍然不知道SP的用途。

aydmsdu9

aydmsdu91#

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 {,个人电脑}...
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.

deyfvvtc

deyfvvtc2#

SP是堆栈寄存器,是键入r13的快捷方式。LR是链接寄存器,是键入r14的快捷方式。PC是程序计数器,是键入r15的快捷方式。
当你执行一个调用,称为分支链接指令,bl,返回地址被放置在r14,链接寄存器。程序计数器pc被改变为你要分支到的地址。
传统ARM内核(cortex-m系列除外)中有几个堆栈指针,当您遇到中断时,例如,您使用的堆栈与在前台运行时不同,您无需更改代码,只需正常使用sp或r13,硬件会为您完成切换,并在解码指令时使用正确的堆栈。
传统的ARM指令集(而不是thumb)让您可以自由地使用堆栈,从较低地址向上增长到较高地址,或从较高地址向下增长到较低地址。编译器和大多数人将堆栈指针设置为高,并让它从较高地址向下增长到较低地址。例如,您可能有从0x 20000000到0x 20008000的RAM,您可以设置链接器脚本来构建运行/使用0x 20000000的程序,并在启动代码中将堆栈指针设置为0x 20008000,至少是系统/用户堆栈指针,如果你需要/使用其他堆栈,你必须为它们划分内存。
堆栈只是内存。处理器通常有特殊的内存读/写指令,这些指令是基于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!,{r5}
...

stm是存储倍数你可以一次保存多个寄存器,最多在一条指令中保存所有寄存器。
db表示递减之前,这是从较高地址到较低地址的向下移动堆栈。
你可以在这里用r13或sp来指示堆栈指针。这个特殊的指令并不局限于堆栈操作,可以用于其他事情。
!表示在r13完成后用新地址更新r13寄存器,这里stm同样可用于非堆栈操作,因此您可能不想更改基址寄存器,在这种情况下,请将!保留为off。
然后在方括号{ }中列出您要保存的寄存器,以逗号分隔。
ldmia是相反的,ldm是负载倍数,ia是之后的增量,其余与stm相同
因此,如果堆栈指针位于0x 20008000,当您命中stmdb指令时,看到列表中有一个32位寄存器,它将在使用r13中的值(即0x 20007 FFC)之前递减,然后将r5写入内存中的0x 20007 FFC,并将值0x 20007 FFC保存在r13中。稍后,假设您在执行ldmia指令r13时没有任何错误,其中包含0x 20007 FFC,列表r5中只有一个寄存器。因此,它读取内存中的0x 20007 FFC,并将该值放入r5,ia表示之后递增,因此0x 20007 FFC将一个寄存器大小递增到0x 20008000,!表示将该数字写入r13以完成指令。
为什么要使用堆栈而不是一个固定的内存位置呢?上面的优点是r13可以在任何地方,它可以是0x 20007654,当你运行代码或0x 20002000或其他代码时,代码仍然有效,如果你在循环中或递归中使用代码,它会更好地工作,对于每一级递归,你都会保存一个r5的新副本,您可能有30个保存的副本,这取决于您在该循环中所处的位置。当它展开时,它会根据需要将所有副本放回。使用一个不起作用的固定内存位置。这直接转换为C代码作为示例:

void myfun ( void )
{
   int somedata;
}

在这样的C程序中,变量somedata位于堆栈中,如果递归调用myfun,那么sometata的值将有多个副本,这取决于递归的深度。此外,由于该变量只在函数中使用,在其他地方不需要,因此您可能不希望在程序的生命周期中为该变量消耗大量的系统内存,您只希望在该函数中使用这些字节。函数,并在不使用该函数时释放内存。2这就是堆栈作用。
在堆栈上找不到全局变量。
正在返回...
假设你想实现并调用这个函数,当你调用myfun函数时,你会有一些代码/函数。myfun函数想在操作某个东西时使用r5和r6,但它不想丢弃别人调用的任何东西,所以在myfun的持续时间内使用r5和r6()您可能希望将这些寄存器保存在堆栈上。同样,如果您查看分支链接指令(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++,通常具有“调用约定”或应用程序接口(ABI和EABI是ARM定义的函数的名称)。如果每个函数都遵循调用约定,将传递给被调用函数的参数放入正确的寄存器中或按照约定放在堆栈上。每个函数都遵循这样的规则,即哪些寄存器不必保留内容,哪些寄存器必须保留内容,然后您可以让函数调用函数调用函数并执行递归和其他各种操作,只要堆栈没有太深以至于进入用于全局变量和堆等的内存,你就可以整天调用函数并从它们返回。上述myfun的实现与你所看到的编译器的实现非常相似。
ARM现在有很多内核和一些指令集,cortex-m系列的工作方式稍有不同,因为没有一堆模式和不同的堆栈指针。在thumb模式下执行thumb指令时,您使用的是push和pop指令,这不会给予您像stm那样自由使用任何寄存器,它只使用r13(sp),并且您不能只保存所有寄存器的一个特定子集。

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

在ARM代码和Thumb代码中。对于ARM代码,它编码了正确的STMDB和LDMIA。(在Thumb模式中,您也没有选择何时何地使用DB,在之前递减,在之后递增)。
不,您绝对不必使用相同的寄存器,也不必将相同数量的寄存器配对。

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

假设在这些指令之间没有其他堆栈指针修改,如果您记得sp将在压入操作中递减12个字节,比如从0x 1000到0x 0 FF 4,则r5将被写入0xFF 4,r6设置为0xFF 8,r7设置为0xFFC,则堆栈指针将变为0x 0 FF 4。第一次弹出将取0x 0 FF 4处的值并将其放入r2,然后取0x 0 FF 8处的值并将其放入r3,堆栈指针将得到值0x 0 FFC。稍后,最后一次弹出,读取sp为0x 0 FFC并将值放入r1,然后堆栈指针在其开始处得到值0x 1000。
ARM ARM,ARM架构参考手册(infocenter.arm.com,参考手册,查找适用于ARMv 5的参考手册并下载,这是传统的ARM ARM与ARM和拇指指令)包含伪代码的ldm和stm ARM指令的完整图片,以了解如何使用这些。同样,以及整本书是关于手臂和如何编程它。前面的程序员模型一章会带你了解所有模式下的所有寄存器,等等。

如果要对ARM处理器进行编程,则应首先确定(芯片供应商应该会告诉您,ARM不生产芯片,它生产的是芯片供应商放在芯片中的内核),然后转到ARM网站,找到该系列的ARM和TRM(技术参考手册),包括修订版(如果供应商已提供(r2 p0意味着修订版2.0(2.0,2 p0)),即使存在较新的修订版,请使用供应商在其设计中使用的手册。不是每个内核都支持每种指令或模式TRM会告诉您所支持的模式和指令ARM ARM会覆盖该内核所在的整个处理器系列。请注意,ARM 7 TDMI是ARMv 4,而不是ARMv7,同样,ARM9也不是ARMv 9。ARMvNUMBER是系列名称ARM 7,不带v的ARM 11是内核名称。较新的内核使用Cortex和mpcore等名称,而不是ARMNUMBER,这减少了混乱。当然,他们不得不通过制造ARMv7-m来增加混乱(cortex-MNUMBER)和ARMv7-a的关系(Cortex-ANUMBER),它们是非常不同的系列,一个用于重负载、台式机、笔记本电脑等,另一个用于微控制器,时钟和咖啡机上闪烁的灯等等。google beagleboard(Cortex-A)和stm32价值线发现板(Cortex-M)来感受差异。或者甚至open-rd.org板,它使用超过1GHz的多核,或者nvidia的更新的tegra 2,Same deal超级定标器,多核,cortex-m几乎没有突破100 MHz的障碍,内存以千字节为单位,尽管如果你想让它运行几个月的话,它可能需要一个电池,而cortex-a则没有那么多。
很抱歉这篇文章太长了,希望对大家有用。

相关问题