为什么PUSH的寄存器列表不能包含PC?

问题描述

PUSH {R0,PC} ; A1477E: This register combination results in UNPREDICTABLE behavIoUr

为什么它会导致不可预测的行为?为什么在寄存器列表中允许 LR 而不允许 PC?我认为在堆栈中使用 LR 实现递归函数会容易得多,但是当我们仍然可以使用 PC 编写递归函数时,为什么它没有包含在有效的寄存器列表中?

解决方法

试试这些说明

PUSH {R0,R7}
PUSH {R0,R8}

然后拆解,你明白了吗:

0:  e92d0081    push    {r0,r7}
4:  e92d0101    push    {r0,r8}

或者这个:

0:  b481        push    {r0,r7}
2:  e92d 0101   stmdb   sp!,{r0,r8}

还是寄存器列表无效的错误(r8无效)?

如果您获得第一组,那么您是为 ARM 而不是拇指构建,并且警告/错误消息更有意义(对于 r15)。如果您正在构建拇指,它应该说无效(对于 r15 和/或 r8)。

早期的 ARM 文档(用于 ARM)

如果在存储的值中指定了 R15,则实现定义

现在他们说:

SP 和 PC 可以在 ARM 指令的列表中,但不能在 Thumb 指令中。但是:

• ARM 弃用在列表中包含 PC 的 ARM 指令

阅读更多(你现在应该已经阅读了)

str pc,[sp,#-4]!

0:  e52df004    push    {pc}        ; (str pc,#-4]!)

在 armv7-a 中也显示为已弃用,而较旧的实现定义为当时的一些附加注释:

读取程序计数器

当一条指令读取 R15 而不破坏其使用的任何限制时,读取的值是指令的地址加上 8 个字节。由于 ARM 指令始终是字对齐的,因此结果值的位 [1:0] 始终为零。 (在架构的 T 变体中,此行为在 Thumb 状态执行期间发生变化 - 有关详细信息,请参阅第 A6 章 Thumb 指令集。)这种读取 PC 的方式主要用于对附近指令和数据进行快速、位置无关的寻址,包括程序中与位置无关的分支。

当 STR 或 STM 指令存储 R15 时,会出现上述规则的一个例外。此类指令可以存储指令地址加 8 个字节,就像其他读取 R15 的指令一样,也可以存储指令自己的地址加 12 个字节。使用 8 的偏移量还是使用 12 的偏移量是 IMPLEMENTATION DEFINED 。对于存储 R15 的所有 STR 和 STM 指令,实现必须使用相同的偏移量。其中一些不能使用 8,而另一些不能使用 12。

由于这个异常,通常最好避免使用存储 R15 的 STR 和 STM 指令。如果这很困难,请在程序中使用合适的指令序列来确定实现使用的偏移量。

您应该已经注意到,在 ARMv7-M 中它也使用了 16 位寄存器掩码,但是 r15 和 r13 位置被标记为 (0)。

现在如果你进入并输入机器码 0xe92d8101 (thumb2) 你至少会看到 gnu 反汇编程序显示

0: e92d 8101 stmdb sp!,r8,pc}

但我必须挖出一个或几个 cortex-m3 才能看到它们的行为。

Keil 可能只是简单地采用了之前存在的 ARM 警告并将其应用于拇指编码(或回收警告而不是创建新警告),程序计数器的掩码中有一个位置,您不应该使用它和/或不应对其使用做出假设。而不是推送一些东西,使用你推送的其中一个东西来获取当时电脑的副本,然后推送它,这将是可预测的,无论你认为通过推送电脑提供什么价值,你都可以拥有。

,

您在 ARM(至少 ARMv7)中调用子例程的方式是使用 BLBLX 指令。 PUSH 直到子例程开始执行时才会发生,到那时 PC 中的地址指向一两个指令通过推送,而不是通过分支和关联。当您到达 PUSH 时,PC 不再具有从子例程返回的有用信息。

链接寄存器由处理器硬件加载,作为执行分支和链接的一部分,此时处理器确实知道正确的返回地址。

现在,当从子程序返回时,我们基本上希望将链接寄存器中的值返回到程序计数器中。有几种方法可以做到这一点。正如您所注意到的,您可以 PUSH {LR}POP {PC} 但除非您的子程序本身调用子程序,否则没有必要推送 LR。简单的函数可以只使用 BX LR。其他一些说明允许您将 PC 用作目标,但其中一些已被弃用。一个有趣的例子是 ARMv7-A 的特殊 return-from-exception:SUBS PC,LR,#4