我有一种情况,其中某些地址空间很敏感,因为您阅读它会崩溃,因为那里没有人响应该地址。
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
在这种情况下,处理器正在等待从弹出(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 代码。
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 之类的返回(例如 pop {r4,lr}; bx lr 在 arm 模式下)而不带行李或在 pop pc 之后立即将分支放到 self (似乎解决了问题管道并不混淆 b 作为无条件分支。
编辑
ldr pc,[something]
bx rn
也会导致预取。这不会属于-march = armv4t。gcc 故意生成 ldrls pc,[]; b 某处用于 switch 语句,这很好。没有检查后端是否有其他 ldr pc,[] 指令生成。
编辑
看起来 ARM 确实将此报告为勘误表(勘误表 720247,“可以在内存映射中的任何位置进行推测性指令提取”),希望我在我们花了一个月的时间之前就知道...