自修改代码有哪些用途?

53

自修改代码有实际用途吗?

我知道它们可用于构建蠕虫/病毒,但我想知道是否存在一些程序员必须使用自修改代码的好原因。

有什么想法吗?假设情况也可以。

15个回答

52

原来维基百科关于 "self-modifying code" 的条目有一个很好的列表:

  1. 半自动优化状态相关循环。
  2. 运行时代码生成,或在运行时或加载时专门为算法进行特化(例如,在实时图形领域中非常流行),例如一般的排序实用程序准备代码以执行特定调用中描述的关键比较。
  3. 修改对象内联状态,或模拟闭包的高级构造。
  4. 修补子例程地址调用,通常在动态库的加载时间完成,或在每次调用时修补子例程的内部引用以使用它们的实际地址。这是否被视为“自修改代码”取决于术语的使用。
  5. 进化计算系统,例如遗传编程。
  6. 隐藏代码以防止逆向工程,例如通过使用反汇编器或调试器。
  7. 隐藏代码以回避病毒/间谍软件扫描等检测。
  8. 在某些体系结构中使用重复操作码的滚动模式填充100%的内存,以擦除所有程序和数据,或者烧入硬件
  9. 压缩代码以在运行时进行解压缩和执行,例如在内存或磁盘空间有限时。
  10. 一些非常有限的指令集别无选择,只能使用自修改代码来实现某些功能。例如,“一指令集计算机”机器仅使用减法和负数分支“指令”,无法进行间接复制(类似于C编程语言中的“*a = **b”的等效内容)而不使用自修改代码。
  11. 更改指令以实现容错
关于使用自修改代码来挫败黑客的观点:
在几次固件更新的过程中,DirectTV逐渐在他们的智能卡上组装了一个程序,以摧毁已被黑客破解以非法接收未付费频道的卡。有关更多信息,请参阅Jeff的Coding Horror文章Black Sunday Hack

DirectTV 的黑色星期天黑客攻击? - Brian
猴子补丁也可以被视为自修改代码。当依赖于第三方库时,它可能非常有用。 - Mikko Koho

15

我看到过自修改代码用于以下方面:

  1. 通过让程序在运行时编写更多的代码,来进行速度优化

  2. 进行混淆以使得反向工程变得更加困难


历史上,这在游戏软件的防拷机制中非常流行。 - ConcernedOfTunbridgeWells
顺便提一下,在一些旧的8位微型计算机(BBC)游戏中,需要使用它来从磁盘而不是卡带上运行。 - Alnitak

11

在以前,由于内存有限,使用自修改代码来节省内存。现在,例如应用程序压缩工具UPX被用来在加载应用程序的压缩图像后解压缩/修改自身代码。


我认为这些二进制压缩器只在磁盘上进行压缩,在加载到内存时才进行解压缩?我曾经读过一次,因为它们在加载到内存时被解压缩,所以它们不能被分页到磁盘上,因此它们会消耗更多的RAM。这不是事实吗? - Peter Morris
1
打包的可执行文件具有“引导程序”应用程序,该应用程序被加载到内存中并在那里启动。然后,它会加载压缩数据,对其进行解压缩,并将解压缩的指令附加到自己的代码中。当解压缩完成时,此代码将启动。分页按照通常的方式进行。 - Kosi2801
自解压缩JavaScript在网页上被广泛使用。 - Jader Dias

7

因为它非常酷,有时候这就足够了。


7

人工智能?


7

由于Commodore 64寄存器较少,处理器速度只有1Mhz。当您需要读取一个偏移值的内存地址时,修改源代码会更容易。

@Reader:
LDA $C000
STA $D020
INC Reader+1
JMP Reader

那是我最后一次编写自修改代码了 :-)


(意思是作者不再使用自修改代码,因为这种代码难以理解和维护)

7

20世纪60年代的汇编语言使用自修改代码来实现函数调用而不需要堆栈。

《计算机程序设计艺术》第一卷第一版,第182页:

MAX100  STJ   EXIT   ;Subroutine linkage
        ENT3  100    ;M1. Initialize
        JMP   2F
1H      CMPA  X,3    ;M3. Compare
        JGE   *+3
2H      ENT2  0,3    ;M4. Change m
        LDA   X,3    ;(New maximum found)
        DEC3  1      ;M5. Decrease k
        J3P   1B     ;M2. All tested?
EXIT    JMP   *      ;Return to main program

在包含此代码作为子程序的更大程序中,单条指令“JMP MAX100”将导致寄存器A被设置为位置X+1到X+100的当前最大值,并且最大值的位置将出现在rI2中。在这种情况下,子程序链接由指令“MAX100 STJ EXIT”和稍后的“EXIT JMP *”实现。由于J寄存器的操作方式,退出指令将跳转到最初对MAX100的引用位置之后的位置。
编辑:即使在这里进行简要说明,也可能很难理解正在发生的事情。在行“MAX100 STJ EXIT”中,“MAX100”是指令(因此也是整个过程)的标签,“STJ”表示存储跳转寄存器(我们刚刚来自哪里), “EXIT”表示标记为“EXIT”的内存位置是存储的目标。我们稍后看到,“EXIT”是最后一条指令的标签。所以它正在覆盖代码!但是,许多指令(包括这里的“STJ”)隐含地仅覆盖指令字的操作数部分。因此,“JMP”保持不变,“*”是一个虚拟标记,因为那里实际上没有任何有意义的东西,它只会被覆盖。
自修改代码也用于注册间接寻址不可用的情况,而需要的地址正好在寄存器中。PDP-1 LISP:
dap .+1  ;deposit address part of accumulator in (IP+1)
lac xy   ;load accumulator with (ADDRESS) [xy is a dummy symbol, just like * above]

这两条指令通过修改装载指令的操作数执行ACC := (ACC)。这种修改相对安全,在古老的架构上是必要的。

5
许多原因。我随意想了一下:
- 运行时类构建和元编程。例如,有一个类工厂,它接收到一个连接到 SQL 表的参数并生成一个专门针对该表的客户端类(具有列访问器、查找方法等)。 - 当然,还有著名的 bitblt 示例和正则表达式模拟。 - 基于实时信息动态优化,就像跟踪 JIT。 - 在积累环境中进行 ada 风格通用函数的子类型专业化。
-- MarkusQ

很多这些涉及到运行时代码生成,这并不一定涉及自我修改。虽然这个区分有些模糊;我可以说即时编译器正在修改/重新生成其他代码,但是如果你将在动态即时编译器下运行的应用程序视为一个整体,那么整体肯定是自我修改的。 - undefined

4

动态链接是一种自我修改的方式(修补绝对和/或相对跳转位置)...通常由操作系统的程序加载器完成。


3

1
我不确定神经网络是否修改代码。我从未知道这一点。http://www.hoozi.com/Articles/Neural-Networks-Artificial-Neuron.htm - Niyaz
我相信神经网络结构的任何更改都可以在数据部分完成。为什么要修改代码呢? - Niyaz
2
神经网络不是自修改代码。它们只是由训练确定权重的复杂非线性转换而已。 - Alnitak
他们实际上并不修改代码本身,而是修改函数。进化算法也是如此,它们可以在不改变实际代码的情况下实现。 - Georg Schölly
3
是否可以在代码生成器之上实现进化算法来完成进化过程? - Erik Forbes
这要看情况,两种都可能。 - Georg Schölly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接