0

我正在查看这段 C 代码的反汇编代码:

#define GPIO_PORTF_DATA_R       (*((volatile unsigned long *)0x400253FC))
int main(void){    
    // Initialization code
    while(1) {
        SW1 = GPIO_PORTF_DATA_R&0x10;  // Read PF4 into SW1
        // Other code
        SW2 = GPIO_PORTF_DATA_R&0x01;
    }
}

SW1=行的程序集是(抱歉无法复制代码):

https://imgur.com/dnPHZrd

以下是我的问题:

  • 在第一行,PC = 0x00000A56,PC + 92 = 0x00000AB2,不等于 0x00000AB4,如图所示。为什么?

我对 SO 做了一些研究,发现 PC 实际上指向要执行的 Next Next 指令。

当 pc 用于读取时,ARM 模式下有 8 字节偏移,Thumb 模式下有 4 字节偏移。

但是 0x00000AB4 - 0x00000A56 = 0x5E = 94,它也不匹配 92+8 或 92+4。我哪里做错了?

参考

ldr [pc, #value] 的奇怪行为

为什么ARM PC寄存器指向下一条要执行的指令之后?

LDR Rd,-Label 与 LDR Rd,[PC+Offset]

4

3 回答 3

2

来自 ARM 文档:

Operation 
  address = (PC[31:2] << 2) + (immed_8 * 4) 
  Rd = Memory[address, 4]

pc 是 0xA56+4,因为前面有两条指令,这是拇指所以 4 个字节。

(0xA5A>>2)<<2 + (0x17*4)
or
(0x00000A5A&0xFFFFFFFC) + (0x17<<2)
0xA58+92=0xA64

这是一个 LDR,因此理想情况下它是一个基于字的地址。因为 thumb 指令可以在一个非字对齐的地址上,所以您首先当然要添加两个指令(thumb2 使这变得复杂,但为 thumb 添加四个)。然后将偏移量的低两位(LDR)归零,因此需要将其转换为字节,乘以四。如果您考虑它的每个部分,这会使编码更有意义。在 arm 模式下,PC 已经是字对齐的,因此不需要 step(在 arm 模式下,立即数有更多位,因此它是基于字节而不是基于字的),这使得 arm 和 thumb 之间的偏移编码可能令人困惑。

各种文件将以不同的方式显示数学,但它仍然是相同的数学。PC 是唯一令人困惑的部分,尤其是对于拇指而言。对于 ARM,您在前面添加 8,两个,对于 thumb,它基本上是 4,因为执行无法判断是否有 thumb2 到来,如果他们尝试这样做会破坏很多东西。所以在前面的两个加上 4,作为拇指。由于 thumb 被压缩,它们不使用字节偏移,而是使用 4 倍范围的字偏移。同样,这个和/或其他指令只能向前看,不能向后看,所以无符号偏移。这就是为什么在拇指中组装东西时会出现对齐错误,而手臂中的东西只是未对齐(并且您得到的结果取决于架构和设置)。Thumb 不能为这样的指令编码任何地址。

为了理解指令编码,特别是基于 pc 的寻址,最好回到早期的 ARM ARM(在 armv5 之前,但如果不是,则只需获取 armv5 )以及 armv6-m 和 armv7-m 以及完整的大小 armv7-ar。并查看每个的伪代码。较旧的通常具有最好的伪代码,但有时它们会忽略地址低位的掩码。没有文件是完美的,它们和其他所有文件一样都有错误。自然,与您使用的内核相关的架构是芯片供应商使用的 IP 的官方文档(甚至到 TRM 的特定版本,因为这些可能以不兼容的方式从一个到另一个)。但是,如果该文档不是很清楚,您有时可以从其他人那里得到一个想法,这些想法在检查后具有兼容的说明和架构特征。

于 2021-12-27T11:31:08.090 回答
1

您错过了 Thumb 模式规则的关键部分,在您链接的一个问题中引用(为什么 ARM PC 寄存器指向要执行的下一个指令之后的指令?):

对于所有其他使用标签的指令,PC 的值是当前指令的地址加上 4 个字节,结果的 bit[1] 清零以使其字对齐。

  • (0xA56 + 4) & -4=0xA58是 PC 相关事物在执行期间的相对位置ldr r0, [PC, #92]

  • ((0xA56 + 4) & -4) + 92= 0xab4,反汇编程序计算的位置。

  • 它相当于 do 0xA56 & -4= 0xa54then +4 + 92,因为+4不修改位 #1; 您可以考虑在添加之前或之后清除它+4。但是在添加 PC 相对偏移后,您无法清除该位;对于其他指令,例如ldrb. (Thumb 模式ldr用字对偏移进行编码,以更好地利用有限的位数,因此缩放的偏移和最终加载地址始终具有位 [1:0] 清除。)

(感谢 Raymond Chen 发现这一点;我最初也错过了它!)

另请注意,您的调试器在断点处停止时会向您显示 PC 值,但这是您停止的指令的地址。(因为这就是 ARM 异常的工作方式,我假设,保存要返回的实际指令,而不是一些偏移量。)指令执行期间,与 PC 相关的东西遵循不同的规则。并且调试器不会“烹饪”这个值来显示 PC在其执行期间将是什么。

该规则不是“相对于这条指令的结尾/下一条指令的开始”。 说明该规则的答案和评论在这种情况下碰巧得到了正确的答案,但在其他 Thumb 情况下会得到错误的答案,例如发生 PC 相对加载指令的LDR Rd,-Label vs LDR Rd,[PC+Offset]从一个 4 字节对齐的地址开始,因此 PC 的第 1 位已经被清除。

您的 LDR 位于0xA56设置第 1 位的地址,因此向下舍入会产生影响。而且您的ldr指令使用了 2 字节编码,而不是您可能需要更大偏移量的 Thumb2 32 位指令。这两件事都意味着向下取整 + 4 恰好是下一条指令的地址,而不是 2 条指令之后或这条指令的中间。

于 2021-12-27T06:21:01.277 回答
-1

由于程序计数器指向下一条指令,当它在地址 处执行 LDR 时0x00000A56,程序计数器将保存下一条指令的地址,即0x00000A58

0x0A58 + 0x5C (decimal 92) == 0x00000AB4

于 2021-12-27T03:56:15.773 回答