Lo primero que necesita es algo como este archivo . Esta es la base de datos de instrucciones para procesadores x86 que utiliza el ensamblador NASM (que ayudé a escribir, aunque no las partes que realmente traducen las instrucciones). Vamos a 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
. Existen múltiples 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). Una instrucción de ensamblaje de ejemplo que usaría 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 al valor 42
). Una vez que tenga esta estructura, recorre la base de datos de instrucciones y encuentra la línea que coincide tanto con el nombre de la instrucción como con los tipos de operandos. Si no encuentra una coincidencia, es un error que debe presentarse al usuario (el texto habitual es "combinación ilegal de código de operación 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: uno un operando modr/m
(registro o memoria) (lo que significa que tendremos que agregar un modr/m
byte al final de la instrucción, que veremos más adelante) y uno una instrucción inmediata (que ser utilizado en la descripción de la instrucción).
- El siguiente es
hle
. Esto identifica cómo manejamos el prefijo de "bloqueo". No hemos usado "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 anulación 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 continuaré.
- 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 byte modr / m, y hace que lo generemos. El modr/m
byte se usa para codificar registros o referencias indirectas de memoria. Tenemos un solo 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 mod
campo es 0b11
.
- El
reg
campo es el número del registro que estamos usando,0b000
- Debido a que solo hay un registro en esta instrucción, necesitamos completar el
rm
campo con algo. Para eso /0
estaban los datos adicionales especificados , así que los ponemos en el rm
campo 0b000
,.
- El
modr/m
byte es por lo tanto 0b11000000
o 0xC0
. Nosotros sacamos esto.
- El siguiente es
ib,s
. Esto especifica un byte inmediato firmado. Observamos los operandos y observamos que tenemos un valor inmediato disponible. Lo convertimos a un byte firmado y lo enviamos ( 42
=> 0x2A
).
Por tanto, la instrucción de ensamblado completo es: 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 (el módulo de salida puede necesitar saber si lo hacen).
Repita para cada instrucción. Mantenga un registro de las etiquetas para saber qué insertar cuando se hace referencia a ellas. Agregue funciones para macros y directivas que se pasan a los módulos de salida de su archivo de objeto. Y así es básicamente cómo funciona un ensamblador.