¿Cómo pasamos del ensamblaje al código de máquina (generación de código)?

13

¿Existe una manera fácil de visualizar el paso entre el código de ensamblaje y el código de máquina?

Por ejemplo, si abre un archivo binario en el bloc de notas, verá una representación del código de máquina con formato de texto. ¿Supongo que cada byte (símbolo) que ves es el carácter ASCII correspondiente para su valor binario?

Pero, ¿cómo vamos de ensamblaje a binario, qué sucede detrás de escena?

    
pregunta user12979 06.02.2014 - 21:53

4 respuestas

25

Mire la documentación del conjunto de instrucciones, y encontrará entradas como esta en un microcontrolador pic para cada instrucción:

Lalíneade"codificación" indica cómo se ve esa instrucción en binario. En este caso, siempre comienza con 5 unidades, luego un bit de "no importa" (que puede ser uno o cero), luego las "k" representan el literal que está agregando.

Los primeros bits se denominan "código de operación" y son únicos para cada instrucción. La CPU básicamente mira el código de operación para ver qué instrucción es, luego sabe descifrar las "k" como un número que debe agregarse.

Es tedioso, pero no tan difícil de codificar y decodificar. Tuve una clase de pregrado donde tuvimos que hacerlo a mano en los exámenes.

Para crear un archivo ejecutable completo, también tiene que hacer cosas como asignar memoria, calcular compensaciones de bifurcaciones y ponerlo en un formato como ELF , dependiendo de su sistema operativo.

    
respondido por el Karl Bielefeldt 06.02.2014 - 22:15
10

Los códigos de operación de ensamblaje tienen, en su mayor parte, una correspondencia uno a uno con las instrucciones de la máquina subyacente. Así que todo lo que tiene que hacer es identificar cada código de operación en el lenguaje ensamblador, asignarlo a la instrucción de la máquina correspondiente y escribir la instrucción de la máquina en un archivo, junto con sus parámetros correspondientes (si corresponde). Luego repite el proceso para cada código de operación adicional en el archivo fuente.

Por supuesto, se necesita más que eso para crear un archivo ejecutable que se cargue y ejecute correctamente en un sistema operativo, y los ensambladores más decentes tienen algunas capacidades adicionales más allá del simple mapeo de códigos de operación a instrucciones de la máquina (como macros, por ejemplo). ejemplo).

    
respondido por el Robert Harvey 06.02.2014 - 22:05
5

Lo primero que necesitas es algo como este archivo . Esta es la base de datos de instrucciones para los procesadores x86 que usa el ensamblador NASM (que ayudé a escribir, aunque no son las partes que traducen las instrucciones). Permite elegir una línea arbitraria de la base de datos:

ADD   rm32,imm8    [mi:    hle o32 83 /0 ib,s]      386,LOCK

Lo que esto significa es que describe la instrucción ADD . Hay varias variantes de esta instrucción, y la específica que se describe aquí es la variante que toma un registro de 32 bits o una dirección de memoria y agrega un valor inmediato de 8 bits (es decir, una constante incluida directamente en la instrucción). Un ejemplo de instrucciones de ensamblaje que utilizarían esta versión es esta:

add eax, 42

Ahora, debe tomar su entrada de texto y analizarla en instrucciones y operandos individuales. Para la instrucción anterior, esto probablemente resultaría en una estructura que contiene la instrucción, ADD , y una matriz de operandos (una referencia al registro EAX y el valor 42 ). Una vez que tenga esta estructura, ejecute la base de datos de instrucciones y encuentre la línea que coincida con el nombre de la instrucción y los tipos de los operandos. Si no encuentra una coincidencia, es un error que debe presentarse al usuario (el texto habitual es "combinación ilegal de opcode y operandos" o similar).

Una vez que tenemos la línea de la base de datos, miramos la tercera columna, que para esta instrucción es:

[mi:    hle o32 83 /0 ib,s] 

Este es un conjunto de instrucciones que describen cómo generar la instrucción de código de máquina que se requiere:

  • El mi es una descripción de los operandos: un operando modr/m (registro o memoria) (lo que significa que tendremos que agregar un byte modr/m al final de la instrucción, que más adelante) y una instrucción inmediata (que se utilizará en la descripción de la instrucción).
  • El siguiente es hle . Esto identifica cómo manejamos el prefijo de "bloqueo". No hemos utilizado el "bloqueo", por lo que lo ignoramos.
  • El siguiente es o32 . Esto nos dice que si estamos ensamblando código para un formato de salida de 16 bits, la instrucción necesita un prefijo de reemplazo de tamaño de operando. Si estuviéramos produciendo una salida de 16 bits, produciríamos el prefijo ahora ( 0x66 ), pero asumiré que no lo somos y continuaremos.
  • El siguiente es 83 . Este es un byte literal en hexadecimal. Lo sacamos.
  • El siguiente es /0 . Esto especifica algunos bits adicionales que necesitaremos en el bytem modr / m, y hace que lo generemos. El byte modr/m se utiliza para codificar registros o referencias de memoria indirectas. Tenemos un solo tal operando, un registro. El registro tiene un número, que se especifica en otro archivo de datos :

    eax     REG_EAX         reg32           0
    
  • Verificamos que reg32 está de acuerdo con el tamaño requerido de la instrucción de la base de datos original (lo hace). El 0 es el número del registro. Un modr/m byte es una estructura de datos especificada por el procesador, que se ve así:

     (most significant bit)
     2 bits       mod    - 00 => indirect, e.g. [eax]
                           01 => indirect plus byte offset
                           10 => indirect plus word offset
                           11 => register
     3 bits       reg    - identifies register
     3 bits       rm     - identifies second register or additional data
     (least significant bit)
    
  • Debido a que estamos trabajando con un registro, el campo mod es 0b11 .

  • El campo reg es el número del registro que estamos usando, 0b000
  • Como solo hay un registro en esta instrucción, debemos completar el campo rm con algo. Para eso eran los datos adicionales especificados en /0 , así que los colocamos en el campo rm , 0b000 .
  • El byte modr/m es, por lo tanto, 0b11000000 o 0xC0 . Lo sacamos.
  • El siguiente es ib,s . Esto especifica un byte inmediato firmado. Miramos los operandos y notamos que tenemos un valor inmediato disponible. Lo convertimos en un byte firmado y lo imprimimos ( 42 = > 0x2A ).

La instrucción completa ensamblada es por lo tanto: 0x83 0xC0 0x2A . Envíelo a su módulo de salida, junto con una nota de que ninguno de los bytes constituye referencias de memoria (es posible que el módulo de salida necesite saber si lo hacen).

Repita para cada instrucción. Lleve un registro de las etiquetas para que sepa qué insertar cuando se hace referencia. Agregue facilidades para macros y directivas que se pasan a sus módulos de salida de archivos de objetos. Y esto es básicamente cómo funciona un ensamblador.

    
respondido por el Jules 04.06.2016 - 20:16
1

En la práctica, un assembler generalmente no produce directamente algún binario < a href="https://en.wikipedia.org/wiki/Executable"> ejecutable , pero algunos archivo de objeto (se enviará más tarde al enlazador ). Sin embargo, hay excepciones (puedes usar algunos ensambladores para producir directamente algún ejecutable binario; son poco comunes).

Primero, observe que muchos ensambladores son los programas de software libre free . Entonces descargue y compile en su computadora el código fuente de GNU as (una parte de binutils ) y de nasm . Luego estudia su código fuente. Por cierto, recomiendo usar Linux para ese propósito (es un sistema operativo muy amigable para el desarrollador y el software libre).

El archivo de objeto producido por un ensamblador contiene notablemente un segmento de código y reubicación instrucciones. Está organizado en un formato de archivo bien documentado, que depende del sistema operativo. En Linux, ese formato (utilizado para archivos de objetos, bibliotecas compartidas, volcados de memoria y ejecutables) es ELF . Ese archivo de objeto se ingresa posteriormente a vinculador (que finalmente produce un ejecutable). Las reubicaciones se especifican mediante el ABI (por ejemplo, x86-64 ABI ). Lea el libro de Levine enlazadores y cargadores para obtener más información.

El segmento de código en dicho archivo de objeto contiene código de máquina con agujeros (que se completará, con la ayuda de la información de reubicación, por parte del vinculador). El código de máquina (reubicable) generado por un ensamblador obviamente es específico de una arquitectura de conjunto de instrucciones . La x86 o x86 -64 (utilizado en la mayoría de los procesadores de computadoras portátiles o de escritorio) los ISA son terriblemente complejos en sus detalles. Pero un subconjunto simplificado, llamado y86 o y86-64, ha sido inventado para propósitos de enseñanza. Lea las diapositivas sobre ellas. Otras respuestas a esta pregunta también explican un poco de eso. Es posible que desee leer un buen libro sobre arquitectura de computadoras .

La mayoría de los ensambladores están trabajando en dos pases , el segundo emite una reubicación o corrige parte de la salida de la primera pasada. Usan las técnicas habituales análisis (así que lea quizás El Libro del Dragón ).

Cómo inicia un ejecutable el sistema operativo kernel (por ejemplo, cómo funciona la llamada al sistema execve Linux) es una pregunta diferente (y compleja). Por lo general, configura algunos espacio de direcciones virtuales (en process haciendo eso execve (2) ...) luego reinicialice el estado interno del proceso (incluidos los registros user-mode ). Un enlazador dinámico , como ld-linux.so(8) en Linux- podría estar involucrado en el tiempo de ejecución. Lea un buen libro, como Sistema operativo: Tres piezas sencillas . La wiki OSDEV también proporciona información útil.

PS. Su pregunta es tan amplia que necesita leer varios libros al respecto. He dado algunas referencias (muy incompletas). Deberías encontrar más de ellos.

    
respondido por el Basile Starynkevitch 01.09.2018 - 15:10

Lea otras preguntas en las etiquetas