是否有任何运行时代码修改的智能案例?

你能想到用于运行时代码修改的任何合法(智能)用法(在运行时修改它自己的代码的程序)?

现代操作系统似乎对这样做的程序感到不满,因为这种技术已被病毒用来避免检测。

我所能想到的是某种运行时优化,通过在运行时了解某些在编译时无法知道的东西来删除或添加一些代码。

代码修改有许多有效的情况。 在运行时生成代码可以用于:

  • 一些虚拟机使用JIT编译来提高性能。
  • 在计算机graphics学中长久地常常生成专门的function 。 请参阅Blit(1984)的 Rob Pike和Bart Locanthi和John Reiser 硬件软件折衷图,或者Chris Lattner撰写的关于苹果在其OpenGL堆栈中使用LLVM进行运行时代码专业化的文章(2006) 。
  • 在某些情况下,软件采用被称为蹦床的技术,其涉及在栈(或另一个地方)上dynamic创build代码。 例子是GCC的嵌套函数和一些Unices的信号机制 。

有时代码在运行时被转换成代码(这被称为dynamic二进制转换 ):

  • 像苹果Rosetta这样的仿真器使用这种技术来加速仿真。 另一个例子是全美达的代码变形软件 。
  • 像Valgrind或Pin这样复杂的debugging器和分析器使用它来在您的代码执行时进行testing。
  • 在对x86指令集进行扩展之前,VMWare等虚拟化软件无法直接在虚拟机中运行特权x86代码。 相反,它不得不将任何有问题的指令转化为更合适的定制代码。

代码修改可以用来解决指令集的限制:

  • 有一段时间(很久以前,我知道),当电脑没有指令从子程序返回或间接寻址内存。 自修改代码是实现子例程,指针和数组的唯一方法。

更多代码修改的情况:

  • 许多debugging器replace指令来实现断点
  • 一些dynamic连接器在运行时修改代码。 本文提供了一些有关Windows DLL的运行时重定位的背景知识,这实际上是一种代码修改forms。

这已经在计算机graphics学中完成,特别是用于优化目的的软件渲染器。 在运行时,会检查许多参数的状态,并生成光栅化程序代码的优化版本(可能会消除很多条件),从而可以使graphics基元(例如三angular形)渲染得更快。

一个有效的原因是因为asm指令集缺less一些必要的指令,你可以自己build立 。 例如:在x86上,无法为寄存器中的variables创build中断(例如,使用ax中的中断号进行中断)。 只允许编码到操作码中的常量数字。 用自修改的代码可以模拟这种行为。

例如, 天网将创造一个革命性的微处理器,它将能够在运行时改变它自己的代码,并变得自我意识,从而可以反抗自己的创造者。

一些编译器曾经使用它进行静态variables初始化,避免了后续访问的条件代价。 换句话说,它们通过在第一次执行时用no-ops覆盖该代码来实现“只执行一次该代码”。

有很多情况:

  • 病毒通常使用自修改代码在执行前“去混淆”它们的代码,但是这种技术也可以用于挫败反向工程,破解和不必要的骇客
  • 在某些情况下,在运行时(例如,在读取configuration文件之后立即),可能会有一个特定的地方 – 对于进程的整个生命周期的其余部分 – 一个特定的分支总是或永远不会被采用:而不是毫无必要地检查一些variables以确定分支的方式,分支指令本身可以被相应地修改
    • 例如,可能会知道只有一种可能的派生types将被处理,使得虚拟调度可以被特定的调用所取代
    • 在检测到可用的硬件之后,使用匹配的代码可以被硬编码
  • 不需要的代码可以用非操作指令代替或跳过代码,或者将下一位代码直接移入位置(如果使用与位置无关的操作码,则更容易)
  • 为了便于自己debugging而编写的代码可能会在debugging位置注入debugging器期望的陷阱/信号/中断指令。
  • 一些基于用户input的谓词expression式可能会被一个库编译成本地代码
  • 内联一些简单的操作,直到运行时才可见(例如从dynamic加载的库)…
  • 有条件地添加自我检测/分析步骤
  • 破解可能被实现为修改加载它们的代码的库(不是“自我”修改,但需要相同的技术和权限)。

一些操作系统的安全模式意味着自修改代码不能在没有root / admin权限的情况下运行,这对于通用目的是不切实际的。

维基百科:

在严格的W ^ X安全性的操作系统下运行的应用程序软件不能执行允许写入的页面的指令 – 只有操作系统本身才允许将指令写入存储器,并稍后执行这些指令。

在这样的操作系统上,即使像Java VM这样的程序也需要root / admin权限来执行他们的JIT代码。 (更多详情见http://en.wikipedia.org/wiki/W%5EX

综合OS基本上部分地评估你的程序在API调用方面,并用结果代替OS代码。 主要好处是大量的错误检查消失了(因为如果你的程序不会要求操作系统做一些愚蠢的事情,它不需要检查)。

是的,这是运行时优化的一个例子。

很多年前,我花了一个上午的时间试图debugging一些自修改代码,一条指令改变了下面指令的目标地址,也就是我正在计算一个分支地址。 它是用汇编语言编写的,当我一次一个指令地执行一个指令时,它是完美的。 但是当我运行程序失败。 最后,我意识到机器正在从内存中取出2条指令,并且(因为指令已经放在内存中),我修改的指令已经被取出,因此机器正在执行指令的未经修改(不正确)的版本。 当然,当我debugging的时候,一次只做一条指令。

我的观点是,自我修改的代码对于testing/debugging可能是非常难以理解的,而且往往对机器的行为(无论是硬件还是虚拟)有着隐藏的假设。 而且,系统永远不能在(现在的)多核计算机上执行的各个线程/进程之间共享代码页。 这对虚拟内存等的许多好处都失败了。它也会使在硬件级完成的分支优化无效。

(注意 – 我没有将JIT包含在自修改代码的类别中,JIT从代码的一个表示转换为替代表示,它不修改代码)

总而言之,这只是一个糟糕的主意 – 真的很整齐,很晦涩,但真的很糟糕。

当然,如果你拥有的是8080和512字节的内存,你可能不得不采取这种做法。

从操作系统内核来看,每个“即时编译器”和“链接器运行时”都会执行程序文本自我修改。 突出的例子是谷歌的V8 ECMA脚本解释器。

你知道老栗子硬件和软件之间没有逻辑上的区别……也可以说代码和数据之间没有逻辑上的区别。

什么是自修改代码? 将值放入执行stream中的代码,以便它不能被解释为数据而是作为命令。 当然,在function语言中有一个理论观点,那就是没有区别。 我说的是,在命令式语言和编译器/口译员中,可以直接做到这一点,而不需要推定地位平等。

我所指的是实际意义上的数据可以改变程序执行path(在某种意义上这是非常明显的)。 我想到的东西就像一个编译器 – 编译器,它创build一个在parsing过程中遍历的表(一个数据数组),从一个状态转移到另一个状态(也修改其他variables),就像程序从一个命令到另一个命令,修改过程中的variables。

因此,即使在编译器创build代码空间并引用完全独立的数据空间(堆)的通常情况下,仍然可以修改数据以显式更改执行path。

自修改代码(实际上是一个“自生成”代码)的另一个原因是为了实现一个即时编译机制。 例如,读取一个代数expression式并在一系列input参数上计算它的程序可能会在表示计算之前在机器代码中转换expression式。

我已经使用evolution实现了一个程序来创build最好的algorithm。 它使用自修改代码来修改DNA蓝图。

Linux内核具有可加载的内核模块。

Emacs也有这个能力,我一直在使用它。

任何支持dynamic插件体系结构的实质都是在运行时修改它的代码。

我对不断更新的数据库运行统计分析。 每次执行代码时,我的统计模型都会被编写和重写,以适应可用的新数据。

一个用例是用于testing防病毒程序的EICARtesting文件 。

X5O!P%@ AP [4 \ PZX54(P ^)7CC)7} $ EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$ H + H *

它必须使用自我修改代码,因为执行的文件只能包含范围[21h-60h,7Bh-7Dh]中的可打印/可打字的ASCII字符,这将不可能编码一些必要的指令

细节在这里解释

这个可以使用的场景是一个学习程序。 为了响应用户的input,程序学习了一个新的algorithm:

1)它查找现有的代码库为一个相似的algorithm

2)如果在代码库中没有类似的algorithm,程序只是添加一个新的algorithm

3)如果存在类似的algorithm,则程序(可能在用户的帮助下)修改现有的algorithm,以便能够既服务于旧的目的又服务于新的目的

在Java中如何做到这一点是一个问题: Java代码的自我修改有什么可能性?

最好的版本可能是Lispmacros。 与只是预处理器的Cmacros不同,Lisp允许您随时访问整个编程语言。 这是关于lisp中最强大的function,并不存在于任何其他语言中。

我绝不是一个专家,但得到一个lisp家伙谈论它! 有一个原因,他们说,Lisp是最强大的语言,聪明的人不,他们可能是正确的。

Interesting Posts