En ciencias de la computación , el código auto modificable es código que altera sus propias instrucciones mientras se está ejecutando , generalmente para reducir la longitud de la ruta de instrucción y mejorar el rendimiento o simplemente para reducir el código repetidamente similar, simplificando así el mantenimiento. La auto-modificación es una alternativa al método de "configuración de banderas" y bifurcación condicional del programa, que se utiliza principalmente para reducir el número de veces que es necesario probar una condición. El término generalmente solo se aplica al código donde la auto modificación es intencional, no en situaciones en las que el código se modifica accidentalmente debido a un error, como un desbordamiento del búfer .
El método se usa con frecuencia para invocar condicionalmente código de prueba / depuración sin requerir una sobrecarga computacional adicional para cada ciclo de entrada / salida .
Las modificaciones se pueden realizar:
- solo durante la inicialización , según los parámetros de entrada (cuando el proceso se describe más comúnmente como ' configuración ' de software y es algo análogo, en términos de hardware, a la configuración de puentes para placas de circuito impreso ). La alteración de los punteros de entrada del programa es un método indirecto equivalente de auto-modificación, pero requiere la coexistencia de una o más rutas de instrucción alternativas, aumentando el tamaño del programa .
- a lo largo de la ejecución ("sobre la marcha") - basado en estados de programa particulares que se han alcanzado durante la ejecución
En cualquier caso, las modificaciones se pueden realizar directamente en las instrucciones del código de máquina , superponiendo nuevas instrucciones sobre las existentes (por ejemplo: alterando una comparación y una bifurcación a una bifurcación incondicional o, alternativamente, un ' NOP ').
En el conjunto de instrucciones IBM / 360 y Z / Architecture , una instrucción EXECUTE (EX) superpone lógicamente el segundo byte de su instrucción de destino con los 8 bits de orden inferior del registro 1. Esto proporciona el efecto de auto-modificación aunque la instrucción real en almacenamiento no se altera.
Aplicación en lenguajes de bajo y alto nivel
La auto-modificación se puede lograr de varias formas dependiendo del lenguaje de programación y su soporte para punteros y / o acceso a compiladores dinámicos o 'motores' de intérpretes:
- superposición de instrucciones existentes (o partes de instrucciones como código de operación, registro, banderas o dirección) o
- creación directa de instrucciones completas o secuencias de instrucciones en la memoria
- creación o modificación de declaraciones de código fuente seguidas de una 'mini compilación' o una interpretación dinámica (ver declaración eval )
- crear un programa completo de forma dinámica y luego ejecutarlo
Lenguaje ensamblador
El código de modificación automática es bastante sencillo de implementar cuando se usa lenguaje ensamblador . Las instrucciones pueden crearse dinámicamente en la memoria (o superponerse sobre el código existente en el almacenamiento del programa no protegido), en una secuencia equivalente a las que un compilador estándar puede generar como código objeto . Con los procesadores modernos, puede haber efectos secundarios no deseados en la memoria caché de la CPU que deben tenerse en cuenta. El método se utilizó con frecuencia para probar condiciones de "primera vez", como en este ejemplo de ensamblador IBM / 360 debidamente comentado . Utiliza la superposición de instrucciones para reducir la longitud de la ruta de instrucción en (N × 1) −1 donde N es el número de registros en el archivo (−1 es la sobrecarga para realizar la superposición).
SUBRTN NOP ABRIÓ POR PRIMERA VEZ AQUÍ?* El NOP es x'4700 'OI SUBRTN + 1, X'F0 'SÍ, CAMBIAR NOP A RAMA INCONDICIONAL (47F0 ...) ABRIR INPUT Y ABRIR EL ARCHIVO DE ENTRADA YA QUE ES LA PRIMERA VEZABIERTO OBTENGA ENTRADA PROCESAMIENTO NORMAL RESUME AQUÍ ...
El código alternativo puede implicar probar una "bandera" cada vez. La rama incondicional es un poco más rápida que una instrucción de comparación, además de reducir la longitud total de la ruta. En sistemas operativos posteriores para programas que residen en almacenamiento protegido, esta técnica no podría usarse y, por lo tanto, se usaría el cambio del puntero a la subrutina . El puntero residiría en el almacenamiento dinámico y podría modificarse a voluntad después de la primera pasada para omitir el OPEN (tener que cargar un puntero primero en lugar de una rama directa y un enlace a la subrutina agregaría N instrucciones a la longitud de la ruta, pero habría sería una reducción correspondiente de N para la rama incondicional que ya no sería necesaria).
A continuación se muestra un ejemplo en lenguaje ensamblador Zilog Z80 . El código incrementa el registro "B" en el rango [0,5]. La instrucción de comparación "CP" se modifica en cada bucle.
; =============================================== ===================== ORG 0H LLAMADA FUNC00 HALT ; ======================= ============================================= FUNC00: LD A , 6 LD HL , label01 + 1 LD B , ( HL ) label00: INC B LD ( HL ), B label01: CP $ 0 JP NZ , label00 RET ; ============== ================================================ ======
El código de modificación automática se utiliza a veces para superar las limitaciones en el conjunto de instrucciones de una máquina. Por ejemplo, en el conjunto de instrucciones Intel 8080 , no se puede ingresar un byte desde un puerto de entrada especificado en un registro. El puerto de entrada está codificado estáticamente en la propia instrucción, como el segundo byte de una instrucción de dos bytes. Usando código auto-modificable, es posible almacenar el contenido de un registro en el segundo byte de la instrucción, luego ejecutar la instrucción modificada para lograr el efecto deseado.
Idiomas de alto nivel
Algunos lenguajes compilados permiten explícitamente la modificación automática del código. Por ejemplo, el verbo ALTER en COBOL puede implementarse como una instrucción de bifurcación que se modifica durante la ejecución. [1] Algunas técnicas de programación por lotes implican el uso de código que se modifica automáticamente. Clipper y SPITBOL también proporcionan facilidades para la auto-modificación explícita. El compilador de Algol en los sistemas B6700 ofrecía una interfaz para el sistema operativo mediante la cual el código de ejecución podía pasar una cadena de texto o un archivo de disco con nombre al compilador de Algol y luego podía invocar la nueva versión de un procedimiento.
Con los lenguajes interpretados, el "código de máquina" es el texto fuente y puede ser susceptible de ser editado sobre la marcha: en SNOBOL, las declaraciones fuente que se ejecutan son elementos de una matriz de texto. Otros lenguajes, como Perl y Python , permiten que los programas creen código nuevo en tiempo de ejecución y lo ejecuten usando una función eval , pero no permiten que se mute el código existente. La ilusión de modificación (aunque no se esté sobrescribiendo ningún código de máquina) se logra modificando punteros de función, como en este ejemplo de JavaScript:
var f = function ( x ) { return x + 1 }; // asigna una nueva definición a f: f = new Function ( 'x' , 'return x + 2' );
Las macros Lisp también permiten la generación de código en tiempo de ejecución sin analizar una cadena que contiene el código del programa.
El lenguaje de programación Push es un sistema de programación genética que está diseñado explícitamente para crear programas auto modificables. Si bien no es un lenguaje de alto nivel, no es tan bajo como el lenguaje ensamblador. [2]
Modificación compuesta
Antes de la llegada de múltiples ventanas, los sistemas de línea de comandos podían ofrecer un sistema de menús que implicaba la modificación de un script de comandos en ejecución. Suponga que un archivo de secuencia de comandos DOS (o "lote") Menu.bat contiene lo siguiente:
: StartAfresh <-Una línea que comienza con dos puntos marca una etiqueta. ShowMenu.exe
Al iniciar Menu.bat desde la línea de comandos, ShowMenu presenta un menú en pantalla, con posible información de ayuda, usos de ejemplo, etc. Finalmente, el usuario realiza una selección que requiere que se ejecute un comando con un nombre : ShowMenu sale después de reescribir el archivo Menu.bat para que contenga
:Comenzar de nuevo ShowMenu.exe LLAME C: \ Commands \ somename .bat GOTO StartAfresh
Debido a que el intérprete de comandos de DOS no compila un archivo de secuencia de comandos y luego lo ejecuta, ni lee todo el archivo en la memoria antes de iniciar la ejecución, ni tampoco se basa en el contenido de un búfer de registro, cuando ShowMenu sale, el intérprete de comandos encuentra un nuevo comando para ejecutar (es invocar el somename del archivo de script , en una ubicación de directorio y a través de un protocolo conocido por ShowMenu), y después de que ese comando se completa, vuelve al inicio del archivo de script y reactiva ShowMenu listo para la siguiente selección . Si la opción del menú fuera salir, el archivo se reescribirá a su estado original. Aunque este estado inicial no tiene uso para la etiqueta, se requiere, o una cantidad equivalente de texto, porque el intérprete de comandos de DOS recuerda la posición del byte del siguiente comando cuando debe iniciar el siguiente comando, por lo tanto, el archivo reescrito debe mantener la alineación para que el siguiente punto de inicio del comando sea el inicio del siguiente comando.
Aparte de la conveniencia de un sistema de menú (y posibles características auxiliares), este esquema significa que el sistema ShowMenu.exe no está en la memoria cuando se activa el comando seleccionado, una ventaja significativa cuando la memoria es limitada.
Tablas de control
Los intérpretes de la tabla de control se pueden considerar, en cierto sentido, 'auto-modificados' por los valores de datos extraídos de las entradas de la tabla (en lugar de codificarse específicamente a mano en declaraciones condicionales de la forma "IF inputx = 'yyy'").
Programas de canal
Algunos métodos de acceso de IBM usaban tradicionalmente programas de canal auto-modificables , donde un valor, como una dirección de disco, se lee en un área referenciada por un programa de canal, donde un comando de canal posterior lo usa para acceder al disco.
Historia
El IBM SSEC , demostrado en enero de 1948, tenía la capacidad de modificar sus instrucciones o tratarlas exactamente como si fueran datos. Sin embargo, la capacidad rara vez se utilizó en la práctica. [3] En los primeros días de las computadoras, el código de modificación automática se usaba a menudo para reducir el uso de memoria limitada o mejorar el rendimiento, o ambos. A veces también se usaba para implementar llamadas y devoluciones de subrutinas cuando el conjunto de instrucciones solo proporcionaba instrucciones simples de bifurcación o omisión para variar el flujo de control . Este uso sigue siendo relevante en determinadas arquitecturas ultra- RISC , al menos teóricamente; ver, por ejemplo, una computadora con un conjunto de instrucciones . La arquitectura MIX de Donald Knuth también usó código de modificación automática para implementar llamadas a subrutinas. [4]
Uso
El código de modificación automática se puede utilizar para varios propósitos:
- Semi-automático de optimización de un bucle dependiente del estado.
- En tiempo de ejecución generación de código, o de especialización de un algoritmo en tiempo de ejecución o tiempo de carga (que es popular, por ejemplo, en el dominio de los gráficos en tiempo real), como una utilidad general especie - la preparación de código para realizar la comparación de claves descrito en una específica invocación.
- Alterar el estado en línea de un objeto o simular la construcción de alto nivel de cierres .
- Parcheo de llamadas de dirección de subrutina ( puntero ), generalmente como se realiza en el momento de carga / inicialización de las bibliotecas dinámicas , o bien en cada invocación, parcheando las referencias internas de la subrutina a sus parámetros para usar sus direcciones reales. (es decir, "auto-modificación" indirecta).
- Sistemas informáticos evolutivos como neuroevolución , programación genética y otros algoritmos evolutivos .
- Ocultación de código para evitar la ingeniería inversa (mediante el uso de un desensamblador o depurador ) o para evadir la detección por software de escaneo de virus / spyware y similares.
- Llenar el 100% de la memoria (en algunas arquitecturas) con un patrón continuo de códigos de operación repetidos , para borrar todos los programas y datos, o para grabar hardware.
- Comprimir código para descomprimirlo y ejecutarlo en tiempo de ejecución, por ejemplo, cuando la memoria o el espacio en disco son limitados.
- Algunos conjuntos de instrucciones muy limitados no dejan otra opción que utilizar código de modificación automática para realizar determinadas funciones. Por ejemplo, una máquina de computadora con un conjunto de instrucciones (OISC) que usa solo la "instrucción" restar y bifurcar-si-es-negativo no puede hacer una copia indirecta (algo así como el equivalente de "* a = ** b" en C idioma ) sin utilizar código de modificación automática.
- Arranque . Las primeras microcomputadoras solían utilizar código de modificación automática en sus cargadores de arranque. Dado que el cargador de arranque se ingresaba a través del panel frontal en cada encendido, no importaba si el cargador de arranque se modificaba a sí mismo. Sin embargo, incluso hoy en día, muchos cargadores de arranque se reubican automáticamente y algunos incluso se modifican automáticamente.
- Modificación de las instrucciones para la tolerancia a fallos. [5]
Optimización de un bucle dependiente del estado
Ejemplo de pseudocódigo :
repetir N veces { si ESTADO es 1 aumentar A en uno demás disminuir A en uno haz algo con A}
El código de modificación automática, en este caso, sería simplemente una cuestión de reescribir el ciclo de esta manera:
repetir N veces { aumentar A en uno haz algo con A cuando STATE tiene que cambiar { reemplace el código de operación "aumentar" arriba con el código de operación para disminuir, o viceversa } }
Tenga en cuenta que el reemplazo de 2 estados del código de operación se puede escribir fácilmente como 'xor var en la dirección con el valor "opcodeOf (Inc) xor opcodeOf (dec)"'.
La elección de esta solución debe depender del valor de 'N' y de la frecuencia de cambio de estado.
Especialización
Suponga que se va a calcular un conjunto de estadísticas como promedio, extremos, ubicación de extremos, desviación estándar, etc. para un conjunto de datos grande. En una situación general, puede haber una opción de asociar ponderaciones con los datos, por lo que cada x i se asocia con aw i y en lugar de probar la presencia de ponderaciones en cada valor de índice, podría haber dos versiones del cálculo, una para usar con pesas y una no, con una prueba al principio. Ahora considere una opción adicional, que cada valor puede tener asociado un booleano para indicar si ese valor debe omitirse o no. Esto podría manejarse produciendo cuatro lotes de código, uno para cada permutación y resultados de código hinchado. Alternativamente, el peso y las matrices de omisión podrían fusionarse en una matriz temporal (con ponderaciones cero para los valores que se omitirán), a costa del procesamiento y aún así hay hinchazón. Sin embargo, con la modificación del código, a la plantilla para calcular las estadísticas se podría agregar, según corresponda, el código para omitir valores no deseados y para aplicar ponderaciones. No habría pruebas repetidas de las opciones y se accedería a la matriz de datos una vez, al igual que a las matrices de peso y omisión, si estuvieran involucradas.
Usar como camuflaje
El código de modificación automática se utilizó para ocultar las instrucciones de protección contra copias en programas basados en disco de la década de 1980 para plataformas como IBM PC y Apple II . Por ejemplo, en una PC IBM (o compatible ), la instrucción de acceso a la unidad de disquete ' int 0x13' no aparecería en la imagen del programa ejecutable, pero se escribiría en la imagen de la memoria del ejecutable después de que el programa comenzara a ejecutarse.
El código de modificación automática también lo utilizan a veces programas que no quieren revelar su presencia, como los virus informáticos y algunos códigos de shell . Los virus y shellcodes que usan código auto-modificable lo hacen principalmente en combinación con código polimórfico . La modificación de un fragmento de código en ejecución también se usa en ciertos ataques, como los desbordamientos de búfer .
Sistemas de aprendizaje automático autorreferenciales
Los sistemas tradicionales de aprendizaje automático tienen un algoritmo de aprendizaje preprogramado y fijo para ajustar sus parámetros . Sin embargo, desde la década de 1980, Jürgen Schmidhuber ha publicado varios sistemas auto modificables con la capacidad de cambiar su propio algoritmo de aprendizaje. Evitan el peligro de auto-reescrituras catastróficas al asegurarse de que las auto-modificaciones sobrevivirán solo si son útiles de acuerdo con una función de aptitud , error o recompensa dada por el usuario . [6]
Sistemas operativos
Debido a las implicaciones de seguridad del código que se modifica automáticamente, todos los principales sistemas operativos tienen cuidado de eliminar las vulnerabilidades a medida que se conocen. Por lo general, la preocupación no es que los programas se modifiquen intencionalmente, sino que un exploit pueda modificarlos maliciosamente .
Como consecuencia de los problemas que pueden causar estos exploits, se ha desarrollado una característica del sistema operativo llamada W ^ X (para "escribir xo ejecutar") que prohíbe que un programa haga que cualquier página de la memoria sea tanto escribible como ejecutable. Algunos sistemas evitan que una página en la que se puede escribir se cambie para que sea ejecutable, incluso si se elimina el permiso de escritura. Otros sistemas proporcionan una especie de " puerta trasera ", lo que permite que múltiples asignaciones de una página de memoria tengan diferentes permisos. Una forma relativamente portátil de omitir W ^ X es crear un archivo con todos los permisos y luego asignar el archivo a la memoria dos veces. En Linux, se puede usar un indicador de memoria compartida SysV no documentado para obtener memoria compartida ejecutable sin necesidad de crear un archivo. [ cita requerida ]
Independientemente, en un metanivel , los programas aún pueden modificar su propio comportamiento cambiando los datos almacenados en otro lugar (ver metaprogramación ) o mediante el uso de polimorfismo .
Interacción de caché y código auto modificable
En arquitecturas sin caché de instrucciones y datos acoplados (algunos núcleos ARM y MIPS), el código de modificación debe realizar explícitamente la sincronización de la caché (vaciar la caché de datos e invalidar la caché de instrucciones para el área de memoria modificada).
En algunos casos, las secciones cortas de código que se modifica automáticamente se ejecutan más lentamente en los procesadores modernos. Esto se debe a que un procesador moderno generalmente intentará mantener bloques de código en su memoria caché. Cada vez que el programa reescribe una parte de sí mismo, la parte reescrita debe cargarse nuevamente en la caché, lo que da como resultado un ligero retraso, si el codelet modificado comparte la misma línea de caché con el código de modificación, como es el caso cuando la memoria modificada La dirección se encuentra dentro de unos pocos bytes a la del código de modificación.
El problema de invalidación de la caché en los procesadores modernos generalmente significa que el código de modificación automática sería aún más rápido solo cuando la modificación ocurrirá raramente, como en el caso de un cambio de estado dentro de un bucle interno. [ cita requerida ]
La mayoría de los procesadores modernos cargan el código de la máquina antes de ejecutarlo, lo que significa que si se modifica una instrucción que está demasiado cerca del puntero de la instrucción , el procesador no lo notará, sino que ejecutará el código como estaba antes de que se modificara. Consulte cola de entrada de captación previa (PIQ). Los procesadores de PC deben manejar correctamente el código de modificación automática por razones de compatibilidad con versiones anteriores, pero están lejos de ser eficientes al hacerlo. [ cita requerida ]
Núcleo de síntesis de Massalin
El núcleo de síntesis presentado en el Ph.D. de Alexia Massalin . thesis [7] es un pequeño kernel de Unix que adopta un enfoque estructurado , o incluso orientado a objetos , para el código auto-modificable, donde el código se crea para quajects individuales , como filehandles. La generación de código para tareas específicas permite que el núcleo de Synthesis (como lo haría un intérprete JIT) aplique una serie de optimizaciones , como el plegado constante o la eliminación de subexpresiones comunes .
El kernel de Synthesis era muy rápido, pero estaba escrito completamente en ensamblador. La falta de portabilidad resultante ha impedido que las ideas de optimización de Massalin sean adoptadas por cualquier núcleo de producción. Sin embargo, la estructura de las técnicas sugiere que podrían ser capturadas por un lenguaje de nivel superior , aunque uno más complejo que los lenguajes de nivel medio existentes. Dicho lenguaje y compilador podría permitir el desarrollo de aplicaciones y sistemas operativos más rápidos.
Paul Haeberli y Bruce Karsh se han opuesto a la "marginación" del código que se modifica automáticamente, y la optimización en general, a favor de unos costes de desarrollo reducidos. [8]
Ventajas
- Se pueden establecer rutas rápidas para la ejecución de un programa, reduciendo algunas ramas condicionales repetitivas .
- El código de modificación automática puede mejorar la eficiencia algorítmica .
Desventajas
El código que se modifica automáticamente es más difícil de leer y mantener porque las instrucciones en la lista del programa fuente no son necesariamente las instrucciones que se ejecutarán. La auto-modificación que consiste en la sustitución de punteros de función puede no ser tan críptica, si está claro que los nombres de las funciones que se llamarán son marcadores de posición para las funciones que se identificarán más adelante.
El código de modificación automática se puede reescribir como código que prueba una bandera y se ramifica a secuencias alternativas según el resultado de la prueba, pero el código de modificación automática normalmente se ejecuta más rápido.
En los procesadores modernos con una canalización de instrucciones , el código que se modifica a sí mismo con frecuencia puede ejecutarse más lentamente, si modifica instrucciones que el procesador ya ha leído de la memoria en la canalización. En algunos de estos procesadores, la única forma de garantizar que las instrucciones modificadas se ejecuten correctamente es vaciar la tubería y volver a leer muchas instrucciones.
El código de modificación automática no se puede utilizar en absoluto en algunos entornos, como los siguientes:
- El software de aplicación que se ejecuta en un sistema operativo con estricta seguridad W ^ X no puede ejecutar instrucciones en las páginas en las que está permitido escribir; solo el sistema operativo puede escribir instrucciones en la memoria y luego ejecutarlas.
- Muchos microcontroladores de la arquitectura de Harvard no pueden ejecutar instrucciones en la memoria de lectura y escritura, sino solo instrucciones en la memoria en la que no puede escribir, ROM o memoria flash no autoprogramable .
- Una aplicación multiproceso puede tener varios subprocesos que ejecutan la misma sección de código auto-modificable, lo que posiblemente resulte en errores de cálculo y fallas en la aplicación.
Ver también
- Código polimórfico
- Motor polimórfico
- Estructura de datos persistente
- Código AARD
- Eficiencia algorítmica
- declaración eval
- IBM 1130 (ejemplo)
- Compilación justo a tiempo : esta técnica a menudo puede brindar a los usuarios muchos de los beneficios del código que se modifica automáticamente (excepto el tamaño de la memoria) sin las desventajas.
- Eliminación dinámica de códigos muertos
- Homoiconicidad
- PCASTL
- Quine (informática)
- Autorreplicación
- Reflexión (informática)
- Parche de mono : una modificación del código en tiempo de ejecución que no afecta el código fuente original de un programa
- Programación extensible : un paradigma de programación en el que un lenguaje de programación puede modificar su propia sintaxis.
- Virus de computadora
- Autohospedaje
Referencias
- ^ "La declaración ALTER". Referencia del lenguaje COBOL . Micro Focus .
- ^ "Push, PushGP y Pushpop" .
- ^ Bashe, CJ; Buchholz, W .; Hawkins, GV; Ingram, JJ; Rochester, N. (septiembre de 1981). "La arquitectura de las primeras computadoras de IBM" (PDF) . IBM Journal of System Development . 25 (5): 363–376. CiteSeerX 10.1.1.93.8952 . doi : 10.1147 / rd.255.0363 .
El SSEC fue la primera computadora operativa capaz de tratar sus propias instrucciones almacenadas exactamente como datos, modificarlas y actuar sobre el resultado.
- ^ "MMIX 2009 (que describe características obsoletas de MIX)" .
- ^ "Sobre el Código Auto Modificable y el Sistema Operativo del Transbordador Espacial - Carlos Enrique Ortiz" .
- ^ Publicaciones de Jürgen Schmidhuber sobre código auto modificable para sistemas de aprendizaje automático autorreferenciales
- ^ Pu, Calton ; Massalin, Henry ; Ioannidis, John (1992). Síntesis: una implementación eficiente de los servicios fundamentales del sistema operativo (PDF) (tesis doctoral). Nueva York, NY, EE.UU .: Departamento de Ciencias de la Computación, Universidad de Columbia . Núm. De pedido de UMI GAX92-32050. Archivado (PDF) desde el original el 4 de julio de 2017 . Consultado el 25 de abril de 2012 . Resumen de Lay (2008-02-20). [1]
- ^ Haeberli, Paul ; Karsh, Bruce (3 de febrero de 1994). "Io Noi Boccioni - Antecedentes de la programación futurista" . Grafica Obscura . Archivado desde el original el 4 de julio de 2017 . Consultado el 4 de julio de 2017 .
enlaces externos
- Uso de código auto modificable en Linux
- Código C auto modificable
- Código auto modificable certificado