You are on page 1of 43

Programación en assembler

bajo emu8086
Organización de Computadoras
Depto. de Cs. e Ing. de la Computación
Copyright

Copyright © 2010 A. G. Stankevicius


Se asegura la libertad para copiar, distribuir y
modificar este documento de acuerdo a los
términos de la GNU Free Documentation License,
Version 1.2 o cualquiera posterior publicada por la
Free Software Foundation, sin secciones invariantes
ni textos de cubierta delantera o trasera.
Una copia de esta licencia está siempre disponible
en la página http://www.gnu.org/copyleft/fdl.html.
La versión transparente de este documento puede
ser obtenida de la siguiente dirección:
http://cs.uns.edu.ar/~ags/teaching
2
Contenidos

Entorno de programación.
Partes de un programa.
Directivas al compilador.
Arquitectura i8086.
Estructuras de control.
Accediendo al sistema operativo.
Definición de procedimientos.
Dispositivos virtuales.
3
Entorno de programación

El lenguaje assembler nos permite


hacer un uso de la computadora
a muy bajo nivel.
Para mantener a raya la posibilidad de
dañar al hardware y/o al software,
haremos uso del simulador emu8086.
Este simulador no solo recrea al
hardware de la PC sino también parte
del software de la misma (su BIOS,
algunas funciones del SO, etc.).
4
Ciclo de desarrollo bajo
emu8086
Escribir el código fuente.
Podemos hacer uso del editor propio del
entorno o bien editar el código fuente en
cualquier otro editor externo.
Simular la ejecución del programa.
El entorno de programación compila el
código fuente al momento de solicitar su
ejecución.
De no haber errores, el código máquina
resultante es cargado en el CPU simulado
y queda listo para su ejecución.
5
Pasos para depurar un
programa
En la mayoría de los lenguajes de
programación es complicado escribir
código 100% libre de errores.
Peor aun... en assembler, es todavía
más difícil.
Por suerte el propio entorno de
ejecución, hace las veces de un
poderoso entorno de depuración
(debugger).
6
Estructura de un archivo
fuente
Los archivos fuente son esencialmente
archivos de texto.
Están compuestos de diferentes
secciones, dependiendo del tipo
de ejecutable que se desea obtener.
En general trabajaremos en el marco de
la plantilla para obtener .com, por lo que
no va a hacer falta distinguir las distintas
secciones de nuestro código fuente.

7
Directivas al compilador

Las directivas instruyen al compilador,


sin generar código máquina.
name fija el nombre del programa que
estamos escribiendo:
name “holamundo”
include nos permite incorporar código de
otros archivos fuente:
include “mismacros.inc”
equ asocia el resultado de una expresión
constante a una etiqueta:
TOPE equ 25*4
8
Directivas al compilador

Existen dos directivas para reservar


locaciones de memoria:
db, para reservar un byte:
contador db 0
dw, para reservar un word:
resultado dw 0
Si no interesa inicializar su contenido,
se puede hacer uso de un “?”.
Cada locación de memoria puede
tener una etiqueta asociada.
9
Etiquetas

Las etiquetas son básicamente


direcciones de memoria.
Pueden apuntar a locaciones que
contienen datos o bien código.
A la izquierda del dato al cual se asocian:
arreglo db 16 dup(?)
A la izquierda de la instrucción a la cual se
asocian, pero separada por un “:”:
lazo: mov ax, 1234h

10
Arquitectura i8086

La arquitectura i8086 fue introducida


en 1978.
Su éxito dio a lugar a la serie de
procesadores 8086, 8088, 80186,
80286, 80386, 80486, Pentium, etc.

11
Registros disponibles

La arquitectura i8086 tiene apenas 8


registros de 16 bits.
SP y BP: suelen tener un rol específico.
AX, BX, CX, DX, SI, DI: registros de
propósito general.
También cuenta con cuatro registros
auxiliares de segmento:
CS (code segment), DS (data segment),
ES (extra segment) y SS (stack segment).

12
Registros disponibles

Algunos de estos registros permiten a


su vez acceder a los dos campos de 8
bits que lo componen.
Por caso: AX se compone de AH (los 8
bits más significativos) y AL (los 8 bits
menos significativos).
El procesador está en modo real:
A diferencia de los nuevos procesadores,
no brinda protección de memoria.
Los distintos procesos comparten el
mismo espacio de direcciones. 13
Organización de la
memoria
i8086 cuenta con un espacio
de direcciones de 20 bits.
Esto es, ¡¡¡apenas 1 megabyte!!! (menos
que la memoria disponible en un teléfono
celular berreta).
Cómo los registros son de sólo 16 bits,
se apela a la segmentación para poder
acceder a la totalidad de la memoria.
Para obtener la dirección efectiva se debe
hacer uso de un registro de segmento.
14
Organización de la
memoria
El registro de segmento que se debe
usar se determina según el contexto.
Por caso, si BX contiene al valor 1234h
y DS al valor 5678h, la referencia [BX]
denota en realidad a la locación de
memoria 579B4h.
Matemáticamente:
EA = BX + DS * 10h
579B4h = 1234h + 5678h * 10h

15
Instrucción MOV

Sintaxis: mov dest, origen


mov bx, 3 ; guarda un 3 en bx
mov ax, bx ; copia bx en ax
mov ax, CONT ; guarda CONT en ax
Restricciones:
El destino no puede ser constante.
Puede referencia a lo sumo una dirección
de memoria.
Origen y destino deben ser compatibles,
esto de, de igual cantidad de bits.
16
Accediendo a memoria

Los corchetes denotan el modo de


direccionado indirecto:
mov bx, CONT ; guarda CONT en bx
mov cx, [bx] ; guarda el contenido
; de CONT en cx
mov cx, [CONT] ; inst. equivalente
¡No todos los registros pueden ser
utilizados para acceder a la memoria!
mov ax, [bx] ; válido
mov bx, [ax] ; inválido
17
Accediendo a memoria

La arquitectura i8086 sólo permite


acceder a la memoria mediante los
registros BX, SI, DI y BP.
Estos registros se combinan entre sí
para obtener un modo de direccionado
base-indexado.
También es posible incluir un
desplazamiento constante:
bx si
[ ó + ó + offset ]
bp di 18
Accediendo a memoria

Ya que ambos operandos tienen que


ser compatibles, en ocasiones
debemos explicitar el tamaño:
mov word prt [CONT], 1
mov bx, CONT
mov ax, [CONT]
mov byte ptr [bx], FFh
Los modificadores son byte ptr y
word ptr.
19
Instrucciones aritméticas

La arquitectura i8086 cuenta con las


operaciones aritméticas usuales:
add destino, operando
sub destino, operando
inc destino
dec destino
not destino
neg destino
mul operando / imul operando
div divisor / idiv divisor
20
Estructuras de control

Los lenguajes de alto nivel hacen uso


de diversas estructuras de control.
Assembler es más básico, sólo cuenta
con las siguientes facilidades:
La instrucción cmp: para comparar
magnitudes entre sí.
Los flags: para recordar el resultado de la
última comparación u otras operaciones.
Los saltos: para cambiar el flujo de
ejecución de forma condicionada o no.
21
Principales Flags

La arquitectura i8086 cuenta con


diversos flags, los cuales reflejan el
resultado de la última comparación.
Para los enteros no signados se usan los
flags zero (ZF) y carry (CF).
Para los enteros signados se usan los flags
overflow (OF) y sign (SF).
La mayoría de las operaciones
aritméticas también afectan los flags.

22
Instrucción CMP

Sintáxis: cmp primero, segundo


Computa primero – segundo y luego
modifica los flags de manera acorde:
Si resultado = 0 (primero == segundo):
ZF = 1; CF = 0
Si resultado > 0 (primero > segundo):
ZF = 0; CF = 0
SF = OF
Si resultado < 0 (primero < segundo):
ZF = 0; CF = 1
SF != OF
23
Instrucción JMP

Sintaxis: jmp dest / jmp short dest


jmp infinite-loop
jmp short label-cercano
Los saltos incondicionales siempre
transfieren el control a una cierta
dirección de memoria sin tener en
cuenta el estado de los flags.
La instrucción a continuación de un
jmp jamás se ejecuta...
...salvo que sea el destino de un salto.
24
Saltos condicionales

En los saltos condicionales no siempre


se produce la transferencia de control.
El salto se realiza o no dependiendo
del estado de uno o más flags.
Los saltos más sencillos dependen
de sólo un flag (no son frecuentes).
Los saltos más complejos dependen
de múltiples flags a la vez (son más
frecuentes).
25
Saltos condicionales

Saltos simples:
jz dest / jnz dest: depende de ZF.
jo dest / jno dest: depende de OF.
js dest / jns dest: depende de SF.
jc dest / jnc dest: depende de CF.
jp dest / jnp dest: depende de PF.
Si el flag en cuestión está activo se
produce el salto.
Caso contrario, la ejecución continúa
en la instrucción que sigue al salto. 26
Saltos condicionales

Saltos condicionales para magnitudes


signadas (suponiendo que se acaba
de ejecutar cmp ax, bx):
je dest: salta si ax == bx.
jne dest: salta si ax != bx.
jl dest / jnge dest: salta si ax < bx.
jle dest / jng dest: salta si ax <= bx.
jg dest / jnle dest: salta si ax > bx.
jge dest / jnl dest: salta si ax >= bx.

27
Saltos condicionales

Saltos condicionales para magnitudes


no signadas (suponiendo que se acaba
de ejecutar cmp ax, bx):
je dest: salta si ax == bx.
jne dest: salta si ax != bx.
jb dest / jnae dest: salta si ax < bx.
jbe dest / jna dest: salta si ax <= bx.
ja dest / jnbe dest: salta si ax > bx.
jae dest / jnb dest: salta si ax >= bx.

28
IF-THEN-ELSE

La estructura de control condicional se


codifica fácilmente:
; if (a > 15) { b = 32; }
; else { a = a + 1; }
mov ax, [a]
cmp ax, 15 ; comparo a con 15
jng else ; ir a “else” si <=
mov word ptr [b], 32 ; brazo “then”
jmp seguir ;
else: inc ax ; brazo “else”
seguir: … ;resto del programa
29
Estructuras de control

Las restantes estructuras de control


también se pueden codificar de una
manera similar.
Ejercicios para ir pensando:
¿Cómo se codifica una secuencia?
¿Cómo se codifica una repetición estilo
while o repeat-until? ¿Y una estilo for?
En este punto, estamos en condiciones
de comenzar a resolver el Práctico 8.

30
Nuestro primer ¡hola
mundo!
Para poder encarar nuestro primer
“hola mundo” en assembler, sólo resta
resolver un aspecto: la entrada/salida.
El sistema operativo es el encargado
de gestionar ese aspecto.
El entorno de programación también
emula una parte de un antiguo
sistema operativo llamado DOS.
También se emula una parte del
propio BIOS de la máquina.
31
Llamadas al sistema

La invocación a los servicios provistos


por el sistema operativo se realiza
mediante las interrupciones.
Por caso, el DOS brinda muchos de sus
servicios mediante la interrupción 21h.
El servicio solicitado se suele
especificar mediante alguno de
los registros de la máquina.
Bajo DOS, el valor del registro AH al
momento de la interrupción indica
el servicio solicitado. 32
Llamadas al sistema

Análogamente se pueden acceder a


los servicios provistos por el BIOS.
El propio DOS implementa el conjunto
de servicios que brinda apelando a los
servicios provistos por el BIOS.
El listado preciso de los servicios
emulados por el entorno se encuentra
disponible en la ayuda del mismo.

33
Ejemplo concreto

Por ejemplo, para mostrar una cadena


de texto por pantalla podemos usar el
servicio 09h de la interrupción 21h:
Cargar el número de servicio solicitado en
el registro AH.
Cargar el offset del mensaje que se quiere
mostrar por pantalla en el registro DX.
Invocar a la interrupción correspondiente.
Cabe acotar que DOS asume que toda
cadenas de texto termina con un “$”.
34
Nuestro primer programa

org 100h
jmp comienzo ; ¿por qué hará falta?
texto db 'Hola mundo!',10, 13, '$'
comienzo:
mov ah, 09h
mov dx, offset texto
int 21h ; el texto aparecerá en
; donde esté el cursor
int 20h ; indicamos al DOS que
; la ejecución ha finalizado
35
Invocación a
procedimientos
La invocación a un procedimiento no
difiere en gran medida de los saltos
convencionales:
Se adopta la convención de dejar en el
tope la pila la dirección a la cuál se
debe retornar.
Las instrucciones call y ret permiten
simplificar la invocación a los
procedimientos y el posterior retorno.
36
Invocación a
procedimientos
Existen dos alternativas a la hora de
evitar que se pierda información al
invocar a un procedimiento:
Quien invoca puede encargarse de
almacenar los registros que no desea se
vean afectados por el procedimiento.
O bien, quien es invocado puede ocuparse
de guardar sólo los registros que serán
usados por el procedimiento.
Ambas soluciones tienen ventajas y
desventajas... ¿cuáles son? 37
Ejemplo de “quien invoca
guarda”
mov ax, [unvalor]
mov bx, [otrovalor]
push bx ; alcena bx
call triplicar ; procedimiento para
; triplicar ax
pop bx ; restaura bx
...
triplicar:
mov cx, 3
mul cx ; multiplica por 3
ret ; retorna
38
Ejemplo de “quien es
invocado guarda”
mov ax, [unvalor]
mov bx, [otrovalor]
call triplicar ; procedimiento para
... ; triplicar ax
triplicar:
push cx ; almacena cx
push dx ; almacena dx
mov cx, 3
mul cx ; multiplica por 3
pop dx ; recupera dx
pop cx ; recupera cx
ret ; retorna 39
Definición de un
procedimiento
A fin de mejorar la legibilidad del
código fuente se puede hacer uso
de las directivas proc y endp.
Por caso, la rutina del ejemplo anterior
puede ser reescrita como el siguiente
procedimiento:
triplicar proc
... ; cuerpo del procedimiento
ret
triplicar endp
40
Macros predefinidas

El entorno de programación brinda un


conjunto de macros predefinidas para
simplificar la gestión de la interacción
con el usuario.
Consultar el documento “emu8086.inc”,
presente en la carpeta “inc”.
Es posible definir nuestras propias
macro mediante las directivas al
compilador macro y endm.
La sintáxis es análoga a proc y endp.
41
Dispositivos virtuales

El entorno de programación emu8086


brinda acceso al programador a un
conjunto de dispositivos virtuales.
printer (simula una impresora).
display (simula a un display de siete
segmentos de varios dígitos).
thermostate (simula a un termómetro
calentado por un mechero bunsen)
stepper_motor (simula a un motor
eléctrico paso a paso)
42
Dispositivos virtuales

La comunicación con estos dispositivos


es directa, sin pasar por el SO.
Un determinado puerto de lectura y/o
de escritura permite acceder a sus
principales funciones.
Por ejemplo, el dispositivo display se
controla enviando palabras al puerto 199.
mov ax, 1234
out 199, ax

43

You might also like