程序计数器的值如何递增?

How does the value of the program counter increment?

我有一个指令块,我想知道它的 pc 寄存器是如何工作的。它在维基百科上说,pc 寄存器保存下一条要执行的指令的值,但是,查看二进制 ninja 的反汇编图对于同一指令块似乎并不完全正确。

这是二进制 ninja 的反汇编图的一部分,其中在每次内存加载前写入加载发生的内存地址。

000080ec         ldr r3, [pc, #76] -> 0x813c = 0x80f0 + 0x4c -> pc = 80f0 ?? (shouldnt it be 80ee).
000080ee         cmp r3, #0
000080f0         it eq
000080f2         ldreq r3, [pc, #68] -> 0x8138 = 0x80f4 + 0x44 -> pc = 80f4 (this makes sense).
000080f4         mov sp, r3
000080f6         sub.w sl, r3, #65536 (edited) 

这也发生在代码中,并不总是 pc 保存下一条要执行的指令的地址。有什么我应该考虑的吗?

pc 相关 ldr 指令的 thumb 编码最近刚刚在 SO 上介绍过。当您查看指令集的文档时,正如我们所指出的那样,您会知道从文档的角度来看,在 thumb2 之前的早期,PC 领先两个字节,但现在对于 thumb,它比指令地址领先 4 个字节. pc偏移量以字为单位编码,因此使用的地址是

((instruction address + 4 ) & 0xFFFFFFFC) + (immed<<2)

消除所有关于前面两件事的困惑。

现实情况是有多个程序计数器,使用单个程序计数器实际获取内容和进行 pc 相对寻址的日子在较旧、较简单的体系结构中已成为历史的一部分。

这两个领先的东西是过去的一部分,但出于兼容性原因,从 acorn 一直延续到现在的 arm 产品,就像 x86 和其他有遗留的东西不再是他们所说的那样(分支shadow/defer 插槽(以 mips 为单位)。

管道是不同的,人们会假设每一种不同的 arm 产品(不是体系结构,而是产品 cortex-m0、cortex-m4、cortex-a7 等)的管道实现以及核心跟踪事物的方式各不相同。前面的两个由某种形式的程序计数器合成,跟踪管道中的指令。同样,fetch/prefetch/branch 预测是程序计数器的所有形式,但不假定为单个程序计数器。 r15 本身在寄存器文件中要么是真实的,要么是假的,或者两者都是(我希望不在寄存器文件中,为什么要烧掉那些周期而不增加价值)。

就像在软件中一样,您可以有一个 reg[15] 数组项、一个 pc_fetch、一个 pc_current_inst、pc_execution、一个 pc_possible_branch、一个 pc_branch_prediction 一组变量来跟踪处理器的模拟,逻辑也可以。而在什么时候使用哪个取决于你在做什么。正如在指令操作中所描述的那样,我们作为 PC 的程序员所关心的是一个地址,该地址比指令所在的地址“提前两个”。对于 thumb2,前两个不再有意义,因此对于 thumb 模式,它是 4 个字节,对于 arm 模式,是指令地址前 8 个字节。然后您按照文档了解在执行指令期间如何使用该 PC。

对于 BX 和其他能够模式切换的指令,成为“程序计数器”的地址的定义是不同的,lsbit 驱动模式切换到(并被它不存在的分支剥离)程序计数器,有一个 psr 位来处理它)。这些地址也是程序计数器的一种形式,暂时至少是要分支到的指令的实际地址,而不是前面两个。

在很多早期的处理器实现中,你有一个或一个程序计数器的想法,你在继续下一个之前一次获取、解码和执行一条指令(并不意味着人们不再做那些设计,你可以用老式的方式制作小巧高效的小控制器,人们仍然这样做,并且在我们使用的产品中)。在这种情况下,pc 用于获取可能超过一个字节的指令,一旦指令被完全获取,程序计数器至少暂时指向下一条指令。由于提取和解码已经完成,现在可以开始执行该指令。如果程序计数器用作该指令的输入,则它指向下一条指令,如果用作跳转或分支中的目标,则它会被修改,完成后,下一次提取发生在它恰好指向的任何地方。这些架构中的许多都是可变长度指令集,因此一条指令可能是一、二、三……字节长,因此 pc 地址相对于执行时的指令地址是变化的。早期的 arm 来自具有固定大小指令的流水线类型解决方案,因此如果您只有一个程序计数器,那么,根据流水线设计,如果您使用教科书式的流水线设计,那么执行将在流水线中的固定深度进行,这意味着当你执行时,程序计数器会提前那么多。

您缺少的关键是 Thumb ldr Rd, [Pc, #imm] 指令中 PC 的值在使用前对齐到 4 个字节。 ARMv7 Architecture Reference Manual 中略微删节的伪代码是:

t = UInt(Rt);
imm32 = ZeroExtend(imm8:’00’, 32);
base = Align(PC,4);
address = base + imm32;
data = MemU[address,4];
R[t] = data;

所以回到你的例子:

000080ec         ldr r3, [pc, #76]

我们知道PC在Thumb模式下读为当前地址加4字节,所以PC读为0x80f0。此值已对齐到 4 个字节,因此 base 具有相同的值。为此,我们添加 76(立即数始终是四的倍数,不存储两个最低有效位)得到 0x813c.

第二个例子:

000080f2         ldreq r3, [pc, #68]

这与上面的ldr指令相同。反汇编程序向助记符添加 eq 后缀,因为指令受前面的 IT 块的条件执行约束。不过,这不会以任何方式影响指令编码。 PC 读作 0x80f6,与 0x80f4 对齐 4 个字节。为此,我们添加 68,获得 0x8138 作为加载地址。

有关详细信息,请参阅 ARM 体系结构参考手册。