理解带有 pc 偏移量的 Cortex-M 汇编 LDR

Understanding Cortex-M assembly LDR with pc offset

我在看这段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

这是我的问题:

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

When pc is used for reading there is an 8-byte offset in ARM mode and 4-byte offset in Thumb mode.

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

参考:

Strange behaviour of ldr [pc, #value]

Why does the ARM PC register point to the instruction after the next one to be executed?

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

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

您错过了 Thumb 模式规则的一个关键部分,在您链接的问题之一中引用 (Why does the ARM PC register point to the instruction after the next one to be executed?):

For all other instructions that use labels, the value of the PC is the address of the current instruction plus 4 bytes, with bit[1] of the result cleared to 0 to make it word-aligned.

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

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

  • 相当于0xA56 & -4 = 0xa54然后+4 + 92,因为+4不修改位#1;您可以考虑在添加 +4 之前或之后清除它。但是你不能在加上PC-relative offset之后清除这个位;对于 ldrb 等其他指令可以不对齐。 (Thumb 模式 ldr 在字中编码偏移量以更好地利用有限的位数,因此缩放偏移量和最终加载地址始终清除位 [1:0]。)

(感谢陈峰发现,我一开始也看漏了!)

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

规则不是“相对于这条指令的结尾/下一条指令的开始”。 说明该规则的答案和评论恰好在这种情况下得到正确答案,但在其他 Thumb 情况下会得到错误答案,例如 ,其中 PC 相对加载指令恰好从 4 字节对齐的地址开始,因此 PC 的第 1 位已被清除。

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

来自 ARM 文档:

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

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

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

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

不同的文档会以不同的方式显示数学,但它是相同的数学。 PC 是唯一令人困惑的部分,尤其是对于拇指而言。对于 ARM,你添加 8,前面两个,对于 thumb,它基本上是 4,因为执行无法判断是否有 thumb2 来了,如果他们尝试这样做,它会破坏很多东西。因此,为拇指前面的两个加 4。由于 thumb 是压缩的,所以他们不使用字节偏移量,而是使用提供 4 倍范围的字偏移量。同样这个 and/or 其他指令只能向前看不能向后看所以无符号偏移量。这就是为什么在用 thumb 组装东西时会出现对齐错误,而在 arm 中组装东西时会出现对齐错误(根据体系结构和设置,你会得到你得到的东西)。 Thumb 无法为这样的指令编码任何地址。

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