是否存在运行时代码修改的智能案例?

deo*_*deo 119 executable platform-agnostic cpu-architecture instructions self-modifying

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

现代操作系统似乎对执行此操作的程序不屑一顾,因为病毒已使用此技术来避免检测.

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

Mac*_*ser 117

有许多有效的代码修改案例.在运行时生成代码可用于:

  • 某些虚拟机使用JIT编译来提高性能.
  • 动态生成专用函数在计算机图形学中一直很常见.参见例如Rob Pike和Bart Locanthi以及John Reiser 硬件软件权衡对Blit的位图图形(1984)或Chris Lattner的这篇文章(2006),Apple对使用LLVM在其OpenGL堆栈中运行时代码专业化的使用.
  • 在某些情况下,软件采用称为蹦床的技术,该技术涉及在堆栈(或其他地方)上动态创建代码.例子是GCC的嵌套函数和一些Unices 的信号机制.

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

  • 像Apple的Rosetta这样的仿真器使用这种技术来加速仿真.另一个例子是Transmeta的代码变形软件.
  • ValgrindPin这样的复杂调试器和分析器使用它来在执行代码时检测代码.
  • 在对x86指令集进行扩展之前,像VMWare这样的虚拟化软件无法在虚拟机内直接运行特权x86代码.相反,它必须动态地将任何有问题的指令翻译成更合适的自定义代码.

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

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

更多代码修改案例:

  • 许多调试器替换指令来实现断点.
  • 一些动态链接器在运行时修改代码.本文提供了有关Windows DLL的运行时重定位的一些背景知识,这实际上是一种代码修改形式.

  • 此列表似乎混合了修改自身的代码示例,以及修改其他代码的代码,如链接器. (9认同)
  • @AShelly:好吧,如果你认为动态链接器/加载器是代码的一部分,那么它确实会自行修改.他们住在同一个地址空间,所以我认为这是一个有效的观点. (6认同)

tre*_*nki 35

这已经在计算机图形学中完成,特别是用于优化目的的软件渲染器.在运行时,检查许多参数的状态,并生成光栅化器代码的优化版本(可能消除许多条件),这允许人们更快地渲染图形基元,例如三角形.

  • 一篇有趣的读物是Michael Abrash关于DDJ的3部分Pixomatic文章:http://drdobbs.com/architecture-and-design/184405765,http://drdobbs.com/184405807,http://drdobbs.com/184405848.第二个链接(第2部分)讨论像素管道的Pixomatic代码焊接器. (5认同)
  • Charles Petzold在一本名为"Beautiful Code"的书中解释了这种类型的一个例子:http://www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047 (5认同)
  • 这个答案谈到*生成*代码,但问题是询问*修改*代码...... (3认同)
  • @Timwi - 它确实修改了代码.它不是处理一个大的if链,而是解析了一次形状并重新编写了渲染器,因此设置了正确的形状,而不必每次都检查.有趣的是,这在opencl代码中很常见 - 因为它是动态编译的,你可以在运行时为特定的情况重写它 (3认同)

flo*_*olo 23

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

  • @Alexandre C.:如果我记得正确的许多运行时库(C,Pascal,...)不得不在DOS时间执行一个函数来执行中断调用.因为这样的函数得到中断号作为参数你必须提供这样的函数(当然,如果数字是常数,你可以生成正确的代码,但这不保证).并且所有库都使用自修改代码实现了它. (4认同)

Ton*_*roy 17

有很多情况:

  • 病毒通常使用自修改代码在执行之前对其代码进行"反混淆处理",但该技术也可用于阻止逆向工程,破解和不需要的hackery
  • 在某些情况下,在运行期间(例如,在读取配置文件之后立即)可能存在特定点 - 当已知 - 在过程的剩余生命周期中 - 将始终或永远不会采用特定分支:而不是不必要地检查一些变量以确定分支的方式,可以相应地修改分支指令本身
    • 例如,可以知道将仅处理可能的派生类型中的一个,使得可以用特定呼叫替换虚拟分派
    • 在检测到哪些硬件可用之后,可以硬编码使用匹配代码
  • 不必要的代码可以用无操作指令替换或跳过它,或者将下一位代码直接移位到位(如果使用与位置无关的操作码,则更容易)
  • 为便于自己调试而编写的代码可能会在调整位置注入调试器所期望的陷阱/信号/中断指令.
  • 基于用户输入的一些谓词表达式可能会被库编译为本机代码
  • 内联一些直到运行时才可见的简单操作(例如,来自动态加载的库)...
  • 有条件地添加自我检测/分析步骤
  • 破解可以实现为修改加载它们的代码的库(不是"自我"修改,但需要相同的技术和权限).
  • ...

某些操作系统的安全模型意味着自修改代码无法在没有root/admin权限的情况下运行,这使得它对于通用目的而言不切实际.

来自维基百科:

在具有严格W ^ X安全性的操作系统下运行的应用软件不能在允许写入的页面中执行指令 - 仅允许操作系统本身向存储器写入指令并随后执行那些指令.

在这样的操作系统上,即使像Java VM这样的程序也需要root/admin权限来执行它们的JIT代码.(有关详细信息,请参阅http://en.wikipedia.org/wiki/W%5EX)

  • 您不需要root权限来自行修改代码.Java VM也不是. (2认同)

Joe*_*oeG 17

一些编译器过去常常将其用于静态变量初始化,从而避免了后续访问的条件成本.换句话说,它们通过在第一次执行时使用无操作覆盖该代码来实现"仅执行一次此代码".

  • 真?这对于基于ROM的代码或在写保护代码段中执行的代码有何影响? (2认同)

Ira*_*ter 15

合成OS基本上部分相对于API调用评估程序,并更换OS代码的结果.主要的好处是大量的错误检查消失了(因为如果你的程序不会要求操作系统做一些愚蠢的事情,那就不需要检查).

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

  • 对于相对微不足道和非I/O绑定的系统调用,节省的费用很高.例如,如果您正在为Unix编写一个守护程序,那么您可以使用一堆样板来断开stdio,设置各种信号处理程序等等.如果您知道调用的参数是常量,那么结果将始终相同(例如,关闭stdin),在一般情况下执行的许多代码是不必要的. (2认同)

Jay*_*Jay 9

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

我的观点是,自修改代码对于测试/调试来说非常讨厌,并且通常对机器的行为(无论是硬件还是虚拟)有隐藏的假设.此外,系统永远不会在(现在)多核机器上执行的各种线程/进程之间共享代码页.这会破坏虚拟内存等许多好处.它还会使在硬件级别完成的分支优化无效.

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

总而言之,这只是一个坏主意 - 真的很整洁,真的很模糊,但非常糟糕.

当然 - 如果你拥有8080和512字节的内存,你可能不得不求助于这种做法.


dat*_*olf 7

从操作系统内核的角度看,每个Just In Time Compiler和Linker Runtime都会执行程序文本自我修改.突出的例子是Google的V8 ECMA Script Interpreter.


Giu*_*ini 5

自修改代码(实际上是"自生成"代码)的另一个原因是为了实现性能的即时编译机制.例如,读取代数表达式并在一系列输入参数上计算它的程序可以在说明计算之前转换机器代码中的表达式.


小智 5

您知道旧的栗子,硬件和软件之间没有逻辑差异......也可以说代码和数据之间没有逻辑差异.

什么是自修改代码?将值放入执行流中的代码,以便可以将其解释为数据而不是命令.当然,功能语言中的理论观点确实没有区别.我在说e可以在命令式语言和编译器/解释器中以直截了当的方式做到这一点,而没有假设平等的地位.

我所指的是实际意义上的数据可以改变程序执行路径(在某种意义上,这是非常明显的).我正在考虑像编译器编译器这样的东西,它创建一个表(数据数组),在解析,遍历状态(以及修改其他变量)时遍历,就像程序从命令移动到命令一样,修改过程中的变量.

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

  • 没有逻辑差异,是的.但是,没有看到过多的自修改集成电路. (4认同)