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

goo*_*ing 68 assembly stack arm

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

编辑:如果你能用例子来解释它,那就太棒了.

编辑:终于找出了LR的用途,仍然没有得到SP的用途.

Guy*_*ton 84

LR是链接寄存器,用于保存函数调用的返回地址.

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} ...

此链接提供了一个简单子例程的示例.

下面是一个示例,说明如何在调用之前将寄存器保存在堆栈中,然后弹出以恢复其内容.

  • 只是想说,不幸的是,您的两个链接现在都已失效。 (2认同)

old*_*mer 42

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

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

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

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

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

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

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

传统的(不是一直回到开头)arm语法:

...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

在类似的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.
Run Code Online (Sandbox Code Playgroud)

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

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

push {r5,r6}
...
pop {r5,r6}
Run Code Online (Sandbox Code Playgroud)

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

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

push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}
Run Code Online (Sandbox Code Playgroud)

假设在这些指令之间没有其他堆栈指针修改,如果你记得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没有av是核心名称.较新的内核有像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的屏障,并且以千字节为单位测量记忆,尽管如果你想让它在一个皮质 - 而不是那么多的地方,它可能会运行几个月的电池.

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

  • 我对你的学习装配的github项目很感兴趣,但看起来你的项目已经不见了.你有替代品吗?:) (5认同)
  • 我相信该项目的当前地址是 https://github.com/dwelch67/lsasim (截至今天,2020 年 9 月 7 日有效)。 (2认同)
  • 读到一半的时候,我想“伙计,所有这些解释和例子,它让我想起了 4 年前帮助我学习一些汇编基础知识的人......”,果然是@old_timer!始终感谢您精彩的解释! (2认同)