我正在尝试在我的Kabe Lake 7600U上创建一个 specpoline (参见 Henry Wong),我使用的是CentOS 7。
完整的测试存储库可在GitHub上找到。
我的specpoline版本如下(参见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+RELOAD1将specpoline插入到分析上下文中,同一汇编文件还包含:
buffer
一个连续的缓冲区跨越了256个不同的高速缓存行,每个高速缓存行之间由GAP-1
行分隔,总共为256*64*GAP
字节。
GAP用于防止硬件预取。
下面是图形化描述(每个索引紧随其后)。
timings
一个由256个DWORD组成的数组,每个条目都保存着访问F+R缓冲区中相应行所需的核心周期时间。
flush
一个小函数,用于触碰F+R缓冲区中的每一页(使用存储器操作,确保COW在我们这一边),并驱逐指定的行。
'profile`
标准的配置文件函数,使用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程序被用来“引导”测试工具。
运行测试
代码使用预处理条件来有条件地在架构路径(实际上是在specpoline本身中)放置存储,如果定义了ARCH_STORE
,并且有条件地在推测路径中放置存储,如果定义了SPEC_STORE
。
两个存储访问F+R缓冲区的第一行。
运行make run_spec
和make run_arch
将使用相应的符号汇编spec.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
我在体系结构路径中放置了一个存储器来测试时序功能,似乎可以工作。
然而,我无法通过投机路径获得相同的结果。
为什么CPU没有进行投机执行存储操作?
1 我承认我从未花时间区分所有缓存分析技术。我希望我使用了正确的名称。通过FLUSH+RELOAD,我指的是清除一组行,推测执行一些代码,然后记录访问每个清除行所需的时间的过程。
sqrtpd
依赖链可能是你最好的选择。 - Peter Cordessqrtpd
并得到了类似的结果。然而,我没有初始化用作输入(和输出)的XMM寄存器,认为这并不重要。我再次进行了测试,但这次我使用了两个值为1e200
的双精度数来初始化寄存器,结果是间歇性的。有时候该行会被推测获取,有时候则不会。似乎movups xmm0, [...]
后跟十个sqrtpd xmm0, xmm0
是最佳组合,尽管不是100%可靠。现在有点晚了,我明天会更新答案。感谢您的建议! - Margaret Bloom