Chr*_*ris 23 c x86 caching self-modifying
我被告知并且从英特尔的手册中读到可以将指令写入内存,但是指令预取队列已经获取了陈旧的指令并将执行那些旧的指令.我没有成功观察到这种行为.我的方法如下.
英特尔软件开发手册从第11.6节开始说明
对当前在处理器中高速缓存的代码段中的存储器位置的写入导致相关联的高速缓存行(或多个行)无效.此检查基于指令的物理地址.此外,P6系列和奔腾处理器检查对代码段的写入是否可以修改已经预取执行的指令.如果写入影响预取指令,则预取队列无效.后一种检查基于指令的线性地址.
所以,看起来如果我希望执行陈旧的指令,我需要有两个不同的线性地址引用相同的物理页面.所以,我将内存映射到两个不同的地址.
int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd>=0);
write(fd, zeros, 0x1000);
uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);
Run Code Online (Sandbox Code Playgroud)
我有一个汇编函数,它接受一个参数,一个指向我想要更改的指令的指针.
fun:
push %rbp
mov %rsp, %rbp
xorq %rax, %rax # Return value 0
# A far jump simulated with a far return
# Push the current code segment %cs, then the address we want to far jump to
xorq %rsi, %rsi
mov %cs, %rsi
pushq %rsi
leaq copy(%rip), %r15
pushq %r15
lretq
copy:
# Overwrite the two nops below with `inc %eax'. We will notice the change if the
# return value is 1, not zero. The passed in pointer at %rdi points to the same physical
# memory location of fun_ins, but the linear addresses will be different.
movw $0xc0ff, (%rdi)
fun_ins:
nop # Two NOPs gives enough space for the inc %eax (opcode FF C0)
nop
pop %rbp
ret
fun_end:
nop
Run Code Online (Sandbox Code Playgroud)
在C中,我将代码复制到内存映射文件中.我从线性地址调用函数a1
,但我将指针传递给a2
代码修改的目标.
#define DIFF(a, b) ((long)(b) - (long)(a))
long sz = DIFF(fun, fun_end);
memcpy(a1, fun, sz);
void *tochange = DIFF(fun, fun_ins);
int val = ((int (*)(void*))a1)(tochange);
Run Code Online (Sandbox Code Playgroud)
如果CPU选择了修改后的代码,则val == 1.否则,如果执行过时指令(两个nops),则val == 0.
我在1.7GHz Intel Core i5(2011 macbook air)和Intel(R)Xeon(R)CPU X3460 @ 2.80GHz上运行.但是,每次都看到val == 1表示CPU始终注意到新指令.
有没有人经历过我想观察的行为?我的推理是否正确?我对提到P6和奔腾处理器的手册有点困惑,以及缺乏提及我的Core i5处理器的问题.也许正在发生的其他事情导致CPU刷新其指令预取队列?任何见解都会非常有帮助!
osg*_*sgx 25
我想,您应该检查CPU 的MACHINE_CLEARS.SMC
性能计数器(MACHINE_CLEARS
事件的一部分)(它可以在您的Air powerbook中使用的Sandy Bridge 1中获得;也可以在Xeon上使用,这是Nehalem 2 - 搜索"smc" ).您可以使用oprofile
,perf
或英特尔的Vtune
找到它的价值:
机器清除
指标说明
某些事件需要在最后一条退役指令之后清除并重新启动整个管道.此度量标准测量三种此类事件:内存排序违规,自修改代码以及对非法地址范围的某些加载.
可能的问题
执行时间的很大一部分用于处理机器清除.检查MACHINE_CLEARS事件以确定具体原因.
SMC:http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html
MACHINE_CLEARS事件代码:0xC3 SMC掩码:0x04
检测到自修改代码(SMC).
检测到自修改代码机清除的数量.
英特尔还谈到了smc http://software.intel.com/en-us/forums/topic/345561(与英特尔性能瓶颈分析仪的分类标准相关联)
检测到自修改代码时会触发此事件.这通常可以由进行二进制编辑以强制它采取某种路径(例如黑客)的人使用.此事件计算程序写入代码段的次数.自修改代码会导致所有Intel 64和IA-32处理器严重受损.修改后的高速缓存行被写回L2和LLC高速缓存.此外,需要重新加载指令,从而导致性能损失.
我想,你会看到一些这样的事件.如果是,那么CPU就能够检测到自动修改代码的行为,并提出"机器清除" - 完全重启管道.第一阶段是Fetch,他们会向L2缓存询问新的操作码.我对每次执行代码的SMC事件的确切计数非常感兴趣 - 这将给我们一些关于延迟的估计..(SMC计算在一些单位中,假设1个单位是1.5个cpu周期--B.6.2.英特尔优化手册6)
我们可以看到英特尔说"从最后一次退役指令之后重新启动.",所以我认为最后退休的指令将是mov
; 你的nops已经在管道中了.但SMC将在mov退休时被提升,它将杀死管道中的所有东西,包括nops.
这个SMC引起的管道重启并不便宜,Agner在Optimizing_assembly.pdf中有一些测量- "17.10自修改代码(所有处理器)"(我认为任何Core2/CoreiX就像PM一样):
修改后立即执行一段代码的代价是P1约为19个时钟,PMMX约为31个时钟,PPro,P2,P3,PM为150-300.P4将在自修改代码后清除整个跟踪缓存.80486及更早版本的处理器需要在修改代码和修改代码之间跳转才能刷新代码缓存....
自修改代码不被认为是良好的编程习惯.只有当速度增益很大并且修改后的代码执行了很多次才能使用它时,才能使用它,这有利于超过使用自修改代码的惩罚.
这里建议使用不同的线性地址来使SMC检测器失效:https: //stackoverflow.com/a/10994728/196561 - 我将尝试查找实际的英特尔文档...现在无法真正回答您的真实问题.
这里可能有一些提示:优化手册,248966-026,2012年4月 "3.6.9混合代码和数据":
将可写数据放在代码段中可能无法与自修改代码区分开来.代码段中的可写数据可能会遭受与自修改代码相同的性能损失.
和下一节
软件应该避免写入正在执行的同一个1 KB的子页面中的代码页,或者在正在写入的同一个2 KB的子页面中获取代码.此外,将包含直接或推测执行的代码的页面与另一个处理器共享作为数据页面可以触发SMC条件,该条件导致机器的整个流水线和跟踪高速缓存被清除.这是由于自修改代码条件.
因此,可能有一些原理图控制可写子页面和可执行子页面的交叉点.
您可以尝试从其他线程(交叉修改代码)进行修改 - 但需要非常仔细的线程同步和管道刷新(您可能希望在编写器线程中包含一些强制延迟;同步后的CPUID是理想的).但你应该知道他们已经使用" 核武器 " 解决了这个问题- 请查看US6857064专利.
我对提到P6和奔腾处理器的手册感到有些困惑
如果你已经获取,解码并执行了一些陈旧版本的英特尔指导手册,这是可能的.您可以重置管道并检查此版本:订单号:325462-047US,2013年6月 "11.6自修改代码".此版本仍未提及有关较新CPU的任何内容,但提到当您使用不同的虚拟地址进行修改时,微架构之间的行为可能不兼容(它可能适用于您的Nehalem/Sandy Bridge并且可能无法正常运行... Skymont)
11.6自修改代码 对当前在处理器中高速缓存的代码段中的存储器位置的写入导致相关联的高速缓存行(或多个行)无效.此检查基于指令的物理地址.此外,P6系列和奔腾处理器检查对代码段的写入是否可以修改已经预取执行的指令.如果写入影响预取指令,则预取队列无效.后一种检查基于指令的线性地址.对于Pentium 4和Intel Xeon处理器,代码段中的指令的写入或窥探(其中目标指令已经被解码并驻留在跟踪高速缓存中)使整个跟踪高速缓存无效.
实际上,检查线性地址不应该在IA-32处理器之间产生兼容性问题.包含自修改代码的应用程序使用相同的线性地址来修改和获取指令.
使用与用于获取指令的线性地址不同的线性地址修改指令的系统软件(例如调试器)将在执行修改的指令之前执行序列化操作,例如CPUID指令,这将自动重新同步指令缓存和预取队列.(有关使用自修改代码的更多信息,请参见第8.1.3节"处理自身和交叉修改代码".)
对于Intel486处理器,对高速缓存中的指令的写入将在高速缓存和存储器中对其进行修改,但是如果在写入之前预取了指令,则旧指令的指令可以是执行的指令.为了防止旧指令被执行,在任何修改指令的写操作之后立即通过编写跳转指令来刷新指令预取单元
真实更新,谷歌搜索"SMC检测"(带引号),并有一些细节如何现代Core2/Core iX检测SMC以及许多勘误列表与Xeons和Pentiums挂在SMC探测器:
http://www.google.com/patents/US6237088用于跟踪管道中的飞行指令的系统和方法@ 2001
DOI 10.1535/itj.1203.03(google for it,citeseerx.ist.psu.edu有免费版) - Penryn中增加了"包含过滤器"以减少错误的SMC检测次数; "现有包含检测机制"如图9所示
http://www.google.com/patents/US6405307 - 有关SMC检测逻辑的较早专利
根据专利US6237088(图5,摘要),存在"行地址缓冲器"(具有许多线性地址,每个取出指令一个地址 - 或者换句话说,缓冲器充满具有高速缓存行精度的取出IP).每个商店,或每个商店的更精确的"商店地址"阶段将被送入并行比较器进行检查,将与任何当前正在执行的指令存储交叉.
两项专利都没有明确说明,他们是否会在SMC逻辑中使用物理或逻辑地址... Sandy网桥中的L1i是VIPT(虚拟索引,物理标记,标记的索引和物理地址的虚拟地址.)根据http ://nick-black.com/dankwiki/index.php/Sandy_Bridge所以我们在L1缓存返回数据时有物理地址.我认为英特尔可能会在SMC检测逻辑中使用物理地址.
更重要的是,http://www.google.com/patents/US6594734 @ 1999(2003年出版,只记得CPU设计周期大约3 - 5年)在"摘要"部分中说SMC现在处于TLB并使用物理地址(或换句话说 - 请不要试图欺骗SMC探测器):
使用转换后备缓冲器来检测自修改代码.[其]存储有物理页面地址,在该物理页面地址上可以使用存储器的物理存储器地址来执行窥探....为了提供比一页地址更精细的粒度,FINE HIT位包含在高速缓存中的每个条目中,将高速缓存中的信息与存储器内的页面部分相关联.
(页面的一部分,在专利US6594734中称为象限,听起来像1K子页面,不是吗?)
然后他们说
因此,由存储指令触发到存储器中的窥探可以通过将存储在指令高速缓存内的所有指令的物理地址与存储在相关页面的存储器内的所有指令的地址进行比较来执行SMC检测.如果存在地址匹配,则表示已修改内存位置.在地址匹配的情况下,指示SMC条件,退出单元刷新指令高速缓存和指令流水线,并从存储器中取出新指令以存储到指令高速缓冲存储器中.
因为用于SMC检测的窥探是物理的,并且ITLB通常接受将线性地址转换为物理地址作为输入,所以ITLB另外形成为物理地址上的内容可寻址存储器并且包括附加的输入比较端口(参考作为窥探端口或反向转换端口)
- 所以,检测SMC,他们迫使商店通过窥探转发物理地址返回指令缓存(类似窥探将从其他核心/ CPU或从DMA传送写入缓存我们....),如果窥探的物理层.解决与缓存行冲突的问题,存储在指令缓冲区中,我们将通过从iTLB传送到退出单元的SMC信号重新启动流水线.能想象有多少个CPU时钟会从DTLB这种窥探循环通过ITLB和退役单元(它不能退下一个"NOP"指令,虽然它早于MOV执行且没有副作用)被浪费.但是WAT?ITLB具有物理地址输入和第二个CAM(大而热),仅用于支持和防御疯狂和欺骗自修改代码.
PS:如果我们将使用大页面(4M或可能是1G)怎么办?L1TLB有很大的页面条目,并且可能有很多错误的SMC检测到1/4的4 MB页面...
PPS:有一种变体,只有早期的P6/Ppro/P2存在错误处理具有不同线性地址的SMC ...
我被告知并且从英特尔的手册中读到可以将指令写入内存,但是指令预取队列已经[可能]已经获取了陈旧的指令并且[可能]执行那些旧的指令.我没有成功观察到这种行为.
是的,你会的.
所有或几乎所有现代英特尔处理器都比手册更严格:
他们根据物理地址窥探管道,而不仅仅是线性.
允许处理器实现比手册更严格.
他们可能会选择这样做,因为他们遇到的代码不符合手册中的规则,他们不想破坏.
或者......因为遵守架构规范的最简单方法(在SMC的情况下,以前正式"直到下一个序列化指令",但在实践中,对于遗留代码,"直到下一个采用的分支,超过???字节距离")可能更严格.
Sandybridge-family(至少是Skylake)仍然有相同的行为,显然是在窥探物理地址。
不过,您的测试有点过于复杂。我没有看到远跳转的意义,如果您将 SMC 函数组装(并在必要时链接)到一个平面二进制文件中,您只需打开 + mmap 它两次即可。makea1
和a2
函数指针,然后return a1(a2)
映射后main就可以了。
这是一个简单的测试工具,以防有人想在自己的机器上尝试:(open/assert/mmap 块是从问题中复制的,感谢您的起点。)
(缺点是,您每次都必须重建 SMC 平面二进制文件,因为映射它MAP_SHARED
实际上会修改它。IDK 如何获取同一物理页的两个映射,而不会修改底层文件;写入 MAP_PRIVATE 会使其一个不同的物理页。因此,将机器代码写入文件并将其映射是有意义的,现在我意识到了这一点。但我的 asm 仍然简单得多。)
// smc-stale.c
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
typedef int (*intfunc_t)(void *); // __attribute__((sysv_abi)) // in case you're on Windows.
int main() {
int fd = open("smc-func", O_RDWR);
assert(fd>=0);
intfunc_t a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
intfunc_t a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);
return a1(a2);
}
Run Code Online (Sandbox Code Playgroud)
测试函数的 NASM 来源:
(请参阅如何使用 GNU GAS 汇编器生成普通二进制文件,如 nasm -f bin?的as
+ld
替代方案nasm -f
)
;;build with nasm smc-func.asm -fbin is the default.
bits 64
entry: ; rdi = another mapping of the same page that's executing
mov byte [rdi+dummy-entry], 0xcc ; trigger any copy-on-write page fault now
mov r8, rbx ; CPUID steps on call-preserved RBX
cpuid ; serialize for good measure
mov rbx, r8
; mfence
; lfence
mov dword [rdi + retmov+1 - entry], 0 ; return 0 for snooping
retmov:
mov eax, 1 ; opcode + imm32 ; return 1 for stale
ret
dummy: dd 0xcccccccc
Run Code Online (Sandbox Code Playgroud)
在运行 Linux 4.20.3-arch1-1-ARCH 的 i7-6700k 上,我们没有观察到过时的代码获取。用 a覆盖mov
立即数的 确实在运行之前修改了该指令。1
0
peter@volta:~/src/experiments$ gcc -Og -g smc-stale.c
peter@volta:~/src/experiments$ nasm smc-func.asm && ./a.out; echo $?
0
# remember to rebuild smc-func every time, because MAP_SHARED modifies it
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
2563 次 |
最近记录: |