为什么这种谱线在Kaby湖上不起作用?

时间:2019-04-05 20:01:13

标签: assembly x86-64 cpu-architecture branch-prediction speculative-execution

我正在尝试在specpoline (cfr. Henry Wong)上创建一个Kabe lake 7600U,并且正在运行CentOS7。

完整的测试库可在GitHub上找到。

我的specpoline版本如下(cfr. spec.asm):

specpoline:
        ;Long dependancy chain
        fld1
        TIMES 4 f2xm1
        fcos
        TIMES 4 f2xm1
        fcos
        TIMES 4 f2xm1

        %ifdef ARCH_STORE
            mov DWORD [buffer], 241     ;Store in the first line
        %endif

        add rsp, 8
        ret

此版本与Henry Wong的版本不同之处在于流程被转移到建筑路径中。当原始版本使用固定地址时,我将目标传递到堆栈中。
这样,add rsp, 8将删除原始的寄信人地址,并使用人为的地址。

在函数的第一部分中,我使用一些旧的FPU指令创建了一个长延迟依赖关系链,然后创建了一个独立的链,试图欺骗CPU返回堆栈预测变量。


代码说明

使用FLUSH + RELOAD 1 将specpoline插入到配置文件上下文中,同一程序集文件还包含:

buffer

一个连续的缓冲区,它跨越256个不同的缓存行,每个缓存行由GAP-1行隔开,总计256*64*GAP个字节。

GAP用于防止硬件预取。

随后是图形描述(每个索引紧接另一个)。

F+R buffer

timings

由256个DWORD组成的数组,每个条目保留访问 R缓冲区中相应行所需的时间,以核心周期为单位。

flush

一种小功能,可以触摸F + R缓冲区的每一页(有一个商店,请确保COW在我们这边)并逐出指定的行。

“个人资料”

标准配置文件功能,可以很好地使用lfence+rdtsc+lence来配置F + R缓冲区中每一行的负载,并将结果存储在timings数组中。

leak

这是完成实际工作的函数,调用specpoline在推测路径中放置商店,并在体系结构路径中调用profile函数。

;Flush the F+R lines
        call flush

        ;Unaligned stack, don't mind
        lea rax, [.profile]
        push rax
        call specpoline

        ;O.O 0
        ; o o o SPECULATIVE PATH
        ;0.0 O

        %ifdef SPEC_STORE
            mov DWORD [buffer], 241        ;Just a number
        %endif

        ud2                             ;Stop speculation

   .profile:
        ;Ll Ll
        ;  !  !  ARCHITECTURAL PATH
        ;Ll Ll

        ;Fill the timings array
        call profile

一个小的C程序用于“引导”测试工具。

运行测试

如果定义了{{1},则代码使用预处理器有条件地将商店有条件地放置在体系结构路径中(实际上是在specpoline中),如果ARCH_STORE则有条件地将预处理器有条件地放置在推测路径中被定义为。

两个存储区都访问F + R缓冲区的第一行。

运行SPEC_STOREmake run_spec将用相应的符号组装make run_arch,编译并运行测试。

测试显示了F + R缓冲区每一行的时序。

存储在架构路径中

spec.asm

存储在推测路径中

 38    230    258    250    212    355    230    223    214    212    220    216    206    212    212    234
213    222    216    212    212    210   1279    222    226    301    258    217    208    212    208    212
208    208    208    216    210    212    214    213    211    213    254    216    210    224    211    209
258    212    214    224    220    227    222    224    208    212    212    210    210    224    213    213
207    212    254    224    209    326    225    216    216    224    214    210    208    222    213    236
234    208    210    222    228    223    208    210    220    212    258    223    210    218    210    218
210    218    212    214    208    209    209    225    206    208    206   1385    207    226    220    208
224    212    228    213    209    226    226    210    226    212    228    222    226    214    230    212
230    211    226    218    228    212    234    223    228    216    228    212    224    225    228    226
228    242    268    226    226    229    224    226    224    212    299    216    228    211    226    212
230    216    228    224    228    216    228    218    228    218    227    226    230    222    230    225
228    226    224    218    225    252    238    220    229   1298    228    216    228    208    230    225
226    224    226    210    238    209    234    224    226    255    230    226    230    206    227    209
226    224    228    226    223    246    234    226    227    228    230    216    228    211    238    216
228    222    226    227    226    240    236    225    226    212    226    226    226    223    228    224
228    224    229    214    224    226    224    218    229    238    234    226    225    240    236    210

我在架构路径中放置了一家商店来测试计时功能,这似乎可行。

但是,我无法在推测路径中的商店中获得相同的结果。

为什么CPU不以推测方式执行存储?


1 我承认,我从未真正花费时间来区分所有缓存分析技术。我希望我使用正确的名字。所谓FLUSH + RELOAD,是指逐出一组行,以推测方式执行一些代码,然后记录访问每条逐出行的时间。

1 个答案:

答案 0 :(得分:3)

您的“长dep链”来自那些微编码的x87指令。 fcos在SKL上为53-105微秒,周期吞吐量为50-130。因此,每个uop延迟大约1个周期,并且“仅”调度程序/预留站(RS)在SKL / KBL中有97个条目。同样,将以后的指令放入乱序的后端也可能是一个问题,因为微码接管了前端,并且需要某种机制来决定接下来要发出哪些指令,这可能取决于某些计算的结果。 (已知微码的数量与数据有关。)

如果您希望从充满未执行的uo的RS中获得最大的延迟,则sqrtpd依赖链可能是最好的选择。例如

    xorps  xmm0,xmm0                   ; avoid subnormals that might trigger FP assists
    times 40 sqrtsd xmm0, xmm0

    ; then make the store of the new ret addr dependent on that chain
    movd   ebx, xmm0
    ; and  ebx, 0            ; not needed, sqrt(0) = 0.0 = integer bit pattern 0
    mov [rsp+rbx], rax
    ret

自从Nehalem以来,英特尔CPU借助分支顺序缓冲区(可对OoO状态(包括RAT以及可能包括RS))进行快照的分支顺序缓冲区What exactly happens when a skylake CPU mispredicts a branch?来快速恢复分支未命中。 因此他们可以完全恢复到错误的预测,而不必等待错误的预测成为退休状态。

mov [rsp], rax可以在进入RS后立即执行,或者至少不依赖于sqrt dep链。只要存储转发可以产生值,ret uop就可以执行并检查预测,并在sqrt dep链仍处于崩溃状态时检测到错误预测。 (ret是1个微融合uop,用于装载端口+端口6(分支执行单元所在的端口。)

sqrtsd的dep链耦合到存储新的返回地址可防止ret提前执行执行执行端口中的ret uop =检查预测并检测是否存在错误预测。

(与Meltdown相反,“错误”路径一直运行,直到有故障的负载退役,而您希望尽快执行该任务(只是不退役)。但是您通常希望放置一个在诸如TSX或谱线之类的其他事物的阴影下进行完整的Meltdown攻击,在这种情况下,您将需要这样的东西并使整个熔毁在此dep链的阴影下。然后,Meltdown不会需要自己的sqrtsd深度链。)


({vsqrtpd ymm在SKL上仍然是1 uop,吞吐量比xmm差,但是它具有相同的 latency 。因此,请使用sqrtsd,因为它的长度相同,并且大概更加节能。)

最佳情况下的延迟是15个周期,而SKL / KBL(https://agner.org/optimize)上的最坏情况是16个周期,因此开始输入什么都没有关系。


  

我最初使用sqrtpd具有类似结果。但是,我没有初始化用作输入(和输出)的XMM寄存器,以为这没有关系。我再次测试,但是这次我用值1e200的两个双精度数初始化了寄存器,得到的结果是间歇性的。有时是推测性地提取了该行。

如果XMM0保持次正规状态(例如,位模式是一个小整数),则sqrtpd将使用微码辅助。 ({fp_assist.any性能计数器)。即使结果正常,但输入也不正常。我通过以下循环在SKL上测试了这两种情况:

  pcmpeqd   xmm0,xmm0
  psrlq     xmm0, 61        ; or 31 for a subnormal input whose sqrt is normalized
  addpd     xmm0,xmm0       ; avoid domain-crossing vec-int -> vec-fp weirdness

  mov   ecx, 10000000
.loop:
    sqrtpd  xmm1, xmm0
    dec    ecx
    jnz   .loop

 mov eax,1
 int 0x80   ; sys_exit

perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,branches,instructions,uops_issued.any,uops_executed.thread,fp_assist.any对于非常规输入,每次迭代显示1个辅助,并发出951M个微指令(每个迭代〜160个周期)。因此,我们可以得出结论,在这种情况下,sqrtpd的微码辅助大约需要95 uop,并且背靠背发生时的吞吐成本大约为160个周期。

vs。总输入量为2000万次uo = NaN(全1),每次迭代有4.5个周期。 (该循环运行10M sqrtpd微码和10M宏融合的dec / jcc微码。)