- 对于 B、BL、CBNZ 和 CBZ 指令,PC 的值是当前指令的地址加4字节。 - 对于所有其他使用标签的指令,PC 的值是当前指令的地址加4字节,结果的第1位被清零以使其字对齐。
简单地说,PC寄存器的值指向下一个指令之后的指令。这是我不理解的事情。通常(特别是在x86上),程序计数器寄存器用于指向将要执行的下一条指令的地址。
那么,这背后的前提是什么呢?也许是条件执行吗?
这是一个糟糕的遗留抽象泄漏问题。
原始的ARM设计有一个三级流水线(取指-译码-执行)。为了简化设计,他们选择让PC寄存器读取当前指令获取地址线上的值,而不是两个周期前正在执行的指令。由于大多数PC相对地址是在链接时计算的,所以更容易让汇编器/链接器来处理这个2个指令的偏移量,而不是设计所有逻辑来“修正”PC寄存器。
当然,这都是“30年前有意义的事情”。现在想象一下,在今天的15+级、多发射、乱序流水线上保持寄存器中有有意义的值需要付出的代价,你可能会明白为什么现在很难找到一个CPU设计师认为将PC寄存器作为一个通用寄存器是一个好主意。
不过,也好过像延迟槽那样可怕。相反,与您所想的相反,使每个指令执行有条件性实际上只是围绕预取偏移量的另一个优化。而不是总是在绕过条件代码时花费流水线刷新延迟(或像疯子一样执行剩下的内容),您可以完全避免非常短的分支;流水线保持繁忙状态,解码的指令在标志不匹配时只需执行NOP。再次说明,现在我们有有效的分支预测器,它最终成为了一个妨碍,但对于1985年来说,这很酷。
bx
、blx
或bxj
指令的目标地址的最低有效位上才会控制指令集切换。当前状态在CPSR的第5位中表示。 - Notlikethatldr r15,[r0]
跳转到由r0标识的存储在内存中的指针。你有任何想法为什么在ARM7-TDMI上需要这样做吗? - supercat以下是一个例子: C程序:
int f,g,y;//global variables
int sum(int a, int b){
return (a+b);
}
int main(void){
f = 2;
g = 3;
y = sum(f, g);
return y;
}
00008390 <sum>:
int sum(int a, int b) {
return (a + b);
}
8390: e0800001 add r0, r0, r1
8394: e12fff1e bx lr
00008398 <main>:
int f, g, y; // global variables
int sum(int a, int b);
int main(void) {
8398: e92d4008 push {r3, lr}
f = 2;
839c: e3a00002 mov r0, #2
83a0: e59f301c ldr r3, [pc, #28] ; 83c4 <main+0x2c>
83a4: e5830000 str r0, [r3]
g = 3;
83a8: e3a01003 mov r1, #3
83ac: e59f3014 ldr r3, [pc, #20] ; 83c8 <main+0x30>
83b0: e5831000 str r1, [r3]
y = sum(f,g);
83b4: ebfffff5 bl 8390 <sum>
83b8: e59f300c ldr r3, [pc, #12] ; 83cc <main+0x34>
83bc: e5830000 str r0, [r3]
return y;
}
83c0: e8bd8008 pop {r3, pc}
83c4: 00010570 .word 0x00010570
83c8: 00010574 .word 0x00010574
83cc: 00010578 .word 0x00010578
看上面 LDR 的 PC 值——这里用于将变量 f、g 和 y 的地址加载到 r3 中。
83a0: e59f301c ldr r3, [pc, #28];83c4 main+0x2c
PC=0x83c4-28=0x83a8-0x1C = 0x83a8
PC的值是当前执行指令的下一个指令的下一个指令。由于ARM使用32位指令,但使用字节地址,因此+8表示8个字节,即两个指令的长度。
因此,附加的ARM架构的5级流水线是:获取、解码、执行、内存、写回。
每个时钟周期PC寄存器增加4,因此当指令冒泡到执行——即当前指令时,PC寄存器已经过去了2个时钟周期!现在是+8。实际上这意味着:PC指向“获取”指令,当前指令表示“执行”指令,因此PC表示将要执行的下一个下一个指令。
顺便说一句: 图片来自Harris的《数字设计和计算机体系结构ARM版》。
lea rax,[rip]
。在x86-32上,最直接的方法可能是使用call
指令,它将EIP作为返回地址push
。不过,在ARM上,它的暴露程度远不及它在任何指令或寻址模式中都可以成为src或dst,如果我没记错的话。 - Peter Cordes