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 de código de máquina hace que la CPU realice una tarea específica. Algunos ejemplos de estas tareas son:

  1. Cargar una palabra desde la memoria a un registro de la CPU
  2. Ejecutar una operación de unidad lógica aritmética (ALU) en uno o más registros o ubicaciones de memoria
  3. Saltar o saltar a una instrucción que no es la siguiente

En general, cada familia de arquitectura (por ejemplo, x86 , ARM ) tiene su propia arquitectura de conjunto de instrucciones (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 arquitectura IA-64 , que incluye soporte opcional del conjunto de instrucciones IA-32 ; y el microprocesador PowerPC 615 , que puede procesar de forma nativa conjuntos de instrucciones PowerPC y x86.

El código de 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 un mapa directo entre el código de máquina numérico y un mnemónico legible por humanos. En el lenguaje ensamblador, los códigos de operación y operandos numéricos se reemplazan con mnemónicos y etiquetas. Por ejemplo, la arquitectura x86 tiene disponible el código de operación 0x90; se representa como NOP en el código fuente del ensamblador . Si bien es posible escribir programas directamente en código de máquina, administrar bits individuales y calcular direcciones numéricas es tedioso y propenso a errores. Por lo tanto, los programas rara vez se escriben directamente en código de máquina. Sin embargo, un programa de código de máquina existente puede editarse si el código fuente del ensamblador no está disponible.

La mayoría de los programas actuales están escritos en un lenguaje de alto nivel . Un compilador puede traducir un programa de alto nivel a código de máquina .

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.

Lenguajes ensambladores

Traducción del lenguaje máquina al lenguaje ensamblador

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]

Ejemplos

IBM 709x

Los IBM 704, 709, 704x y 709x almacenan una instrucción en cada palabra de instrucción; IBM numera el bit desde la izquierda como S, 1, ..., 35. La mayoría de las instrucciones tienen uno de dos formatos:

Genérico
S,1-11
Bandera 12-13, ignorada en algunas instrucciones
14-17 sin usar
Etiqueta 18-20
21-35 años
Control de registro de índice, distinto de TSX
Código de operación S,1-2
3-17 Decremento
Etiqueta 18-20
21-35 años

En todos los modelos, excepto en los IBM 7094 y 7094 II, hay tres registros de índice designados A, B y C; la indexación con varios bits 1 en la etiqueta resta el o lógico de los registros de índice seleccionados y la carga con varios bits 1 en la etiqueta carga todos los registros de índice seleccionados. Los modelos 7094 y 7094 II tienen siete registros de índice, pero cuando se encienden están en modo de etiquetas múltiples , en el que utilizan solo tres de los registros de índice de una manera compatible con máquinas anteriores, y requieren una instrucción Leave Multiple Tag Mode ( LMTM ) para acceder a los otros cuatro registros de índice.

La dirección efectiva normalmente es YC(T), donde C(T) es 0 para una etiqueta de 0, la dirección lógica o de los registros de índice seleccionados en el modo de etiquetas múltiples o el registro de índice seleccionado si no está en el modo de etiquetas múltiples. Sin embargo, la dirección efectiva para las instrucciones de control de registro de índice es simplemente Y.

Una bandera con ambos bits 1 selecciona direccionamiento indirecto; la palabra de dirección indirecta tiene una etiqueta y un campo Y.

Además de las instrucciones de transferencia (ramificación), estas máquinas tienen instrucciones de omisión que omiten condicionalmente una o dos palabras, por ejemplo, Comparar acumulador con almacenamiento (CAS) hace una comparación de tres vías y omite condicionalmente NSI, NSI+1 o NSI+2, dependiendo del resultado.

MIPS

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 el recuento de Kruskal , [7] [6 ] [8] [9] [10] a veces es posible, a través de la 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 1] 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 2]

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 1]

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.

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 del 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, basándose en 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 se trata de 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. ^ 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 .
  2. ^ 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 basado en disparadores encubiertos (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) desde el 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