Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 1 INTRODUCCIÓN El documento que está visualizando tiene la función primordial de introducirlo a la programación en lenguaje Ensamblador. Su contenido se enfoca completamente hacia las computadoras que operan con procesadores de la familia x86 de Intel y, considerando que el ensamblador basa su funcionamiento en los recursos internos del procesador, los ejemplos descriptos no son compatibles con ninguna otra arquitectura. GENERALIDADES Unidades de información Para que la PC pueda procesar la información es necesario que ésta se encuentre en celdas especiales llamadas registros. Los registros son conjuntos de 8 o 16 flip-flops (basculadores o biestables). Un flip-flop es un dispositivo capaz de almacenar dos niveles de voltaje, uno bajo, regularmente, alrededor, de 0.5 volts y otro alto comúnmente de 5 volts. El nivel bajo de energía en el flip-flop se interpreta como apagado o 0, y el nivel alto como prendido o 1. A estos estados se les conoce usualmente como bits, que son la unidad más pequeña de información en una computadora. A un grupo de 16 bits se le conoce como palabra, una palabra puede ser dividida en grupos de 8 bits llamados bytes, y a los grupos de 4 bits les llamamos nibbles. Sistemas numéricos El sistema numérico que utilizamos a diario es el sistema decimal, pero este sistema no es conveniente para las máquinas debido a que la información se maneja codificada en forma de bits prendidos o apagados; esta forma de codificación nos lleva a la necesidad de conocer el cálculo posicional que nos permita expresar un número en cualquier base que lo necesitemos. Proceso de creación de un programa Para la creación de un programa es necesario seguir cinco pasos: ♦ Diseño del algoritmo. ♦ Codificación del mismo. ♦ Su traducción a lenguaje máquina. ♦ La prueba del programa. ♦ La depuración del programa. En la etapa de diseño se plantea el problema a resolver y se propone la mejor solución, creando diagramas esquemáticos utilizados para el mejor planteamiento de la solución. La codificación del programa consiste en escribir el programa en algún lenguaje de programación; en este caso específico en ensamblador, tomando como base la solución propuesta en el paso anterior. La traducción al lenguaje máquina es la creación del programa objeto, esto es, el programa escrito como una secuencia de ceros y unos que pueda ser interpretado por el procesador. La prueba del programa consiste en verificar que el programa funcione sin errores, o sea, que haga lo que tiene que hacer. La última etapa es la eliminación de las fallas detectadas en el programa durante la fase de prueba. La corrección de una falla normalmente requiere la repetición de los pasos comenzando desde el primero o el segundo. Para crear un programa en ensamblador existen dos opciones, la primera es utilizar algún software específico, por ejemplo el MASM (Macro Assembler, de Microsoft), y la segunda es utilizar el debugger del DOS; en esta primera sección utilizaremos este último ya que se encuentra en cualquier PC con el sistema operativo MS-DOS, lo cual lo pone al alcance de cualquier usuario que tenga acceso a una máquina con estas características. Debug solo puede crear archivos con extensión .COM, y por las características de este tipo de programas no pueden ser mayores de 64 Kb, además deben comenzar en el desplazamiento, offset, o dirección de memoria 0100H dentro del segmento específico. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 2 Ing. Sánchez Rivero Componentes Básicos de un Sistema MS-DOS Fig. 1: Componentes de un sistema DOS. Las operaciones de un sistema de computación incluyendo un IBM PC’s y compatibles están basadas en un concepto simple. Ellas guardan instrucciones y datos en la memoria y usan la CPU para repetir instrucciones y datos recibidos desde la memoria y ejecutan las instrucciones para manipular los datos (Computadoras basadas en la Arquitectura de Von Newmann), por lo tanto la CPU y la memoria son los dos componentes básicos de cualquier sistema de computación. La memoria se encuentra definida en dos tipos: Random Access Memory (RAM) la que permite la escritura y la lectura de cualquier localidad de memoria y la Read Only Memory (ROM), que es la que contiene valores que pueden ser leídos pero no alterados. La ROM es usada para almacenar pequeños primitivos programas para ejecutar instrucciones de entrada y salida y control de periféricos. La RAM es usada por el Sistema Operativo y los programas para los usuarios. El Sistema Operativo es un componente fundamental en un sistema. Este programa de computadoras se toma la tarea de cargar otros programas y ejecutarlos, provee acceso a los archivos del sistema, maneja la E/S, y hace interfaces interactivas con el usuario. El sistema operativo es el que provee al sistema su personalidad. MS-DOS, OS/2, UNIX son ejemplo de algunos Sistema Operativos para PC, similarmente CP/M es un Sistema Operativos para antiguos microprocesadores de INTEL de 8 Bits como el 8080. El hardware de toda computadora, incluyendo las computadoras que usan el MS-DOS, está interconectado. La CPU, memoria, y periféricos de entrada (teclado, escáner, lápiz óptico, lector de código de barras, micrófono, mouse, etc.) y periféricos de salida (monitor, impresora, parlantes, etc.) están todos interconectados por una serie de cables llamados Buses y cada bus esta claramente definido. Un Bus es un hardware que especifica una señal y tiempo estándar que son seguidos y entendidos por la CPU y su circuito de soporte (incluyendo periféricos aún no instalados). Los buses a su vez se clasifican en Bus de Datos, Bus de Direcciones y Bus de Control. ARQUITECTURA INTERNA DEL INTEL 80x86 Fue el primer microprocesador de 16 bits que INTEL fabricó a principios del año 1978. Los objetivos de la arquitectura de dicho procesador fueron los de ampliar la capacidad del INTEL 80x80 de forma simétrica, añadiendo una potencia de proceso no disponible en los micros de 8 bits. Algunas de estas características son: aritmética en 16 bits, multiplicación y división con o sin signo, manipulación de cadena de caracteres y operación sobre bits. También se han realizado mecanismos de software para la construcción de códigos reentrante y reubicable. Su estructura interna esta representada por la Fig. 2. Consta de 2 unidades claramente diferenciadas denominadas EU (Unidad de Ejecución) y BIU (Interfaces del Bus) La EU ejecuta las operaciones requeridas por las instrucciones sobre una UAL de 16 bits. No tiene conexión con el exterior y solamente se comunica con la BIU que es la parte que realiza todas las operaciones en el bus solicitadas por la EU. Un mecanismo, tal vez único dentro de los microprocesadores aunque muy empleado dentro de los mínimos y grandes computadores, es el denominado de búsqueda anticipada de instrucciones (prefetch). En el INTEL 8086 existe una estructura FIFO en RAM de 6 octetos de capacidad que es llenada por la BIU con los contenidos de las instrucciones siguientes a la que la EU está ejecutando en ese momento. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez RiveroPágina 3 Fig. 2: Las unidades EU y BIU El 8086 representa la arquitectura base para todos los microprocesadores de 16 bits de Intel: 8088, 8086, 80188, 80186 y 80286. Aunque han aparecido nuevas características a medida que estos microprocesadores han ido evolucionando; todos los procesadores Intel, usados en la actualidad en los PC’s y compatibles son miembros de la familia 8086. El conjunto de instrucciones, registros y otras características son similares, a excepción de algunos detalles. Toda la familia 80x86 en adelante posee dos características, en común: a) Arquitectura Segmentada: esto significa que la memoria es divida en segmentos con un tamaño máximo de 64k (información importante para el direccionamiento de la memoria en la futura programación segmentada en el lenguaje ensamblador). b) Compatibilidad: las instrucciones y registros de las anteriores versiones son soportados por las nuevas versiones, y estas versiones son soportadas por versiones anteriores. La familia de microprocesadores 80x86 consta de los siguientes microprocesadores: ♦ El 8088 es un microprocesador de 16 bits, usado en las primeras PC´S (XT compatibles). Soporta solamente el modo real. Es capaz de direccionar un megabyte de memoria y posee un bus de datos de 8 bits. ♦ El 8086 es Similar al 8088, con la excepción de que el bus de datos es de 16 bits. ♦ El 80188 es similar al 8088, pero con un conjunto de instrucciones extendido y ciertas mejoras en la velocidad de ejecución. Se incorporan dentro del microprocesador algunos chips que anteriormente eran externos, consiguiéndose unas mejoras en el rendimiento del mismo. ♦ El 80186 es igual al 80188 pero con un bus de datos de 16 bits. ♦ El 80286 Incluye un conjunto de instrucciones extendidos del 80186, pero además soporta memoria virtual, modo protegido y multitarea. ♦ El 80386 soporta procesamientos de 16 y 32 bits. El 80386 es capaz de manejar memoria real y protegida, memoria virtual y multitarea. Es más rápido que el 80286 y contiene un conjunto de instrucciones ampliables. ♦ El 80386SX es similar al 80386 por un bus de datos de solo 16 bits. ♦ El 80486 incorpora un caché interno de 8 kb y ciertas mejoras de velocidad con respecto al 80386. Incluye un coprocesador matemático dentro del mismo chip. ♦ El 80486SX es similar a los 80486 con la diferencia que no posee coprocesador matemático. ♦ El 80486DX2 es similar al 80486, pero con la diferencia de que internamente, trabaja al doble de la frecuencia externa del reloj. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 4 Ing. Sánchez Rivero Modelo Bus de Datos Coprocesador matemático 80386DX 32 Si 80386SL 16 No 80386SX 16 No 80486SX 32 No 80486DX 32 Si El 80x86 tiene dos procesadores en el mismo chip. Estos son La Unidad de Ejecución y La Unidad de Interface con los Buses, como ya se mencionó antes. Cada uno de ellos contiene su propio registro, su propia sección aritmética, sus propias unidades de control y trabajan de manera asincrónica el uno con el otro para proveer la potencia total de cómputo. La Unidad de Interface de Bus se encarga de buscar las instrucciones para adelantar su ejecución y proporciona facilidades en el manejo de las direcciones. Luego, la Unidad de Interface se responsabiliza del control de la adaptación con los elementos externos del CPU central. Dicha Unidad de Interface proporciona una dirección de 20 Bits o un dato de 16 para la unidad de memoria o para la unidad de E/S en la estructura externa del computador. Terminales del microprocesador 8086 El microprocesador 8086 puede trabajar en dos modos diferentes: el modo mínimo y el modo máximo. En el modo máximo el microprocesador puede trabajar en forma conjunta con un microprocesador de datos numérico 8087 y algunos otros circuitos periféricos. En el modo mínimo el microprocesador trabaja de forma más autónoma al no depender de circuitos auxiliares, pero esto a su vez le resta flexibilidad. En cualquiera de los dos modos, las terminales del microprocesador se pueden agrupar de la siguiente forma: • Alimentación • Reloj • Control y estado • Direcciones • Datos El 8086 cuenta con tres terminales de alimentación: tierra (GND) en las terminales 1 y 20 y Vcc = 5V en la terminal 40. En la terminal 19 se conecta la señal de reloj, la cual debe provenir de un generador de reloj externo al microprocesador. El 8086 cuenta con 20 líneas de direcciones (al igual que el 8088). Estas líneas son llamadas A0 a A19 y proporcionan un rango de direccionamiento de 1MB. Para los datos, el 8086 comparte las 16 líneas más bajas de sus líneas de direcciones, las cuales son llamadas AD0 a AD15. Esto se logra gracias a un canal de datos y direcciones multiplexado. En cuanto a las señales de control y estado tenemos las siguientes: La terminal MX/MN controla el cambio de modo del microprocesador. Las señales S0 a S7 son señales de estado, éstas indican diferentes situaciones acerca del estado del microprocesador. La señal RD en la terminal 32 indica una operación de lectura. En la terminal 22 se encuentra la señal READY. Esta señal es utilizada por los diferentes dispositivos de E/S para indicarle al microprocesador si se encuentran listos para una transferencia. La señal RESET en la terminal 21 es utilizada para reinicializar el microprocesador. La señal NMI en la terminal 17 es una señal de interrupción no enmascarable, lo cual significa que no puede ser manipulada por medio de software. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 5 La señal INTR en la terminal 18 es también una señal de interrupción, la diferencia radica en que esta señal si puede ser controlada por software. Las interrupciones se estudian más adelante. La terminal TEST se utiliza para sincronizar al 8086 con otros microprocesadores en una configuración en paralelo. Las terminales RQ/GT y LOCK se utilizan para controlar el trabajo en paralelo de dos o más microprocesadores. La señal WR es utilizada por el microprocesador cuando éste requiere realizar alguna operación de escritura con la memoria o los dispositivos de E/S. Las señales HOLD y HLDA son utilizadas para controlar el acceso al bus del sistema. Unidad aritmético-lógica Conocida también como ALU, este componente del microprocesador es el que realmente realiza las operaciones aritméticas (suma, resta, multiplicación y división) y lógicas (and, or, xor, etc.) que se obtienen como instrucciones de los programas. Buses internos (datos y direcciones) Los buses internos son un conjunto de líneas paralelas (conductores) que interconectan las diferentes partes del microprocesador. Existen dos tipos principales: el bus de datos y el bus de direcciones. El bus de datos es el encargado de transportar los datos entre las distintas partes del microprocesador; por otro lado, el bus de direcciones se encarga de transportar las direcciones para que los datos puedan ser introducidos o extraídos de la memoria o dispositivos de entrada y salida. Cola de instrucciones La cola de instrucciones es una pila de tipo FIFO (primero en entrar, primero en salir) donde las instrucciones son almacenadas antes de que la unidad de ejecución las ejecute. TIPOS DE PROGRAMAS EJECUTABLES ESTRUCTURA DEL PROGRAMA CON EXTENSIÓN .COM Un programa con extensión .COM está almacenado en un archivo que contiene una copia fiel del código aser ejecutado. Ya que no contienen información para la reasignación de localidades, son más compactos y son cargados más rápidamente que sus equivalentes EXE. El MS-DOS no tiene manera de saber si un archivo con extensión .COM es un programa ejecutable válido. Este simplemente lo carga en memoria y le transfiere el control. Debido al hecho de que los programas COM son siempre cargados inmediatamente después del PSP y no contienen encabezado que especifique el punto de entrada al mismo, siempre debe comenzar en la dirección 0100h. Esta dirección deberá contener la primera instrucción ejecutable. La longitud máxima de un programa COM es de 65536 bytes, menos la longitud de PSP (256 bytes) y la longitud de la pila (mínimo 2 bytes). Cuando el sistema operativo transfiere el control a un programa COM, todos los registros de segmento apuntan al PSP. El registro apuntador de pila (SP) contiene el valor en la memoria de OFFFEh si la memoria lo permite. En otro caso adopta el mínimo valor posible menos dos bytes (el MS-DOS introduce un cero en la pila antes de transferir el control al programa). Aún cuando la longitud de un programa COM no puede exceder de los 64 kb; las versiones actuales del MS-DOS reservan toda la memoria disponible. Si un programa .COM debe ejecutar otro proceso, es necesario que el mismo libere la memoria no usada de tal manera que pueda ser empleada por otra aplicación. Cuando un programa .COM termina; puede retornar al control del sistema operativo por varios medios. El método preferido es el uso de la función 4Ch de la Int 21h, la cual permite que el programa devuelva un código de retorno al proceso que invocó. Sin embargo, si el programa está ejecutándose bajo la versión 1.00 del MS.DOS, el control debe ser retornado mediante el uso de la Int 20h. Un programa .COM puede ser ensamblado a partir de varios módulos objeto, con la condición de todos ellos empleen los mismos nombres y clases de segmentos y asegurando que el módulo inicial, con el punto de entrada en 0100h sea enlazado primero. Adicionalmente todos los procedimientos y funciones deben tener el atributo NEAR, ya que todo el código ejecutable estará dentro del mismo segmento. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 6 Ing. Sánchez Rivero Al enlazar un programa .COM, el enlazador mostrará el siguiente mensaje; "Warning: no stack segment". Este mensaje puede ser ignorado, ya que el mismo se debe a que se ha instruido al enlazador para que genere un programa con extensión .EXE donde el segmento de pila debe ser indicado de manera explícita, y no así en los .COM donde ésta es asumida por defecto. En la zona desde 000Ah hasta 0015h dentro del PSP se encuentran las direcciones de las rutinas manejadoras de los eventos Ctrl-C y Error crítico. Si el programa de aplicación altera estos valores para sus propios propósitos, el MS-DOS los restaura al finalizar la ejecución del mismo. ESTRUCTURA DEL PREFIJO DE PROGRAMA (PSP) 0000h INT 20 0002h Segmento final del bloque de asignación 0004h Reservado 0005h Invocación FAR a la función despachadora del MS-DOS 000Ah Vector de interrupción de terminación (Int22h) 000Eh Vector de interrupción Ctrl-C (Int23h) 0012h Vector de interrupción de error crítico (Int24h) 0016h Reservado 002Ch Segmento de bloque de variables de ambiente 002Eh 005Ch Bloque de control de archivo por defecto (#1) 006Ch Bloque de control de archivo por defecto (#2) 0080h Líneas de comandos y área de transferencia de disco 00FFh Final del PSP La palabra de datos en desplazamiento 002Ch contiene la dirección del segmento de bloque de variables de ambiente (Environment block), el cual contiene una serie de cadenas ASCII. Este bloque es heredado del proceso que causó la ejecución del programa de aplicación. Entre la información que contiene tenemos el paso usado por el COMAND.COM para encontrar el archivo ejecutable, el lugar del disco donde se encuentra el propio COMAND.COM y el formato del prompt empleado por éste. La cola de comandos, la cual está constituida por los caracteres restantes en la línea de comandos, después del nombre del programa, es copiada a partir de la localidad 0081h en el PSP. La longitud de la cola, sin incluir el carácter de retorno al final, está ubicada en la posición 0080h. Los parámetros relacionados con redireccionamiento o piping no aparecen en esta posición de la línea de comandos, ya que estos procesos son transparentes a los programas de aplicación. Para proporcionar compatibilidad con CP/M, el MS-DOS coloca los dos primeros comandos en la cola, dentro de los bloques de control del archivo (FCB) por defecto en las direcciones PSP:005Ch y PSP:006Ch asumiendo que pueden ser nombres de archivos. Sin embargo, si alguno de estos comandos son nombres de archivos que incluyen especificaciones del paso, la información colocada en los FCB no será de utilidad ya que estas estructuras no soportan el manejo de estructuras jerárquicas de archivos y subdirectorios. Los FCB son de muy escaso uso en los programas de aplicación modernos. El área de 128 bytes ubicado entre las direcciones 0080h y 00FFh en el PSP puede también servir como área de transferencia de disco por defecto (DTA), la cual es establecida por el MS-DOS antes de transferir el control al programa de aplicación. A menos que el programa establezca de manera explícita otra DTA, este será usado como buffer de datos para cualquier intercambio con disco que este efectué. Los programas de aplicación no deben alterar la información contenida en el PSP a partir de la dirección 005Ch. ESTRUCTURA DE UN PROGRAMA DE EXTENSIÓN .EXE Los programas .EXE son ilimitados en tamaño (el límite lo dictamina la memoria disponible del equipo). Además, los programas .EXE pueden colocar el código, los datos y la pila en distintos segmentos de la memoria. La oportunidad de colocar las diversas partes de un LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 7 programa en fragmentos diferentes de memoria y la de establecer segmentos de memoria con solamente códigos que pudieran ser compartidos por varias tareas, es muy significativo para ambientes multitareas tales como el Microsoft Windows. El cargador del MS-DOS sitúa al programa .EXE, inmediatamente después del PSP, aunque el orden de los segmentos que lo constituyen pueden variar. El archivo .EXE contiene un encabezado, bloque de información de control, con un formato característico. El tamaño de dicho encabezado puede variar dependiendo del número de instrucciones que deben ser localizadas al momento de carga del programa, pero siempre será múltiplo de 512. Antes de que el MS-DOS transfiera el control al programa, se calculan los valores iniciales del registro del segmento de código (CS) y el apuntador de instrucciones (IP) basados en la información sobre el punto de entrada al programa, contenida en el encabezado del archivo .EXE. Esta información es generada a partir de la instrucción END en el módulo principal del programa fuente. Los registros de segmentos de datos y segmentos extras son inicializados de manera que apunten al PSP de modo que el programa pueda tener acceso a la información contenida. IMAGEN DE MEMORIA DE UN PROGRAMA .EXE TÍPICO SS:SP Segmento de Pila SS:0000h Datos del Programa CS:0000h Código del Programa DS:0000h Prefijo del segmento del Programa ES: 0000h FORMATO DE UN ARCHIVO DE CARGA EXE 0000h Primera partedel identificador del archivo EXE (4Dh) 0001h Segunda parte del identificador de archivo EXE (5Ah) 0002h Longitud del archivo MOD 512 0004h Tamaño del archivo, en páginas de 512 bytes, incluyendo encabezado 0008h Número de ítems en la tabla de relocalizaciones 000Ah Tamaño del encabezado en párrafos (16 bytes) 000Ch Número mínimo de párrafos requeridos para el programa 000Eh Máximo número de párrafos deseables para el programa 0010h Desplazamiento del segmento del módulo de pila 0012h Suma de chequeo 0016h Contenido del apuntador de instrucciones al comenzar el programa 0018h Desplazamiento del segmento del módulo de código 001Ah Desplazamiento del primer ítem en la tabla de relocalizaciones 001Bh Número de overplay (0 para la parte residente del programa) Tabla de relocalizaciones Espacio reservado (longitud variable) Segmento de programas y datos Segmento de pila El contenido inicial del segmento de pila y del apuntador de pila proviene también del encabezado del archivo. Esta información es derivada de la declaración del segmento de pila efectuada mediante la sentencia STACK. El espacio reservado para la pila puede ser inicializado o no dependiendo de la manera como este haya sido declarado. Puede ser conveniente en muchos casos inicializar el segmento de pila con un patrón de caracteres predeterminados que permitan su posterior inspección. Cuando el programa .EXE finaliza su ejecución debe retornar el control al sistema operativo mediante la función 4Ch de la Int 21h. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 8 Ing. Sánchez Rivero Existen otros métodos, pero no ofrecen ninguna otra ventaja y son considerablemente menos convenientes "Generalmente requieren que el registro CS apunte al segmento de PSP". Un programa .EXE puede ser construido a partir de varios módulos independientes. Cada módulo puede tener nombres diferentes para el segmento de código y los procedimientos pueden llevar el atributo NEAR o FAR, dependiendo del tamaño del programa ejecutable. El programador debe asegurarse de que los módulos a ser enlazados solo tengan una declaración de segmento de pila y que haya sido definido un único punto de entrada (por medio de la directiva END). La salida del enlazador es un archivo con extensión .EXE el cual puede ser ejecutado inmediatamente. REGISTROS DE LA CPU Los registros del procesador se emplean para controlar instrucciones en ejecución, manejar direccionamiento de memoria y proporcionar capacidad aritmética. Los registros son espacios físicos dentro del microprocesador con capacidad de 4 bits hasta 64 bits dependiendo del microprocesador que se emplee. Los registros son direccionables por medio de una viñeta, que es una dirección de memoria. Los bits, por conveniencia, se numeran de derecha a izquierda (15,14,13…. 3,2,1,0), los registros están divididos en seis grupos los cuales tienen un fin especifico. Los registros se dividen en: • Registros de segmento. • Registro apuntador de instrucciones. • Registros apuntadores. • Registros de propósitos generales. • Registros índices. • Registro de banderas. REGISTROS DE SEGMENTO Un registro de segmento se utiliza para alinear en un limite de párrafo o dicho de otra forma codifica la dirección de inicio de cada segmento y su dirección en un registro de segmento supone cuatro bits 0 a su derecha. Un registro de segmento tiene 16 bits de longitud y facilita un área de memoria para direccionamientos conocidos como el segmento actual. Los registros de segmento son: • Registro CS • Registro DS • Registro SS • Registro ES • Registro FS y GS Registro CS El DOS almacena la dirección inicial del segmento de código de un programa en el registro CS. Esta dirección de segmento, más un valor de desplazamiento en el registro apuntador de instrucciones (IP), indica la dirección de una instrucción que es buscada para su ejecución. Para propósitos de programación normal, no se necesita referenciar el registro CS. Registro DS La dirección inicial de un segmento de datos de un programa es almacenada en el registro DS. En términos sencillos, esta dirección más un valor de desplazamiento en una instrucción, genera una referencia a la localidad de un byte especifico en el segmento de datos. Registro SS El registro SS permite la colocación en memoria de una pila, para almacenamiento temporal de direcciones y datos. El DOS almacena la dirección de inicio del segmento de pila de un programa en el registro SS. Esta dirección de segmento, más un valor de desplazamiento en el registro apuntador de la pila (SP), indica la palabra actual en la pila que está siendo direccionada. Para propósitos de programación normal, no se necesita referenciar el registro SS. Registro ES Algunas operaciones con cadenas de caracteres (datos de caracteres) utilizan el registro de segmento extra para manejar el direccionamiento de memoria. En este contexto, el registro LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 9 ES esta asociado con el registro DI (índice). Un programa que requiere el uso del registro ES puede inicializarlo con una dirección apropiada. Registros FS y GS Son registros extra de segmento en los procesadores 80386 y posteriores a estos procesadores. Registro Apuntador de instrucciones (IP) El registro apuntador de instrucciones (IP) de 16 bits contiene el desplazamiento de dirección de la siguiente instrucción que se ejecuta. El registro IP esta asociado con el registro CS en el sentido de que el IP indica la instrucción actual dentro del segmento de código que se está ejecutando actualmente. En el ejemplo siguiente, el registro CS contiene 25A4[0]H y el IP contiene 412H. Para encontrar la siguiente instrucción que será ejecutada el procesador combina las direcciones en el CS y el IP así: Segmento de dirección en el registro CS: 25A40H Desplazamiento de dirección en el registro IP: + 412H Dirección de la siguiente instrucción: 25E52H Registros apuntadores Los registros apuntadores están asociados con el registro SS y permiten al procesador accesar datos en el segmento de pila; los registros apuntadores son dos: • El registro SP • El registro BP Registro SP El apuntador de pila SP de 16 bits está asociado con el registro SS y proporciona un valor de desplazamiento que se refiere a la palabra actual que está siendo procesada en la pila. El ejemplo siguiente el registro SS contiene la dirección de segmento 27B3[0]H y el SP el desplazamiento 312H Para encontrar la palabra actual que esta siendo procesada en la pila el microprocesador combina las direcciones en el SS y el SP: Dirección de segmento en el registro SS: 27B30H Desplazamiento en el registro SP: + 312H Dirección en la Pila: 27E42H ……. 27B3[0]H 312H Dirección del segmento SS Desplazamiento del SP Registro BP El registro BP de 16 bits facilita la referencia de parámetros, los cuales son datos y direcciones transmitidos vía la pila. Registros de propósitos generales. Los registros de propósitos generales AX, BX, CX y DX son los caballos de batalla o las herramientas del sistema. Son únicos en el sentido de que se puede direccionarlos como una palabra o como una parte de un byte. Elbyte de la izquierda es la parte "alta", y el byte de la derecha es la parte "baja". Por ejemplo, el registro CX consta de una parte CH (alta) y una parte CL (baja), y usted puede referirse a cualquier parte por su nombre. Las instrucciones siguientes mueven ceros a los registros CX, CH y CL respectivamente: Mov CX, 00 Mov CH, 00 Mov CL, 00 Los procesadores 80386 y posteriores permiten el uso de todos estos registros de propósito general, más las versiones de 32 bits: EAX, EBX, ECX y EDX. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 10 Ing. Sánchez Rivero Registros AX El registro AX, el acumulador principal, es utilizado para operaciones que implican entrada/salida y la mayor parte de la aritmética. Por ejemplo, las instrucciones para multiplicar, dividir y traducir suponen el uso del AX. También, algunas operaciones generan código más eficientes si se refiere al AX en lugar de los otros registros. Registro BX El BX es conocido como el registro base ya que es el único registro de propósito general que puede ser índice para el direccionamiento indexado. También es común emplear al BX para cálculos. Registro CX El CX es conocido como el registro contador. Puede contener un valor para controlar el número de veces que un ciclo se repite o un valor para corrimiento de bits, hacia la derecha o hacia la izquierda. El CX también es usado para muchos cálculos. Registro DX El DX es conocido como el registro de datos. Algunas operaciones de entrada/salida requieren su uso, y las operaciones de multiplicación y división con cifras grandes suponen al DX y al AX trabajando juntos. Puede usar los registros de propósito general para suma y resta de cifras de 8, 16 o 32 bits. Registros índices Los registros SI y DI están disponibles para direccionamientos indexados y para sumas y restas. Registro SI El registro índice fuente SI de 16 bits es requerido por algunas operaciones con cadenas (de caracteres). En este contexto, el SI está asociado con el registro DS. Los procesadores 80386 y posteriores permiten el uso de un registro ampliado a 32 bits: el ESI. Registro DI El registro índice destino DI también es requerido por algunas operaciones con cadenas de caracteres. En este contexto, el DI está asociado con el registro ES. Los procesadores 80386 y posteriores permiten el uso de un registro ampliado a 32 bits: el EDI. Registro de banderas Los registros de banderas sirven parar indicar el estado actual de la máquina y el resultado del procesamiento. Cuando algunas instrucciones piden comparaciones o cálculos aritméticos cambian el estado de las banderas. Las banderas están en el registro de banderas en las siguientes posiciones: bits 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 O D I T S Z A P C Banderas Las banderas más comunes son las siguientes: OF (Overflow flag, desbordamiento) Indica el desbordamiento de un bit de orden alto (más a la izquierda) después de una operación aritmética. DF (Direction flag, Dirección) Designa la dirección hacia la izquierda o hacia la derecha para mover o comparar cadenas de caracteres. IF (Interruption flag, Interrupción) Indica que una interrupción externa, como la entrada desde el teclado sea procesada o ignorada. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 11 TF (Trap flag, Trampa) Examina el efecto de una instrucción sobre los registros y la memoria. Los programas depuradores como DEBUG, activan esta bandera de manera que pueda avanzar en la ejecución de una sola interrupción a un tiempo. SF (Sign flag, Signo) Contiene el signo resultante de una operación aritmética (0 = positivo y 1 = negativo). ZF (Zero flag, Cero) Indica el resultado de una operación aritmética o de comparación (0 = resultado diferente de cero y 1 = resultado igual a cero) AF (Auxiliary carry flag, Acarreo auxiliar) Contiene un acarreo externo del bit 3 en un dato de 8 bits, para aritmética especializada PF (Parity flag, Paridad) Indica paridad par o impar de una operación en datos de ocho bits de bajo orden (más a la derecha) CF (Carry flag, Acarreo) Contiene el acarreo de orden más alto (más a la izquierda) después de una operación aritmética; también lleva el contenido del ultimo bit en una operación de corrimiento o rotación. CUADRO COMPARATIVO TIPOS DE REGISTROS FUNCIÓN Registros de Segmento Un registro de segmento tiene 16 bits de longitud y facilita un área de memoria para el direccionamiento conocida como el segmento actual Registros de Apuntador de Instrucciones Este registro esta compuesto por 16 bits y contiene el desplazamiento de la siguiente instrucción que se va a ejecutar. Los procesadores 80386 y posteriores tiene un IP ampliado de 32 bits llamado EIP. Registros Apuntadores Permiten al sistema accesar datos al segmento de la pila. Los procesadores 80386 tiene un apuntador de pila de 32 bits llamado ESP. El sistema maneja de manera automática estos registros. Registros de Propósito General Son los caballos de batalla del sistema y pueden ser direccionados como una palabra o como una parte de un bytes. Los procesadores 80386 y posteriores permiten el uso de todos los registros de propósitos general más sus versiones ampliadas de 32 bits llamados EAX, EBX, ECX y EDX. Registros Índices Sirven para el direccionamiento de indexado y para las operaciones de sumas y restas. Registros de Banderas Sirven para indicar el estado actual de la máquina y el resultado del procesamiento. De los 16 bits de registro de bandera 9 son comunes a toda la familia de los procesadores 8086. Ejemplo de Representación de los Registros Después de haber conceptualizado e interpretado los diferente tipos de registro nes necesario plantear un ejemplo no muy practico pero si muy significativo, en el cual se representa la forma estructurada de un programa en el lenguaje ensamblador y como se utilizan los diferentes términos investigados, se verá que en el programa o en una pequeña porción de él se muestra como se colocan dentro, los diferentes tipos registros. TITLE P17HANRD(EXE) Lectura secuencial de registros. .MODEL SMALL .STACK 64 LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 12 Ing. Sánchez Rivero /------------------------------------------------------------------------------------------ .DATA ENDCDE DB 00 ;FIN DEL INDICADOR DE PROCESO. HANDLE DW ? IOAREA DB 32 DUP(´ ´) OPENMSG DB ´*** Open error ***´ 0DH, 0AH PATHNAM DB ´D:\NAMEFILE.SRT´, 0 READMSD DB ´*** Read error ***´ 0DH, 0AH ROW DB 00 /--------------------------------------------------------------------------------------------- .CODE BEGIN PROC FAR MOV AX,@data ;inicializa MOV DS,AX ;registro de MOV ES,AX ;segmento MOV AX,0600H Es posible visualizar los valores de los registros internos de la UCP utilizando el programa Debug. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez RiveroPágina 13 MODOS DE DIRECCIONAMIENTO Se les llama modos de direccionamiento a las distintas formas de combinar los operadores según el acceso que se hace a memoria. Dicho de otra manera, un modo de direccionamiento será una forma de parámetro para las instrucciones. Una instrucción que lleve un parámetro, por lo tanto, usará un modo de direccionamiento, que dependerá de cómo direccionará (accesará) al parámetro; una instrucción de dos parámetros, combinará dos modos de direccionamiento. Estos pueden clasificarse en 5 grupos: 1. Direccionamientos accesando dato inmediato y registro de datos (modos inmediato y de registro) 2. Direccionamiento accesando datos en memoria (modo memoria) 3. Direccionamiento accesando puertos E/S. (modo E/S) 4. Direccionamiento relativo 5. Direccionamiento implícito. 1. DIRECCIONAMIENTO ACCESANDO DATO Y REGISTRO INMEDIATO 1.1 Direccionamiento de registro. Especifica el operando fuente y el operando destino. Los registros deben ser del mismo tamaño. Ej. MOV DX, CX MOV CL, DL. 1.2 Direccionamiento inmediato. Un dato de 8 o 16 bits se especifica como parte de la instrucción. p.ej. MOV CL, 03H. Aquí el operando fuente está en modo inmediato y el destino en modo registro. 2. DIRECCIONAMIENTO ACCESANDO DATOS EN MEMORIA 2.1 Direccionamiento directo. La dirección efectiva (EA) de 16 bits se toma directamente del campo de desplazamiento de la instrucción. El desplazamiento se coloca en la localidad siguiente al código de operación. Esta EA o desplazamiento es la distancia de la localidad de memoria al valor actual en el segmento de datos (DS) en el cual el dato está colocado. Ej. MOV CX, START. START puede definirse como una localidad de memoria usando las pseudoinstrucciones DB o DW. 2.2 Direccionamiento de registro indirecto. La dirección efectiva EA está especificada en un registro apuntador o un registro índice. El apuntador puede ser el registro base BX o el apuntador base BP; el registro índice puede ser el Índice Fuente (SI) o el Índice Destino (DI). Ej. MOV (DI),BX 2.3 Direccionamiento base (Relativo a la base). La EA se obtiene sumando un desplazamiento (8 bits con signo o 16 bits sin signo) a los contenidos de BX o BP. Los segmentos usados son DS y SS. Cuando la memoria es accesada, la dirección física de 20 bits es calculada de BX y DS, por otra parte, cuando la pila es accesada, la dirección es calculada de BP y SS. Ej. MOV AL, START (BX), el operando fuente está en modo base, y la EA se obtiene sumando los valores de START y BX. 2.4 Direccionamiento indexado (Indexado Directo). EA se calcula sumando un desplazamiento (8 o 16 bits) a los contenidos de SI o DI. Ej. MOV BH,START (SI). LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 14 Ing. Sánchez Rivero 2.5 Direccionamiento base indexado. EA se calcula sumando un registro base (BX o BP), un registro índice (DI o SI), y un desplazamiento (8 o 16 bits). Ej. MOV ALPHA (SI)(BX),CL. Este direccionamiento proporciona una forma conveniente para direccionar un arreglo localizado en la pila. 2.6 Direccionamiento (cadena) Este modo usa registros índice. La cadena de instrucciones automáticamente asume que SI apunta al primer byte o palabra del operando destino. Los contenidos de SI y DI son incrementados automáticamente (poniendo a 0 DF mediante la instrucción CLD) o decrementados (poniendo a 1 DF mediante la instrucción STD) para apuntar al siguiente byte o palabra. El segmento del operando fuente es DS y puede ser encimado. El segmento del operando destino debe ser ES y no puede ser encimado. Ej. MOVS BYTE. 3. DIRECCIONAMIENTO ACCESANDO PUERTOS (E/S) Hay dos tipos de direccionamiento usando puertos: directo e indirecto. En el modo directo, el número de puerto es el operando inmediato de 8 bits, lo cual permite accesar puertos numerados del 0 al 255. Ej. OUT 05H,AL. En el modo indirecto, el número de puerto se toma de DX, permitiendo así 64K puertos de 8 bits o 32K puertos de 16 bits. Las transferencias E/S de 8 y 16 bits deben hacerse vía AL y AX, respectivamente. 4. DIRECCIONAMIENTO RELATIVO. En este modo el operando se especifica como un desplazamiento de 8 bits con signo, relativo al PC. Ej. JNC START. Si C=0, entonces el PC se carga con PC+el valor de START. 5. DIRECCIONAMIENTO IMPLICITO. Las instrucciones que usan este modo no tienen operandos. Ej. CLC. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 15 TRABAJANDO CON EL DEBUG Para empezar a trabajar con Debug digite en el prompt de la computadora: C:\> Debug [Enter] En la siguiente línea aparecerá un guión, éste es el indicador del Debug, en este momento se pueden introducir las instrucciones del Debug. Utilizando el comando: - r [Enter] Se desplegaran todos los contenidos de los registros internos de la UCP; una forma alternativa de mostrarlos es usar el comando "r" utilizando como parámetro el nombre del registro cuyo valor se quiera visualizar. Por ejemplo: - rbx [Enter] Esta instrucción desplegará únicamente el contenido del registro BX y cambia el indicador del Debug de " - " a " : " Estando así el prompt es posible cambiar el valor del registro que se visualiza tecleando el nuevo valor y a continuación [Enter], o se puede dejar el valor anterior presionando [Enter] sin teclear ningún valor. Estructura de una instrucción en ensamblador En el lenguaje ensamblador las líneas de código están constituidas de dos partes: la primera es el nombre de la instrucción que se va a ejecutar y la segunda son los parámetros del comando u operandos. Por ejemplo: add ah, bh Aquí "add" es el comando a ejecutar (en este caso una adición) y tanto "ah" como "bh" son los parámetros. El nombre de las instrucciones en este lenguaje esta formado por dos, tres o cuatro letras. A estas instrucciones también se les llama nombres mnemónicos o códigos de operación, ya que representan alguna función que habrá de realizar el procesador. Existen algunos comandos que no requieren parámetros para su operación, así como otros que requieren solo un parámetro. Algunas veces se utilizarán las instrucciones como sigue: add al, [170] Los corchetes en el segundo parámetro nos indican que vamos a trabajar con el contenido de la casilla de memoria número 170 y no con el valor 170, a esto se le conoce como direccionamiento directo. Primer programa Se creará un programa que sirva para ilustrar lo analizado. Lo que se hará es una suma de dos valores que introduciremos directamente en el programa. El primer paso es iniciar el Debug, este paso consiste únicamente en teclear debug [Enter] en el prompt del sistema operativo. Para ensamblar un programa en el Debug se utiliza el comando "a" (Assembler); cuando se utiliza este comando se le puede dar como parámetro la dirección donde se desea que se inicie el ensamblado. Si se omite el parámetro el ensamblado se iniciará en la localidad especificada por CS: IP, usualmente 0100H, que es la localidad donde deben iniciar los programas con extensión .COM, y será la localidad que utilizaremos debido a que Debug solo puede crear este tipo específico de programas. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 16Ing. Sánchez Rivero Aunque en este momento no es necesario darle un parámetro al comando "a" es recomendable hacerlo para evitar problemas una vez que se haga uso de los registros CS: IP, por lo tanto tecleamos: - a0100 [Enter] Al hacer esto aparecerá en la pantalla algo como: 0C1B:0100 y el cursor se posiciona a la derecha de estos números. Nótese que los primeros cuatro dígitos (en sistema hexadecimal) pueden ser diferentes, pero los últimos cuatro deben ser 0100, ya que es la dirección que indicamos como inicio. Ahora podemos introducir las instrucciones: 0C1B:0100 mov ax,0002 ;coloca el valor 0002 en el registro ax 0C1B:0103 mov bx,0004 ;coloca el valor 0004 en el registro bx 0C1B:0106 add ax,bx ;le adiciona al contenido de ax el contenido de bx 0C1B:0108 int 20 ;provoca la terminación del programa. 0C1B:010A No es necesario escribir los comentarios que van después del ";". Una vez digitado el último comando, int 20, se le da [Enter] sin escribir nada más, para volver al prompt del debugger. La última línea escrita no es propiamente una instrucción de ensamblador, es una llamada a una interrupción del sistema operativo, estas interrupciones serán tratadas más a fondo posteriormente, por el momento solo es necesario saber que nos ahorran un gran número de líneas y son muy útiles para accesar a funciones del sistema operativo. Para ejecutar el programa que escribimos se utiliza el comando "g", al utilizarlo veremos que aparece un mensaje que dice: "Program terminated normally". Naturalmente con un mensaje como éste no podemos estar seguros que el programa haya hecho la suma, pero existe una forma sencilla de verificarlo, utilizando el comando "r" del Debug podemos ver los contenidos de todos los registros del procesador, simplemente teclee: - r [Enter] Aparecerá en pantalla cada registro con su respectivo valor actual: AX=0006 BX=0004 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0C1B ES=0C1B SS=0C1B CS=0C1B IP=010A NV UP EI PL NZ NA PO NC 0C1B:010A 0F DB oF Existe la posibilidad de que los registros contengan valores diferentes, pero AX y BX deben ser los mismos, ya que son los que acabamos de modificar. Otra forma de ver los valores, mientras se ejecuta el programa es utilizando como parámetro para "g" la dirección donde queremos que termine la ejecución y muestre los valores de los registros, en este caso sería: g108, esta instrucción ejecuta el programa, se detiene en la dirección 108 y muestra los contenidos de los registros. También se puede llevar un seguimiento de lo que pasa en los registros utilizando el comando "t" (trace), la función de este comando es ejecutar línea por línea lo que se ensambla mostrando cada vez los contenidos de los registros. Para salir del Debug se utiliza el comando "q" (quit). Guardar y cargar los programas No sería práctico tener que digitar todo un programa cada vez que se necesite, para evitar eso es posible guardar un programa en el disco, con la enorme ventaja de que ya ensamblado no será necesario correr de nuevo Debug para ejecutarlo. Los pasos a seguir para guardar un programa ya almacenado en la memoria son: ♦ Obtener la longitud del programa restando la dirección final de la dirección inicial, naturalmente en sistema hexadecimal. ♦ Darle un nombre al programa y extensión. ♦ Poner la longitud del programa en el registro CX. ♦ Ordenar a Debug que escriba el programa en el disco. Utilizando como ejemplo el programa anterior se tendrá una idea más clara de como llevar estos pasos. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 17 Al terminar de ensamblar el programa se vería así: 0C1B:0100 mov ax,0002 0C1B:0103 mov bx,0004 0C1B:0106 add ax,bx 0C1B:0108 int 20 0C1B:010A - h 10a 100 020a 000a - n prueba.com - rcx CX 0000 :000a -w Writing 000A bytes Para obtener la longitud de un programa se utiliza el comando "h", el cual muestra la suma y resta de dos números en hexadecimal. Para obtener la longitud de nuestro programa le proporcionamos como parámetros el valor de la dirección final de nuestro programa (10A) y el valor de la dirección inicial (100). El primer resultado que nos muestra el comando es la suma de los parámetros y el segundo es la resta. El comando "n" nos permite poner un nombre al programa. El comando "rcx" nos permite cambiar el contenido del registro CX al valor que obtuvimos del tamaño del archivo con "h", en este caso 000a, ya que nos interesa el resultado de la resta de la dirección inicial a la dirección final. Por último el comando “w” escribe nuestro programa en el disco, indicándonos cuantos bytes escribió. Para cargar un archivo ya guardado son necesarios dos pasos: ♦ Proporcionar el nombre del archivo que se cargará. ♦ Cargarlo utilizando el comando "l" (load). Para obtener el resultado correcto de los siguientes pasos es necesario que previamente se haya creado el programa anterior. Dentro del Debug escribimos lo siguiente: - n prueba.com - l - u 100 109 0C3D:0100 B80200 MOV AX,0002 0C3D:0103 BB0400 MOV BX,0004 0C3D:0106 01D8 ADD AX,BX 0C3D:0108 CD20 INT 20 El último comando, "u", se utiliza para verificar que el programa se cargó en memoria, lo que hace es desensamblar el código y mostrarlo ya desensamblado. Los parámetros le indican a Debug desde donde y hasta donde desensamblar. Debug siempre carga los programas en memoria en la dirección 100H, a menos que se le indique alguna otra dirección. Condiciones, ciclos y bifurcaciones Estas estructuras, o formas de control le dan a la máquina un cierto grado de decisión basado en la información que recibe. La forma más sencilla de comprender este tema es por medio de ejemplos. Se crearán tres programas que hagan lo mismo: desplegar un número determinado de veces una cadena de caracteres en la pantalla. - a100 0C1B:0100 jmp 125 ; brinca a la dirección 125H 0C1B:0102 [Enter] - e 102 'Cadena a visualizar 15 veces' 0d 0a '$' - a125 0C1B:0125 MOV CX,000F ; veces que se desplegará la cadena 0C1B:0128 MOV DX,0102 ; copia cadena al registro DX LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 18 Ing. Sánchez Rivero 0C1B:012B MOV AH,09 ; copia valor 09 al registro AH 0C1B:012D INT 21 ; despliega cadena 0C1B:012F LOOP 012D ; si CX>0 brinca a 012D 0C1B:0131 INT 20 ; termina el programa. Por medio del comando "e" es posible introducir una cadena de caracteres en una determinada localidad de memoria, dada como parámetro, la cadena se introduce entre comillas, le sigue un espacio, luego el valor hexadecimal del retorno de carro, un espacio, el valor de línea nueva y por último el símbolo '$' que el ensamblador interpreta como final de la cadena. La interrupción 21 utiliza el valor almacenado en el registro AH para ejecutar una determinada función, en este caso mostrar la cadena en pantalla, la cadena que muestra es la que está almacenada en el registro DX. La instrucción LOOP decrementa automáticamente el registro CX en uno y si no ha llegado el valor de este registro a cero brinca a la casilla indicada como parámetro, lo cual crea un ciclo que se repite el número de veces especificado por el valor de CX. La interrupción 20 termina la ejecución del programa. Otra forma de realizar la misma función pero sin utilizar el comando LOOP es la siguiente: - a100 0C1B:0100 jmp 125 ; brinca a la dirección 125H 0C1B:0102 [Enter] - e 102 'Cadena a visualizar 15 veces'0d 0a '$' - a125 0C1B:0125 MOV BX,000F ; veces que se desplegará la cadena 0C1B:0128 MOV DX,0102 ; copia cadena al registro DX 0C1B:012B MOV AH,09 ; copia valor 09 al registro AH 0C1B:012D INT 21 ; despliega cadena 0C1B:012F DEC BX ; decrementa en 1 a BX 0C1B:0130 JNZ 012D ; si BX es diferente a 0 brinca a 012D 0C1B:0132 INT 20 ; termina el programa. En este caso se utiliza el registro BX como contador para el programa, y por medio de la instrucción "DEC" se disminuye su valor en 1. La instrucción "JNZ" verifica si el valor de B es diferente a 0, esto con base en la bandera NZ, en caso afirmativo brinca hacia la dirección 012D. En caso contrario continúa la ejecución normal del programa y por lo tanto se termina. Una última variante del programa es utilizando de nuevo a CX como contador, pero en lugar de utilizar LOOP utilizaremos decrementos a CX y comparación de CX a 0. - a100 0C1B:0100 jmp 125 ; brinca a la dirección 125H 0C1B:0102 [Enter] - e 102 'Cadena a visualizar 15 veces' 0d 0a '$' - a125 0C1B:0125 MOV DX,0102 ; copia cadena al registro DX 0C1B:0128 MOV CX,000F ; veces que se desplegará la cadena 0C1B:012B MOV AH,09 ; copia valor 09 al registro AH 0C1B:012D INT 21 ; despliega cadena 0C1B:012F DEC CX ; decrementa en 1 a CX 0C1B:0130 JCXZ 0134 ; si CX es igual a 0 brinca a 0134 0C1B:0132 JMP 012D ; brinca a la dirección 012D 0C1B:0134 INT 20 ; termina el programa En este ejemplo se usó la instrucción JCXZ para controlar la condición de salto, el significado de tal función es: brinca si CX=0 El tipo de control a utilizar dependerá de las necesidades de programación en determinado momento. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 19 PROGRAMACIÓN EN ENSAMBLADOR IMPORTANCIA DEL LENGUAJE ENSAMBLADOR El lenguaje ensamblador es la forma más básica de programar un microprocesador para que éste sea capaz de realizar las tareas o los cálculos que se le requieran. El lenguaje ensamblador es conocido como un lenguaje de bajo nivel, esto significa que nos permite controlar el 100 % de las funciones de un microprocesador, así como los periféricos asociados a éste. A diferencia de los lenguajes de alto nivel, por ejemplo "Pascal", el lenguaje ensamblador no requiere de un compilador, esto es debido a que las instrucciones en lenguaje ensamblador son traducidas directamente a código binario y después son colocadas en memoria para que el microprocesador las tome directamente. Aprender a programar en lenguaje ensamblador no es fácil, se requiere un cierto nivel de conocimiento de la arquitectura y organización de las computadoras, además del conocimiento de programación en algún otro lenguaje La primera razón para trabajar con ensamblador es que proporciona la oportunidad de conocer más a fondo la operación de su PC, lo que permite el desarrollo de software de una manera más consistente. La segunda razón es el control total de la PC que se tiene con el uso del mismo. Otra razón es que los programas de ensamblador son más rápidos, más compactos y tienen mayor capacidad que los creados en otros lenguajes. Por último el ensamblador permite una optimización ideal en los programas tanto en su tamaño como en su ejecución. El Lenguaje Ensamblador es importante como se puede ver; es directamente traducible al Lenguaje de Máquina, y viceversa; simplemente, es una abstracción que facilita su uso para los seres humanos. Por otro lado, la computadora no entiende directamente al Lenguaje Ensamblador; es necesario traducirle a Lenguaje de Máquina. Pero, al ser tan directa la traducción, pronto aparecieron los programas Ensambladores, que son traductores que convierten el código fuente (en Lenguaje Ensamblador) a código objeto (es decir, a Lenguaje de Máquina). Surge como una necesidad de facilitar al programador la tarea de trabajar con lenguaje máquina sin perder el control directo con el hardware. VENTAJAS Y DESVENTAJAS DEL LENGUAJE ENSAMBLADOR Conocida es la evolución de los lenguajes de programación, cabe preguntarse: ¿En estos tiempos "modernos", para qué quiero el Lenguaje Ensamblador? El proceso de evolución trajo consigo algunas desventajas, que se verán luego así como las ventajas de usar el Lenguaje Ensamblador, respecto a un lenguaje de alto nivel: Ventajas del lenguaje ensamblador: • Velocidad de ejecución de los programas • Tamaño • Flexibilidad VELOCIDAD El proceso de traducción que realizan los intérpretes, implica un proceso de cómputo adicional al que el programador quiere realizar. Por ello, nos encontraremos con que un intérprete es siempre más lento que realizar la misma acción en Lenguaje Ensamblador, simplemente porque tiene el costo adicional de estar traduciendo el programa, cada vez que lo ejecutamos. De ahí nacieron los compiladores, que son mucho más rápidos que los intérpretes, pues hacen la traducción una vez y dejan el código objeto, que ya es Lenguaje de Máquina, y se puede ejecutar muy rápidamente. Aunque el proceso de traducción es más complejo y costoso que el de ensamblar un programa, normalmente podemos despreciarlo, contra las ventajas de codificar el programa más rápidamente. Sin embargo, la mayor parte de las veces, el código generado por un compilador es menos eficiente que el código equivalente que un programador escribiría. La razón es que el compilador no tiene tanta inteligencia, y requiere ser capaz de crear código genérico, que sirva tanto para un programa como para otro; en cambio, un programador humano puede LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 20 Ing. Sánchez Rivero aprovechar las características específicas del problema, reduciendo la generalidad pero al mismo tiempo, no desperdicia ninguna instrucción, no hace ningún proceso que no sea necesario. Para darnos una idea, en una PC, y suponiendo que todos son buenos programadores, un programa para ordenar una lista tardará cerca de 20 veces más en Visual Basic (un intérprete), y 2 veces más en C (un compilador), que el equivalente en Ensamblador. Por ello, cuando es crítica la velocidad del programa, Ensamblador se vuelve un candidato lógico como lenguaje. Ahora bien, esto no es un absoluto; un programa bien hecho en C puede ser muchas veces más rápido que un programa mal hecho en Ensamblador; sigue siendo sumamente importante la elección apropiada de algoritmos y estructuras de datos. Por ello, se recomienda buscar optimizar primero estos aspectos, en el lenguaje que se desee, y solamente usar Ensamblador cuando se requiere más optimización y no se puede lograr por estos medios. TAMAÑO Por las mismas razones que vimos en el aspecto de velocidad, los compiladores e intérpretes generan más código máquina del necesario; por ello, el programa ejecutable crece. Así, cuando es importante reducir el tamaño del ejecutable, mejorando el uso de la memoria y teniendo también beneficios en velocidad, puede convenir usar el lenguaje Ensamblador. Entre los programas que es crítico el uso mínimo de memoria, tenemos a los virus y manejadores de dispositivos (drivers). Muchos de ellos, por supuesto, están escritos en lenguaje Ensamblador. FLEXIBILIDAD Las razones anteriores son cuestión de grado: podemos hacer las cosas en otro lenguaje, pero queremos hacerlas más eficientemente. Pero todos los lenguajes de alto nivel tienen limitantes en el control; al hacer abstracciones, limitan su propia capacidad. Es decir, existen tareasque la máquina puede hacer, pero que un lenguaje de alto nivel no permite. Por ejemplo, en Visual Basic no es posible cambiar la resolución del monitor a medio programa; es una limitante, impuesta por la abstracción del GUI Windows. En cambio, en ensamblador es sumamente sencillo, pues tenemos el acceso directo al hardware del monitor. Por otro lado, al ser un lenguaje más primitivo, el Ensamblador tiene ciertas desventajas respecto a los lenguajes de alto nivel: Desventajas del lenguaje ensamblador: • Tiempo de programación • Programas fuentes grandes • Peligro de afectar recursos inesperadamente • Falta de portabilidad TIEMPO DE PROGRAMACIÓN Al ser de bajo nivel, el Lenguaje Ensamblador requiere más instrucciones para realizar el mismo proceso, en comparación con un lenguaje de alto nivel. Por otro lado, requiere de más cuidado por parte del programador, pues es propenso a que los errores de lógica se reflejen más fuertemente en la ejecución. Por todo esto, es más lento el desarrollo de programas comparables en Lenguaje Ensamblador que en un lenguaje de alto nivel, pues el programador goza de una menor abstracción. PROGRAMAS FUENTE GRANDES Por las mismas razones que aumenta el tiempo, crecen los programas fuentes; simplemente, requerimos más instrucciones primitivas para describir procesos equivalentes. Esto es una desventaja porque dificulta el mantenimiento de los programas, y nuevamente reduce la productividad de los programadores. PELIGRO DE AFECTAR RECURSOS INESPERADAMENTE LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 21 Tenemos la ventaja de que todo lo que se puede hacer en la máquina, se puede hacer con el Lenguaje Ensamblador (flexibilidad). El problema es que todo error que podamos cometer, o todo riesgo que podamos tener, podemos tenerlo también en este Lenguaje. Dicho de otra forma, tener mucho poder es útil pero también es peligroso. En la vida práctica, afortunadamente no ocurre mucho; sin embargo, al programar en este lenguaje verán que es mucho más común que la máquina se "cuelgue", "bloquee" o "se le vaya el avión"; y que se reinicialize. ¿Por qué?, porque con este lenguaje es perfectamente posible (y sencillo) realizar secuencias de instrucciones inválidas, que normalmente no aparecen al usar un lenguaje de alto nivel. En ciertos casos extremos, puede llegarse a sobrescribir información del CMOS de la máquina (no he visto efectos más riesgosos); pero, si no la conservamos, esto puede causar que dejemos de "ver" el disco duro, junto con toda su información. FALTA DE PORTABILIDAD Como ya se mencionó, existe un lenguaje ensamblador para cada máquina; por ello, evidentemente no es una selección apropiada de lenguaje cuando deseamos codificar en una máquina y luego llevar los programas a otros sistemas operativos o modelos de computadoras. Si bien esto es un problema general a todos los lenguajes, es mucho más notorio en ensamblador: yo puedo reutilizar un 90% o más del código que desarrollo en "C", en una PC, al llevarlo a una RS/6000 con UNIX, y lo mismo si después lo llevo a una Macintosh, siempre y cuando esté bien hecho y siga los estándares de "C", y los principios de la programación estructurada. En cambio, si escribimos el programa en Ensamblador de la PC, por bien que lo desarrollemos y muchos estándares que sigamos, tendremos prácticamente que reescribir el 100 % del código al llevarlo a UNIX, y otra vez lo mismo al llevarlo a Mac. LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 22 Ing. Sánchez Rivero SINTAXIS DE UNA LÍNEA EN ENSAMBLADOR Un programa fuente en ensamblador contiene dos tipos de sentencias: las instrucciones y las directivas. Las instrucciones se aplican en tiempo de ejecución, pero las directivas sólo son utilizadas durante el ensamblaje. El formato de una sentencia de instrucción es el siguiente: [etiqueta] nombre_instrucción [operandos] [comentario] Los corchetes, como es normal al explicar instrucciones en informática, indican que lo especificado entre ellos es opcional, dependiendo de la situación que se trate. Campo de etiqueta: Es el nombre simbólico de la primera posición de una instrucción, puntero o dato. Consta de hasta 31 caracteres que pueden ser las letras de la A a la Z, los números del 0 al 9 y algunos caracteres especiales como «@», «_», «.» y «$». Reglas: • Si se utiliza el punto «.», éste debe colocarse como primer carácter de la etiqueta. • El primer carácter no puede ser un dígito. • No se pueden utilizar los nombres de instrucciones o registros como nombres de etiquetas. Las etiquetas son de tipo NEAR cuando el campo de etiqueta finaliza con dos puntos (:); esto es, se considera cercana: quiere esto decir que cuando realizamos una llamada sobre dicha etiqueta el ensamblador considera que está dentro del mismo segmento de código (llamadas intrasegmento) y el procesador sólo carga el puntero de instrucciones IP. Téngase en cuenta que hablamos de instrucciones; las etiquetas empleadas antes de las directivas, como las directivas de definición de datos por ejemplo, no llevan los dos puntos y sin embargo son cercanas. Las etiquetas son de tipo FAR si el campo de etiqueta no termina con los dos puntos: en estas etiquetas la instrucción a la que apunta no se encuentra en el mismo segmento de código sino en otro. Cuando es referenciada en una transferencia de control se carga el puntero de instrucciones IP y el segmento de código CS (llamadas intersegmento). Campo de nombre: Contiene el mnemónico de las instrucciones o bien una directiva. Campo de operandos: Indica cuales son los datos implicados en la operación. Puede haber 0, 1 ó 2; en el caso de que sean dos al 1º se le llama destino y al 2º -separado por una coma- fuente. mov ax, es:[di] --> ax destino --> es:[di] origen Campo de comentarios: Cuando en una línea hay un punto y coma (;) todo lo que sigue en la línea es un comentario que realiza aclaraciones sobre lo que se está haciendo en ese Programa; resulta de gran utilidad de cara a realizar futuras modificaciones al mismo. CONSTANTES Y OPERADORES Las sentencias fuente -tanto instrucciones como directivas- pueden contener constantes y operadores. CONSTANTES Pueden ser binarias (ej. 10010b), decimales (ej. 34d), hexadecimales (ej. 0E0h) u octales (ej. 21o ó 21q); también las hay de tipo cadena (ej. 'pepe', "juan") e incluso con comillas dentro de comillas de distinto tipo (como 'hola,"amigo"'). En las constantes hexadecimales, si el primer dígito no es numérico hay que poner un 0. Sólo se puede poner el signo (-) en las decimales (en las demás, calcúlese el complemento a dos) Por defecto, las numéricas están en base 10 si no se indica lo contrario con una directiva (poco recomendable como se verá). OPERADORES ARITMÉTICOS LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 23 Pueden emplearse libremente (+), (-), (*) y (/) - en este último caso la división es siempreentera-. Es válida, por ejemplo, la siguiente línea en ensamblador (que se apoya en la directiva DW, que se verá más adelante, para reservar memoria para una palabra de 16 bits): Dato DW 12*(numero+65)/7 También se admiten los operadores MOD (resto de la división) y SHL/SHR (desplazar a la izquierda/derecha cierto número de bits). Obviamente, el ensamblador no codifica las instrucciones de desplazamiento (al aplicarse sobre datos constantes el resultado se calcula en tiempo de ensamblaje): Dato DW (12 SHR 2) + 5 OPERADORES LÓGICOS Pueden ser el AND, OR, XOR y NOT. Realizan las operaciones lógicas en las expresiones. Ej.: MOV BL,(255 AND 128) XOR 128 ; BL = 0 OPERADORES RELACIONALES Devuelven condiciones de Verdadero (0FFFFh ó 0FFh) o Falso (0) evaluando una expresión. Pueden ser: EQ (igual), NE (no igual), LT (menor que), GT (mayor que), LE (menor o igual que), GE (mayor o igual que). Ejemplo: dato EQU 100 ; «dato» vale 100 MOV AL,dato GE 10 ; AL = 0FFh (Verdadero) MOV AH,dato EQ 99 ; AH = 0 (Falso) OPERADORES DE RETORNO DE VALORES * Operador SEG: devuelve el valor del segmento de la variable o etiqueta, sólo se puede emplear en programas de tipo EXE: MOV AX,SEG tabla_datos * Operador OFFSET: devuelve el desplazamiento de la variable o etiqueta en su segmento: MOV AX,OFFSET variable Si se desea obtener el offset de una variable respecto al grupo (directiva GROUP) de segmentos en que está definida y no respecto al segmento concreto en que está definida: MOV AX,OFFSET nombre_grupo:variable También es válido: MOV AX,OFFSET DS:variable * Operador .TYPE: devuelve el modo de la expresión indicada en un byte. El bit 0 indica modo «relativo al código» y el 1 modo «relativo a datos»; si ambos bits están inactivos significa modo absoluto. El bit 5 indica si la expresión es local (0 si está definida externamente o indefinida); el bit 7 indica si la expresión contiene una referencia externa. Este operador es útil sobre todo en las macros para determinar el tipo de los parámetros: info .TYPE variable * Operador TYPE: devuelve el tamaño (bytes) de la variable indicada. No válido en variables DUP: kilos DW 76 MOV AX,TYPE kilos ; AX = 2 LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 24 Ing. Sánchez Rivero Tratándose de etiquetas -en lugar de variables- indica si es lejana o FAR (0FFFEh) o cercana o NEAR (0FFFFh). * Operadores SIZE y LENGTH: devuelven el tamaño (en bytes) o el nº de elementos, respectivamente, de la variable indicada (definida obligatoriamente con DUP): matriz DW 100 DUP (12345) MOV AX,SIZE matriz ; AX = 200 MOV BX,LENGTH matriz ; BX = 100 * Operadores MASK y WIDTH: informan de los campos de un registro de bits (véase RECORD). OPERADORES DE ATRIBUTOS * Operador PTR: redefine el atributo de tipo (BYTE, WORD, DWORD, QWORD, TBYTE) o el de distancia (NEAR o FAR) de un operando de memoria. Por ejemplo, si se tiene una tabla definida de la siguiente manera: Tabla DW 10 DUP (0) ; 10 palabras a 0 Para colocar en AL el primer byte de la misma, la instrucción MOV AL,tabla es incorrecta, ya que tabla (una cadena 10 palabras) no cabe en el registro AL. Lo que desea el programador debe indicárselo en este caso explícitamente al ensamblador de la siguiente manera: MOV AL,BYTE PTR tabla Trabajando con varios segmentos, PTR puede redefinir una etiqueta NEAR de uno de ellos para convertirla en FAR desde el otro, con objeto de poder llamarla. * Operadores CS:; DS:; ES: y SS: el ensamblador genera un prefijo de un byte que indica al microprocesador el segmento que debe emplear para acceder a los datos en memoria. Por defecto, se supone DS para los registros BX, DI o SI (o sin registros de base o índice) y SS para SP y BP. Si al acceder a un dato éste no se encuentra en el segmento por defecto, el ensamblador añadirá el byte adicional de manera automática. Sin embargo, el programador puede forzar también esta circunstancia: MOV AL,ES:variable En el ejemplo, variable se supone ubicada en el Segmento Extra. Cuando se referencia una dirección fija hay que indicar el segmento, ya que el ensamblador no conoce en qué segmento está la variable; es uno de los pocos casos en que debe indicarse. Por ejemplo, la siguiente línea dará un error al ensamblar: MOV AL,[0] Para solucionarlo hay que indicar en qué segmento está el dato (incluso aunque éste sea DS): MOV AL,DS:[0] En este último ejemplo el ensamblador no generará el byte adicional ya que las instrucciones MOV operan por defecto sobre DS (como casi todas), pero ha sido necesario indicar DS para que el ensamblador nos entienda. Sin embargo, en el siguiente ejemplo no es necesario, ya que midato está declarado en el segmento de datos y el ensamblador lo sabe: MOV AL,midato Por lo general no es muy frecuente la necesidad de indicar explícitamente el segmento: al acceder a una variable el ensamblador mira en qué segmento está declarada (véase la directiva SEGMENT) y según como estén asignados los ASSUME, pondrá o no el prefijo adecuado según sea conveniente. Es responsabilidad exclusiva del programador inicializar los registros de segmento al principio de los procedimientos para que el ASSUME no se quede en tinta mojada... sí se emplean con bastante frecuencia, sin embargo, los prefijos CS LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Ing. Sánchez Rivero Página 25 en las rutinas que gestionan interrupciones (ya que CS es el único registro de segmento que apunta en principio a las mismas, hasta que se cargue DS u otro). * Operador SHORT: indica que la etiqueta referenciada, de tipo NEAR, puede alcanzarse con un salto corto (-128 a +127 posiciones) desde la actual situación del contador de programa. El ensamblador TASM, si se solicitan dos pasadas, coloca automáticamente instrucciones SHORT allí donde es posible, para economizar memoria (el MASM no) * Operador '$': indica la posición del contador de posiciones («Location Counter») utilizado por el ensamblador dentro del segmento para llevar la cuenta de por dónde se llega ensamblando. Muy útil: frase DB "simpático" longitud EQU $-OFFSET frase En el ejemplo, longitud tomará el valor 9. * Operadores HIGH y LOW: devuelven la parte alta o baja, respectivamente (8 bits) de la expresión: dato EQU 1025 MOV AL,LOW dato ; AL = 1 MOV AH,HIGH dato ; AH = 4 LENGUAJE ENSAMBLADOR LABORATORIO DE COMPUTADORAS Página 26 Ing. Sánchez Rivero PRINCIPALES DIRECTIVAS La sintaxis de una sentencia directiva es muy similar a la de una sentencia de instrucción: [nombre] nombre_directiva [operandos] [comentario] Sólo es obligatorio el campo «nombre_directiva»; los campos han de estar separados por al menos un espacio en blanco. La sintaxis de «nombre» es análoga a la de la «etiqueta» de las líneas de instrucciones, aunque nunca se pone el sufijo «:». El campo de comentario cumple también las mismas normas.
Compartir