gcc - ARM预取解决方法

标签 gcc assembly arm armv6

我遇到的情况是某些地址空间很敏感,因为没有人响应该地址,因此您将其读取会崩溃。

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的指令集的数量以及在某种程度上将其视为Non的指令集。 -特殊的很少。

我正在寻找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还可以为弹出式窗口后的文字池生成类似此类的手臂代码。

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


希望某人知道通用的或特定于手臂的选项,可以像做return这样的armv4t(例如pop {r4,lr};在arm模式下为bx lr)而无需携带行李,或者在pop pc之后立即将一个分支置于自身(似乎可以解决问题)问题管道对于b作为无条件分支并不感到困惑。

编辑

ldr pc,[something]
bx rn


也导致预取。不会落在-march = armv4t下。 gcc故意生成ldrls pc,[]; b在某处有switch语句,那很好。没有检查后端以查看是否还有其他ldr pc,[]指令生成。

最佳答案

https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html有一个-mpure-code 选项,该选项不会在代码段中放置常量。 “仅当使用MOVT指令为M轮廓目标生成非图片代码时,此选项才可用。”因此,它可能会使用一对mov立即指令而不是从常量池中加载常量。

但是,这不能完全解决您的问题,因为带有伪造的寄存器内容的常规指令的推测执行(在函数内部的条件分支之后)仍然可能触发对不可预测地址的访问。或仅仅是另一个功能的第一条指令可能是一个负载,因此陷入另一个功能也不总是安全的。



我可以尝试阐明为什么它如此晦涩难懂,以至于编译器还没有避免它。

通常,推测性执行错误指令不是问题。在变为非推测性之前,CPU不会真正处理该故障。错误的(或不存在的)分支预测可能会使CPU在找出正确的路径之前做一些缓慢的事情,但是永远不会存在正确性问题。

通常,大多数CPU设计都允许来自内存的推测性负载。但是显然必须保护具有MMIO寄存器的存储区。例如,在x86中,内存区域可以是WB(正常,可写回缓存,允许推测负载)或UC(不可缓存,没有推测负载)。更不用说写合并直写...

您可能需要类似的解决方案来解决您的正确性问题,以阻止推测性执行执行实际上会爆炸的事情。这包括由推测bx r0触发的推测指令获取。 (很抱歉,我不了解ARM,所以我无法建议您如何做。
但这就是为什么即使大多数系统具有无法通过推测方式读取的MMIO寄存器,对于大多数系统来说,这只是一个较小的性能问题。)

我认为,有一种设置可以让CPU从使系统崩溃的地址中进行推测性负载,而不是仅当它们成为非推测性时引发异常,这是非常不寻常的。




在这种情况下,我们关闭了分支预测器


这就是为什么您总是看到投机执行超出了无条件分支(pop)的原因,而不是很少见到的。

使用bx返回的出色侦探工作,表明您的CPU在解码时检测到了这种无条件分支,但没有检查pc中的pop位。 :/

通常,分支预测必须在解码之前进行,以避免获取气泡。给定获取块的地址,请预测下一个块获取地址。预测也是在指令级别而不是提取块级别生成的,供内核的后续阶段使用(因为一个块中可以有多个分支指令,并且您需要知道采用哪个分支指令)。

那是通用理论。分支预测不是100%,因此您不能依靠它来解决您的正确性问题。



x86 CPU可能存在性能问题,其中间接jmp [mem]jmp reg的默认预测是下一条指令。如果推测性执行启动了一些取消较慢的操作(例如某些CPU上的div)或触发了缓慢的推测性内存访问或TLB未命中,则一旦确定正确的路径,它可能会延迟执行。

因此,建议(根据优化手册)在ud2之后放置int3(非法指令)或jmp reg(调试陷阱)或类似内容。或更好的方法是,在其中放置一个跳转表目标,以便在某些时候“掉线”是正确的预测。 (如果BTB没有预测,则下一条指令是它唯一可以做的明智的选择。)

但是,x86通常不会将代码与数据混合在一起,因此,对于文字池很常见的体系结构,这更有可能成为问题。 (但伪造地址的负载仍然可能在间接分支或错误预测的正常分支之后进行推测性的发生。

例如if(address_good) { call table[address](); }可能很容易预测错误并从错误的地址触发推测性代码获取。但是,如果最终的物理地址范围被标记为不可缓存,则加载请求将在内存控制器中停止,直到已知它是非推测性的



返回指令是一种间接分支,但是下一条指令的预测不太可能使用。那么也许bx lr停滞不前是因为投机性下降不太可能有用吗?

在解码阶段未将pop {pc}(又名来自堆栈指针的LDMIA)检测为分支(如果未专门检查pc位),或将其视为通用间接分支。当然,还有其他将ld用作非返回分支的用例,因此将其检测为可能的返回将需要检查源寄存器编码以及pc位。

也许有一个特殊的(内部隐藏的)返回地址预测变量堆栈,当与pc配对时,有助于每次正确地预测bx lr吗? x86这样做是为了预测bl / call指令。



您是否测试过ret是否比pop {r4, pc} / pop {r4, lr}更有效?如果bx lr的特殊处理不仅仅是避免推测性执行垃圾,那么最好让gcc这样做,而不是让它使用bx lr指令或其他内容引导其文字池。

关于gcc - ARM预取解决方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46118893/

相关文章:

vim - 如何在vim中的quickfix中忽略Make输出?

c++ - 无法在 VS 代码中使用 gcc 查看 std::vector 的元素

c - 如何从单独的库文件中包含 syscalls.c?

c - x86 结构体 scanf

c++ - 'ret' 指令访问冲突

ARM M3 重定位代码 -> 故障

c - 如何在 Sublime Text 2 [MAC OS X] 上运行和编译 .c

assembly - 自修改代码看到 0xCC 字节,但调试器没有显示它?

c - arm多少字节对齐?

c++ - ARM 平台的数据转换(来自 x86/x64)