Ir al contenido

Programación en lenguaje ensamblador/Primeros conceptos

De Wikilibros, la colección de libros de texto de contenido libre.

Registros de la CPU

[editar]

La 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

[editar]

La 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

[editar]

Dado 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

[editar]

A 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

[editar]

La ú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

[editar]

Saltos condicionales

[editar]

Son 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

[editar]

La 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.