Programación en lenguaje ensamblador/Primeros conceptos
Registros de la CPU
editarLa CPU x86 tiene 14 registros internos y básicos. Algunos son realmente de 32 bits pero por ahora se utilizará el modo real que es compatible con el procesador 8086 (igualmente accesibles a la parte alta de éstos registros, inclusive en el modo real). Los registros son los siguientes (estos registros son de 16 bits nombrados de la siguiente manera, a excepción del registro de banderas).
Registros de uso general
- AX: Acumulador (AL:AH)
- BX: Registro base (BL:BH)
- CX: Registro contador (CL:CH)
- DX: Registro de datos (DL:DH)
Registros de segmento (Solo se pueden usar para los usos mencionados a excepción de ES)
- DS: Registro del segmento de datos
- ES: Registro del segmento extra
- SS: Registro del segmento de pila
- CS: Registro del segmento de código
Registros punteros (También pueden tener uso general)
- BP: Registro de apuntadores base
- SI: Registro índice fuente
- DI: Registro índice destino
Registros especiales (Solo se pueden usar para los usos mencionados)
- SP: Registro apuntador de la pila
- IP: Registro apuntador de la siguiente instrucción
- F: Registro de banderas (8 bits)
La parte baja del registro AX se llama AL y la parte alta AH. La parte baja del registro BX se llama BL y la parte alta BH, y también ocurre lo mismo con el registro CX y DX.
Bits del registro de banderas
Overflow
- NV (Apagado): No hay desbordamiento
- OV (Encendido): Si lo hay
Direction
- UP: Hacia adelante
- DN: Hacia atras
Interrupts
- DI: Desactivadas
- EI: Activadas
Sign
- PL: Positivo
- NG: Negativo
Zero
- NZ: No es cero
- ZR: Si lo es
Auxilary carry
- NA: No hay acarreo auxiliar
- AC: Hay acarreo auxiliar
Parity
- PO: Impar
- PE: Paridad par
Carry
- NC: No hay acarreo
- CY: Si lo hay
Sintaxis del lenguaje ensamblador
editarLa sintaxis es la siguiente
Nombre de la instrucción Operando 1, Operando 2, Operando 3, Operando 4, ...
El nombre de la instrucción está formada por 2 o 3 letras, los operandos pueden ser registros, constantes o direcciones de memoria. La cantidad de operandos dependerá de la instrucción.
Por ejemplo:
MOV AL, [1000]
Esta instrucción indica que se copie el valor de la porción de la memoria que esté en la ubicación 1000 (En hexadecimal) a la parte baja del registro AX (AL). Cuando un operando es un valor de una dirección de memoria, ésta dirección se escribe entre corchetes, recordar que el operando 1 es el destino y el operando 2 es el origen. Y cuando es una constante dependerá del ensamblador, en el caso del debug (Un programa que sirve para crear y editar aplicaciones que viene con el DOS) se interpretarán como hexadecimales, en los siguientes ejemplos se interpretará que las constantes son números hexadecimales.
También se puede tomar un valor de la memoria apuntado por un registro, por ejemplo:
MOV AL, [DI]
DI está apuntado al valor que está en la memoria que será copiado al registro AL. El nombre MOV viene de la palabra move, que es una palabra del ingles que significa mover. Justamente la instrucción mencionada significa, mover el valor apuntado por DI a AL.
También se puede copiar el valor de un registro a otro
MOV AL, BL
En este caso se copia el valor de BL a AL
Igualmente se puede copiar el valor de la parte baja de un registro a la parte alta de otro registro
MOV CH, DL
Así como también operar con las partes altas
MOV AH, DH
Inclusive se puede copiar el valor de un registro a una dirección de memoria
MOV [1000], AL
Igualmente apuntada la dirección de memoria a DI
MOV [DI], AL
Y también con los registros completos (Solamente completos en el procesador 8086)
MOV AX, DX
También trabajar con los registros completos para todos los procesadores de 32 bits
MOV EBX, EDX
En éste caso mueve la totalidad del registro DX a la totalidad del registro BX, en éste caso se está trabajando con los registros en forma extendida (32 bits), pero hay que tener precaución ya que el procesador 8086 no interpretará correctamente ésta instrucción (El procesador 8086 es obsoleto por ésta desventaja y otras más, por ejemplo sólo puede direccionar 1 MB), además el debug no puede interpretar ésta instrucción.
No se puede realizar lo siguiente porque no se pueden pasar valores en la memoria sin la intervención de un registro, además no se ha especificado el tamaño
MOV [1000], [2000]
Igualmente no se puede hacer lo siguiente
MOV [DI], [1000]
Así como también lo siguiente
MOV [DI], [SI]
Sin embargo lo siguiente es correcto
MOV [1000], AX
Pero no lo siguiente porque no se está especificando el tamaño
MOV [SI], 1F
Lo correcto sería lo siguiente
Si se quiere transferir un byte
MOV byte [SI], 1F
Si se quiere transferir una palabra (16 bits)
MOV word [SI], 1F
Si se quiere transferir una doble palabra (32 bits)
MOV dword [SI], 1F
Direccionamientos
editarDado que es necesario transferir datos, existen los siguientes tipos de direccionamiento:
- Valor de un registro Ejemplo: MOV AX, DX
- Constante Ejemplo: MOV BX, 1AB
- Valor apuntado por una constante Ejemplo: MOV AX, [1000]
- Apuntado por un registro de segmento y uno de offset (Solo puede ser BX, BP, DI o SI) Ejemplo: MOV CX, ES:[DI]
- Apuntado por DS y la suma de BX con una constante o apuntado por SS y la suma de BP con una constante Ejemplo: MOV DX, DS:[BP+1000]
- Apuntado por DS y la suma de DI o SI con una constante Ejemplo: MOV BX, DS:[SI+6F9]
- Apuntado por DS y la suma de BX y SI o BX y DI con una constante o apuntado por SS y la suma de BP y SI o BP y DI con una constante
Ejemplos
MOV AX, DS:[BX][SI]+1FB9
MOV DX, SS:[BP][DI]+C9F8
El primer programa
editarÉste programa sumará 1000 + 2000 y volcará el resultado en AX
- En DOS, en el símbolo de sistema, escribir Cls y luego Debug. Aparecerá un guión (De ésta manera se inicia el debug, con el comando Debug).
- Escribir "A 0100" y presionar la tecla Enter. "A" es una orden del debug que permite ensamblar instrucciones a partir de una dirección (Normalmente se empieza en la dirección 0100h), el Debug generalmente utiliza números hexadecimales.
- Aparecerá lo siguiente ("xxxx" es el segmento en donde está el programa, aparecerá un número en éste lugar, que será el valor de CS).
-A 0100
xxxx:0100
- Escribir las siguientes instrucciones en assembler
MOV AX, 1000
MOV BX, 2000
ADD AX, BX
INT 20
- Presionar la tecla Enter
- Escribir "p 0100" (Con ésta orden se ejecuta el programa y se mostrarán los sucesivos cambios de los registros). Luego aparece "el programa ha terminado de forma normal" o "Program terminated normally".
Luego (Al terminar el programa), antes del mensaje que indica el fin del programa, aparece lo siguiente (Bloque de estado):
AX=3000 BX=2000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=XXXX ES=XXXX SS=XXXX CS=XXXX IP=0108 NV UP EI PL NZ NA PE NC
0CB3:0108 CD20 INT 20
El Debug muestra el estado de los registros antes de ejecutar la instrucción que muestra en el bloque de estado.
El programa hace lo siguiente
La instrucción MOV AX, 1000 ordena que AX tome el valor de 1000.
La instrucción MOV BX, 2000 ordena que BX tome el valor de 2000.
La instrucción ADD AX, BX ordena que AX sea sumado con BX
Por lo tanto es lógico que AX tenga el valor 3000 luego de la instrucción "ADD AX, BX", ya que se le sumó a AX el valor de BX.
La instrucción INT 20 ordena provocar una interrupción que es 20h, que le cede el control a DOS, y DOS termina el programa. Una interrupción es una interrupción de la ejecución de un programa para realizar una determinada tarea, a veces el control no vuelve al programa original (Por ejemplo en el caso de INT 20h en el DOS).
Las interrupciones pueden ejecutarse por las circunstancias más variadas, por ejemplo el usuario presiona una tecla y ésto provoca una interrupción y se ejecuta las instrucciones del controlador de teclado y almacena la tecla presionada, luego le devuelve el control al programa original (Con una instrucción sin operandos llamada IRET).
Cuando ocurre una interrupción se empuja a la pila CS e IP, y éstos 2 registros se modifican para apuntar a la rutina deseada. Los valores de éstos 2 registros están almacenados en la tabla de vectores de interrupción (Ésta está ubicada en el comienzo de la memoria en 0000:0000). Para encontrar éstos 2 valores (Llamado vector de interrupción), se multiplica el número de interrupción por 4 (El offset), y se lee 4 bytes en el offset mencionado y en el segmento 0000. Los primeros 2 bytes (El primer word) del vector de interrupción es el offset (IP), y los 2 restantes (El segundo word) es el segmento (CS).
En el caso de la interrupción 20h fue provocada por el mismo programa, para evitar interrupciones que no sean provocadas por el programa se utiliza la instrucción CLI y para luego activarlas se utiliza STI, pero no se pueden enmascarar las interrupciones menores de 8 ya que no son enmascarables. Hay que tener en cuenta que en la arquitectura x86, el microprocesador almacena en la memoria, desde los bytes menos significativos hasta los más significativos (Si no se tiene en cuenta ésto puede haber misteriosos fallos del programa que se está programando). Por lo tanto en un vector de interrupción, el primer word (Palabra) contiene el valor que se le asignará a IP (Offset), el segundo word contiene el valor que se le asignará a CS (Segmento), cuando se provoque la interrupción del correspondiente vector.
Hola mundo
editarÉste programa mostrará "Hola mundo" en la pantalla, utilizando servicios de DOS.
Se entra al Debug y se escribe lo siguiente (Sin utilizar la orden ensamblar)
E 1000 'Hola mundo$'
"E" significa escribir, se escribe en el segmento de datos (Indicado por DS). La sintaxis de ésta orden del debug es la siguiente:
E Offset '[Cadena de carácteres]'
El carácter "$" que está a la derecha de la frase "Hola mundo", sirve para que el servicio de DOS que se va a utilizar para mostrar Hola mundo en la pantalla no muestre información más alla del carácter que está a la izquierda del carácter '$', es decir éste carácter limitador es el fin de la cadena, es decir a partir de éste no mostrará más caracteres.
Importante: Si no finaliza la cadena con el carácter '$', al llamar al servicio de DOS para mostrar el carácter, probablemente se bloqueará la PC hasta encontrar un carácter '$', inclusive puede ser que nunca lo encuentre.
A continuación ensamblar las siguientes instrucciones (A partir de 0100h):
MOV AH, 09
MOV DX, 1000
INT 21
INT 20
Luego ejecutar el ejemplo con la orden "g"
El programa mostrará el mensaje "Hola mundo", luego el debug mostrará un mensaje diciendo que el programa ha terminado normalmente.
Al provocar la interrupción 21h se llama a un servicio de DOS, el número de servicio está determinado por la parte alta de AX (AH).
El servicio 09h muestra la información en forma de carácteres a partir del comienzo apuntado por el registro DX (El offset, en éste caso 1000h), muestra la información hasta el carácter anterior al carácter '$' (Anteriormente mencionado).
Luego se provoca la interrupción 20h que finaliza el programa.
La orden escribir del debug también acepta bytes individuales, por ejemplo:
E 3000 0, BF, 5F, C4
También acepta en forma mixta (Bytes individuales y cadenas de carácteres)
E 3000 'JRJGOR', 0, 21, 'Ejd', 2F
Es decir la orden ensamblar está acompañada del offset de los datos a escribir y de los items.
Un item puede ser un byte individual o una cadena de carácteres (Que ésta última debe estar entre simples comillas).
Saltos incondicionales y condicionales
editarA veces un programa debe cambiar el flujo del programa en forma incondicional o bajo una condición (Para tomar una decisión), por lo tanto debe haber instrucciones que permitan cambiar el flujo de un programa sin ningún requisito, o en caso de que una condición se cumpla. Existen instrucciones para éste propósito. Son las instrucciones de saltos incondicionales y condicionales, que saltan a un determinado punto si se cumpla la condición.
Saltos incondicionales
editarLa única instrucción que existe para éste fin es JMP (Abreviatura de JUMP, que significa en inglés SALTAR). La sintaxis es la siguiente.
JMP XXXX
XXXX: Es la ubicación de la instrucción en donde se continuará el programa (A partir de ésta se ejecutan las siguientes).
Ejemplo:
XXXX:0100 MOV AX, 1000
XXXX:0103 JMP 0107
XXXX:0105 XOR AX, AX
XXXX:0107 INT 20
En éste caso al ejecutarse la instrucción de salto incondicional (JMP), se continúa la ejecución a partir de la instrucción (INT 20h), no ejecutandose la instrucción XOR (Ésta instrucción realiza la operación XOR de el operando 2 sobre el operando 1) que provocaría el borrado de registro AX (Que provocaría que AX tome el valor 0), si se ejecuta.
Es decir, se ejecutan las siguientes instrucciones:
MOV AX, 1000
JMP 0107
INT 20
No se ejecuta "XOR AX, AX" por el salto incondicional.
El operando puede ser una dirección constante (Por ejemplo 0107), un salto a nivel de offset, también puede ser un salto largo (Que cambie los valores de los registros CS e IP), (Por ejemplo FFFF:0000, que salta al ROM BIOS). También puede ser el valor de un registro, por ejemplo:
JMP DI
En éste caso salta a la instrucción apuntada por DI.
También puede ser un valor apuntado por un registro puntero, por ejemplo:
JMP [SI]
En éste caso salta a la instrucción apuntada por el valor apuntado por SI.
El operando puede ser cualquier direccionamiento válido (Puede ser cualquiera de los direccionamientos utilizados en el operando origen de la instrucción MOV, explicados anteriormente).
Comparaciones
editarSaltos condicionales
editarSon similares a JMP en la sintaxis, pero la diferencia es el nombre.
Las instrucciones son las siguientes:
JE o JZ: Salta si está prendido el bit Zero del registro de banderas.
Objetivo: Saltar si la última comparación realizada da igual.
JA o JNBE: Salta si el bit carry (CF) o el bit zero (ZF) del registro de banderas está desactivado.
Objetivo: Saltar si la última comparación realizada con números naturales da mayor.
JB o JNAE: Salta si CF está activada.
Objetivo: Saltar si la última comparación realizada con números naturales da menor.
JG o JNLE: Salta si ZF es cero o si OF y SF son iguales.
Objetivo: Saltar si la última comparación realizada con números enteros da mayor.
JL o JNGE: Saltar si SF es diferente a OF
Objetivo: Saltar si la última comparación realizada con números enteros da menor.
JC: Saltar si CF está prendida
Objetivo: Saltar si hay acarreo
JO: Saltar si OF está prendido
Objetivo: Saltar si hay desbordamiento
JP: Saltar si PF está prendido
Objetivo: Saltar si hay paridad
JS: Saltar si SF está prendido
Objetivo: Saltar si es negativo
JNE o JNZ: Comportamiento inverso a JE o JZ
JNA o JBE: Comportamiento inverso a JA o JNBE
JNB o JAE: Comportamiento inverso a JB o JNAE
JNG o JLE: Comportamiento inverso a JG o JNLE
JNL o JGE: Comportamiento inverso a JL o JNGE
JNC: Comportamiento inverso a JC
JNO: Comportamiento inverso a JO
JNP o JPO: Comportamiento inverso a JP
JNS: Comportamiento inverso a JS
Hay otras instrucciones que hacen saltos condicionales, pero que no necesitan la instrucción CMP, son las siguientes:
JCXZ: Salta si el registro CX es cero.
LOOP: Decrementa CX, restándole 1 y salta si CX es distinto de cero.
Objetivo: Hacer un bucle, utilizando como contador CX
LOOPE: Decrementa CX en 1 y salta si CX es distinto de cero y ZF está prendido.
Objetivo: Hacer un bucle, utilizando como contador CX y terminar si el contador llega a cero, o se apaga el bit Zero.
LOOPNE: Decrementa CX en 1 y salta si ZF está prendido
Objetivo: Hacer un bucle que siga funcionando hasta que se apague el bit Zero.
La sintaxis de éstas instrucciones son similares a la instrucción JMP, lo único que cambia es el nombre.
Instrucción genérica para comparar
editarLa instrucción es CMP, ésta permite comparar 2 operandos, la sintaxis es la siguiente:
CMP <Operador 1>, <Operador 2>
El operador 1, se compara con el 2.
Los operandos pueden ser cualquier direccionamiento válido, cumpliendo las reglas de la instrucción MOV con respecto a los operandos.
Ésta instrucción compara 2 operandos y luego cambia el registro de banderas en base al resultado de la comparación.