我有一个情况,其中一些地址空间非常敏感,如果你读取它,你会崩溃,因为那里没有人来响应那个地址。
pop {r3,pc}
bx r0
0: e8bd8008 pop {r3, pc}
4: e12fff10 bx r0
8: bd08 pop {r3, pc}
a: 4700 bx r0
bx不是编译器创建的指令,而是由于一个32位常数无法在单个指令中立即适应,因此设置了一个PC相对载入。这基本上是文字池。它恰好有一些类似于bx的位。
可以轻松编写一个测试程序来生成该问题。
unsigned int more_fun ( unsigned int );
unsigned int fun ( void )
{
return(more_fun(0x12344700)+1);
}
00000000 <fun>:
0: b510 push {r4, lr}
2: 4802 ldr r0, [pc, #8] ; (c <fun+0xc>)
4: f7ff fffe bl 0 <more_fun>
8: 3001 adds r0, #1
a: bd10 pop {r4, pc}
c: 12344700 eorsne r4, r4, #0, 14
看起来正在发生的是处理器在等待从pop(ldm)返回数据,然后继续执行下一条指令bx r0,并开始在r0中的地址处预取。这导致ARM挂起。
作为人类,我们将pop视为无条件分支,但处理器并不会停止,而是继续通过管道。
预取和分支预测并不新鲜(在这种情况下我们关闭了分支预测器),已经有数十年的历史,不限于ARM,但具有PC作为GPR并且在某种程度上视其为非特殊指令的指令集数量很少。
我正在寻找一个gcc命令行选项来防止这种情况发生。我无法想象我们是第一个遇到这个问题的人。
我当然可以这样做
-march=armv4t
00000000 <fun>:
0: b510 push {r4, lr}
2: 4803 ldr r0, [pc, #12] ; (10 <fun+0x10>)
4: f7ff fffe bl 0 <more_fun>
8: 3001 adds r0, #1
a: bc10 pop {r4}
c: bc02 pop {r1}
e: 4708 bx r1
10: 12344700 eorsne r4, r4, #0, 14
防止这个问题发生
请注意,不仅限于拇指模式,gcc也可以为像这样的情况生成ARM代码,只需在pop之后使用文字池即可。
unsigned int more_fun ( unsigned int );
unsigned int fun ( void )
{
return(more_fun(0xe12fff10)+1);
}
00000000 <fun>:
0: e92d4010 push {r4, lr}
4: e59f0008 ldr r0, [pc, #8] ; 14 <fun+0x14>
8: ebfffffe bl 0 <more_fun>
c: e2800001 add r0, r0, #1
10: e8bd8010 pop {r4, pc}
14: e12fff10 bx r0
希望有人知道一个通用的或针对arm特定的选项,可以执行类似于armv4t返回(例如在arm模式下pop {r4,lr}; bx lr),而不需要额外的负担或在pop pc之后立即放置分支到本身(似乎解决了管道对b的误解作为无条件分支的问题)。
编辑:
ldr pc,[something]
bx rn
还会引起预取,这不会被视为-march=armv4t。gcc有意为switch语句生成ldrls pc,[]; b somewhere,并且这是可以接受的。没有检查后端以查看是否生成其他ldr pc,[]指令。
编辑
看起来ARM已经将此报告为勘误(勘误720247,“可以在内存映射中的任何位置进行推测性指令提取”),真希望我之前知道这一点就好了,我们花了一个月的时间解决它...