我在理解 ARM Assembly 中的 ED 堆栈时遇到问题

Posted

技术标签:

【中文标题】我在理解 ARM Assembly 中的 ED 堆栈时遇到问题【英文标题】:I'm having problems understanding ED stacks in ARM Assembly 【发布时间】:2021-06-01 15:20:51 【问题描述】:

我的教授布置了一个家庭作业,事情是这样的。这是 ARM 程序集,想象这是一个空降序堆栈。这意味着内存地址从高地址移动到低地址,空意味着堆栈指针指向堆栈上方的空白空间。在此示例中,地址位于括号中。我会用 | |为空的空间。 TOS 是栈顶,SP 是栈帧的当前位置。

|___|            (80)  

|___|             (84)

|___|             (88)

|___|  SP      (92)

|___| TOS    (96)

|___|            (100)

这是有问题的代码。我会解释我认为在每一行之后会发生什么

STMED sp!, fp,lr(FP为R11,LR为R13。因为低位寄存器进入低位地址,所以当前值FP存放在88,LR存放在92。堆栈是ED堆栈,所以SP在84 ,FP上方的一个点)

MOV fp,sp(FP现在指向与SP相同的位置,84。FP的先前值存储在位置88)

SUB SP,SP,#4(SP 指向 80)

STR R3, [fp, #12](FP为84,所以R3存储在84+12等于96,替换旧TOS)

STR R6, [fp,#-4](R6存储在84-4即80)

所以这是我的逻辑,对我来说很有意义,但我的教授说我错了。她说我不应该使用 FP 指向的位置,而是放在堆栈上的 FP 的值(位于位置 88)。这意味着 R3 将存储在点 100,而 R6 将存储在点 84。她坚信这是正确的,并表示帧指针一旦放入堆栈就不能更改,它是堆栈帧的基础。我明白这一切,但我不明白她的逻辑。我们将值存储在堆栈中,然后将其更改为指向其他内容。为什么我们仍然使用旧值?谁能给我解释一下?

【问题讨论】:

我们不能也不应该从这些信息中知道放入堆栈的 FP 的值。该 FP 属于调用者,他可能已经设置了 FP,也可能没有设置 FP。即使他们这样做了,他们的 FP 也可能指向更高的堆栈。此外,这个函数可以被不同的调用者调用。所以,旧的 FP 和新的 FP 彼此仅相差 4,并且在这种情况下总是彼此相差 4,并不对我有意义。 (两种解释的差异4似乎更像是ED和FD的差异。) @ErikEidt 在这种情况下,堆栈的寻址和 FP 的值并不重要,因为它不是一个实际的程序,而是一个测试我们对堆栈理解的示例。 4 的差异对我来说也没有意义,但我想象当我们将旧的 FP 存储在位置 88 时,我们可能会将返回地址存储到堆栈上的另一个点,然后新的 SP 是我们的偏移量用于导航堆栈。我不太明白,所以如果我离开了,我很抱歉。 【参考方案1】:

从你的解释看来,教授似乎是错误的。但是,可能存在误解(即,您误解了问题)。也就是说,编译器将先前的 FP 保存到堆栈(先前的堆栈帧)然后在更新的堆栈上使用新的 FP 是很常见的。这是新的堆栈帧。一个处理 spill 到内存的先前函数局部变量,更新的值用于新的 spills、可变大小的数组等。我相信这在ARM link register and frame pointer 中得到了回答。

STMED sp!, fp,lr - 执行典型的序言操作以存储先前帧指针和链接寄存器。这必须在非叶函数中完成。 A()B(); B()C(); C 函数 A()B() 具有更复杂的主体。

MOV fp,sp - 将帧指针更新为新的堆栈帧(接下来为该例程保留堆栈空间)。

SUB SP,SP,#4 - 在堆栈上为新例程保留空间。

STR R3, [fp, #12] - 这不是标准的,因为通常你不会回顾堆栈历史。当然,在汇编程序中一切都是有效的,但这通常是一个错误。

STR R6, [fp,#-4] - 这可能是为了将寄存器保存到堆栈帧中,以便可以将其重用于其他计算。

STMLDM 的另一个用途是加载和存储结构和数组数据。但是,它没有与fplr 等结合使用。我认为练习的目的是在 32 位 ARM CPU 上显示函数序言和结尾。


我认为您的教授可能有更好的教学时间来描述编译器如何使用这些机制来映射常见的高级/中级构造。

通常,如果您使用汇编程序进行编码,您会尽量避免像瘟疫一样使用堆栈。如果您在汇编程序中使用堆栈,则它是与高级语言的接口。 99% 的汇编程序都是叶函数,不会包含这样的代码。自定义汇编器会尽量使用寄存器。

但是,在某些情况下,您通常希望了解编译器在做什么,并且需要查看生成的汇编器。对于这个用例,编译器使用的模式类型很重要。

教人是一项艰巨的工作,我认为您只是对教授试图提出的概念有误解。所以我认为没有人可以回答谁是对的问题?希望以上内容可以帮助您了解编译器如何使用堆栈、帧指针和链接寄存器。这是理解 ARM 汇编器的一个实用方面。

【讨论】:

可能想更清楚地说,STR R3, [fp, #12] 将存储到调用者拥有的空间中,假设一个正常的调用约定。 (对于不包括传入 SP 上方的阴影空间的 ARM)。 好吧,我不太确定我是否误解了这个问题,因为这正是它。这不是我们必须运行的代码或程序,但我们得到了这种情况,并在这些指令通过后询问位置。当您说编译器将 FP 保存为对旧函数的引用并使用新 FP 时,我同意您的意思。通常对于 FD 堆栈,我们使用新的 FP 指向我们存储旧 FP 的相同位置,因此我们有一个偏移量来 nabigate 堆栈并快速访问前一个函数的位置。对于 ED,我想这会是一样的,新的 我们使用新的 FP 来导航堆栈,而旧的 FP 存储在它的正下方。我绝对不会声称自己高于我的教授,但我真的很困惑,我的老教授都没有提到这一点,我想了解这个概念。我也知道堆栈使用在汇编中很少见,但这只是为了教我们原则,不一定鼓励我们使用它。而不标准的部分只是为了测试我们的理解,即她说我们应该使用存储在88的FP来做这个计算,这就是她包含[fp,12]的原因。

以上是关于我在理解 ARM Assembly 中的 ED 堆栈时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

在理解刷新令牌、存储它们的位置和方式以及存储内容方面需要帮助

堆结构及堆排序详解

在理解自动布局方面需要帮助

我在理解事件系统方面有问题

在理解依赖注入时遇到问题

为啥过滤统计信息被忽略