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.