问题描述
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)中调用子例程的方式是使用 BL
或 BLX
指令。 PUSH
直到子例程开始执行时才会发生,到那时 PC
中的地址指向一两个指令通过推送,而不是通过分支和关联。当您到达 PUSH
时,PC 不再具有从子例程返回的有用信息。
链接寄存器由处理器硬件加载,作为执行分支和链接的一部分,此时处理器确实知道正确的返回地址。
现在,当从子程序返回时,我们基本上希望将链接寄存器中的值返回到程序计数器中。有几种方法可以做到这一点。正如您所注意到的,您可以 PUSH {LR}
和 POP {PC}
但除非您的子程序本身调用子程序,否则没有必要推送 LR。简单的函数可以只使用 BX LR
。其他一些说明允许您将 PC 用作目标,但其中一些已被弃用。一个有趣的例子是 ARMv7-A 的特殊 return-from-exception:SUBS PC,LR,#4
。