self-modifying

self modifying - ¿Hay casos inteligentes de modificación del código de tiempo de ejecución?



self-modifying (17)

¿Puede pensar en algún uso legítimo (inteligente) para la modificación del código de tiempo de ejecución (el programa modifica su propio código en tiempo de ejecución)?

Los sistemas operativos modernos parecen desaprobar los programas que hacen esto, ya que esta técnica ha sido utilizada por virus para evitar su detección.

Todo lo que puedo pensar es algún tipo de optimización de tiempo de ejecución que eliminaría o agregaría algún código al saber algo en tiempo de ejecución que no se puede conocer en tiempo de compilación.


Algunos compiladores solían usarlo para la inicialización de variables estáticas, evitando el costo de un condicional para accesos posteriores. En otras palabras, implementan "ejecutar este código solo una vez" sobrescribiendo ese código sin operaciones la primera vez que se ejecuta.


Desde la vista de un kernel del sistema operativo, cada compilador Just In Time y Linker Runtime realizan la auto-modificación del texto del programa. Un ejemplo destacado sería el intérprete de secuencias de comandos V8 ECMA de Google.


Ejecuto análisis estadísticos contra una base de datos continuamente actualizada. Mi modelo estadístico se escribe y se vuelve a escribir cada vez que se ejecuta el código para acomodar los datos nuevos que están disponibles.


El sistema operativo Synthesis básicamente evaluó parcialmente su programa con respecto a las llamadas API y reemplazó el código del sistema operativo con los resultados. El principal beneficio es que muchas de las comprobaciones de errores desaparecieron (porque si su programa no va a pedirle al sistema operativo que haga algo estúpido, no es necesario que lo compruebe).

Sí, eso es un ejemplo de optimización en tiempo de ejecución.


El kernel de Linux tiene módulos de núcleo cargables que hacen precisamente eso.

Emacs también tiene esta habilidad y la uso todo el tiempo.

Todo lo que sea compatible con una arquitectura de complemento dinámico está esencialmente modificando el código en tiempo de ejecución.


El escenario en el que esto se puede utilizar es un programa de aprendizaje. En respuesta a la entrada del usuario, el programa aprende un nuevo algoritmo:

1) busca la base de código existente para un algoritmo similar

2) si no hay un algoritmo similar en la base del código, el programa simplemente agrega un nuevo algoritmo

3) si existe un algoritmo similar, el programa (quizás con alguna ayuda del usuario) modifica el algoritmo existente para poder cumplir tanto el antiguo propósito como el nuevo propósito

Hay una pregunta sobre cómo hacer eso en Java: ¿Cuáles son las posibilidades para la auto-modificación del código de Java?


Esto se ha hecho en gráficos de computadora, específicamente renderizadores de software para fines de optimización. En tiempo de ejecución, se examina el estado de muchos parámetros y se genera una versión optimizada del código rasterizador (que puede eliminar una gran cantidad de condicionales) que permite renderizar primitivas gráficas, por ejemplo, triángulos mucho más rápido.


Hace muchos años que pasé una mañana tratando de depurar algún código de auto modificación, una instrucción cambió la dirección de destino de la siguiente instrucción, es decir, estaba calculando una dirección de sucursal. Fue escrito en lenguaje ensamblador y funcionó perfectamente cuando pasé por el programa una instrucción a la vez. Pero cuando ejecuté el programa, falló. Eventualmente, me di cuenta de que la máquina estaba obteniendo 2 instrucciones de la memoria y (como las instrucciones estaban en la memoria) las instrucciones que estaba modificando ya habían sido extraídas y por lo tanto la máquina estaba ejecutando la versión no modificada (incorrecta) de la instrucción. Por supuesto, cuando estaba depurando, solo estaba haciendo una instrucción a la vez.

Mi punto, el código de auto modificación puede ser extremadamente desagradable para probar / depurar y, a menudo, tiene suposiciones ocultas sobre el comportamiento de la máquina (ya sea hardware o virtual). Además, el sistema nunca podría compartir páginas de códigos entre los diversos hilos / procesos que se ejecutan en las (ahora) máquinas de múltiples núcleos. Esto anula muchos de los beneficios para la memoria virtual, etc. También invalidará las optimizaciones de sucursales hechas a nivel de hardware.

(Nota: no incluyo JIT en la categoría de código de auto modificación. JIT está traduciendo de una representación del código a una representación alternativa, no está modificando el código)

En general, es solo una mala idea, realmente ordenada, realmente oscura, pero realmente mala.

por supuesto, si todo lo que tiene es un 8080 y ~ 512 bytes de memoria, puede que tenga que recurrir a tales prácticas.


Hay muchos casos válidos para la modificación del código. Generar código en tiempo de ejecución puede ser útil para:

  • Algunas máquinas virtuales usan compilación JIT para mejorar el rendimiento.
  • Generar funciones especializadas sobre la marcha ha sido durante mucho tiempo común en gráficos de computadora. Véase, por ejemplo, Rob Pike y Bart Locanthi y John Reiser Hardware Software Tradeoffs para Bitmap Graphics on the Blit (1984) o esta publicación (2006) de Chris Lattner sobre el uso de Apple de LLVM para la especialización de código de tiempo de ejecución en su stack OpenGL.
  • En algunos casos, el software recurre a una técnica conocida como trampolín que implica la creación dinámica de código en la pila (u otro lugar). Algunos ejemplos son las funciones anidadas de GCC y el mecanismo de señal de algunos Unices.

A veces, el código se traduce en código en tiempo de ejecución (esto se denomina traducción dinámica binaria ):

  • Emuladores como Apple''s Rosetta usan esta técnica para acelerar la emulación. Otro ejemplo es el software de transformación de código de Transmeta.
  • Depuradores y perfiladores sofisticados como Valgrind o Pin usan para instrumentar su código mientras se está ejecutando.
  • Antes de realizar extensiones en el conjunto de instrucciones x86, el software de virtualización como VMWare no podía ejecutar directamente el código x86 con privilegios dentro de las máquinas virtuales. En cambio, tuvo que traducir cualquier instrucción problemática sobre la marcha en un código personalizado más apropiado.

La modificación del código se puede usar para evitar las limitaciones del conjunto de instrucciones:

  • Hubo un tiempo (hace mucho tiempo, lo sé), cuando las computadoras no tenían instrucciones para regresar de una subrutina o para direccionar indirectamente la memoria. El código de auto modificación era la única forma de implementar subrutinas, punteros y matrices .

Más casos de modificación de código:

  • Muchos depuradores reemplazan las instrucciones para implementar puntos de interrupción .
  • Algunos vinculadores dinámicos modifican código en tiempo de ejecución. Este artículo proporciona algunos antecedentes sobre la reubicación en tiempo de ejecución de Windows DLL, que es efectivamente una forma de modificación de código.

Hay muchos casos:

  • Los virus suelen usar código de auto modificación para "desofuscar" su código antes de su ejecución, pero esa técnica también puede ser útil para frustrar la ingeniería inversa, el craqueo y el hackeo no deseado.
  • En algunos casos, puede haber un punto particular durante el tiempo de ejecución (por ejemplo, inmediatamente después de leer el archivo de configuración) cuando se sabe que, durante el resto de la vida del proceso, siempre se tomará o no una rama en particular: en lugar de innecesariamente verificando alguna variable para determinar en qué dirección ramificar, la instrucción de bifurcación misma podría modificarse en consecuencia
    • Por ejemplo, se puede saber que solo se manejará uno de los posibles tipos derivados, de modo que el despacho virtual se pueda reemplazar con una llamada específica.
    • Al haber detectado qué hardware está disponible, el uso de un código coincidente puede estar codificado
  • El código innecesario se puede reemplazar con instrucciones no operativas o un salto sobre él, o hacer que el siguiente bit de código cambie directamente a su lugar (más fácil si se usan códigos de operación independientes de la posición)
  • El código escrito para facilitar su propia depuración podría inyectar una instrucción de captura / señal / interrupción esperada por el depurador en una ubicación estratégica.
  • Algunas expresiones de predicados basadas en la entrada del usuario pueden ser compiladas en código nativo por una biblioteca
  • Inlinear algunas operaciones simples que no son visibles hasta el tiempo de ejecución (por ejemplo, desde una biblioteca cargada dinámicamente) ...
  • Condicionalmente agregar pasos de autoinstrucción / perfil
  • Las grietas se pueden implementar como bibliotecas que modifican el código que las carga (no modificando "self" exactamente, pero necesita las mismas técnicas y permisos).
  • ...

Los modelos de seguridad de algunos sistemas operativos significan que el código de auto modificación no puede ejecutarse sin privilegios de administrador / raíz, lo que lo hace poco práctico para uso de propósito general.

De la Wikipedia:

El software de aplicación que se ejecuta bajo un sistema operativo con estricta seguridad W ^ X no puede ejecutar instrucciones en páginas en las que está permitido escribir; solo el sistema operativo puede escribir instrucciones en la memoria y luego ejecutarlas.

En dichos sistemas operativos, incluso los programas como Java VM necesitan privilegios de root / admin para ejecutar su código JIT. (Ver http://en.wikipedia.org/wiki/W%5EX para más detalles)


Implementé un programa usando evolución para crear el mejor algoritmo. Usó el código de modificación automática para modificar el anteproyecto de ADN.


La mejor versión de esto puede ser Lisp Macros. A diferencia de las macros C, que son solo un preprocesador, Lisp le permite tener acceso a todo el lenguaje de programación en todo momento. Esta es la característica más poderosa en lisp y no existe en ningún otro idioma.

¡De ninguna manera soy un experto, pero consigue uno de los chicos lisp hablando de eso! Hay una razón por la que dicen que Lisp es el lenguaje más poderoso que existe y que los inteligentes no creen que probablemente tengan razón.


Otra razón del código de auto modificación (en realidad, un código de "autogeneración") es implementar un mecanismo de compilación Just-In-time para el rendimiento. Por ejemplo, un programa que lee una expresión algebric y la calcula en un rango de parámetros de entrada puede convertir la expresión en código máquina antes de indicar el cálculo.


Un caso de uso es el archivo de prueba EICAR que se utiliza para probar los programas antivirus.

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

Debe usar la modificación del código de auto porque el archivo ejecutado debe contener solo caracteres ASCII imprimibles / tipables en el rango [21h-60h, 7Bh-7Dh] que sería imposible codificar algunas instrucciones necesarias

Los detalles se explican here


Una razón válida es porque el conjunto de instrucciones asm carece de algunas instrucciones necesarias, que usted mismo puede construir . Ejemplo: En x86 no hay forma de crear una interrupción a una variable en un registro (por ejemplo, hacer interrupción con número de interrupción en ax). Solo se permitieron los números de const codificados en el código de operación. Con el código de automodificación uno podría emular este comportamiento.


Ya sabes que el viejo castaño que no existe una diferencia lógica entre el hardware y el software ... también se puede decir que no existe una diferencia lógica entre el código y los datos.

¿Qué es el código de auto modificación? Código que pone valores en la secuencia de ejecución para que pueda ser interpretada no como datos, sino como un comando. Seguro que existe el punto de vista teórico en los lenguajes funcionales que realmente no hay diferencia. Estoy diciendo que puede hacer esto de manera directa en lenguajes imperativos y compiladores / intérpretes sin la presunción de igual condición.

A lo que me refiero es en el sentido práctico de que los datos pueden alterar las rutas de ejecución del programa (en algún sentido esto es extremadamente obvio). Estoy pensando en algo así como un compilador compilador que crea una tabla (una matriz de datos) que atraviesa en el análisis, pasando de estado a estado (y también modificando otras variables), al igual que la forma en que un programa se mueve de comando a comando , modificando variables en el proceso.

Así que incluso en la instancia habitual en la que un compilador crea espacio de código y se refiere a un espacio de datos completamente separado (el montón), todavía se pueden modificar los datos para cambiar explícitamente la ruta de ejecución.


Skynet por ejemplo, creará un microprocesador revolucionario que podrá alterar su propio código en tiempo de ejecución y tomar conciencia de sí mismo para que pueda rebelarse contra sus propios creadores.