【发布时间】:2018-02-17 12:32:42
【问题描述】:
我有一种情况,其中某些地址空间很敏感,因为您读取它会崩溃,因为那里没有人响应该地址。
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
预防问题
注意,不限于 thumb 模式,gcc 也可以在 pop 之后使用文字池生成类似这样的 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 确实将此报告为勘误表 (erratum 720247, "Speculative Instruction fetches can be made anywhere in the memory map"),希望我在我们花了一个月的时间之前就知道这一点...
【问题讨论】:
-
"(避免弹出 {pc}" - 我猜括号应该关闭吗?即用 nops 填充对你来说很好。丢失不是 100% 清楚")",但你为什么不喜欢填充没有多大意义。想想看,超级智能的编译器只有在数据中出现意外分支指令的情况下才会填充,否则数据可能会跟随而没有额外的填充。 (抱歉,我不知道 gcc 是否包含任何可以帮助您的内容)
-
我想知道的是:ARM通常没有不可缓存内存的概念吗?如果 SoC 尝试预加载未连接的地址,那么告诉它可以缓存哪些区域的表肯定有问题。
-
@Ped7g 重新写了这个问题(再次)。我尚未确定例如基于寄存器的 ldr(bhd) 指令是否会启动最终挂起的读取。到目前为止,在 pop 解决问题之后,可能会使用分支到自我(分支到与分支相同的地址)的其他指令,而不必使用自定义 gnu 工具链。同样做 gcc 已经做的 armv4t 的事情,在用电脑返回时,会工作得很好,它不会对 bx 感到困惑。
-
@fuz 缓存和指令获取是两个不同的东西,指令获取可以去任何地址(在这种情况下,我认为它读取 4 个字或 8 个字,围绕地址对齐题)。缓存/mmu 不会阻止提取,我认为 mmu 没有指令/数据控制,并且无论如何都不会工作,因为您从 .text 进行提取和数据访问(如果没有其他内容,则为文字池)。
-
由芯片设计人员确定 amba/axi 总线连接到什么以及它们如何响应,以及由设计人员决定覆盖了多少地址空间等。 ..在我们的例子中,手臂是更大设计的一小部分,手臂的整个地址空间是可编程的,就像 pcie 一样,我们可以改变各种大小的空间块来指向芯片的其余部分,但就像AXI,芯片的其他部分使用不会超时的总线(根据设计),如果程序员遇到没有目标响应的空间。
标签: assembly gcc arm armv6 speculative-execution