为什么这个 specpoline 在 Kaby 湖上不起作用?
Why is this specpoline not working on Kaby lake?
我正在尝试创建一个 specpoline (cfr. Henry Wong) on my Kabe lake 7600U,我正在 运行ning CentOS 7。
完整的测试存储库可在 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
将删除原来的 return 地址并使用人造地址。
在函数的第一部分,我使用一些旧的 FPU 指令创建了一个长延迟依赖链,然后是一个试图欺骗 CPU return 堆栈预测器的独立链。
代码说明
使用 FLUSH+RELOAD1 将 specpoline 插入到分析上下文中,相同的程序集文件还包含:
buffer
一个跨越 256 个不同缓存行的连续缓冲区,每个缓存行由 GAP-1
行分隔,总共 256*64*GAP
字节。
GAP 用于防止硬件预取。
随后是图形描述(每个索引紧接着另一个)。
timings
一个包含 256 个 DWORD 的数组,每个条目包含在 核心周期 中访问 F+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 程序用于“bootstrap”测试工具。
运行 测试
如果定义了 ARCH_STORE
,代码使用有条件的预处理器有条件地在架构路径中放置一个存储(实际上在 specpoline 本身),如果 [=26] 有条件地在推测路径中放置一个存储=] 已定义。
两者都访问 F+R 缓冲区的第一行。
运行make run_spec
和make run_arch
将assemblespec.asm
加上相应的符号,编译测试和运行就可以了。
测试显示了 F+R 缓冲区每一行的计时。
存放在架构路径
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
存放在推测路径
298 216 212 205 205 1286 206 206 208 251 204 206 206 208 208 208
206 206 230 204 206 208 208 208 210 206 202 208 206 204 256 208
206 208 203 206 206 206 206 206 208 209 209 256 202 204 206 210
252 208 216 206 204 206 252 232 218 208 210 206 206 206 212 206
206 206 206 242 207 209 246 206 206 208 210 208 204 208 206 204
204 204 206 210 206 208 208 232 230 208 204 210 1287 204 238 207
207 211 205 282 202 206 212 208 206 206 204 206 206 210 232 209
205 207 207 211 205 207 209 205 205 211 250 206 208 210 278 242
206 208 204 206 208 204 208 210 206 206 206 206 206 208 204 210
206 206 208 242 206 208 206 208 208 210 210 210 202 232 205 207
209 207 211 209 207 209 212 206 232 208 210 244 204 208 255 208
204 210 206 206 206 1383 209 209 205 209 205 246 206 210 208 208
206 206 204 204 208 246 206 206 204 234 207 244 206 206 208 206
208 206 206 206 206 212 204 208 208 202 208 208 208 208 206 208
250 208 214 206 206 206 206 208 203 279 230 206 206 210 242 209
209 205 211 213 207 207 209 207 207 211 205 203 207 209 209 207
我在architectural path中放了一个store来测试timings功能,好像可以。
但是,我无法通过推测路径中的商店获得相同的结果。
为什么 CPU 没有推测性地执行存储?
1 我承认我从来没有真正花时间区分所有缓存分析技术。我希望我使用了正确的名字。通过 FLUSH+RELOAD 我的意思是驱逐一组行的过程,推测性地执行一些代码,然后记录访问每个被驱逐的行的时间。
您的 "long dep chain" 是来自那些微编码 x87 指令的许多微指令。 fcos
在 SKL 上是 53-105 微指令,具有 50-130 周期吞吐量。所以每个 uop 延迟大约 1 个周期,调度程序/保留站 (RS) "only" 在 SKL/KBL 中有 97 个条目。此外,让后面的指令进入无序后端可能是一个问题,因为微代码接管了前端并需要某种机制来决定接下来要发出哪些微指令,这可能取决于某些计算的结果。 (已知 uops 的数量取决于数据。)
如果你想从充满未执行微指令的 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)。 因此他们可以准确地恢复到错误预测,而无需等待错误预测成为退休状态。
mov [rsp], rax
可以在进入 RS 后立即执行,或者至少不依赖于 sqrt
dep 链。一旦存储转发可以产生值,ret
uop 就可以执行并检查预测,并在 sqrt dep 链仍在运行时检测错误预测。 (ret
是 1 个微融合 uop 加载端口 + 端口 6,其中分支执行单元是。)
将 sqrtsd
dep 链耦合到存储新的 return 地址可防止 ret
提前执行 。 在执行端口中执行一个ret
uop = 检查预测并检测是否有错误预测。
(与 Meltdown 对比,其中 "wrong" 路径保持 运行 直到故障负载退役,并且您 想要 它尽快执行(只是不是退休)。但是你通常想把整个崩溃攻击放在其他东西的阴影下,比如 TSX 或 specpoline,在这种情况下你需要这样的东西并且让整个崩溃 在阴影下这个 dep 链。那么 Meltdown 就不需要它自己的 sqrtsd
dep 链了。)
(vsqrtpd ymm
在 SKL 上仍然是 1 uop,吞吐量比 xmm 差,但它具有相同的 latency。所以使用 sqrtsd
因为它是相同的长度,大概更节能。)
在 SKL/KBL (https://agner.org/optimize) 上,最佳情况下的延迟为 15 个周期,而最坏情况下为 16 个周期,因此从什么输入开始并不重要。
I initially used sqrtpd with similar results. However I didn't initialise the XMM register used as input (and output) thinking it didn't matter. I tested again but this time I initialised the register with two doubles of value 1e200 and what I get is an intermittent result. Sometime the line is fetched speculatively sometime it is not.
如果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 微指令,并且当它背靠背发生时吞吐量成本约为 160 个周期。
对比输入 = NaN(全一)总共发出 20M uops,每次迭代 4.5 个周期。 (循环运行 10M sqrtpd
uops,和 10M 宏融合 dec/jcc uops。)
我正在尝试创建一个 specpoline (cfr. Henry Wong) on my Kabe lake 7600U,我正在 运行ning CentOS 7。
完整的测试存储库可在 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
将删除原来的 return 地址并使用人造地址。
在函数的第一部分,我使用一些旧的 FPU 指令创建了一个长延迟依赖链,然后是一个试图欺骗 CPU return 堆栈预测器的独立链。
代码说明
使用 FLUSH+RELOAD1 将 specpoline 插入到分析上下文中,相同的程序集文件还包含:
buffer
一个跨越 256 个不同缓存行的连续缓冲区,每个缓存行由 GAP-1
行分隔,总共 256*64*GAP
字节。
GAP 用于防止硬件预取。
随后是图形描述(每个索引紧接着另一个)。
timings
一个包含 256 个 DWORD 的数组,每个条目包含在 核心周期 中访问 F+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 程序用于“bootstrap”测试工具。
运行 测试
如果定义了 ARCH_STORE
,代码使用有条件的预处理器有条件地在架构路径中放置一个存储(实际上在 specpoline 本身),如果 [=26] 有条件地在推测路径中放置一个存储=] 已定义。
两者都访问 F+R 缓冲区的第一行。
运行make run_spec
和make run_arch
将assemblespec.asm
加上相应的符号,编译测试和运行就可以了。
测试显示了 F+R 缓冲区每一行的计时。
存放在架构路径
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
存放在推测路径
298 216 212 205 205 1286 206 206 208 251 204 206 206 208 208 208
206 206 230 204 206 208 208 208 210 206 202 208 206 204 256 208
206 208 203 206 206 206 206 206 208 209 209 256 202 204 206 210
252 208 216 206 204 206 252 232 218 208 210 206 206 206 212 206
206 206 206 242 207 209 246 206 206 208 210 208 204 208 206 204
204 204 206 210 206 208 208 232 230 208 204 210 1287 204 238 207
207 211 205 282 202 206 212 208 206 206 204 206 206 210 232 209
205 207 207 211 205 207 209 205 205 211 250 206 208 210 278 242
206 208 204 206 208 204 208 210 206 206 206 206 206 208 204 210
206 206 208 242 206 208 206 208 208 210 210 210 202 232 205 207
209 207 211 209 207 209 212 206 232 208 210 244 204 208 255 208
204 210 206 206 206 1383 209 209 205 209 205 246 206 210 208 208
206 206 204 204 208 246 206 206 204 234 207 244 206 206 208 206
208 206 206 206 206 212 204 208 208 202 208 208 208 208 206 208
250 208 214 206 206 206 206 208 203 279 230 206 206 210 242 209
209 205 211 213 207 207 209 207 207 211 205 203 207 209 209 207
我在architectural path中放了一个store来测试timings功能,好像可以。
但是,我无法通过推测路径中的商店获得相同的结果。
为什么 CPU 没有推测性地执行存储?
1 我承认我从来没有真正花时间区分所有缓存分析技术。我希望我使用了正确的名字。通过 FLUSH+RELOAD 我的意思是驱逐一组行的过程,推测性地执行一些代码,然后记录访问每个被驱逐的行的时间。
您的 "long dep chain" 是来自那些微编码 x87 指令的许多微指令。 fcos
在 SKL 上是 53-105 微指令,具有 50-130 周期吞吐量。所以每个 uop 延迟大约 1 个周期,调度程序/保留站 (RS) "only" 在 SKL/KBL 中有 97 个条目。此外,让后面的指令进入无序后端可能是一个问题,因为微代码接管了前端并需要某种机制来决定接下来要发出哪些微指令,这可能取决于某些计算的结果。 (已知 uops 的数量取决于数据。)
如果你想从充满未执行微指令的 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)
mov [rsp], rax
可以在进入 RS 后立即执行,或者至少不依赖于 sqrt
dep 链。一旦存储转发可以产生值,ret
uop 就可以执行并检查预测,并在 sqrt dep 链仍在运行时检测错误预测。 (ret
是 1 个微融合 uop 加载端口 + 端口 6,其中分支执行单元是。)
将 sqrtsd
dep 链耦合到存储新的 return 地址可防止 ret
提前执行 。 在执行端口中执行一个ret
uop = 检查预测并检测是否有错误预测。
(与 Meltdown 对比,其中 "wrong" 路径保持 运行 直到故障负载退役,并且您 想要 它尽快执行(只是不是退休)。但是你通常想把整个崩溃攻击放在其他东西的阴影下,比如 TSX 或 specpoline,在这种情况下你需要这样的东西并且让整个崩溃 在阴影下这个 dep 链。那么 Meltdown 就不需要它自己的 sqrtsd
dep 链了。)
(vsqrtpd ymm
在 SKL 上仍然是 1 uop,吞吐量比 xmm 差,但它具有相同的 latency。所以使用 sqrtsd
因为它是相同的长度,大概更节能。)
在 SKL/KBL (https://agner.org/optimize) 上,最佳情况下的延迟为 15 个周期,而最坏情况下为 16 个周期,因此从什么输入开始并不重要。
I initially used sqrtpd with similar results. However I didn't initialise the XMM register used as input (and output) thinking it didn't matter. I tested again but this time I initialised the register with two doubles of value 1e200 and what I get is an intermittent result. Sometime the line is fetched speculatively sometime it is not.
如果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 微指令,并且当它背靠背发生时吞吐量成本约为 160 个周期。
对比输入 = NaN(全一)总共发出 20M uops,每次迭代 4.5 个周期。 (循环运行 10M sqrtpd
uops,和 10M 宏融合 dec/jcc uops。)