我被告知并且从英特尔的手册中读到可以将指令写入内存,但是指令预取队列已经获取了陈旧的指令并将执行那些旧的指令 . 我没有成功观察到这种行为 . 我的方法如下 .
英特尔软件开发手册从第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);
我有一个汇编函数,它接受一个参数,一个指向我想要更改的指令的指针 .
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
在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);
如果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刷新其指令预取队列?任何见解都会非常有帮助!
2 回答
我想,你应该检查CPU的 MACHINE_CLEARS.SMC 性能计数器(
MACHINE_CLEARS
事件的一部分)(它可以在你的Air powerbook中使用的Sandy Bridge 1中找到;也可以在你的Xeon上使用,这是Nehalem 2 - 搜索"smc" ) . 您可以使用oprofile
,_perf
或Intel的Vtune
来查找其值:http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/GUID-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm
SMC:http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html
英特尔还谈到smc http://software.intel.com/en-us/forums/topic/345561(链接自Intel Performance Bottleneck Analyzer's taxonomy
我想,你会看到一些这样的事件 . 如果是,那么CPU就能够检测到自动修改代码的行为,并提出“机器清除” - 完全重启管道 . 第一阶段是Fetch,他们会向L2缓存询问新的操作码 . 我对每次执行代码的SMC事件的确切计数非常感兴趣 - 这将给我们一些关于延迟的估计..(SMC计算在一些单位中,假设1个单位是1.5个cpu周期--B.6.2 . 英特尔优化手册6)
我们可以看到英特尔说"restarted from just after the last retired instruction.",所以我认为最后退休的指令将是
mov
;你的nops已经在管道中了 . 但SMC将在mov退休时被提升,它将杀死管道中的所有东西,包括nops .这个SMC引起的管道重启并不便宜,Agner在_1181309中有一些测量 - "17.10 Self-modifying code (All processors)"(我认为任何Core2 / CoreiX都像PM一样):
这里建议使用不同的线性地址来使SMC检测器失效:https://stackoverflow.com/a/10994728/196561 - 我实际上回答了你真正的问题 .
这里可能有一些提示:Optimization manual, 248966-026, April 2012 "3.6.9 Mixing Code and Data":
和下一节
因此,可能有一些原理图控制可写子页面和可执行子页面的交叉点 .
您可以尝试从其他线程(交叉修改代码)进行修改 - 但需要非常仔细的线程同步和管道刷新(您可能希望在编写器线程中包含一些强制延迟;同步后的CPUID是理想的) . 但你应该知道他们已经使用“ nukes ”解决了这个问题 - 检查US6857064专利 .
如果你已经获取,解码并执行了一些陈旧版本的英特尔指导手册,这是可能的 . 您可以重置管道并检查此版本:Order Number: 325462-047US, June 2013 "11.6 SELF-MODIFYING CODE" . 此版本仍未提及有关较新CPU的任何内容,但提到当您使用不同的虚拟地址进行修改时,微架构之间的行为可能不兼容(它可能适用于您的Nehalem / Sandy Bridge并且可能无法正常运行... Skymont)
REAL Update ,用Google搜索 "SMC Detection" (带引号)并且有一些细节如何现代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(Virtually indexed, physically tagged,索引的虚拟地址和标签中的物理地址 . ),因此我们有了_118324_ L1缓存返回数据时的物理地址 . 我认为英特尔可能会在SMC检测逻辑中使用物理地址 .
更重要的是,http://www.google.com/patents/US6594734 @ 1999(2003年出版,只记得CPU设计周期大约3 - 5年)在"Summary"部分说SMC现在在TLB并使用物理地址(换句话说 - 请不要试试愚弄SMC探测器):
(页面的一部分,在专利US6594734中称为象限,听起来像1K子页面,不是吗?)
然后他们说
PS:如果我们将使用大页面(4M或可能是1G)怎么办? L1TLB有很大的页面条目,并且可能有很多错误的SMC检测到1/4的4 MB页面...
PPS:有一种变体,只有早期的P6 / Ppro / P2存在错误处理具有不同线性地址的SMC ...
是的,你会的 .
全部或几乎全部所有现代英特尔处理器都比手册更严格:
他们根据物理地址窥探管道,而不仅仅是线性 .
允许处理器实现比手册更严格 .
他们可能会选择这样做,因为他们遇到的代码不符合手册中的规则,他们不想破坏 .
或者......因为遵守架构规范的最简单方法(在SMC的情况下,以前正式“直到下一个序列化指令”,但在实践中,对于遗留代码,“直到下一个采用的分支,超过???字节距离“)可能更严格 .