当前位置: 首页 > 知识库问答 >
问题:

用自修改代码观察x86上陈旧的取指令

柴衡
2023-03-14
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
#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。否则,如果执行了过时的指令(两个NOP),则val==0。

我在1.7GHz的Intel Core i5(2011款macbook air)和2.80GHz的Intel(注册商标)至强(注册商标)CPU X3460上运行了这个功能。但是,每次我都看到val==1,表示CPU总是注意到新指令。

有没有人体验过我想要观察的行为?我的推理正确吗?我有点困惑手册提到P6和奔腾处理器,还有什么缺少提到我的酷睿i5处理器。也许是其他原因导致CPU刷新其指令预取队列?任何洞察力都会很有帮助!

共有1个答案

屠锐
2023-03-14

我认为,您应该检查CPU的machine_clears.smc性能计数器(machine_clears事件的一部分)(它在Sandy Bridge 1中可用,它在您的Air PowerBook中使用;在您的Xeon中也可用,它是Nehalem 2-搜索“smc”)。您可以使用oprofileperf或Intel的vtune查找其值:

http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/guid-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm

机器清除

度量描述

某些事件需要清除整个流水线,并从上次退出指令之后重新启动。该度量度量三个这样的事件:内存排序违规、自修改代码以及对非法地址范围的某些加载。

MACHINE_CLEARS事件代码:0xC3 SMC掩码:0x04

检测到自修改代码(SMC)。

检测到得自修改代码计算机清除数.

我们可以看到Intel说“Restarted from just after the last revented instruction.”,所以我认为last revented instruction应该是mov;你的NOP已经在准备中了。但SMC将在MOV退休时提高,它将扼杀所有正在酝酿中的东西,包括NOP。

这个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-我将尝试查找实际的intel文档...现在还不能回答你真正的问题。

这里可能有一些提示:优化手册,248966-026,2012年4月“3.6.9混合代码和数据”:

在代码段中放置可写数据可能无法与自修改代码区分开来。代码段中的可写数据可能会受到与自修改代码相同的性能损失。

和下一节

软件应避免写入正在执行的相同1KB子页中的代码页,或在正在写入的相同2KB子页中提取代码。此外,与另一个处理器共享包含直接或推测执行的代码的页作为数据页,可以触发SMC条件,该条件导致机器和跟踪缓存的整个流水线被清除。这是由于自修改代码条件造成的。

这是可能的,如果您获取,解码和执行一些过时版本的英特尔的指导手册。您可以重置管道并检查此版本:订单号:325462-047US,2013年6月“11.6自我修改代码”。这个版本仍然没有说任何关于较新的CPU,但是提到当你正在使用不同的虚拟地址进行修改时,行为可能在微架构之间不兼容(它可能在你的Nehalem/Sandy桥上工作,可能在..Skymont上不工作)

11.6自修改代码对当前高速缓存在处理器中的代码段中的存储位置的写入导致相关联的高速缓存行无效。这种检查基于指令的物理地址。此外,P6系列和奔腾处理器检查对代码段的写入是否可能修改已预取执行的指令。如果写入影响预取指令,则预取队列无效。后一种检查基于指令的线性地址。对于Pentium 4和Intel Xeon处理器,写入或窥探代码段中的指令(其中目标指令已经解码并驻留在跟踪缓存中)会使整个跟踪缓存无效。后一种行为意味着,在奔腾4和英特尔至强处理器上运行时,自修改代码的程序可能导致性能严重下降。

实际上,对线性地址的检查不应该在IA-32处理器之间产生兼容性问题。包含自修改代码的应用程序使用相同的线性地址来修改和提取指令。

可能使用不同于提取指令的线性地址修改指令的系统软件,例如调试器,将在执行修改的指令之前执行串行化操作,例如CPUID指令,这将自动重新同步指令高速缓存和预取队列。(有关使用自修改代码的更多信息,请参见第8.1.3节“处理自修改和交叉修改代码”。)

对于Intel486处理器,写入高速缓存中的指令将在高速缓存和存储器中修改该指令,但如果在写入之前预取该指令,则可以执行该指令的旧版本。为了防止旧指令被执行,通过在修改指令的任何写入之后立即编码跳转指令来清除指令预取单元

真实更新,谷歌搜索“SMC检测”(带引号),有一些现代Core2/Core iX如何检测SMC的细节,也有很多在SMC检测器中挂着至强和奔腾的勘误表:

DOI 10.1535/ITJ.1203.03(google for it,在citeseerx.ist.psu.edu有免费版本)-在Penryn中添加了“包含过滤器”,以减少错误SMC检测的数量;图9示出了“现有包含检测机制”

两项专利都没有明确说,它们会在SMC逻辑中使用物理地址还是逻辑地址……根据http://nick-black.com/dankwiki/index.php/sandy_bridge,Sandy bridge中的L1i是VIPT(虚拟索引、物理标记、索引的虚拟地址和标记中的物理地址),因此我们有L1缓存返回数据时的物理地址。我认为intel可能会在SMC检测逻辑中使用物理地址。

使用转换lookaside缓冲区检测到自修改代码。其中存储有物理页地址,可以使用存储器中存储的物理存储器地址对这些物理页地址执行监听。...为了提供比地址页更细的粒度,在高速缓存中的每个条目中包括细命中位,将高速缓存中的信息与存储器中的页的部分相关联。

 类似资料:
  • 问题内容: 您是否曾经用Java 创建或遇到过自我修改的代码?如果是,请发布链接或直接发布代码。 问题答案: 忽略悲伤世界,您可能会通过自我修改代码(!)导致自己陷入困境,在我看来,这里有3种选择: 使用Java 6的内置编译器支持以及写入/重新编译/重新加载类 使用Apache BCEL字节码操作库直接编写您的类 利用Java 6的内置脚本支持(或使用Apache BSF)以您选择的JVM脚本语

  • 在Autoconf的第2版,大部分宏被重新命名以使用更加统一和具有描述性的命名方案。下面是被重新命名了的宏的原来名字, 随后给出了这些宏现在的名字。虽然为了保持向后兼容,旧名字仍然能够被autoconf程序所接受,旧名字都 被看作过时的。关于新的命名方案,参见 宏名 。AC—ALLOCAAC—FUNC—ALLOCA AC—ARG—ARRAY 因为用途有限而被删除了。AC—CHAR—UNSIGNED

  • 1、修改AndroidManifest.xml文件 1.添加权限和服务,参考demo <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission androi

  • 问题内容: angular js如何监视自定义指令上的属性以接受要绑定的角度值 这是我到目前为止的内容: 但观察到的值返回为未定义 问题答案: 来自http://docs.angularjs.org/api/ng.$compile.directive.Attributes: 所有这些在Angular中均视为等效: 所以属性归一化为 因此,这就是您想要的:

  • 问题内容: 我正在使用RxJava Observable api使用以下代码: 我的期望是观察代码,即subscribe()方法中的代码,在我指定了计算调度程序后将并行执行。相反,代码仍在单线程上按顺序执行。如何使用RxJava api使代码并行运行。 问题答案: RxJava在异步/多线程方面经常被误解。多线程操作的编码很简单,但是了解抽象是另一回事。 关于RxJava的一个常见问题是如何实现并

  • 有没有办法获取HTTP状态码(比如200400…)在观察实时数据之前? 这是我的实现: 后果 基本存储库 我的存储库 视图模型 在我的代码中,我执行以下操作: 在哪里可以查看状态代码?我不知道在哪里实现状态码返回。