stringtranslate.com

Código máquina

Monitor de lenguaje de máquina que se ejecuta en un microprocesador W65C816S , que muestra el desensamblaje del código y volcados de registros y memoria del procesador

En programación informática , el código máquina es un código informático que consiste en instrucciones en lenguaje máquina , que se utilizan para controlar la unidad central de procesamiento (CPU) de una computadora. Para las computadoras binarias convencionales , el código máquina es "la representación binaria de un programa informático que es realmente leído e interpretado por la computadora. Un programa en código máquina consiste en una secuencia de instrucciones de máquina (posiblemente intercaladas con datos)". [1]

Cada instrucción hace que la CPU realice una tarea muy específica, como una carga, un almacenamiento, un salto o una operación de unidad aritmética lógica (ALU) en una o más unidades de datos en los registros o la memoria de la CPU .

Las primeras CPU tenían un código de máquina específico que podía romper la compatibilidad con las nuevas CPU que se lanzaban al mercado. La noción de una arquitectura de conjunto de instrucciones (ISA) define y especifica el comportamiento y la codificación en memoria del conjunto de instrucciones del sistema, sin especificar su implementación exacta. Esto actúa como una capa de abstracción, que permite la compatibilidad dentro de la misma familia de CPU, de modo que el código de máquina escrito o generado de acuerdo con la ISA para la familia se ejecutará en todas las CPU de la familia, incluidas las CPU futuras.

En general, cada familia de arquitectura (por ejemplo, x86 , ARM ) tiene su propia ISA y, por lo tanto, su propio lenguaje de código de máquina específico. Existen excepciones, como la arquitectura VAX , que incluye soporte opcional del conjunto de instrucciones PDP-11 ; la IA-64 , que incluye soporte opcional del conjunto de instrucciones IA-32 ; y la PowerPC 615 , que puede procesar de forma nativa conjuntos de instrucciones PowerPC y x86.

El código máquina es un lenguaje estrictamente numérico y es la interfaz de nivel más bajo para la CPU destinada a un programador. El lenguaje ensamblador  proporciona una asignación directa entre el código máquina numérico y una versión legible por humanos donde los códigos de operación y operandos numéricos se reemplazan por cadenas legibles (por ejemplo, 0x90 como la instrucción NOP en x86 , con 0xB8 siendo la instrucción MOV, 0xE8 significa CALL o 0x0F05 representando la instrucción SYSCALL). Si bien es posible escribir programas directamente en código máquina, administrar bits individuales y calcular direcciones numéricas y constantes manualmente es tedioso y propenso a errores. Por esta razón, los programas rara vez se escriben directamente en código máquina en contextos modernos, pero se puede hacer para depuración de bajo nivel , parcheo de programas (especialmente cuando la fuente de ensamblador no está disponible) y desensamblaje de lenguaje ensamblador .

La mayoría de los programas prácticos actuales se escriben en lenguajes de alto nivel . Estos programas se traducen a código de máquina mediante un compilador o se interpretan mediante un intérprete , generalmente después de traducirlos a un código intermedio, como un bytecode , que luego se interpreta. [nb 1]

El código de máquina es, por definición, el nivel más bajo de detalle de programación visible para el programador, pero internamente muchos procesadores utilizan microcódigo u optimizan y transforman instrucciones de código de máquina en secuencias de microoperaciones . El microcódigo y las microoperaciones no se consideran generalmente código de máquina; excepto en algunas máquinas, el usuario no puede escribir microcódigo ni microoperaciones, y la operación del microcódigo y la transformación de instrucciones de código de máquina en microoperaciones suceden de forma transparente para el programador, salvo por efectos secundarios relacionados con el rendimiento.

Conjunto de instrucciones

Cada procesador o familia de procesadores tiene su propio conjunto de instrucciones . Las instrucciones son patrones de bits , dígitos o caracteres que corresponden a comandos de máquina. Por lo tanto, el conjunto de instrucciones es específico para una clase de procesadores que utilizan (en su mayoría) la misma arquitectura . Los diseños de procesadores sucesores o derivados a menudo incluyen instrucciones de un predecesor y pueden agregar nuevas instrucciones adicionales. Ocasionalmente, un diseño sucesor discontinuará o alterará el significado de algún código de instrucción (generalmente porque es necesario para nuevos propósitos), lo que afectará la compatibilidad del código hasta cierto punto; incluso los procesadores compatibles pueden mostrar un comportamiento ligeramente diferente para algunas instrucciones, pero esto rara vez es un problema. Los sistemas también pueden diferir en otros detalles, como la disposición de la memoria, los sistemas operativos o los dispositivos periféricos . Debido a que un programa normalmente depende de tales factores, los diferentes sistemas normalmente no ejecutarán el mismo código de máquina, incluso cuando se use el mismo tipo de procesador.

El conjunto de instrucciones de un procesador puede tener instrucciones de longitud fija o de longitud variable. La forma en que se organizan los patrones varía según la arquitectura particular y el tipo de instrucción. La mayoría de las instrucciones tienen uno o más campos de código de operación que especifican el tipo de instrucción básica (como aritmética, lógica, salto , etc.), la operación (como sumar o comparar) y otros campos que pueden indicar el tipo de operando (s), el modo de direccionamiento (s), el desplazamiento o índice de direccionamiento, o el valor del operando en sí (estos operandos constantes contenidos en una instrucción se denominan inmediatos ). [2]

No todas las máquinas o instrucciones individuales tienen operandos explícitos. En una máquina con un solo acumulador , el acumulador es implícitamente tanto el operando izquierdo como el resultado de la mayoría de las instrucciones aritméticas. Algunas otras arquitecturas, como la arquitectura x86 , tienen versiones de acumulador de instrucciones comunes, con el acumulador considerado como uno de los registros generales por instrucciones más largas. Una máquina de pila tiene la mayoría o todos sus operandos en una pila implícita. Las instrucciones de propósito especial también carecen a menudo de operandos explícitos; por ejemplo, CPUID en la arquitectura x86 escribe valores en cuatro registros de destino implícitos. Esta distinción entre operandos explícitos e implícitos es importante en los generadores de código, especialmente en las partes de asignación de registros y seguimiento de rango en vivo. Un buen optimizador de código puede rastrear operandos implícitos y explícitos que pueden permitir una propagación constante más frecuente , plegado constante de registros (un registro asignado al resultado de una expresión constante liberada al reemplazarlo por esa constante) y otras mejoras de código.

Programas

Un programa de computadora es una lista de instrucciones que puede ejecutar una unidad central de procesamiento (CPU). La ejecución de un programa se realiza para que la CPU que lo ejecuta resuelva un problema y, por lo tanto, logre un resultado. Mientras que los procesadores simples pueden ejecutar instrucciones una tras otra, los procesadores superescalares pueden, en determinadas circunstancias (cuando el pipeline está lleno), ejecutar dos o más instrucciones simultáneamente. Por ejemplo, el Intel Pentium original de 1993 puede ejecutar como máximo dos instrucciones por ciclo de reloj cuando su pipeline está lleno.

El flujo del programa puede verse influenciado por instrucciones especiales de "salto" que transfieren la ejecución a una dirección (y, por lo tanto, a una instrucción) distinta de la siguiente dirección numéricamente secuencial. La ocurrencia de estos saltos condicionales depende de una condición, como que un valor sea mayor, menor o igual a otro valor.

Lenguajes ensambladores

Una representación mucho más amigable para los humanos del lenguaje de máquina, llamada lenguaje ensamblador , utiliza códigos mnemotécnicos para referirse a las instrucciones del código de máquina, en lugar de usar los valores numéricos de las instrucciones directamente, y utiliza nombres simbólicos para referirse a las ubicaciones de almacenamiento y, a veces, a los registros . [3] Por ejemplo, en el procesador Zilog Z80 , el código de máquina 00000101, que hace que la CPU disminuya el B registro de propósito general , se representaría en lenguaje ensamblador como DEC B. [4]

Ejemplo

La arquitectura MIPS proporciona un ejemplo específico para un código de máquina cuyas instrucciones tienen siempre 32 bits de longitud. [5] : 299  El tipo general de instrucción se proporciona mediante el campo op (operación), los 6 bits más altos. Las instrucciones de tipo J (salto) y de tipo I (inmediato) se especifican completamente mediante op . Las instrucciones de tipo R (registro) incluyen un campo adicional funct para determinar la operación exacta. Los campos utilizados en estos tipos son:

 6 5 5 5 5 6 bits[ op | rs | rt | rd | shamt | funct] Tipo R[ op | rs | rt | dirección/inmediato] Tipo I[ op | dirección de destino ] Tipo J

rs , rt y rd indican operandos de registro; shamt proporciona una cantidad de desplazamiento; y los campos de dirección o inmediatos contienen un operando directamente. [5] : 299–301 

Por ejemplo, sumar los registros 1 y 2 y colocar el resultado en el registro 6 se codifica: [5] : 554 

[ op | rs | rt | rd | shamt | función] 0 1 2 6 0 32 decimal 000000 00001 00010 00110 00000 100000 binario

Cargar un valor en el registro 8, tomado de la celda de memoria 68 celdas después de la ubicación indicada en el registro 3: [5] : 552 

[ op | rs | rt | dirección/inmediata] 35 3 8 68 decimal 100011 00011 01000 00000 00001 000100 binario

Saltando a la dirección 1024: [5] : 552 

[ op | dirección de destino ] 2 1024 decimal 000010 00000 00000 00000 10000 000000 binario

Instrucciones superpuestas

En arquitecturas de procesadores con conjuntos de instrucciones de longitud variable [6] (como la familia de procesadores x86 de Intel ), dentro de los límites del fenómeno de resincronización del flujo de control conocido como recuento de Kruskal , [7] [6] [8] [9] [10] a veces es posible, mediante programación a nivel de código de operación, organizar deliberadamente el código resultante de modo que dos rutas de código compartan un fragmento común de secuencias de código de operación. [nb 2] Estas se denominan instrucciones superpuestas , códigos de operación superpuestos , código superpuesto , código superpuesto , escisión de instrucción o salto al medio de una instrucción . [11] [12] [13]

En los años 1970 y 1980, a veces se utilizaban instrucciones superpuestas para preservar el espacio de memoria. Un ejemplo fue la implementación de tablas de errores en Altair BASIC de Microsoft , donde las instrucciones intercaladas compartían mutuamente sus bytes de instrucción. [14] [6] [11] La técnica rara vez se utiliza hoy en día, pero aún puede ser necesario recurrir a ella en áreas donde es necesaria una optimización extrema del tamaño a nivel de bytes, como en la implementación de cargadores de arranque que tienen que caber en sectores de arranque . [nb 3]

También se utiliza a veces como técnica de ofuscación de código como medida contra el desmontaje y la manipulación. [6] [9]

El principio también se utiliza en secuencias de código compartido de binarios fat que deben ejecutarse en múltiples plataformas de procesadores incompatibles con el conjunto de instrucciones. [nb 2]

Esta propiedad también se utiliza para encontrar instrucciones no deseadas llamadas gadgets en repositorios de código existentes y se utiliza en programación orientada al retorno como alternativa a la inyección de código para exploits como ataques de retorno a libc . [15] [6]

Relación con el microcódigo

En algunas computadoras, el código de máquina de la arquitectura se implementa mediante una capa subyacente aún más fundamental llamada microcódigo , que proporciona una interfaz de lenguaje de máquina común a lo largo de una línea o familia de diferentes modelos de computadora con flujos de datos subyacentes muy diferentes . Esto se hace para facilitar la transferencia de programas de lenguaje de máquina entre diferentes modelos. Un ejemplo de este uso es la familia de computadoras IBM System/360 y sus sucesores. Con anchos de ruta de flujo de datos de 8 bits a 64 bits y más, presentan, no obstante, una arquitectura común a nivel de lenguaje de máquina en toda la línea.

El uso de microcódigo para implementar un emulador permite que el ordenador presente la arquitectura de un ordenador completamente diferente. La línea System/360 utilizó esto para permitir la migración de programas de máquinas IBM anteriores a la nueva familia de ordenadores, por ejemplo, un emulador IBM 1401/1440/1460 en el IBM S/360 modelo 40.

Relación con el código de bytes

El código de máquina es generalmente diferente del código de bytes (también conocido como código p), que es ejecutado por un intérprete o compilado en código de máquina para una ejecución más rápida (directa). Una excepción es cuando un procesador está diseñado para usar un código de bytes particular directamente como su código de máquina, como es el caso de los procesadores Java .

El código de máquina y el código ensamblador a veces se denominan código nativo cuando se hace referencia a partes de características o bibliotecas de lenguaje que dependen de la plataforma. [16]

Almacenar en memoria

Desde el punto de vista de la CPU, el código de máquina se almacena en la memoria RAM, pero normalmente también se guarda en un conjunto de cachés por razones de rendimiento. Puede haber diferentes cachés para instrucciones y datos, según la arquitectura.

La CPU sabe qué código de máquina ejecutar, en función de su contador de programa interno. El contador de programa apunta a una dirección de memoria y se modifica en función de instrucciones especiales que pueden provocar bifurcaciones programáticas. El contador de programa normalmente se establece en un valor codificado de forma fija cuando se enciende la CPU por primera vez y, por lo tanto, ejecutará cualquier código de máquina que se encuentre en esta dirección.

De manera similar, el contador del programa se puede configurar para ejecutar cualquier código de máquina que se encuentre en una dirección arbitraria, incluso si no es un código de máquina válido. Esto normalmente activará una falla de protección específica de la arquitectura.

En un sistema basado en paginación, los permisos de página suelen indicar a la CPU si la página actual contiene realmente código de máquina mediante un bit de ejecución; las páginas tienen varios bits de permiso de este tipo (de lectura, escritura, etc.) para diversas funciones de mantenimiento. Por ejemplo, en sistemas tipo Unix, las páginas de memoria se pueden cambiar para que sean ejecutables con la mprotect()llamada del sistema y, en Windows, VirtualProtect()se pueden utilizar para lograr un resultado similar. Si se intenta ejecutar código de máquina en una página no ejecutable, normalmente se producirá un fallo específico de la arquitectura. Tratar los datos como código de máquina o encontrar nuevas formas de utilizar el código de máquina existente mediante diversas técnicas es la base de algunas vulnerabilidades de seguridad.

De manera similar, en un sistema basado en segmentos, los descriptores de segmento pueden indicar si un segmento puede contener código ejecutable y en qué anillos puede ejecutarse ese código.

Desde el punto de vista de un proceso , el espacio de código es la parte de su espacio de direcciones donde se almacena el código en ejecución. En sistemas multitarea , esto comprende el segmento de código del programa y, por lo general, las bibliotecas compartidas . En un entorno multihilo , los diferentes hilos de un proceso comparten el espacio de código junto con el espacio de datos, lo que reduce considerablemente la sobrecarga del cambio de contexto en comparación con el cambio de proceso.

Legibilidad para humanos

Existen varias herramientas y métodos para decodificar el código de máquina a su código fuente correspondiente .

El código de máquina se puede decodificar fácilmente a su código fuente en lenguaje ensamblador correspondiente porque el lenguaje ensamblador forma una correspondencia uno a uno con el código de máquina. [17] El método de decodificación del lenguaje ensamblador se denomina desensamblaje .

El código de máquina puede decodificarse nuevamente a su lenguaje de alto nivel correspondiente bajo dos condiciones:

La primera condición es aceptar una lectura ofuscada del código fuente. Se muestra una versión ofuscada del código fuente si el código de máquina se envía a un descompilador del lenguaje fuente.

La segunda condición requiere que el código de máquina tenga información sobre el código fuente codificado en su interior. La información incluye una tabla de símbolos que contiene símbolos de depuración . La tabla de símbolos puede estar almacenada dentro del ejecutable o puede existir en archivos separados. Un depurador puede entonces leer la tabla de símbolos para ayudar al programador a depurar de forma interactiva el código de máquina en ejecución .

Véase también

Notas

  1. ^ Como muchas versiones de BASIC , especialmente las primeras, y Smalltalk , MATLAB , Perl , Python , Ruby y otros lenguajes de programación o de propósito especial .
  2. ^ ab Si bien las instrucciones superpuestas en arquitecturas de procesador con conjuntos de instrucciones de longitud variable a veces se pueden organizar para fusionar diferentes rutas de código en una sola a través de la resincronización del flujo de control , el código superpuesto para diferentes arquitecturas de procesador a veces también se puede diseñar para hacer que las rutas de ejecución se ramifiquen en diferentes direcciones dependiendo del procesador subyacente, como a veces se usa en binarios fat .
  3. ^ Por ejemplo, los registros de arranque maestros (MBR) y los sectores de arranque del DR-DOS (que también contienen la tabla de particiones y el bloque de parámetros del BIOS , dejando menos de 446 y 423 bytes respectivamente para el código) tradicionalmente podían localizar el archivo de arranque en el sistema de archivos FAT12 o FAT16 por sí mismos y cargarlo en la memoria como un todo, en contraste con sus contrapartes en DOS , MS-DOS y PC DOS , que en cambio dependen de que los archivos del sistema ocupen las dos primeras ubicaciones de entrada de directorio en el sistema de archivos y los primeros tres sectores de IBMBIO.COM se almacenen al comienzo del área de datos en sectores contiguos que contienen un cargador secundario para cargar el resto del archivo en la memoria (lo que requiere que SYS se encargue de todas estas condiciones). Cuando se añadió el soporte para FAT32 y el direccionamiento de bloques lógicos (LBA), Microsoft incluso cambió para requerir instrucciones i386 y dividir el código de arranque en dos sectores por razones de tamaño de código, lo que no era una opción para DR-DOS, ya que habría roto la compatibilidad con versiones anteriores y cruzadas con otros sistemas operativos en escenarios de arranque múltiple y carga en cadena , y como con las PC compatibles con IBM PC más antiguas . En cambio, los sectores de arranque de DR-DOS 7.07 recurrieron a código automodificable , programación a nivel de código de operación en lenguaje de máquina, utilización controlada de efectos secundarios (documentados) , superposición de datos/código de múltiples niveles y técnicas de plegado algorítmico para encajar todo en un sector físico de solo 512 bytes sin renunciar a ninguna de sus funciones extendidas.

Referencias

  1. ^ Stallings, William (2015). Organización y arquitectura de computadoras, décima edición . Pearson Prentice Hall. pág. 776. ISBN 9789332570405.
  2. ^ Kjell, Bradley. "Operando inmediato".
  3. ^ Dourish, Paul (2004). Dónde está la acción: los fundamentos de la interacción corporizada. MIT Press . p. 7. ISBN 0-262-54178-5. Consultado el 5 de marzo de 2023 .
  4. ^ Zaks, Rodnay (1982). Programación del Z80 (tercera edición revisada). Sybex . págs. 67, 120, 609. ISBN. 0-89588-094-6. Consultado el 5 de marzo de 2023 .
  5. ^ abcde Harris, David; Harris, Sarah L. (2007). Diseño digital y arquitectura informática. Morgan Kaufmann Publishers . ISBN 978-0-12-370497-9. Consultado el 5 de marzo de 2023 .
  6. ^ abcde Jacob, Matthias; Jakubowski, Mariusz H.; Venkatesan, Ramarathnam [en Wikidata] (20-21 de septiembre de 2007). Hacia la ejecución binaria integral: implementación de hash inconsciente mediante codificaciones de instrucciones superpuestas (PDF) . Actas del 9.º taller sobre multimedia y seguridad (MM&Sec '07). Dallas, Texas, EE. UU.: Association for Computing Machinery . págs. 129-140. CiteSeerX 10.1.1.69.5258 . doi :10.1145/1288869.1288887. ISBN  978-1-59593-857-2. S2CID  14174680. Archivado (PDF) del original el 4 de septiembre de 2018. Consultado el 25 de diciembre de 2021 .(12 páginas)
  7. ^ Lagarias, Jeffrey "Jeff" Clark ; Rains, Eric Michael ; Vanderbei, Robert J. (2009) [13 de octubre de 2001]. Brams, Stephen; Gehrlein, William V.; Roberts, Fred S. (eds.). "El conteo de Kruskal". Las matemáticas de la preferencia, la elección y el orden. Ensayos en honor a Peter J. Fishburn . Berlín/Heidelberg, Alemania: Springer-Verlag : 371–391. arXiv : math/0110143 . ISBN . 978-3-540-79127-0.(22 páginas)
  8. ^ Andriesse, Dennis; Bos, Herbert [en Wikidata] (10 de julio de 2014). Escrito en la Vrije Universiteit Amsterdam, Ámsterdam, Países Bajos. Dietrich, Sven (ed.). Esteganografía a nivel de instrucción para malware encubierto basado en disparadores (PDF) . 11.ª Conferencia internacional sobre detección de intrusiones y malware, y evaluación de vulnerabilidades (DIMVA). Notas de clase en informática . Egham, Reino Unido; Suiza: Springer International Publishing . pp. 41–50 [45]. doi :10.1007/978-3-319-08509-8_3. eISSN  1611-3349. ISBN . 978-3-31908508-1. ISSN  0302-9743. S2CID  4634611. LNCS 8550. Archivado (PDF) desde el original el 2023-08-26 . Consultado el 2023-08-26 .(10 páginas)
  9. ^ ab Jakubowski, Mariusz H. (febrero de 2016). "Modelo basado en gráficos para la protección contra manipulaciones de software". Microsoft . Archivado desde el original el 2019-10-31 . Consultado el 2023-08-19 .
  10. ^ Jämthagen, Christopher (noviembre de 2016). Métodos ofensivos y defensivos en seguridad de software (PDF) (Tesis). Lund, Suecia: Departamento de Tecnología Eléctrica y de la Información, Universidad de Lund . p. 96. ISBN 978-91-7623-942-1. ISSN  1654-790X. Archivado (PDF) del original el 26 de agosto de 2023. Consultado el 26 de agosto de 2023 .(1+xvii+1+152 páginas)
  11. ^ ab "Instrucciones no deseadas en x86". Hacker News . 2021. Archivado desde el original el 2021-12-25 . Consultado el 2021-12-24 .
  12. ^ Más amable, Johannes (24 de septiembre de 2010). Análisis estático de ejecutables x86 [ Statische Analyse von Programmen in x86 Maschinensprache ] (PDF) (Disertación). Múnich, Alemania: Technische Universität Darmstadt . D17. Archivado desde el original el 12 de noviembre de 2020 . Consultado el 25 de diciembre de 2021 .(199 páginas)
  13. ^ "¿Qué es la ofuscación por "instrucciones superpuestas"?". Stack Exchange de ingeniería inversa . 2013-04-07. Archivado desde el original el 2021-12-25 . Consultado el 2021-12-25 .
  14. ^ Gates, William "Bill" Henry , Comunicación personal(NB. Según Jacob et al.)
  15. ^ Shacham, Hovav (2007). La geometría de la carne inocente sobre el hueso: retorno a libc sin llamadas a funciones (en x86) (PDF) . Actas de la ACM, CCS 2007. ACM Press . Archivado (PDF) desde el original el 15 de diciembre de 2021 . Consultado el 24 de diciembre de 2021 .
  16. ^ "Administrado, no administrado, nativo: ¿Qué tipo de código es éste?". developer.com . 28 de abril de 2003. Consultado el 2 de septiembre de 2008 .
  17. ^ Tanenbaum, Andrew S. (1990). Organización de computadoras estructuradas, tercera edición. Prentice Hall . pág. 398. ISBN 978-0-13-854662-5.
  18. ^ "Arquitectura de datos asociada". Ensamblador de alto nivel y función de kit de herramientas .
  19. ^ "Contenido del archivo COBOL SYSADATA". COBOL empresarial para z/OS .
  20. ^ "Información del mensaje SYSADATA". Información de Enterprise PL/I para z/OS 6.1 .
  21. ^ "Símbolos para la depuración de Windows". Microsoft Learn . 2022-12-20.
  22. ^ "Consulta del archivo .Pdb". Microsoft Learn . 12 de enero de 2024.

Lectura adicional

Véase también