Você está na página 1de 67

ASSEMBLER 80x86 Leandro Caniglia ---------------LO PRIMERO QUE HAY QUE SABER (*) -------------------------------INDICE GENERAL DE LA SERIE INTRODUCCION

Que es el Assembler PRIMER MENSAJE Los Registros Internos SEGUNDO MENSAJE Ms sobre Los Registros TERCER MENSAJE Los Registros de Trabajo CUARTO MENSAJE La instruccin MOV Convertir un nmero a un string QUINTO MENSAJE Los Registros de Segmento El Registro DS Cmo fijar el Segmento de Datos SEXTO MENSAJE Los Registros de Segmento (Continuacin) La notacin SEGMENT:OFFSET El Registro ES Ejemplos del uso del Registro ES SEPTIMO MENSAJE Los Registros de Segmento (Continuacin) Los Registro CS e IP OCTAVO MENSAJE Los Registros de Segmento (Continuacin) Los Registro SS y SP El Stack NOVENO MENSAJE Los Registros de Segmento (Continuacin) Cmo usar el Stack DECIMO MENSAJE El Registro de Estados (Primera Parte) Flags Z y C DECIMOPRIMER MENSAJE El Registro de Estados (Segunda Parte) Flag C (continuacin) Flags P, A, S, T, I, O y D DECIMOSEGUNDO MENSAJE Las instrucciones de strings (Primera Parte) CMPS y LODS

DECIMOTERCER MENSAJE Las instrucciones de strings (Segunda Parte) MOVS, SCAS, STOS DECIMOCUARTO MENSAJE Estructura de un programa (Primera Parte) Los programas .COM DECIMOQUINTO MENSAJE Estructura de un programa (Segunda Parte) Cmo terminar un programa .COM DECIMOSEXTO MENSAJE Estructura de un programa (Tercera Parte) Parmetros en la lnea de comandos DECIMOSEPTIMO MENSAJE Estructura de un programa (Cuarta Parte) Los programas .EXE DECIMOCTAVO MENSAJE Indicie General de la Serie APENDICE A Notacin Hexadecimal: Un punto de vista computacional APENDICE B Unos pocos tricks en assembler -----------------------------------------------------------------------(*) Curso de assembler en forma de mensajes. Por Leandro Caniglia y Valeria Murgia La versin 1.0 se termin de escribir el domingo 26 de octubre de 1992. -----------------------------------------------------------------------PD: Eso fue todo! Esperamos que les haya gustado. La seguimos con la serie "TODO(*) sobre las interrupciones". Leandro y Valeria INTRODUCCION -----------Este es el primero de una serie de mensajes destinada a brindarle un empujoncito a aquellos PC-Hostianos que tienen inter s en comenzar a programar en assembler. Es ms larga la lista de cosas que no pretendo que la de cosas que s pretendo. Por ejemplo, no pretendo impartir lecciones a nadie. S, en cambio, espero que la gente participe enviando material: preguntas, explicaciones, ejemplos, nombres de libros, de utilitarios de asistencia a programadores, etc. Quien ms quien menos, todos hemos ledo acerca de ventajas y desventajas de la programacin en assembler. Lo bueno del assembler es que con l uno cobra mayor conciencia respecto del grado de complejidad de cada rutina. Una contra que tiene es que obliga al programador a

estar mucho ms concentrado de lo que estara si usara C o Basic. Creo que a todo aquel que le guste la programacin (en el sentido ms general del t rmino), debera ser sensible a la est tica de este lenguaje. Se dice que el assembler es un lenguaje de bajo nivel, pero no creo que haya lenguajes de alto nivel o de bajo nivel. El nivel es una propiedad del programa, no del lenguaje. Si bien es cierto que es ms cmodo escribir rutinas de bajo nivel en assembler, no todo lo que se escribe en assembler puede ser caracterizado como de bajo nivel. La BIOS y el DOS proveen una cantidad enorme de rutinas listas para usar que producen resultados notables a cambio de muy poco. Los que escribieron las rutinas de la BIOS programaron a bajo nivel. Nosotros, cuando simplemente las llamamos estamos programando en un nivel decididamente ms alto. En fin, espero que esta iniciativa encuentre algn eco entre los PC-Hostianos para que de alguna manera pensemos que tal vez podemos recurrir a alguno de nosotros en lugar de esperar que un amigo que viaja nos traiga una valija llena de "libreras" para el Clipper. Lo primero que hay que saber ---------------------------La familia de microprocesadores 80x86 (el 8088, 8086, 80186, 80286, 80386, 80486, ...) se basa en un patrn de compatibilidad. Todo lo que puede hacer uno de estos micros tambi n lo puede realizar el siguiente de la lista. Pero cada uno es capaz de hacer cosas que sus antecesores no. Desde el punto de vista del programador, la nica diferencia entre el 8088 y el 8086 es la rapidez. El 8086 es ms veloz gracias a la arquitectura de su "bus" de datos. Bsicamente las facilidades que se agregan a los nuevos modelos estn relacionadas con la "multiarea" (diferentes programas conviven en la mquina al mismo tiempo) algo para lo que DOS no fue diseado. Para iniciarse en la programacin en assembler bajo DOS conviene pensar que uno est trabajando con el 8088. Los registros ------------Mientras la memoria, RAM o ROM, en donde estn el programa y los datos es externa al microprocesador, los registros son memorias de trabajo internas donde se ubican los valores sobre los que la CPU opera. Por ejemplo una instruccin puede producir que la CPU sume un valor a uno de sus registros y ubique el resultado de esa suma en ese registro. Programa en RAM: .. .. 05 01 00 .. .. | | | | | +------- Segundo byte del operando | +---------- Primer byte del operando +------------- Cdigo de instruccin

Registros dentro de la CPU:

+----+----+ AX | AH | AL | +----+----+ BX | BH | BL | +----+----+ | | | ... El 8088 tiene 16 registros. Uno de ellos se llama AX. Si por ejemplo el microprocesador est a punto de ejecutar la instruccin de arriba, leer el 05 y lo interpretar como una instruccin elemental. En el caso del 05, esa instruccin significa: sume a AX el nmero contenido en los dos prximos bytes de la memoria (en nuestro ejemplo 01 00) y reemplace AX con el resultado de esa suma. Una vez comprendido esto, el micro sabe que los dos bytes siguientes al 05 son datos y no cdigos de instruccin. Interpreta estos datos como el nmero "1" (ya que por una caracterstica interna invierte el orden de los bytes antes de interpretarlos como una cantidad), suma "1" al contenido de AX y reemplaza el valor de AX con el resultado (AX = AX + 1). Despu s de esto, el microprocesador lee el byte siguiente al 00, lo interpreta como un cdigo de instruccin y contina con la ejecucin del programa. En assembler uno usa nemnicos para referirse a las instrucciones en cdigo mquina. Por ejemplo el ensamblador generar el cdigo de tres bytes: 05 01 00 a partir de la instruccin ADD AX,1 Cdigo fuente ADD AX,1 assembler Cdigo mquina --------> 05 01 00

del lenguaje assembler. Aqu ADD es el nemnico para la suma. Otros nemnicos son SUB para restar, MOV para mover, MUL para multiplicar, CMP para comparar, etc. Un programa en assembler consiste principalmente de transferencias de datos de la memoria a los registros, de operaciones sobre los datos contenidos en los registros y del movimiento de los resultados nuevamente a la memoria. Lo primero que hay que saber (segunda parte) -------------------------------------------Ms sobre los registros ----------------------Los registros del 8088 son todos de 16 bits (2 bytes). Se dividen en grupos segn los usos para los que fueron pensados. El primero de estos grupos lo constituye el de registros de trabajo o de uso general. REGISTROS DE USO GENERAL. Sirven para contener cualquier tipo de informacin. Sus nombres son AX, BX, CX y DX y pueden guardar datos de 2 bytes (1 word). Se supone que los datos del programa residen en la RAM (que es externa a la CPU). Para que el programa pueda operar con ellos debe llevarlos a los registros, operar con los registros y devolver los resultados a la memoria. EJEMPLO. Si queremos tener dos variables de 1 word (2 bytes) de largo escribimos

VALOR_1 DW 305 VALOR_2 DW 1076 Donde VALOR_1 y VALOR_2 son los nombres que les damos a las varibles, DW le informa al programa ensamblador que las variables son "Data Word", o sea van a ocupar 2 bytes cada una. Los valores 305 y 1076 son los valores iniciales que tendrn estas variables. Supongamos que queremos que nuestro programa sume las variables VALOR_1 y VALOR_2, entonces ponemos: MOV AX,VALOR_1 ADD AX,VALOR_2 ; Mover a AX el dato contenido en VALOR_1 ; Sumar a AX el dato contenido en VALOR_2

La primera instruccin (MOV) mueve el contenido de los 2 bytes de la RAM cuya direccin est dada por VALOR_1 al registro AX. Esto hace que se borre el contenido que tena AX y se reemplace por el valor 305. La segunda instruccin suma el contenido de VALOR_2 al contenido de AX y reemplaza a AX con el resultado. Si despu s queremos salvar el resultado de la suma en la RAM, ponemos: MOV RESULTADO,AX ; Mover a la variable RESULTADO el valor de AX

Para eso debimos haber definido previamente la variable RESULTADO poniendo RESULTADO DW ? Donde el signo ? le dice al programa ensamblador que no deseamos inicializar esos dos bytes de RAM. Antes de ejecutarse la 1ra instruccin: En RAM --------------VALOR_1 : 305 VALOR_2 :1076 RESULTADO : ??? En la CPU --------AX : basura

Despu s de ejecutarse la 1ra instruccin: En RAM --------------VALOR_1 : 305 VALOR_2 : 1076 RESULTADO : ??? En la CPU --------AX : 305

Despu s de ejecutarse la 2da instruccin: En RAM --------------VALOR_1 : 305 VALOR_2 : 1076 RESULTADO : ??? En la CPU --------AX : 1381

Despu s de ejecutarse la 3ra instruccin: En RAM En la CPU

--------------VALOR_1 : 305 VALOR_2 : 1076 RESULTADO : 1381

--------AX : 1381

El cdigo fuente del programa sera algo as: JMP SUMA VALOR_1 DW 305 VALOR_2 DW 1076 RESULTADO DW ? SUMA: MOV AX,VALOR_1 ADD AX,VALOR_2 MOV RESULTADO,AX RET ; Saltar por encima de la zona de datos ; Variable de 2 bytes con valor incial ; Variable de 2 bytes con valor incial ; Variable de 2 bytes sin incializar ; ; ; ; ; Nombre de la rutina AX := VALOR_1 AX := AX + VALOR_2 RESULTADO := AX Fin de la rutina

------------------------------------------------------------------------------Lo primero que hay que saber (tercera parte) -------------------------------------------Los registros de trabajo -----------------------En un mensaje anterior dijimos que los registros de trabajo sirven para contener cualquier tipo de informacin. Si bien esto es cierto, cada uno tiene una funcin especfica, para la cual fue diseado. El registro AX que estuvimos usando hasta ahora, aparece en las operaciones aritm ticas y de entrada y salida de datos. Se llama ACUMULADOR. El registro CX se utiliza como contador en operaciones iterativas. Aparece en conjunto con la instruccin LOOP para contar las veces que se ejecutar un bucle. El registro BX se utiliza especialmente para direccionar la memoria. Ejemplo: ------Queremos 'limpiar' una zona de memoria de 20 bytes llamada BUFFER, llenndola con ceros: JMP START BUFFER DB 20 DUP(?) ; Define un buffer en memoria de 20 bytes

START: MOV CX,20 ; Inicializar CX con la cantidad de bytes de BUFFER MOV BX,OFFSET BUFFER ; BX = direccin de la variable BUFFER L1: MOV B[BX],0 INC BX LOOP L1 RET ; Poner un 0 en el byte indicado por BX ; Ubicar a BX apuntando al prximo byte ; Mientras CX no sea 0, repetir ; Fin de la rutina

Cosas nuevas -----------1) MOV BX,OFFSET BUFFER OFFSET es una directiva al ensamblador (no una instruccin de la CPU) para referirnos a la direccin de una variable en vez de a su contenido. Si hubi ramos escrito MOV BX,BUFFER, estaramos guardando en BX los dos primeros bytes de nuestro buffer. 2) MOV B[BX],0 Los corchetes nos indican que nos estamos refiriendo al lugar de la memoria cuya direccin est en BX. Esta instruccin no pone un 0 en BX sino que lee el contenido de BX, lo interpreta como una direccin y pone el 0 en esa direccin. CPU MEMORIA +---------------+ 101 | ... | BX | 1 0 3 | +-------------+ +----------|----+ 102 | ... | | +-------------+ +-------------------> 103 | 0 | MOV B[BX],0 +-------------+ 104 | ... | La letra B (de BYTE) antes de [BX] es para indicar que nos referimos a un solo byte (el de direccion 103 en el ejemplo). Otra posibilidad sera W[BX] (de WORD) con lo cual hubi ramos puesto un 0 en las posiciones 103 y 104. 3) INC BX Suma 1 al contenido de BX : BX:=BX + 1 4) LOOP L1 Esta instruccin hace lo siguiente: - Decrementa el registro CX. - Se fija si CX llego o no a 0. - Si CX no es 0, salta a la direccion L1. - Sino, contina con la instruccin que sigue. Todo en una sola instruccin de lenguaje mquina!! El programa equivalente en Pascal sera: var Buffer : array[1..20] of byte; i : word; begin For i:=1 to 20 do Buffer[i]:=0; end; Nota: Hay maneras mejores de programar este tipo de rutinas. Pero ese ---- es tema del prximo mensaje. ------------------------------------------------------------------------------Lo primero que hay que saber (cuarta parte) ------------------------------------------Primero un repaso. -----------------

Cuando uno trabaja con esa notacin normalmente *no* necesita conocer el equivalente decimal del nmero. La notacin hexadecimal debe ser entendida como una manera abreviada de referirse a los bits de un byte. En assembler es conveniente pensar un byte como una tira de ocho 0s y 1s. Para no escribir tanto, esos 8 bits se dividen en dos grupos de 4 bits cada uno llamaodos nibbles. Cada nibble se representa por un dgito hexadecimal de 0 a 9 y de A a F. Hasta ahora vimos que el 8088 tiene 4 registros de trabajo AX, BX, CX y DX. Cada uno puede guardar 16 bits o dos bytes o 4 dgitos hexadecimales. Es comn llamar *word* o *palabra* a estas unidades de informacin. Por ejemplo, uno puede guardar en DX la palabra 0A13BH con la instruccin: MOV DX,0A13BH Despu s de ejecutarse esta instruccin, DX pasa a contener la tira de 16 bits correspondiente a 0A13BH, o sea 1010 0001 0011 1011. El byte ms significativo o BYTE ALTO es 0A1H y el menos significativo o BYTE BAJO es 03BH. En el 8088 es posible acceder por separado a los bytes altos y bajos de cada uno de los registros de trabajo (no as de los otros registros que veremos ms adelante). Ejemplos: MOV BL,DH MOV DH,DL MOV AL,0

Estas instrucciones operan sobre bytes en lugar de words. La tercera pone a cero el byte bajo de AX. (Aqu H es por High, no por Hexa. L es por Low). -----------------------------------------------------------------------UN CERO A LA IZQUIERDA ---------------------Como habrn visto, siempre que escribimos un nmero hexadecimal ponemos un cero a la izquierda del nmero y una H a la derecha. Ejemplo 0A1BH. Si no lo hici ramos no podramos distinguir entre cosas como: MOV BL,AH y MOV BL,0AH. La primera instruccin copia el registro AH al BL, la segunda copia la constante 0AH (10 decimal) al mismo registro. Hay sin embargo una redundancia que es la 'H' porque podramos convenir en que toda cantidad que empiece en '0' es hexadecimal. De hecho el A86 usa esta convencin, no as otros ensambladores como MASM. A pesar de esa redundancia tal vez sea una notacin til para poner ms en evidencia que se trata de cantidades hexadecimales. Por ejemplo 013 para el A86 es igual a 013H pero con la segunda notacin nos aseguramos de que nadie piense que estamos hablando del 13 decimal. -----------------------------------------------------------------------Con respecto a otras variantes de la instruccin MOV digamos que tambi n es posible volcar el contenido de un registro en memoria. Ejemplos: MOV [0100H],DX MOV BX,DX

En la primera instruccin, la palabra contenida en DX se transfiere a los dos bytes de memoria cuyas direcciones son 0100H y 0101H. En la segunda, el contenido de DX se copia en BX. Miremos con ms detalle la instruccin MOV [0100H],DX.

----------------------------------------------------Aqu se trata de mover 2 bytes desde un registro de la CPU al lugar de RAM indicado por 0100H. Bien, los dos bytes a mover son los contenidos de DH y DL y las dos posiciones de memoria son la nmero 0100H y la nmero 0101H. Pero cul byte va en cul direccin? el byte bajo va en la direccin ms baja y el alto en la ms alta: DL en 0100H y DH en 0101H. CPU +----+----+ MOV [0100H],DX DX | A1 | 3B | ----------------> 0100 +----+----+ DH DL 0101 RAM +----+ | 3B | +----+ | A1 | +----+

Esto al principio puede parecer antinatural ya que uno tendera a guardar primero A1 y despu s 3B. Sin embargo es una convencin usada por muchas CPU (Yo particularmente nunca supe el por qu esta convencin, si alguien sabe o se le ocurre algo avise!). Esta convencin tambi n hay que respetarla cuando se efectan movimientos de datos en sentido inverso. Ejemplo: MOV DX,[0100H] copiar en DL el byte de la direccin 0100H y en DH el de la direccin 0101H. Por supuesto, no tiene sentido una operacin del tipo MOV 0A13BH,DX que trata de mover el contenido de DX en una constante. Convertir un valor a un string -----------------------------Supongamos que queremos mostrar en pantalla el contenido de AL (el byte bajo del registro AX). En ese caso vamos a necesitar convertir el valor de AL a un string. Por ejemplo si AL = 01AH, tenemos que imprimir el string de dos caracteres '0', '1' y 'A' y a continuacin la letra 'H' que indica notacin hexadecimal. El '0' y la 'H' son constantes, no dependen del contenido de AL as que concentremonos en '1' y 'A'. El '1' corresponde al nibble alto de AL y la 'A' al bajo. La idea es asilar el nibble alto, convertirlo a ASCII e imprimirlo, despu s aislar el bajo y hacer lo mismo. Aislar el nibble alto de AL --------------------------Para hacer esto tenemos que usar una instruccin que todava no habamos visto SHR que significa SHift Right (correr hacia la derecha). Esta instruccin bsicamente mueve hacia la derecha la tira de bits del operando: +----------------------+ 0-->| los bits se corren |--> y el ltimo se cae +----------------------+ a un lugar que se Bits: 7->6->5->4->3->2->1->0 llama CARRY que por ahora no nos interesa.

Ejemplo: ------Si AL = 01AH, la instruccin SHR AL,1 desplaza los bits de modo que despu s de ejecutada AL = 05H. +---------+ AL |0001 1010| = 01AH +---------+ | SHR AL,1 | | +---------+ AL |0000 0101| = 05H +---------+ Bien si aplicamos 4 veces la instruccin SHR AL,1 entonces lo que estamos haciendo es desplazar el nibble alto hacia el bajo y llenando con 0s los 4 bits del nibble alto: Si AL = 01AH, entonces despu s de: SHR SHR SHR SHR AL,1 AL,1 AL,1 AL,1

AL = 001H. Esto es lo que llamamos asilar el nibble alto de AL. Aislar el nibble bajo de AL --------------------------Aqu la cosa es mucho ms sencilla porque existe una instruccin que hace el trabajo y es AND. Esta instruccin efecta un AND lgico bit a bit entre sus dos operandos (un AND lgico entre dos bits es igual a la multiplicacin de los bits) Para aislar el nibble bajo, lo nico que tenemos que hacer es reemplazar el alto por ceros. Luego, a cada bit del nibble alto lo multiplicamos por 0 y a cada bit del bajo por 1. Esto corresponde a efectuar un AND con la constante binaria 0000 1111 que en hexadecimal se escribe 0FH. Luego si AL = 01AH, despu s de AND AL,0FH AL = 0AH AL=01AH +---------+ |0001 1010| +---------+ Cte. 0FH +---------+ |0000 1111| +---------+

AL

\ / AND AL,0FH | +---------+ |0000 1010| = 0AH +---------+

Convertir AL a un ASCII

----------------------Supongamos ahora que ya hemos aislado un nibble. Entonces AL tiene 0s en su nibble alto y un valor en su nibble bajo. Vamos a convertir ese valor a un caracter ASCII imprimible en pantalla. Ese caracter ASCII debe corresponder al del dgito hexadecimal representado por el nibble. Si el nibble bajo es 0001 debemos imprimir '1' 0010 '2' ... ... 1000 '8' 1001 '9' 1010 'A' 1011 'B' ... ... 1111 'F' Esto se puede hacer con una tabla: TABLA_HEXA DB '0123456789ABCDEF' La idea es usar el valor de AL como ndice a la tabla TABLA_HEXA y obtener de ah el dgito correspondiente. En assembler esto es trivial. Existe una instruccin que se llama XLAT (por trans LATe) que hace exactamente eso. La direccin de la tabla debe estar en el registro BX. El cdigo es MOV BX,OFFSET TABLA_HEXA XLAT y listo, AL sale con el valor ASCII correspondiente. TABLA_HEXA +--+ |30| '0' +--+ |31| '1' +--+ +--+ |39| '9' +--+ |41| 'A' +--+ | |

+----+ AL | 0E | +----+ | XLAT +-------------+ | accedera a esta direccin: BX | + | AL | | +------>

+--+ |45| 'E' +------< +--+ | |46| 'F' +-------------+ +--+ | +----+ AL | 45 | : 'E' +----+

Poner todo junto. ---------------PRINT_AL:

MOV AH,AL SHR AL,1 SHR AL,1 SHR AL,1 SHR AL,1 MOV BX,OFFSET TABLA_HEXA XLAT CALL SHOW_AL MOV AL,AH AND AL,0FH XLAT CALL SHOW_AL RET

; ; ; ; ; ; ; ; ; ; ; ;

Copiar AL en AH para despu s Ver explicacin arriba de como aislar el nibble alto de AL con 4 instrucciones SHift Right BX = direccin de la tabla Ver explicacin arriba Emitir ASCII del nibble alto Recuperar AL de AH Aislar nibble bajo Ver explicacin arriba Imprimir ASCII del nibble bajo

SHOW_AL es la rutina que imprime en pantalla el ASCII dado en AL. Hay muchas maneras de hacer esto. Ahora damos una, en un prximo mensaje discutiremos otras. SHOW_AL: MOV DX,AX ; Copiar AX en DX MOV AH,02H ; Funcin para imprimir caracter en DL INT 021 ; LLamar al DOS para que imprima MOV AX,DX ; Recuperar AX de DX RET ------------------------------------------------------------------------------Lo primero que hay que saber (quinta parte) ------------------------------------------Los Registros de Segmento ------------------------Ya vimos los registros de trabajo AX, BX, CX y DX. Ahora vamos a ver otro grupo de 4 registros de la CPU que se llaman de *segmentos*: DS ES CS SS Data Segment Extra Segment Code Segment Stack Segment

Los dos primeros (DS y ES) se usan para acceder a los datos de la memoria, los dos ltimos (CS y SS) tienen ms que ver con el cdigo del programa. El Registro DS -------------En mensajes anterior discutimos instrucciones como MOV [0100H],DX. Dijimos que los dos bytes contenidos en DX se copian a los bytes de memoria con direcciones 0100H y 0101H. Bien, esta es una verdad incompleta. Lo que realmente ocurre es algo un poco ms complejo que se llama segmentacin y que vamos a describir ahora. En las instrucciones, todos los direccionamientos son en realidad *desplazamientos* (en ingl s *offsets*) *relativos* a un lugar de memoria que es el comienzo del segmento de datos. De ese modo todas las referencias a los datos se hacen *con respecto* a un segmento. La sintaxis MOV [0100H],DX sobreentiende que la direccin aludida 0100H *no* se refiere a la posicin absoluta 0100H (256 decimal) de memoria, sino a la posicin que se encuentra 256 bytes ms arriba del comienzo del segmento.

MEMORIA +------+ | | +------+ | | ... SEGMENTO DE DATOS +------+ PRIMER BYTE DEL SEGMENTO --> | | +------+ | | +------+ | | +------+ ... | | +------+ | | +------+ ULTIMO BYTE DEL SEGMENTO --> | | +------+ ... | | +------+ OFFSET 0000H 0001H 0002H

FFFEH FFFFH

Los datos dentro del segmento se ubican por medio de su offset. El offset es un valor de 16 bits (2 bytes). En notacin hexadecimal el offset est compuesto por 4 cifras. Por lo tanto los posibles offsets cubren un rango de 64 Kbytes desde 0000H hasta 0FFFFH. El primer byte del segmento tiene offset 0, el siguient 1, y as siguiendo hasta llegar al ltimo byte del segmento que tiene offset 65535 (0FFFFH). El criterio para fijar el comienzo del segmento de datos es el siguiente: Cualquier posicin de memoria puede ser el comienzo de un segmento con la ncia condicin de que su direccin absoluta sea mltipLo de 16. En hexadecimal 16 corresponde a 010H por lo tanto, cualquier direccin ablsoluta cuyo valor hexadecimal termine en 0 puede ser definida como el comienzo del segmento de datos. El 8088 maneja direcciones absolutas de 20 bits (5 cifras hexa). Esto significa que los comienzos de segmentos de datos tienen que tener direcciones absolutas formadas por 4 cifras hexadecimales seguidas de la cifra 0. Ejemplos de direcciones absolutas de comienzos de segmentos *vlidos* 00000H 00400H 0B800H Ejemplos de direcciones absolutas que *no* pueden ser comienzos de segmentos: 00208H A0001H

DIREECIONES ABSOLUTAS: +------------------------------------------------+ | EL 8088 MANEJA DIRECCIONES DE 20 BITS | | O SEA, CADA DIRECCION DE MEMORIA | | ESTA COMPUESTA POR 5 CIFRAS | | HEXADECIMALES | | | | PRIMERA DIRECCION DE MEMORIA: 00000H | | EJEMPLOS DE OTRAS DIRECCIONES: 0A178H, 00465H | | ULTIMA DIRECCION DE MEMORIA: FFFFFH | +------------------------------------------------+ DIRECCIONES LOGICAS: +------------------------------------------------+ | LA MEMORIA TIENE UNA DIVISION LOGICA | | (NO FISICA) EN SEGMENTOS DE 64 KB. | | | | EL SEGMENTO DE DATOS PUEDE COMENZAR EN | | CUALQUIER POSICION CON DIRECCION | | ABSOLUTA TERMINADA EN 0. | | (MULTIPLO DE 16) | | | +------------------------------------------------+ Una vez definido el comienzo del segmento de datos, uno tiene acceso a los 64 Kb del segmento con un offset que va desde 0000H hast FFFFH. En las instrucciones solamente se indica el offset, el comienzo del segmento se sobreentiende. Como se fija el principio del segmento de datos? ------------------------------------------------Muy fcil. Dado que la ltima de las 5 cifras hexadecimales de la direccin absoluta del comienzo de un segmento debe ser 0, resulta que lo que hay que fijar es el valor de las primeras 4. Este valor se pone en DS. Ejemplos: PRINT_ARROBA: MOV AX,0B800H MOV DS,AX MOV AL,'@' MOV [0],AL RET SCROLL_LOCK: MOV AX,040H MOV DS,AX MOV AL,[017H] OR AL,BIT 4 MOV [017H],AL RET ; ; ; ; Cambiar por 0B000H si H rcules Comienzo del Data Seg en B8000H (abs) Caracter a imprimir en pantalla Ponerlo en el offset 0 del segmento

; ; ; ; ;

Ya hablaremos del segmento 040H lo usa la BIOS para sus variables En el OFFSET 017H guarda los SHIFT STATUS El bit 4 corresponde a SCROLL LOCK Encender la lucesita del teclado

NOTA: en los dos ejemplos se pasa el valor a DS por medio de AX. Esto se debe a que *no* existen instrucciones como MOV DS,0B000H o MOV DS,040H.

Resumiendo. ---------Para ubicar un dato en la memoria se necesita conocer dos cosas (1) el comienzo del segmento y (2) el offset dentro del segmento. La CPU conoce la direccin absoluta del comienzo del segmento agregando un cero al valor de DS. El offset dentro del segmento lo proporciona el programa cada vez que quiere hacer un acceso a memoria. ------------------------------------------------------------------------------Lo primero que hay que saber (sexta parte) -----------------------------------------Los registros de segmento (continuacin) ---------------------------------------En el mensaje anterior vimos que 1) La CPU necesita 5 cifras hexadecimales para obtener una direccin de memoria. Por lo tanto para acceder a un byte de la RAM se debe proporcionar esas 5 cifras. 2) En las instrucciones de lenguaje mquina esas 5 cifras no se suministran explcitamente. En cambio de eso aparece un OFFSET o DESPLAZAMIENTO relativo al comienzo de un SEGMENTO. La CPU obtiene internamente la direccin absoluta del comienzo del segmento agregando la cifra hexadecimal 0 a las cuatro cifras hexa del REGISTRO DE SEGMENTO. Ejemplo: ------Supongamos DS = 1B14H. Entonces la instruccin MOV AL,[080H] copia en AL el byte cuya direccin absoluta es +------------------------ SEGMENTO | +---------------- OFFSET 1B140H + 80H = 1B1C0H ----- DIRECCION Notacin y terminologa. ----------------------Esta forma segmentada de calcular posiciones de memoria, es decir con un OFFSET contado a partir de un SEGMENTO se simboliza frecuentemente en la forma SEGMENT:OFFSET donde SEGMENT es un nmero Hexadecimal de 4 cifras que indica al segmento aludido. La direccin absoluta de memoria del comienzo del segmento se obtiene agregando la cifra hexadecimal 0 a las 4 cifras que componen el valor de SEGMENT. OFFSET es el valor del desplazamiento (considerado positivo) a partir del inicio del segmento en donde se encuentra la posicin en cuestin. Ejemplos: -------La notacin 0040H:0017H 1B14H:0080H

corresponde a la

direccin absoluta 00417H 1B1C0H

La cuenta que internamente realiza la CPU es la siguiente SEGMENT*10 + OFFSET ------------DIRECCION ABS 00400H + 0017H ------00417H + 0080H ------1B1C0H 1B140H

El registro ES -------------El registro EXTRA es, como su nombre lo indica, un servir para acceder a posiciones de memoria que no segmento apuntado por el valor actual de DS. Tambi especfico en el juego de instrucciones de cadenas (strings) que veremos ms adelante.

registro que puede caen dentro del n tiene un uso de caracteres

Supongamos que nuestro registro DS apunta a una zona de memoria cualquiera en donde tenemos nuestro segmento de datos. Supongamos que necesitamos acceder a una posicin de memoria que est fuera de los lmites de ese segmento. Entonces podemos hacer que ES apunte a ese segmento particular para hacer nuestro acceso especial. En la prctica hay dos segmentos especiales a los cuales un programa necesita acceder ocasionalmente. Uno de ellos es el segmento donde se encuentra la memoria de Video, el otro es el segmento donde la BIOS almacena sus variables. Ejemplo del uso de ES para acceder a Video RAM ---------------------------------------------La ubicacin de la memoria de video depende del adaptador que est instalado. En la H rcules, es el segmento 0B0000H, en las dems es el 0B8000H. En una pantalla de 80x25 en modo texto, el primer byte del segmento (el que tiene offset 0) contiene el ASCII del caracter que se encuentra en la primera fila y la primera columna de la pantalla. El byte siguiente se llama de ATRIBUTO su valor sirve para determinar el color con el que se visualiza el caracter. En la H rcules (sin color) el atributo dice se el carcter est en Inverso, Subrrayado, Titilante, Intenso, Normal o alguna combinacin de stos. Ejemplo: ------Hacer titilar el primer caracter de la pantalla MOV AX,0B000H MOV ES,AX OR ES:[1],10000000B RET ; Suponemos H rcules ; ES apunta al segmento de video RAM ; Si bit 7 = 1, atributo BLINKING

Existen otras formas equivalentes de escribir OR ES:[1],10000000B Una de ellas es en dos lneas ES: OR [1],10000000B ; Valor binario

que es la forma que usa el programa DEBUG. La otra es ES OR [1],BIT 7 que es la que se puede usar en el A86. Ejemplo del uso de ES para acceder a varibles de la BIOS -------------------------------------------------------Las variables utilizadas por las rutinas de la BIOS estn en el segmento 00400H. Este valor tiene su razn de ser y paso a explicarla. La primera direccin de memoria es la 00000H, a partir de ah existe una tabla de 256 direcciones correspondientes a rutinas especiales del sistema que se llaman INTerrupciones y que estn explicadas en otros mensajes. Sin entrar en detalles sobre el tema de las interrupciones, digamos que por tratarse de direcciones, cada uno de las 256 entradas de la tabla consta de 4 bytes, 2 para el OFFSET y dos para el SEGMENT. Bien, dado que 256 es 0100H, toda la tabla ocupa 4*0100H = 0400H. Como la primera direccin de la tabla es la 0000H, entonces la primera posicin siguiente a la tabla (y fuera de ella) es la 0400H, es decir el comienzo del segmento 040H. Bueno, es justamente en ese segmento en donde la BIOS guarda sus varibles. Ejemplo: ------Determinar la posicin del cursor. MOV MOV MOV MOV MOV RET AX,040H ES,AX AX,ES:[050H] COLUMNA,AL FILA,AH ; ; ; ; ; Recordar que no se puede hacer MOV ES,040H y por eso se pasa el valor mediante AX Leer la posicin del cursor Salvar nmero de columna en una variable Salvar nmero de fila en otra variable

Este ejemplo es un poco ms interesante que el anterior. Las variables de nuestro programa (COLUMNA y FILA) se encuentran en el segmento de datos apuntado por DS por lo tanto las instrucciones MOV COLUMNA,AL y MOV FILA,AH copian los valores de AL y AH en las posiciones de memoria del segmento de datos cuyos offsets estn representados simblicamente por COLUMNA y FILA. En cambio, dado que ES seala al segmento 040H, la instruccin MOV AX,ES:[050H] copia en AX los dos bytes que se encuentran en los offsets 050H y 051H del segmento 040H (no del segmento de datos apuntado por DS). Digamos por ltimo, que si uno tiene que hacer varios accesos a un segmento que est fuera del segmento de datos, conviene reeplazar momentaneamente el valor de DS por el del segmento al que se desea acceder y luego restablecer el valor de DS. Esto puede resultar ms cmodo y elegante que anteponer repetidamente ES: a los offsets de las direcciones en cuestin. ------------------------------------------------------------------------------Lo primero que hay que saber (s ptima parte) -------------------------------------------

Los Registros de Segmento (continuacin) ---------------------------------------Nos queda por ver dos registros de segmento CS y SS. Mientras los registros DS y ES sirven para ubicar datos dentro de un segmento, CS sirve para definir el segmento del cdigo ejecutable. El registro SS es el de *pila* y lo vamos a ver con detenimiento en el prximo mensaje. El registro CS -------------Dado que el cdigo ejecutable de un programa reside en la memoria, la CPU usa la misma forma de direccionamiento segmentado para ubicar las diferentes instrucciones que lo componen. Dentro de la memoria conviven muchas programas. En la zona ms baja estn los residentes, luego viene el espacio disponible para los programas de aplicacin y finalmente en las zonas ms altas se encuentran las rutinas de la BIOS y el DOS. Todo esto abarca un total de 1 Mb de memoria y en consecuencia las diferentes porciones de cdigo se encuentran en distintos segmentos. Para seguir el flujo de un programa, la CPU apunta a la prxima instruccin mediante dos registros internos CS e IP. El registro CS contiene el SEGMENT y el IP (Instruction Pointer) el OFFSET de la instruccin dentro del segmento de cdigo. De esta forma la direccin de la prxima instruccin a ejecutar se puede simbolizar por CS:IP. Cuando uno necesita efectuar un salto hacia otra zona del programa hace cosas como: VER_SI_ES_DIGITO: CMP AL,'0' JB L1 CMP AL,'9'+1 CMC L1: RET ; ; ; ; ; Compara AL con 030H = ASCII de '0' Si es menor, no era. Salir. Si > o =, comparar con el sgte a '9' Invertir el resultado de la comparacin

Los nombres VER_SI_ES_DIGITO y L1 son rtulos o etiquetas (en ingl s "labels") que el programador pone como nombres simblicos. Le indican al programa ensamblador que reemplace cada referencia a ellos por la direccin que representan segn su ubicacin. Observar los dos puntos ":" a continuacin del nombre. Esta rutina puede usarse para determinar si el byte contenido en AL corresponde al ASCII de un dgito decimal: CALL VER_SI_ES_DIGITO ; Ver si AL contiene ASCII de dgito decimal JB NO_ES ; Si sale comparacin por menor, no es ... ; Si sale comparacin por > o =, s es Por ejemplo si VER_SI_ES_DIGITO cae en la direccin 0100H, entonces La instruccin Ocupa las direcciones ------------------------------------------------------CMP AL,'0' 0100H 0101H JB L1 0102H 0103H

CMP AL,'9'+1 CMC RET

0104H 0106H 0107H

0105H

Por lo tanto L1 cae en la direccin 0107H y la instruccin JB L1 efectuar un salto condicional a 0107H, dependiendo del valor de AL. 1) Cuando se comienza a ejecutar VER_SI_ES_DIGITO, CS tiene el valor correspondiente al inicio del SEGMENTo de cdigo. Su valor *no* le interesa al programador. La instruccin CMP AL,'0' est en la direccin 0100H (=256 decimal) posiciones ms arriba del comienzo del segmento. Bien, en ese momento el registro IP vale 0100H. El programa fuente se *desentiende* del registro IP. 2) Cuando se ejecuta la instruccin CMP AL,'0' el valor de IP se incrementa en 2 (ya que CMP AL,'0' ocupa 2 bytes). Ahora la CPU ejecuta la instruccin cuya direccin est dada por el offset 0102H (en el segmento de cdigo). Esta instruccin es JB L1 (Jump if Below). Produce un salto condicional a la direccin simbolizada por L1 que como vimos es 0107H. La condicin para que el salto se efecte es el resultado de la comparacin anterior (salta si es menor). 3) Si AL era menor que '0', entonces se produce un salto a la direccin 0107H. En otras palabras, JB L1 modifica internamente el contenido de IP y lo pone en 0107H. De ese modo, la prxima instruccin que se ejecutar ser la que est en el offset 0107H del segmento de cdigo: RET que indica el fin de la rutina. Si AL era mayor o igual que '0', entonces no se efecta el salto y el contenido de IP pasa a ser 0104H. En este caso la prxima instruccin es CMP AL,'9'+1. Dado que el ASCII del caracter 9 es 039H, esta instruccin compara el contenido de AL con 03AH. Ahora tenemos que avisar que AL no era un dgito decimal en el caso que el resultado de la comparacin sea "mayor o igual". En este caso invertimos el resultado de la comparacin con la instruccin CMC (CoMplemnt Carry) que invierte ese resultado (veremos en un prximo mensaje los detalles sobre el Carry y otros Flags) Ninguna de estas instrucciones altera el valor de CS. Es decir el flujo del programa se mantiene dentro del segmento y pasa de una instruccin a otra en el mismo segmento. El registro que cambia es IP y su modificacin la realiza la CPU a medida que ejecuta las diferentes instrucciones. Otra cosa. Si el programador supiera que L1 caer en el offset 0107H, podra poner JB 0107H en lugar de JB L1. Pero obviamente JB L1 es ms prctico. Ahora bien. Supongamos que el programa necesita acceder a una rutina que est en otro segmento. Esto se puede deber a dos motivos: 1) El programa de aplicacin es demasiado largo para entrar en un solo segmento de 64 K bytes.

2) El progrma necesita hacer una llamada a un programa residente en otro segmento (ya sea un TSR - Terminate and Stay Resident- o a una rutina de la BIOS o el DOS) En esos casos, lo que se hace es un salto FAR (lejano). En este tipo de saltos, no solamente se indica el OFFSET del comienzo de la rutina a ejecutar sino el SEGMENTo de cdigo correspondiente. Cuando se ejecuta un salto FAR, la CPU cambia el contenido de ambos registro CS e IP de acuerdo con lo indicado por la instruccin. Ejemplos: -------WARM_BOOT: MOV AX,040H MOV DS,AX MOV AX,01234H MOV [072H],AX JMP FAR 0FFFFH:0 COLD_BOOT: MOV AX,040H MOV DS,AX MOV [072H],AX JMP FAR 0FFFFH:0 El registro instruccin 040H:072H y segmento en

; ; ; ; ; ; ; ; ;

Pasar al segmento 040H de datos a trav s de AX - BIOS RAM Clave para BOOT tipo Ctrl-Alt-Del debe ir en offsets 72H/72H del seg. 40H Salto FAR, cambian CS e IP a FFFFH y 0 Parecida a la anterior pero produce un Booteo tipo Botn Reset o de encendido Siempre que en 72H/72H no haya 1234H Que es ms profundo que el anterior

de datos apunta a 040H y por lo tanto en ambas rutinas la MOV [072H],AX se refiere a la posiciones de memoria 040H:073H. Mientras tanto el segmento de cdigo CS al dnde est nuestra rutina.

A medida que se ejecutan las instrucciones, CS permanece constante e IP aumenta justo lo necesario para pasar al offset de la prxima instruccin. Finalmente la instruccin de salto incondicional JMP FAR 0FFFFH:0 modifica tanto CS como IP poniendo CS=FFFFH e IP=0. ------------------------------------------------------------------------------Lo primero que hay que saber (octava parte) ------------------------------------------Los registros de segmento. El registro SS. -----------------------------------------El ltimo registro de segmento que nos queda por ver es el SS: Stack Segment o Segmento de Pila. Para comprender la funcin que cumple el registro SS necesitamos saber que es el Stack. El stack es una zona de la memoria principal (RAM). Algunas instrucciones del lenguaje mquina necesitan salvar temporalmente datos en la memoria para recuperarlos ms tarde. Por lo tanto hay que indicarle a la CPU qu zona de memoria puede usar para guardar esas varibles temporales. Por ejemplo, la instruccin CALL del lenguaje mquina del 8088 se usa para hacer una llamada a una subrutina. La idea es que cuando el micro encuentra una instruccin CALL SUBRUTINA

altera momentneamente el flujo secuencial del programa, y salta a la (direccin) SUBRUTINA. La subrutina efecta su trabajo y termina con la instruccin RET. Entonces la CPU vuelve su atencin a la instruccin siguiente a CALL SUBRUTINA y contina secuencialmente. Lo que se hace es destinar una porcin de la memoria para este fin. Esa zona especial se llama stack. Cuando la CPU accede al stack lo hace a trav s de dos registros SS y SP. El registro SP se llama Stack Pointer y contiene el offset dentro del segmento indicado por SS que corresponde a la direccin que se desea acceder. En el ejemplo de abajo se quiere imprimir el contenido del registro AL como dos cifras hexadecimales consecutivas: una correspondiente al nibble alto de AL y la otra al nibble bajo. Por ejemplo si AL contiene el valor 1DH, habr que imprimir el caracter '1' y a continuacin el caracter 'D'. Como se va a imprimir dos veces, creamos una subrutina de impresin: PRINT_CHAR. Esta subrutina se encarga de imprimir el caracter cuyo ASCII est en AL. JMP MOSTAR_AL TABLA_HEX DB '01234567890ABCDEF' MOSTRAR_AL: MOV AH,AL SHR AL,1 SHR AL,1 SHR AL,1 SHR AL,1 MOV BX,OFFSET TABLA_HEX XLAT CALL PRINT_CHAR MOV AL,AH AND AL,0FH XLAT CALL PRINT_CHAR RET PRINT_CHAR: MOV DX,AX MOV AH,02 INT 021H MOV AX,DX RET ; ; ; ; ; ; ; ; ; ; ; ; Salvara AL en AH Pasar el nibble alto de AL al bajo en el 80286 se puede poner SHR AL,4 Es necesario para la instuccin XLAT reemplaza 0 por '0',..,0FH por 'F' Imprimir caracter en AL Recurperar AL Aislar nibble bajo de AL convertir valor a cifra hexa Imprimir cifra hexa del nibble bajo ; Saltar por encima de los datos

; ; ; ; ;

Salvar AX en DX Funcin para imprimir caracter en DL imprimirlo a trav s del DOS Recuperar AX Volver a quien haya llamado

Cuando comienza la ejecucin de MOSTRAR_AL, CS:IP apunta a la direccin de memoria donde se encuentra la primera instruccin de la rutina, es decir MOV AH,AL. A medida que las instrucciones se van ejecutando IP aumenta la cantidad de bytes que ocupa cada instruccin. De ese modo cuando termina la ejecucin CS:IP est apuntado a la prxima instruccin. Cuando CS:IP llega a CALL PRINT_CHAR se debe *saltar* a PRINT_CHAR. Pero como CALL se usa para ir_a_ejecutar_y_volver la CPU debe recordar cul es la direccin de la instruccin que sigue fsicamente al CALL. Cuando la CPU termin de leer la instruccin CALL, el registro IP contiene el offset de la instruccin que sigue fsicamente a CALL.

Internamente la instruccin CALL produce lo siguiente: 1. Salva el contenido de IP en el stack 2. Reempla IP por el offset de la subrutina (en este caso PRINT_CHAR) Luego de esto, CS:IP apunta a PRINT_CHAR. Se van ejecutando una a una las instrucciones de PRINT_CHAR y a medida que IP aumenta. Cuando CS:IP llega a RET entonces internamente pasa lo siguiente: 1. Se reemplaza el valor actual de IP por el valor que se encuentra en el stack. de este modo CS:IP vuelve a apuntar a la direccin siguiente al CALL y el programa contina su flujo normal. Observar que la memorizacin de la direccin de retorno es temporal: una vez que se us ya no se necesita ms y puede ser liberada. La cuestin fundamental es la siguiente. Cmo sabe la CPU en que lugar del stack debe depositar la direccin de retorno y, ms tarde, dnde debe ir a leer el dato que antes deposit en el stack? El stack no podra tener lugar para una sola direccin. Para ver esto imaginemos una situacin en la que la RUTINA_1 llama a la RUTINA_2 y esta a su vez llama a la RUTINA_3. En este caso la CPU necesita guardar la direccin de retorno de RUTINA_2 a RUTINA_1 en un lugar y la direccin de retorno de RUTINA_3 a la RUTINA_2 en otro. Por otro lado las direcciones de retorno deben liberarse inmediatamente despu s que hayan sido ledas porque de otro modo estaran ocupando lugar del stack innecesariamente. Para resolver esto la CPU usa una t cnica especial, simple e ingeniosa que paso a describir. Cuando el DOS carga un progrma en memoria y le transfiere el control SS contiene el valor del segmento de memoria donde va a estar el stack y SP tiene el valor 0FFFEH. Esto significa que SS:SP apunta a la ltima word del stack (los dos bytes con direcciones 0FFFEH y 0FFFFH). Esa word contiene cierta informacin que no vamos a discutir en este momento. El programa comienza a ejecutarse. Cuando se llega al primer CALL pasa lo siguiente: 1. SP se *decrementa* en 2 (pasando a valer 0FFFCH) 2. La CPU guarda la direccin de retorno en la word con direccin SS:SP; es decir, en los 2 bytes con offsets 0FFFCH y 0FFFDH. Continua la ejecucin de la subrutina hasta que sta a su vez llama a otra subrutina, aumentando en 1 el orden de anidamiento. Entonces nuevamente: 1. SP = SP - 2 2. [SS:SP] <- direccin de retorno De este modo, cada vez que se profundiza el anidamiento a trav s de un CALL, internamente la CPU ejecuta los pasos 1. y 2. El stack se va llenando desde el fondo hacia adelante, es decir desde

las direcciones altas de memoria hacia las ms bajas en forma descendente a lo largo del segmento reservado al stack. Este descenso es acompaado por el registro SP. Este registro en todo momento apunta a la ltima word almacenada en el stack. Cuando se llega al ltimo nivel de anidamiento, SP est apuntando al lugar del stack donde est guardada la direccin de retorno al nivel inmediato superior. En ese momento se ejecuta la instruccin RET. Esta hace el trabajo inverso al que hizo CALL: 1. IP = [SS:SP] (pone en IP la word contenida en los bytes con offsets SP y SP+1) 2. SP = SP + 2 Esto produce la liberacin de los dos bytes ms bajos del stack y un salto al nivel inmediato superior. De ese modo, cada RET permite salir de un nivel y RETornar al nivel de arriba en el orden inverso al que se fue descendiendo a traves de las llamadas anidadas hacia subrutinas. Cuando el progrma ternina, nuevamente SP tiene el valor 0FFFEH y DOS recupera su palabra de retorno que se mantuvo intacta a lo largo de la ejecucin del programa. Hay una manera excelente de comprender esta t cnica: verla funcionar. Para eso recomiendo FUERTEMENTE a quien lea esto que ensamble el programa de ejemplo de arriba con el A86 y que despu s lo ejecute paso a paso desde el D86. Para hacer esto tiene que: 1) Copiar el programa a un archivo de texto (formato ASCII) 2) Salvar el archivo de texto con un nombre cualquiera, por ejemplo PRUEBA.8 (en el A86 se acostumbra a usar la extensin .8 para los fuentes.) 3) Salga al DOS y escriba A86 PRUEBA.8 <Enter>

4) Si copi bien no debera producirse ningn error. Si hay error entrar nuevamente al fuente. A86 insterta llamadas de atencin en los lugares donde estn los errores. 5) Una vez que el ensamblado salga bien ejecutar desde el DOS D86 PRUEBA.COM 6) Una vez adentro del D86, ejecutar las instrucciones 1 a 1 pulsando la tecla F1. 7) Observar como cambian los registros. Especialmente el IP y el SP, y observar como los datos son enviados al stack cuando se ejecuta un CALL y son retirados del stack cuando se ejecuta un RET. 8) Para salir del D86 pulsar <Alt> x, o escribir Q <Enter>.

Buena suerte! ------------------------------------------------------------------------------Lo primero que hay que saber (novena parte) ------------------------------------------Los Registros de Segmento (Ultima parte). ----------------------------------------Cmo usar el Stack. ------------------En el mensaje anterior vimos que la CPU usa para s una zona de memoria llamada stack, guardando en ella las direcciones de retorno cuando encuentra una instruccin CALL a una subrutina. Cada vez que el programa encuentra una instruccin CALL, automticamente la CPU se encargar de *apilar* la direccin de la instruccin siguiente (direccin de retorno). Es decir, decrementar el registro SP (Stack Pointer) en 2 y guardar en las direcciones SS:SP y SS:SP+1 el offset de la direccin de retorno. Ms tarde, retorno al (liberando continuar cuando la CPU encuentre un RET, copiar la direccin de registro IP (Instruccion Pointer) e incrementar SP en 2 2 bytes de la pila). De ese modo el flujo del programa en la instruccin siguiente a la del CALL.

Vimos que este mecanismo aseguraba que las rutinas pueden llamarse unas a otras produciendo diferentes niveles de anidacin. Dado que la pila tiene su propio registro de segmento SS, es posible destinarle 64 Kb completos. Como cada nivel de anidamiento produce la utilizacin de 2 bytes de la pila, obtenemos una capacidad de anidacin de ms de 32000 niveles. Toda una exageracin. Sin embargo, la pila no solamente se usa para guardar las direcciones de retorno, tambi n se usa para guardar variables. Por ejemplo, los lenguajes de alto nivel como el C y el Pascal, guardan en la pila los parmetros que una rutina le pasa a una subrutina. En Pascal, cuando la subrutina toma el control ejecuta la instruccin BEGIN. Esta instruccin hace lo siguiente: PUSH BP MOV BP,SP ; Apilar BP ; Copiar SP en BP

1. PUSH BP, "apila" el contenido del reistro BP. Si una rutina (madre) llama a una subrutina (hija), cuando la hija recibe el control, los 2 bytes ms accesibles de la pila contienen la direccin de retorno a la madre. Cuando se apila BP, se agregan 2 bytes a la pila. La instruccin PUSH se encarga de agregar esos dos bytes a la pila y de decrementar en 2 el registro SP: Antes del PUSH BP |Retorno|Parmetros pasados por la madre|Resto del stack +-------+-------------------------------+--------------| memoria alta ---> SP ---->+ Despu s del PUSH BP

|CopiaBP|Retorno|Parmetros pasados... +-------+-------+-------------------------| memoria alta ---> SP ---->+ 2. Depu s de copiar SP en BP, si el primer parmetro es de 2 bytes (ejemplo), el Pascal hace algo as: MOV AX,[BP+4] ; Saltar sobre Copia de BP y dir de Retorno

Observacin: -----------No hay que olvidar que los parmetros estn el segmento apuntado por SS. La CPU asocia por defecto el registro de segmento SS al registro BP cuando se lo usa como en la instruccin MOV de arriba. Por este motivo se usa el registro BP y no otro. Si pusi ramos MOV AX,[SI+4] la CPU copiara en AX los 2 bytes con offset SI+4 del segmento apuntado por DS. (En los programas .EXE los registros DS y SS normalmente contienen valores diferentes, en los .COM no.) -------------------------Fin observacin----------------------En assembler, el uso ms frecuente que se hace de la pila es para preservar el contenido de registros que van a ser estropeados. Ejemplo 1: ---------MATRIZ DB 10 DUP (20 DUP ?) RUTINA: XOR AX,AX MOV SI,OFFSET MATRIZ MOV CX,10 L0: CALL SUBRUTINA LOOP L0 RET ; Matriz de 10 x 20 ; ; ; ; ; ; AX = 0 DS:SI -> MATRIZ Inicializar CX Repetir 10 veces CX=CX-1. Salto a L0 si CX<>0.

Es importante que SUBRUTINA no estropee el valor de CX que est haciendo de contador del bucle. Si la SUBRUTINA va a usar el registro CX, primero debe apilarlo, y antes de salir despilarlo: SUBRUTINA: PUSH CX MOV CX,20 L0: ADD AX,[SI] INC SI LOOP L0 POP CX RET Tambi n podramos haber escrito: ... PUSH CX ; Salvar CX en el stack ; Inicializar CX = 20 ; ; ; ; Sumar el elemento SI Apuntar al elemento siguiente Repetir 20 veces Recuperar CX

CALL SUBRUTINA POP CX ... en lugar de efectuar el PUSH y el POP en la subrutina. Ejemplo 2: ---------La siguiente rutina sirve para transferir un bloque de la pantalla de video a una variable de memoria. El bloque se define por un sector de caracteres consecutivos con atributo inverso (reverse). La rutina primero busca el bloque en la pantalla de video y de encontrarlo lo transfiere en forma de un StrinZ (string terminado con un byte 0) a una variable en memoria llamada BUFFER. En este ejemplo el uso que se hace de la pila es para intercambiar el contenido de los registros de segmento DS y ES. En el 8088 no existe una instruccin que intercambie el contenido de dos registros de segmento. Sin embargo uno puede simular este intercambio de la siguiente manera: PUSH DS PUSH ES POP DS POP ES Despu s del segundo PUSH, la word ms accesible del stack es la que contiene el valor de ES. Al efectuar el POP DS, esa word pasa a DS y luego POP ES recoge en ES el valor apilado por el PUSH DS. El A86 tiene incorporada una macro XCHG que en el caso XCHG DS,ES se desensambla como arriba. Notar que no podra haberse usado el cdigo PUSH DS MOV DS,ES POP ES por la sencilla razn de que no existe la instruccin MOV DS,ES. Precisamente en el A86 tiene una macro MOV incorporada que en el caso MOV DS,ES se ensambla como PUSH ES, POP DS. BUFFER DB 25*80 + 1 DUP ? VIDEO_SEG EQU 0B000 REVERSE EQU 70H TRAER_BLOCK: MOV DI,OFFSET BUFFER MOV AX,VIDEO_SEG MOV ES,AX XCHG DS,ES MOV CX,25*80 CALL FIND_BEGIN JNE > L3 DEC SI,2 ; Buffer para el StrinZ ; 0B800 para CGA, EGA o VGA ; Atributo de caracter inverso ; ; ; ; ; ; ; ; DI indice al BUFFER ES = Segmento de RAM de Video Esto es en realidad una macro 25 filas x 80 columnas Buscar comienzo de bloque Si no hay, bloque ... DS:SI -> Comienzo del bloque

L1: LODSW STOSB CMP AH,REVERSE JNE > L2 LOOP L1 L2: DEC DI,2 L3: XOR AL,AL STOSB XCHG ES,DS RET

; ; ; ; ;

AL = ASCII, AH = Atributo ASCII -> BUFFER Estaba dentro del bloque? No, fin transferencia Repetir hasta fin de pantalla

; Retroceder 2 bytes ; AX = 0 ; Marcar fin de StringZ ; Reparar ES y DS

FIND_BEGIN: PUSH CX ; Salvar CX en el Stack XOR SI,SI ; SI = 0 L1: LODSW ; AL = ASCII, AH = Atributo CMP AH,REVERSE ; Comienzo de bloque? JE > L2 ; S, salir LOOP L1 ; Repetir hasta fin de pantalla L2: POP CX ; Recuperar CX RET ------------------------------------------------------------------------------Lo primero que hay que saber (d cima parte) ------------------------------------------El registro de Estados ---------------------Ya vimos que el 8088 tiene los siguientes registros: AX, BX, CX, DX SI, DI BP SP DS, ES, CS, SS IP Generales: c/u se puede dividir en 2 de 1 byte Generales: ppalmente usados para manejo de strings General: Usado ppalmente para acceder al stack Usado por la CPU para acceder al Stack Contienen el nmero de prrafo de los segmentos Puntero de Instruccin. Usado por la CPU para dirigirse al cdigo ejecutable.

Dentro del micro existe otro registro que no tiene nombre simblico y que se llama *Registro de Estados* o de *Flags*. Este registro contiene informacin en sus bits. Aunque no todos sus bits se utilizan. La idea es que cada bit de este registro es un flag con dos estados posibles: 1 y 0. Ciertas instrucciones del lenguaje mquina ponen a 1 o a 0 uno o ms de esos flags y ciertas otras efectan tareas condiciondas al estado de algunos de esos flags. El programador no necesita recordar qu flag corresponde a qu bit, ya que los diferentes flags tienen nombre simblico y uno se refiere a ellos por su nombre. Veamos una descripcin detallada de los flags con ejemplos de su uso: ;--------;

; FLAG Z ; ;--------; Flag de Zero. Se pone a 1 cuando el resultado de la ltima operacin fue "0" o cuando el resultado de la ltima comparacin fue "igual". En cambio Flag Z=0 cuando el resultado de la ltima operacin fue diferente de "0" o cuando el resutado de la ltima comparacin fue "distinto". Ejemplos: SUB AX,BX JZ DIO_CERO JNZ NO_DIO_CERO CMP AX,BX JE SON_IGUALES JNE SON_DISTINTOS TEST AX,BX JZ DISJUNTOS JNZ BIT_EN_COMUN ; Operacin de resta ; Jump if Zero, salta si Flag Z=1 ; Jump if Not Zero, salta si Flag Z=0 ; Compara AX con BX ; Jump if Equal, salta si Flag Z=1 ; Jump if Not Equal, salta si Flag Z=0 ; Hace un test bit a bit de AX con BX ; Si ningn bit coincide, Flag Z=1 ; Si hay algn bit en comn, Flag Z=0

Conviene notar que JZ y JE son dos nemnicos de la misma instruccin de cdigo mquina. Uno elige JZ o JE segn el contexto para brindar mayor claridad. Por ejemplo despu s de una CoMParacin conviene escribir JE en lugar de JZ. Lo mismo vale para JNZ y JNE. ;--------; ; FLAG C ; ;--------; Flag de Carry. Se pone a 1 cuando en la ltima operacin hubo un acarreo (algo parecido al "me llevo 1" cuando se efecta una suma). Tambi n sirve para saber si el resultado de una comparacin fue "menor" o "mayor o igual". Si Flag C=1, el primer operando de la comparacin era MENOR que el segundo, si Flag C=0, el primer operando de la comparacin era MAYOR O IGUAL que el segundo. Ejemplos: VALOR_1 DW DW RESULTADO DW DW 5678H 1234H ? ? ; ; ; ; Word Word Word Word menos significativa de 12345678H ms significativa menos significativa o LO ms significativa o HI

SUMA: ;------------------------------------; ; Calcua RESULTADO = BX:AX + VALOR_1 ; ; BX:AX contiene un nmero de 4 bytes; ; AX = 2 bytes menos signicativos ; ; BX = 2 bytes ms significativos ; ;------------------------------------; CLC ; Clear Carry. Fuerza Flag C=0 ADC AX,VALOR_1 ; AX = AX + LO(VALOR_1) + Flag C. ; lo pone en el Carry Flag. MOV RESULTADO,AX ; Salvar word menos significativa ADC BX,VALOR_1+2 ; BX = BX + HI(VALOR_1) + Flag C. ; lo pone en el Carry Flag. MOV RESULTADO+2,BX ; Salvar word ms significativa. JC OVERFLOW ; Si Flag C=1, resultado no entra ... ; Si Flag C=0, resultado entr en

Si se lleva 1 Si se lleva 1 en 4 bytes 4 bytes

La instruccin ADC op1,op2 suma as: op1 = op1 + op2 + Flag C. La instruccin ADD op1,op2 suma as: op1 = op1 + op2. Ambas instrucciones dejan Flag C=1 si el resultado de la suma excede la longitud de los operandos (que pueden ser de 1 byte o 2 bytes). CMP AX,BX JB MENOR JAE MAYOR O IGUAL ; Compara AX con BX ; Jump if Below, salta si AX es menor que BX ; Jump if Above, Salta si es mayor o igual

JB y JC son sinnimos. Ambas saltan si Flag C=1 indicando que en la comparacin el primer operando era menor que el segundo. JAE y JNC tambi n son sinnimos. Saltan si Flag C=0, indicando que el resultado de la comparacin fue mayor o igual. El Flag C tambi n se usa para saber si el resutado de una rutina fue o exitoso o no. Ejemplo: VER_SI_MAYUSCULA: CMP AL,'A' JB > L2 CMP AL,'Z'+1 JAE > L2 L1: CLC RET L2: STC RET Luego CALL VER_SI_MAYUSCULA ; LLamar a la rutina de arriba JC NO_MAYUSCULA ; Si vuelve Flag C=1, no era mayscula JNC MAYUSCULA ; Si C=0, AL contena una letra mayscula Una versin mejorada: VER_SI_MAYUSCULA: CMP AL,'A' JB > L1 CMP AL,'Z'+1 CMC L1: RET Resumen parcial: No hay una manera directa de acceder al Flag Z. Para cambiar el Flag C existen las instrucciones: CLC STC CMC Pone Flag C=0 Pone Flag C=1 Invierte Flag C ; ; ; ; Compara AL con "A" Si Below, es porque C=1. Salir. Compara AL con "Z"+1 CompleMent Carry: invierte Flag C ; ; ; ; Compara AL con el ASCII de "A" Si es menor salir con Flag C=1 indicando error Compara AL con el ASCII de "Z" + 1 Si es mayor o igual, error

; Clear Carry indicando salida por OK ; SeT Carry indicando salida por error

Hay otros flags y los vamos a ver en el prximo mensaje.

------------------------------------------------------------------------------Lo primero que hay que saber (decimoprimera parte) -------------------------------------------------El Registro de Estados (segunda parte) ---------------------;-----------------------; ; Flag C (continuacin) ; ;-----------------------; En el mensaje anterior vimos que uno de los usos de Flag C era para sumar nmeros de ms de 15 bits. Bien, tambi n sirve para restar ese tipo de cantidades. Veamos un ejemplo: VALOR_1 DW DW RESULTADO DW DW 5678H 1234H ? ? ; ; ; ; Word Word Word Word menos significativa de 12345678H ms significativa menos significativa o LO ms significativa o HI

RESTA: ;------------------------------------; ; Calcua RESULTADO = BX:AX - VALOR_1 ; ; BX:AX contiene un nmero de 4 bytes; ; AX = 2 bytes menos signicativos ; ; BX = 2 bytes ms significativos ; ;------------------------------------; CLC ; CLear Carry. Fuerza Flag C=0. SBB AX,VALOR_1 ; AX = AX - LO(VALOR_1) - Flag C. MOV RESULTADO,AX ; Salvar word menos significativa. Si pidi 1 ; pone Flag C=1. SBB BX,VALOR_1+2 ; BX = BX - HI(VALOR_1) - Flag C. Si pidi 1 ; pone Flag C=1. MOV RESULTADO+2,BX ; Salvar word ms significativa. JC NEGATIVO ; Si C=1, se rest algo grande de algo chico ... ; Si C=0, resultado entr en 4 bytes La instruccin SBB op1,op2 resta as: op1 = op1 - op2 - Flag C. La instruccin SUB op1,op2 resta as: op1 = op1 - op2. Ambas instrucciones dejan Flag C=1 si op1 era menor que op2 (pidi 1). Aqu conviene tener en cuenta la coherencia que existe entre las instrucciones CMP y SUB con respecto al manejo del Flag C. SUB op1,op2 resta op2 de op1 y pone Flag C=1 si pidi 1. Esto ocurre cuando op1<op2. La instruccin CMP op1,op2 tambi n pone Flag C=1 cuando op1<op2. Esto es as porque CMP internamente realiza una resta del tipo SUB con la diferencia que no altera el contenido del op1. El nombre SBB significa SuBtract with Borrow. --------Quienes tengan experiencia con el 6510 (COMMODORE 64 o 128), notarn que el manejo del Flag C es inverso al del 8088. En el 6510 las instrucciones de comparacin entre op1 y op2 ponen el Flag C a cero cuando op1<op2. Consecuentemente, la instruccin de resta SBC op (SuBtract with Carry) de ese micro hace lo siguiente: Reg A = Reg A - op - Complemento de Flag C. ;--------; ; Flag P ; ;--------; Flag de Paridad. Se pone a 1 cuando el resultado de una operacin es par y a 0 en caso contrario. Se lo usa en combinacin con las instrucciones

JP (Jump if Parity flag) o JNP (Jump if Not Parity flag). ;--------; ; Flag A ; ;--------; Flag Auxiliar. Acta como un Flag C pero a nivel de la mitad de los operandos. Por ejemplo si al restar un byte de otro el nibble bajo le pidi 1 al nibble alto, entonces Flag A=1. Del mismo modo, si al sumar dos words, hubo un acarreo del byte bajo al byte alto, Flag A=1. Lo usan las instrucciones de aritm tica decimal. No hay instrucciones de salto asociadas a este Flag. ;--------; ; Flag S ; ;--------; Flag de Signo. Se pone a 1 cuando el resultado es negativo. Recordemos que el micro usa la t cnica llamada "complemento a 2". Por eso los valores negativos son los que tienen el bit ms alto en 1. Para operandos de 1 byte este es el bit 7, para operandos de 2 bytes este es el bit 15. Ejemplos de representaciones binarias de nmeros negativos. Valor -1 -2 -3 Byte FF FE FD Word FFFF FFFE FFFD

Para convertir un valor negativo de un byte en el mismo valor pero ocupando 2 bytes existe la instruccin CBW (Convert Byte to Word) que hace lo siguiente: Si AL < 80H, pone AH = 0 Si AL >=80H, pone AH = FF Ejemplos: CBW AL ---> AX 1 0001 80 FF80 El Flag S puede usarse en combinacin con las instrucciones de salto JS y JNS. Asociado a este flag tenemos otras instrucciones de salto condicional JG, JL y sus derivados. Como estas instrucciones dependen del valor de tres flags: Z, S y O vamos a explicarlas despu s de ver el Flag O ms abajo. ;--------; ; Flag T ; ;--------; Flag de Trap. Afecta el funcionamiento del micro de la siguiente forma: cuando Flag T = 1, cada vez que se termina de ejecutar una instruccin del lenguaje mquina se produce una interrupcin. La interrupcin que se produce es la INT 3. Este es el flag que utilizan los debuggers para permitir la ejecucin paso a paso de un programa. La instruccin INT (entre otras cosas que hace) pone Flag T=0. ;--------;

; Flag I ; ;--------; Flag de Interrupciones. Cuando Flag I=0 el micro no atiende los pedidos de interrupcin del hardware y solamente atiende las no enmascarables. Las interrupciones del software se atienden aunque Flag I=0. Este flag se lo maneja directamente con las instrucciones CLI STI ; CLear Interrupt que lo pone a 0 ; SeT Interrupt que lo pone a 1

La instruccin INT tambi n pone Flag I=0, impidiendo las interrupciones. Si bien existen formas de deshabilitar selectivamente interrupciones del hardware, normalmente los programas usan CLI y STI para habilitarlas o desabilitarlas en forma conjunta. ;--------; ; Flag O ; ;--------; Flag de Overflow. Se pone a 1 cuando en el resultado se produjo un Overflow. Esto significa que el bit ms alto se vio afectado por un acarreo proveniente de los bits ms bajos. Se usa en las operaciones con signo (recordar que el signo es el bit ms alto) para saber si despu s de la operacin el bit de signo es correcto o incorrecto. Por ejemplo si AX = 4501H, entonces ADD AX,4B00H da como resultado AX = 8001H. Pero atencin: 4501H y 4B00H son ambos positivos y sin embargo 80001 es negativo! Lo que pasa es que si estamos trabajando con enteros con signo de 2 bytes los valores deben entrar en 15 bits ya que el decimosexto bit (bit 15) est reservado para el signo. Esto debe ser as tanto para los operandos como para los resultados. Dado que FFFFH = 65535, los enteros con signo de dos bytes deben estar comprendidos entre -32768 y 32767. Adems de existir las instrucciones de salto condicional JO y JNO, exsite una instruccin especial INTO que produce una INT 4 si Flag O=1. Ahora estamos en condiciones de ver las instrucciones de salto condicional JL y JG: JG ; Jump if Greater: salta si Flag Z=0 y adems Flag S = Flag O ; O sea, el resultado no fue cero y adems: ; el signo es positivo (S=0) y vlido (O=0) ; o bien ; el signo es negativo (S=1) pero invlido (O=1) ; Jump if Less: salta si Flag Z=0 y adems Flag S <> Flag O ; O sea, el resultado no fue cero y adems: ; el signo es negativo (S=1) y vlido (O=0) ; o bien ; el signo es positivo (S=0) pero invlido (O=1)

JL

Las instrucciones asociadas a JG y JL se deducen fcilmente de estas descripciones. ;--------; ; Flag D ; ;--------; Flag de Direccin. Este flag est ligado a un conjunto de instrucciones para manejo de strings de bytes o words. Debido a la importancia de ese

conjunto, vamos a dedicar un mensaje completo a describir el Flag D y a esas instrucciones. ----------------------------------------------------------------------El registro de Flags: F E D C B A 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | O | D | I | T | S | Z | | A | | P | | C | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Los bits sin nombre no se usan en el 8088 (aunque algunos s se usan en otros miembros de la familia 80x86) Otras instrucciones para manejo del Registro de Flags: PUSHF POPF LAHF SAHF ; ; ; ; Apila el Registro de Flags Desapila el Registro de Flags Copia Flags S Z A P C a bits 7 6 4 2 0 de AH Copia bits 7 6 4 2 0 de AH a Flags S Z A P C

Digamos finalmente que la mayora de las instrucciones alteran uno o ms Flags del Registro de Estados. La lista es demasiado larga para recordarla. Un programador necesita tener a mano una cartilla con las instrucciones del micro en donde poder consultar una descripcin del funcionamiento de las instrucciones y en especial la forama en que estas actan sobre los Flags. La Norton Guides brindan una buena asistencia en este sentido. ------------------------------------------------------------------------------Lo primero que hay que saber (d cimosegunda parte) -------------------------------------------------Las instrucciones de strings ---------------------------El 8088 tiene una importante cantidad de instrucciones dedicadas al tratamiento de cadenas de bytes o words (el 80386 tiene -ademsinstrucciones para el manejo de cadenas de bits!). Ellas nos facilitan las tareas con cadenas de datos: buscar un elemento, leerlas, compararlas, copiarlas, etc. En estas instrucciones se usan especialmente los registros SI y DI como ndices a las cadenas a tratar: DS:SI -> "fuente" ES:DI -> "destino" Si bien cada instruccin de este tipo acta sobre un solo byte o palabra, la ventaja de usarlas est en que incrementan automaticamente a los registros ndices y que pueden actuar sobre una cadena entera si se los acompaa con el prefijo REP (y sus derivados). Tambi n cobra especial importancia el Flag D (de Direccin), con el que controlamos el sentido en que se recorrer el string. Todas las instrucciones que vamos a ver tienen dos versiones: agregando al nemnico B o W trabajarn a nivel de bytes o de words. 1) CMPS (CoMPare String)

PASS_LEN EQU 6 PASSWORD DB 'TSOHCP' TIPEADO DB PASS_LEN DUP ?

; Longitud de la password ; Por ejemplo "PCHOST" al rev s ; Poner aqu la password ingresada

CHECK_PASS: ;---------------------------------------------------; ; Compara string de longitud PASS_LEN con una clave ; ; ; ; INPUT: ; ; PASWORD = String Clave ; ; TIPEADO = String a comparar ; ; OUTPUT: ; ; Si Flag Z=1, String=Clave ; ; Si Flag Z=0, String<>Clave ; ;---------------------------------------------------; MOV SI,OFFSET TIPEADO ; DS:SI -> Clave ingresada MOV DI,OFFSET PASSWORD ; ES:DI -> Clave verdadera MOV CX,PASS_LEN ; CX = cantidad de bytes a comparar CLD ; INC SI y DI despu s de c/compararacin REP CMPSB ; Comparar un byte tras otro y DEC CX RET ; Si sale con Flag Z=1, eran iguales Observaciones: ------------a) En el ejemplo estamos suponiendo que DS y ES tienen los valores correctos. b) CLD es una instruccin que pone a cero el Flag D (Direccin) del Registro de Estados. Cuando Flag D=0, las instrucciones de string incrementan los registros ndices (SI y DI). Cuando Flag D=1, los decrementan (recorrido inverso). c) El prefijo REP hace todo esto: 1. Ejecutar CMPSB 2. Si los bytes comparados son diferentes, salir del REP con Flag Z=0 3. Decrementar contador CX 4. Si CX<>0, volver a 1 5. Si CX=0, los strings son iguales. Salir con Flag Z=1. d) CMPSB despu s de comparar incrementa SI y DI. Por lo tanto, si los strings son diferentes, se sale del REP con Flag Z=0 SI y DI apuntando al byte siguiente al que acaba de ser comparado Por eso, decrementando SI y DI, quedaramos apuntando al primer byte donde los strings difieren. e) Tambi n existe la versin CMPSW que compara las words apuntadas por DS:SI y ES:DI. En este caso SI y DI se incrementan o decrementan en 2 (segn el valor del Flag D). 2) LODS (LOaD String) ; StringZ con un valor num rico

NUM_STR DB '1635',0 ASCII_TO_BIN:

;---------------------------------------------; ; Convierte StringZ num rico en valor binario ; ; ; ; INPUT: ; ; NUM_STR = StringZ num rico sin signo ; ; OUTPUT: ; ; BX = Valor binario del strigz ; ;---------------------------------------------; MOV SI,OFFSET NUM_STR ; DS:SI -> StringZ num rico CLD ; Recorrer strings hacia adelante XOR BX,BX ; Guardar el resultado en BX XOR AH,AH ; AH=0 a lo largo de toda la rutina LODSB ; Leer el primer byte en AL CMP AL,0 ; Ver si el stringz era vaco JE RET ; Si vaco, salir con BX=0 L1: AND AL,0FH ; Ya que '0'=30H, '1'=31H,..., '9'=39H SHL BX,1 ; Multiplicar BX por 2 MOV DX,BX ; Guardar en DX SHL BX,1 ; por 4 SHL BX,1 ; por 8 ADD BX,DX ; BX fue multiplicado por 10 ADD BX,AX ; Agregar dgito reci n ledo LODSB ; AL=nuevo dgito CMP AL,0 ; Ver si fin de stringz JNE L1 ; No, continuar RET Observaciones: ------------a) Se supone que DS apunta al segmento de datos donde est NUM_STR. b) En este caso no se necesita usar ES:DI ya que nos referimos solamente a un string. c) Suponemos que el string tiene un valor que entra en dos bytes (inferior a 65.536). d) La instruccin SHL BX,1 produce un corrimiento (Shift) hacia la izquierda de los bits de BX. Esto equivale a multiplicar BX por 2. Si 2*BX es mayor que 65.535, entonces Flag C se pondr a 1. Consultando este Flag despu s de cada SHL se podra controlar un eventual overflow. En ese caso habra que hacer lo mismo despu s de cada ADD. e) Obs rvar que la multiplicacin por 10 de BX se hace a trav s de SHL y ADD y se evita el uso de la instruccin MUL, por ser esta ms lenta e incmoda. f) Tambi n existe LODSW que carga AX con la word apuntada por DS:SI e incrementa (o decrementa) SI en 2. g) No tiene sentido usar LODS en combinacin con REP (ni ninguno de sus derivados). Despu s de ledo un byte o word uno necesita hacer algo con l antes de leer el siguiente. ------------------------------------------------------------------------------Lo primero que hay que saber (d cimotercera parte) -------------------------------------------------Las instrucciones de strings (segunda y ltima parte)

----------------------------------------------------Continuamos describiendo las instrucciones de tratamiento de strings de bytes y words del 8088. 3) MOVS VIDEO_SEG X_WIN Y_WIN W_WIN H_WIN BUFFER (MOVe String) EQU 0B800H DB ? DB ? DB ? DB ? DW 80*25 DUP ? ; ; ; ; ; ; 0B000H para la Hercules Coordenada X (izquierda) Coordenada Y (arriba) Ancho (Width) de la ventana Altura de la ventana Para guradar la ventana

SAVE_WIN: ;--------------------------------------------------------; ; Salva en memoria una ventana de pantalla con atributos ; ; ; ; INPUT: ; ; X_WIN, Y_WIN, W_WIN, H_WIN ; ;--------------------------------------------------------; MOV AX,VIDEO_SEG ; ES = VIDEO_SEG MOV ES,AX ; Hacer el MOV a trav s de AX MOV DI,OFFSET BUFFER ; DI -> Buffer en memoria MOV AL,Y_WIN ; AL = # de filas arriba de la ventana MUL 80 ; Cada fila tiene 80 caracteres ADD AX,X_WIN ; Sumar X_WIN SHL AX ; AX=2*AX, cada caracter ocupa 2 bytes MOV SI,AX ; SI -> comienzo de ventana en VideoRam MOV BL,H_WIN ; Cantidad de renglones MOV DX,80 ; Ancho de pantalla (en bytes) SUB DL,BL ; DX = # de bytes que hay entre el fin SHL DL,1 ; de un rengln y el principio del sgte XOR CH,CH ; CH = 0 MOV AL,W_WIN ; AL = ancho de la ventana XCHG DS,ES ; ES:DI -> BUFFER, DS:SI -> Video RAM CLD ; Recorrer strings hacia adelante L1: MOV CL,AL ; CX = cantidad de words a mover REP MOVSW ; Copiar CX words ADD SI,DX ; Pasar al rengln siguiente DEC BL ; Un rengln menos JNE L1 ; Repetir para todos los renglones XCHG DS,ES ; Reparar DS RET Observaciones: -------------a) Suponemos que DS apunta al segmento de datos. b) XCHG DS,ES es una macro del A86. Equivale a PUSH DS, PUSH ES seguidos de POP DS, POP ES. c) Originalmente se pone en ES el segmento de la fuente y no del destino. Despu s se intercambian DS con ES. Eso se hace desp s de haber hecho referencia a las cuatro variables X_WIN, Y_WIN, W_WIN, H_WIN. De otro modo DS no estara apuntando a nuestro segmento de datos. d) Dentro del bucle que comienza en L1 se evita el uso de variables de

memoria ya que DS apunta al segmento de video y no de datos. e) Antes de salir de la rutina se vuelve DS a la normalidad. No conviene que una rutina destruya el valor de un registro de segmento. f) Hay que tener presente que cada caracter de pantalla ocupa 2 bytes. En el primero est el ASCII del caracter, en el segundo su color (o atributo) g) Por supuesto, tambi n existe la versin MOVSB que mueve de a un byte. 4) SCAS CMD_LIN EQU 80H IDENTIF EQU '/' (SCAn String) ; Segunda parte del PSP ; Identificador de parmetros

FIND_IDENTIF: ;------------------------------------------------------------; ; Busca el identificador '/' en la lnea de comandos del DOS ; ; ; ; OUTPUT: ; ; Si Flag Z = 1, el caracter '/' se encontr ; ; en la lnea de comandos. En ese caso ; ; DI -> caracter siguiente a '/' ; ; Si Flag Z = 0, no se encontr el caracter '/' ; ;------------------------------------------------------------; MOV CX,[CMD_BUF] ; Longitud de lnea de parmetros MOV DI,CMD_BUF+1 ; ES:DI -> parmetros MOV AL,IDENTIF ; AL = caracter a comparar CLD ; Recorrido hacia adelante REPNE SCASB ; Buscar AL dentro del string RET ; Si sale Flag Z=1, se encontr ; Si sale Flag Z=0, no se encontr Observaciones: ------------a) DOS usa los 128 (80H) segundos bytes del PSP como buffer de la lnea de comandos. Si uno llama a un programa desde el prompt del DOS y a continuacin del nombre del programa escribe algo, DOS deja ese algo a partir del byte con offset 81H del PSP (no incluye el nombre del programa). El string termina con 0DH (Carriage Return). En el offset 80H, DOS deja la cantidad de caracteres del string escrito por el operador. Esa longitud no incluye al byte 0DH puesto por DOS. b) Los programas usan esta facilidad que brinda el DOS para tomar desde el prompt ciertos parmetros dejados por el operador. Normalmente esos parmetros se deben ingresar antecedidos por algn caracter especial, como '/'. c) El prefijo REPNE es una variante de REP. Significa "repetir mientras no sea igual". Produce la repeticin de la instruccin SCASB un mximo de CX veces y hasta que el byte en AL sea encontrado. d) SCASB est asociado con ES:DI. En este ejemplo hemos supuesto que ES apunta al segmento del PSP. e) Al salir de REPNE, ES:DI apunta al byte siguiente al ltimo examinado. Por lo tanto, si el identificador fue encontrado, ES:DI sale apuntando al byte siguiente a '/'.

f) Cuando se usa la versin SCASW, lo que se hace es comparar el contenido de AX con el de la word de memoria apuntada por ES:DI. Eso significa que AL se comaparar con el byte en ES:DI y AH con el byte en ES:DI+1. 5) STOS (STOre String)

Esta instruccin hace el trabajo inverso al que realiza LODS. Aqu vamos a aprovechar un ejemplo de Hector Ricciardolo. El programa sirve para llenar la pantalla con un caracter y un color determinados. VIDEO_SEG CARACTER ATTRIBUTO FILAS COLUMNAS EQU EQU EQU EQU EQU 0B800H '' 23 25 80 ; 0B000H para H rcules ; Caracter para llenar la pantalla ; Color. En H rcules 0FH es brillante.

SCR_FILL: MOV AX,VIDEO_SEG ; ES -> Segmento de Video RAM MOV ES,AX ; No existe MOV DS,VIDEO_SEG MOV DI,0 ; ES:DI -> Video RAM MOV AL,CARACTER ; el byte bajo contiene el ASCII MOV AH,ATTRIBUTO ; el bajo, el color o (atributo) MOV CX,FILAS*COLUMNAS ; Cantidad de caracteres de la pantalla CLD ; Avanzar REP STOSW ; Copiar a toda la pantalla RET ------------------------------------------------------------------------------Lo primero que hay que saber (d cimocuarta parte) ------------------------------------------------Estructura de un programa. ------------------------Bajo DOS tenemos dos tipos de archivos ejecutables: los progarmas con terminacin .EXE y los programas con terminacin .COM (tambi n existen archivos ejecutables con extensin .BAT pero esos pertenecen a otra categora puesto que son archivos de texto que llaman a comandos del DOS o a programas de aplicacin los cuales son del tipo .EXE o del tipo .COM) Cuando uno invoca un programa entrando su nombre en el prompt del DOS, ste lo carga y le asigna toda memoria disponible desde el primer prrafo libre hasta el ltimo (recordemos que un prrafo es una direccin absoluta que es mltiplo de 10H). Despu s de esto, el control es transferido al programa. Este proceso de carga es diferente en el caso de los .EXE que en el de los .COM. No se puede saber de antemano cul va a ser ese primer prrafo libre. Las direcciones de 0:0 a 0:3FF son ocupadas por los vectores de interrupcin, las direcciones siguientes (40:0 a 50:0) las usa la BIOS para guardar sus variables y a continuacin vienen los Device Drivers, el COMMAND.COM y otros programas residentes. Despu s de todo esto comienza la memoria libre que como se v depende del tamao del COMMAND.COM (versin del DOS) y de los progrmas residentes que puede haber en ese momento. Los programas .COM ------------------

Una de las caractersticas de los .COM es que los cuatro registros de segmento CS, DS, ES y SS son inicializados al mismo valor (el del primer prrafo libre) en el momento en que DOS efecta la carga en memoria. De ese modo todas las variables y las rutinas tienen una direccin que est determinada por su offset dentro del segmento asignado. Como consecuencia el programa queda confinado a 64 Kbytes (1 segmento) de memoria. Y esa memoria debe ser compartida por el cdigo, las variables, y la pila. Sabemos que la pila crece hacia abajo; es decir, cada vez que se apila una word, el valor de SP decrece en 2. Eso significa que las posiciones ms altas del segmento deberan estar libres de cdigo y datos. De otro modo se corre el riesgo de que la pila sobreescriba a otras partes del programa destruyendo datos o cdigo. El cdigo del programa debe comenzar obligatoriamente en la direccin hexadecimal 100H (=256). Veamos por qu : Cuando DOS carga un programa en memoria (.EXE o .COM) destina los primeros 100H bytes a una tabla llamada PSP (Program Segment Prefix) con informacin que no viene al caso describir ahora. Los bytes ocupados por el PSP son los que tienen offset 00H a FFH dentro del primer segmento asignado al programa. En el caso de un .COM el DOS le transfiere el control al 1er byte que sigue al PSP, es decir al que se encuentra en el offset 100H. Por lo tanto justo en el offset 100H comienza el programa tal cual lo ha escrito el programador en assembler. Por este motivo, un programa .COM debe comenzar con cdigo ejecutable y no con varibles. De otro modo el control se transferira a una zona de datos, el micro tratara de interpretar esos bytes de datos como si fueran cdigo ejecutable y se produciran resultados impredecibles. Notar tambi n que los 100H bytes del PSP no forman parte del archivo ejecutable en disco (sea este .COM o .EXE). El programador no debe preocuparse por el PSP, en l DOS le ha dejado datos que pueden servirle. 0 100H FFFFH memoria --+-------+----+------------+---------- - - - -------+-memoria baja | PSP |JMP | varibles | cdigo stack| alta ------+-------+--|-+------------+|--------- - - - -------+----comienzo | | fin del del segmento +------>--------+ segmento saltar sobre las ^ variables | +--<--- Aqu comenzaba el primer prrafo libre antes que DOS cargara este programa. Por este motivo los programas .COM comienzan con un JMP a la rutina principal y entre el JMP y dicha rutina se ubican las variables. Las variables deben estar definidas antes que las mencione el cdigo del programa. Esto es para que el ensamblador (que no interpreta las instrucciones sino que meramente reemplaza nombres simblicos por direcciones y cdigos de operacin) sepa qu tipo de variables representan esos nombres.

Ejemplo. ------Una buena forma de comprender esto es usando el programa DEBUG. Este programa permite (entre otras cosas) ensamblar directamente cdigo ejecutable en la memoria. C:\A86>DEBUG -A 2F2D:0100 MOV DX,110 2F2D:0103 MOV AH,9 2F2D:0105 INT 21 2F2D:0107 RET 2F2D:0108 -E110 'PC-Host BBS 746-1635 TLD 24Hs',0D,0A,'$' -G=100 PC-Host BBS 746-1635 TLD 24Hs Program terminated normally El prompt del DEBUG es un signo - (menos). Cuando aparece el prompt ingresamos el comando "A" (Assemble) para indicarle al DEBUG que vamos a ensamblar cdigo. En ese momento DEBUG nos escribe la direccin donde vamos a ensamblar el cdigo. El valor del segmento (2F2D en este caso) indica el primer prrafo libre como expliqu ms arriba. El offset 100H es proporcionado automticamente ya que como vimos el programa debe comenzar 100H bytes a partir del principio del segmento. Escribimos un pequeo programa que sirve para imprimir un string en pantalla. El valor 110 que movemos a DX indica el offset donde comienza nuestro string. Al escribir la instruccin MOV DX,110 estamos inventando una direccin (que debe estar ms alla del cdigo) en la que vamos a poner nuestro string. El DEBUG no permite el uso de nombres simblicos de modo que tenemos que referirnos a los datos por sus direcciones num ricas. Una vez que escribimos el programa controlamos que este termine antes de la direccin 110H (termina en la 107H) e invocamos el comando "E" que sirve para Editar memoria. El signo "$" indica a la funcin 9 de la INT 21H el final del string. Los valores hexadecimales 0D y 0A son los ASCII de Carriage Return y Line Feed que llevarn el cursor al principio del rengln siguiente. El comando G=100 sirve para ejecutar nuestro programa. Tambi n podemos grabar nuestro programa en disco. Para eso entramos los siguientes comandos: -RCX CX 0000 :0032 -N PRUEBA.COM -W Writing 00032 bytes RCX nos muestra el valor actual del Registro CX (0000) y nos permite modificarlo. Ponemos 0032 para indicar que queremos salvar 32H bytes (suficientes en este caso para contener el cdigo y los datos). N PRUEBA.COM indica el Nombre que queremos dar al archivo.

W (Write) graba el archivo en disco. Luego podemos salir del DEBUG con el comando Q (Quit). Una vez ah podemos ver: C:\A86>DIR PRUEBA.COM Volume in drive C is STACKER Directory of C:\A86 PRUEBA COM 1 file(s) 50 07/10/92 23:00 50 bytes

que efectivamente hemos creado un archivo de 32H (=50) bytes. Ahora lo podemos ejecutar como a cualquier .COM: C:\A86>PRUEBA PC-Host BBS 746-1635 TLD 24Hs C:\A86> ------------------------------------------------------------------------------Lo primero que hay que saber (d cimoquinta parte) ------------------------------------------------Estructura de un programa (continuacin) ------------------------En el mensaje anterior vimos que los programas .COM con los valores de DS, ES, CS y SS inicializados al primero de la memoria libre contigua). De este modo direcciones de un programa .COM se obtienen como un prrafo.

son cargados por DOS mismo prrafo (el todos las offset dentro de ese

Conviene aclarar sin embargo que los programas .COM pueden usar toda la RAM disponible para guardar datos y para el uso de la pila. Ejemplo 1: Mover la pila fuera del segmento asignado por DOS --------Un programa .COM puede mover la pila fuera del segmento que le ha sido designado del siguiente modo: MOV ADD CMP JAE MOV ... AX,CS AX,01000H AX,0A000H NO_MEM SS,AX ; ; ; ; ; Copiar CS en AX Avanzar 64 Kbytes Controlar que queda dentro de los 640Kb Si fuera de los 640, dejar el Stack quieto Poner el Stack Segment en ese segmento

ATENCION: Si se cambia el Stack Segment, el programa .COM no puede terminar con un simple RET (ver la explicacin ms abajo). Ejemplo 2: Ubicar un gran buffer fuera del segmento asignado por DOS --------BUFF_SEG DW ? ; Salvar aqu el valor del segmento para buffer MOV ADD CMP JAE AX,SS AX,01000H AX,0A000H NO_MEM ; ; ; ; Copiar SS en AX Avanzar 64 Kbytes Controlar que queda dentro de los 640 Kbytes Si fuera de los 640, memoria insuficiente

MOV BUFF_SEG,AX ...

; Salvar nuevo segmento en variable de memoria

Luego, el programa podra usar ese segmento para poner un buffer de lectura de disco (suponemos que el archivo ya haba sido abierto): MOV AH,3FH MOV BX,HANDLE MOV CX,NRO_BYTES MOV DX,BUFF_SEG MOV DS,DX MOV DX,0 INT 21H JC ERROR ... ; ; ; ; ; ; ; ; ; Funcin para leer CX bytes de archivo abierto BX = Handle asignado por DOS al abrir el file CX = Cantidad de bytes que se desean leer La funcin 3FH pide DS:DX -> buffer Inicializamos DS a trav s de DX y ponemos en DX el offset dentro del buffer Leer CX bytes del archivo Las funciones del DOS devuelven Flag C = 1 cuando se produce un error

ATENCION: el cdigo anterior deja DS apuntando al segmento donde est nuesto buffer y no al segmento del programa donde estn las variables. Para acceder a las variables es necesario restituir el valor de DS: PUSH CS POP DS Cmo terminar un programa .COM -----------------------------1) Terminar con RET Cuando DOS carga un .COM inicializa la primera word del stack a 0 (o sea, el programa arranca con SP=0FFFEH y SS:[SP]=0000H). Esto significa que si terminamos con el programa con una instruccin RET, entonces este ltimo RET transferir el control a la direccin con offset 0000H en el CS actual. Esta direccin es el comienzo del PSP. Los dos primeros bytes del PSP contienen el cdigo de la instruccin INT 20H. Esta instruccin termina el programa y transfiere el control al DOS. Por lo tanto si no hemos cambiado el valor de SS (ver ejemplo ms arriba) podemos simplemente terminar nuestro .COM con RET. 2) Terminar con INT 20H Otra forma de terminar un programa .COM es con una llamada explcita a la INT 20H. 3) Terminar con la funcin 4CH de la INT 21H Este ltimo m todo es el ms conveniente. Simplemente hay que escribir el cdigo: MOV AH,04CH INT 21H La ventaja de la funcin 04CH de la INT 21H es que el valor que tenga AL puede ser recogido por un programa .BAT con la variable ERRORLEVEL. Para indicar que el programa ha terminado sin errores tendramos que salir con AL=00H. En general la convencin es que los errores ms severos corresponden a valores ms grandes de AL. En los archivos .BAT hay que comparar la variable ERRORLEVEL con los posibles valores de mayor a menor. Por ejemplo, si sabemos que nuestro porgrama en assembler puede arrojar cdigos de error iguales a 0, 1 o 2, en un .BAT tendramos que escribir:

@ECHO OFF PRUEBA If ERRORLEVEL = 2 Goto Error2 If ERRORLEVEL = 1 Goto Error1 Goto OK

REM REM REM REM REM

Opcional Nombre del programa en assembler ErrorLevel ms alto Repetir bajando en 1 ErrorLevel 0

:Error2 Pause "El programa termin con ERRORLEVEL 2" Goto END :Error1 Pause "El programa termin con ERRORLEVEL 1" Goto END :OK Pause "El programa termin sin errores" :END 4) Terminar quedando residente Los programas que quedan residentes al finalizar terminan su cdigo con la INT 27H (antigua) o la INT 31H (recomendada). Sin embargo no vamos a entrar ahora en detalles de cmo dejar residente un programa ya que es un tema para analizar en profundidad (ver la serie "Todo sobre las interrupciones"). ------------------------------------------------------------------------------Lo primero que hay que saber (d cimosexta parte) -----------------------------------------------Estructura de un programa (continuacin) ------------------------Cuando DOS carga un programa en memoria usa los primeros 100H (=256) bytes para el Program Segment Prefix. Esta tabla contiene informacin til tanto para el programa como para el DOS. No es difcil encontrar descripciones detalladas del PSP en la literatura, de modo que nos vamos a concentrar solamente en una parte de ste. La segunda mitad del PSP (offset 80H en adelante) es de gran utilidad para el programador en assembler. Muchos programas admiten parmetros que corresponden a diferentes opciones. Por ejemplo, la mayora de los procesadores de texto permiten que ingresemos el nombre del documento a editar en la lnea de comandos. Ejemplo: ------C:\A86>QEDIT prueba.8 El DOS toma el string que escribimos a continuacin del nombre del programa que queremos ejecutar y coloca una copia a partir del offset 81H del PSP. De ah los programas pueden analizar la informacin tipeada por el operador y actuar en consecuencia. En realidad el DOS no solamente copia lo que escribimos sino que adems

deja en el byte que est en el offset 80H del PSP la longitud del string y termina con un byte 0DH de Carriage Return. Por ejemplo, si tenemos un programa que se llama PRUEBA, lo podemos llamar del siguiente modo: C:\A86>PRUEBA Escribo lo que quiero hasta 127 bytes Una vez que el programa PRUEBA sea cargado en memoria, en el offset 80H del PSP tendremos la longitud del string: " Escribo lo que quiero hasta 127 bytes"; en el offset 81H comenzar el string y el byte sigiente al ltimo (letra 's' de 'bytes') contendr el valor 0DH. Ejemplo: Usemos el DEBUG y el programita PRUEBA.COM para ver esto. ------C:\A86>DEBUG PRUEBA.COM Hola -D80 389B:0080 05 20 48 6F 6C 61 389B:0090 00 00 00 00 00 00 389B:00A0 00 00 00 00 00 00 389B:00B0 00 00 00 00 00 00 389B:00C0 00 00 00 00 00 00 389B:00D0 00 00 00 00 00 00 389B:00E0 00 00 00 00 00 00 389B:00F0 00 00 00 00 00 00 -U100,107 389B:0100 BA1001 MOV 389B:0103 B409 MOV 389B:0105 CD21 INT 389B:0107 C3 RET -D110,12E 389B:0110 50 43 2D 48 6F 73 389B:0120 2D 31 36 33 35 20 -

0D 00 00 00 00 00 00 00

00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

. Hola.......... ................ ................ ................ ................ ................ ................ ................

DX,0110 AH,09 21 74 20-42 42 53 20 20 37 34 36 54 4C-44 20 32 34 48 73 24 PC-Host BBS 746 -1635 TLD 24Hs$

El comando -D80 sirve para pedir un volcado de memoria (Dump) a partir del offset 80H. Vemos que el string ' Hola' aparece a partir del offset 81H. En el offset 80H est la longitud del string (05) y en el 86H el ASCII de Carriage Return (0D). Cuando termina el PSP (offset 0FFH), comienza nuestro programa PRUEBA cuyo desensamble pedimos con U100,107. Finalmente con D110,12E vemos un volcado del string que imprime PRUEBA. Esto que podemos ver con el DEBUG ocurre del mismo modo cuando simplemente ingresamos: C:\A86>PRUEBA Hola La palabra ' Hola' aparece a partir del offset 81H del PSP. Cuando solamente escribimos el nombre del programa que queremos ejecutar, en el offset 80H del PSP habr un 0 y en el 81H un 0D.

Ejemplo: ------Veamos como ejemplo un programa que convierte un nmero decimal a su expresin hexadecimal. Vamos a suponer que el nmero se escribe en la lnea de comandos a continuacin del nombre del programa, que llamaremos DEC2HEX. ; Ensamblar con el A86 JMP MAIN ; Saltar sobre los datos

TABLA_HEXA DB '0123456789ABCDEF' HLP DB DB DB DB DB DB DB DB USO: MOV MOV INT MOV INT 0DH,0AH ' DEC2HEX convierte un nmero decimal a expresin hexadecimal' 0DH,0AH,0AH ' Sintaxis: DEC2HEX dec_val' 0DH,0AH,0AH ' donde: "dec_val" es un nmero decimal < 65536' 0DH,0AH,0AH '$' DX,OFFSET HLP AH,09 21H AX,4C01H 21H ; ; ; ; ; ; ; ; ; ; ; DS:DX -> String a emitir Funcin del DOS para imprimir un string$ LLamar al DOS Salir con ERRORLEVEL 1 Devolver control a DOS Saltar sobre los espacios antes del parmetro Si no hay parmetro, motrar mensaje de help Transformar parmetro a su valor num rico Emitir las cifras hexadecimales Funcin para terminar (ERRORLEVEL 0) Devolver el control al DOS

MAIN: CALL SALTAR_ESPACIOS JZ USO CALL STRING_TO_BIN CALL EMITIR_BX MOV AX,4C00H INT 21H

SALTAR_ESPACIOS: ;----------------------------------------------------------------; ; Saltea todos los espacios que hay entre el nombre del programa ; ; y el parmetro en la lnea de comandos. ; ; ; ; OUTPUT: ; ; Si Flag Z = 1, no hay parmetro ; ; Si Flag Z = 0, DS:SI -> primer caracter del parmetro ; ; AL = primer carcter del parmetro ; ; CX = cantidad de bytes remanentes ; ;----------------------------------------------------------------; MOV SI,80H ; SI -> Offset 80H del PSP CLD ; Flag D (Direccin) = 0 LODSB ; AL = Longitud del string en lnea de comandos CMP AL,0 ; Ver si hay parmetros JE RET ; Si no hay, salir con Flag Z = 1 XOR CH,CH ; CH = 0 MOV CL,AL ; CX = Longitud del string en lnea de comandos MOV AH,' ' ; AL = ASCII de ' ' L1: LODSB ; Leer un caracter en AL

CMP AL,AH JNE RET LOOP L1 RET

; Comparar AL con ' ' ; Si no es igual, ya se salteraron los espacios ; Si es espacio, continuar (hasta CX=0)

STRING_TO_BIN: ;----------------------------------------------------------------; ; Transforma el string de un nmero decimal en un valor num rico ; ; ; ; NOTA: Cuando la subrutina es llamada AL ya contiene el primer ; ; caracter del string y DS:SI apunta al segundo ; ; OUTPUT en BX ; ;----------------------------------------------------------------; XOR BX,BX ; Resultado inicialmente nulo XOR AH,AH ; AH = 0 a lo largo de toda la rutina L1: SUB AL,'0' ; Restar ASCII de '0' CMP AL,'9'-'0' ; Ver si AL estaba entre '0' y '9' JA RET ; Si mayor, no estaba entre '0' y '9', salir SHL BX,1 ; BX = (BX anterior) * 2 MOV DX,BX ; Salvar en DX SHL BX,1 ; BX = (BX anterior) * 4 SHL BX,1 ; BX = (BX anterior) * 8 ADD BX,DX ; BX = (BX anterior) * 10 ADD BX,AX ; BX = (BX anterior) * 10 + nueva cifra LODSB ; Leer otra cifra del string LOOP L1 ; Y continuar si no se acab el string RET EMITIR_BX: ;---------------------------------------------; ; Emite las cuatro cifras hexadecimales de BX ; ;---------------------------------------------; MOV DX,BX ; Salvar BX en DX MOV BX,OFFSET TABLA_HEXA ; DS:BX -> TABLA_HEXA MOV AL,DH ; Primero emitir DH CALL SHOW_AL ; Hacerlo desde AL MOV AL,DL ; Despu s emitir DL JMP SHOW_AL ; con la misma rutina SHOW_AL: MOV AH,AL SHR AL,1 SHR AL,1 SHR AL,1 SHR AL,1 XLAT CALL PRINT_AL MOV AL,AH AND AL,0FH XLAT JMP PRINT_AL PRINT_AL: PUSH DX XCHG AX,DX MOV AH,02 INT 21H XCHG AX,DX ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Salvar AL en AH Bajar el nibble alto de AL al nibble bajo En las 186, 286 y 386 se puede poner SHR AL,4 AL = cifra hexadecimal correspondiente Emitir la cifra (AL tiene el ASCII) Recuperar valor original de AL Aislar nibble bajo y obtener el ASCII correspondiente Emitir Salvar DX en la pila Intercambiar AX con DX Funcin del DOS para emitir ASCII en DL LLamar al DOS Recuperar AX

POP DX ; Recuparar DX RET ------------------------------------------------------------------------------Lo primero que hay que saber (decimoseptima parte) -------------------------------------------------Los programas .EXE -----------------En los programas .EXE los registros de segmento pueden tener diferentes valores. Por lo general hay que definir un segmento de datos, otro para el stack y otro para el cdigo. Dentro de un programa puede haber diferentes segmentos de datos y de cdigo. El mismo programa se encarga de dar a DS el valor del segmento correspondiente. Por ejemplo, para definir un segmento de datos se escribe: DATOS SEGMENT ... DATOS ENDS donde DATOS es un nombre simblico y SEGMENT y ENDS indican al ensamblador los lmites del segmento. Luego para dar a DS el valor de DATOS, hay que ejecutar: MOV AX,DATOS MOV DS,AX Conviene resaltar que DATOS no tiene por qu ser un segmento de 64 Kbytes. En realidad los "segmentos" definidos con SEGMENT son *fragmentos* de hasta 64 Kbytes de extensin. La directiva SEGMENT tiene parmetros opcionales que dan informacin adicional al ensamblador y al linker. Si un programa tiene ms de un segmento de cdigo, las rutinas de un segmento slo pueden ser llamadas desde otro mediante un CALL FAR. La diferencia entre un CALL y un CALL FAR es que ste ltimo aplia CS e IP (segmento y offset de la direccin de retorno). Por ese motivo las rutinas que van a ser accedidas desde segmentos externos deben terminar con la instruccin RETF (RETurn Far). Esta instruccin desapila dos palabras del stack y las coloca en IP y CS. Es importante comprender que dos rutinas en segmentos diferentes pueden estar a una distancia inferior a los 64 Kbytes. Lo que ocurre es que cada una de ellas se ejecutar con valores diferentes del registro CS. Si una rutina termina con RETF deber ser llamada con un CALL FAR an dentro del mismo segmento. La direccin de memoria donde efectivamente se va a cargar un programa solamente se conoce en el momento mismo de la carga. Por eso el ensamblador no tiene manera de saber cul va a ser el valor de los nombres de segmentos (ej. DATOS). El ensamblador mantiene los nombres simblicos de segmento sin valor

definido y produce un archivo con terminacin .OBJ (OBJect). Estos archivos son procesados por el "Linker". El linkeado resuelve ciertas referencias cruzadas entre nombres de segmentos y arma un "header" (encabezamiento) que define a cada una de esas direcciones como un offset desde el principio del programa. Cuando DOS carga el programa suma la direccin de inicio a los nombres de segmento. La informacin de los lugares donde debe hacer esto la toma del header. Por este motivo, en relacin con los programas .COM, los programas .EXE ocupan ms espacio en disco (el correspondiente al header) y se carguan ms lentamente en memoria. Cuando DOS carga un programa .EXE en memoria inicializa DS y ES al segmento del primer prrafo asignado al programa. Por lo tanto DS:0 y ES:0 apuntan al comienzo del PSP. Como los dos primeros bytes del PSP contienen la instruccin INT 20H, para terminar un .EXE con una instruccin RET se debe ejecutar al principio del programa lo siguiente: PUSH DS XOR AX,AX PUSH AX Esto manda al final del Stack la direccin (segmento y offset) del comienzo del PSP. Luego el programa puede terminar con un RETF que desapilar esta direccin produciendo un salto a la instruccin INT 20H la cual se encarga de devolver el control al DOS. Como vimos en el caso de los programas .COM, es mejor terminar un programa con la funcin 4CH de la INT 21H ya que el valor que demos a AL podr ser ledo desde el DOS por la variable ERRORLEVEL. Por ejemplo, un programa que quiera terminar con cdigo de error 0, puede hacerlo con: MOV AX,4C00H INT 21H En el header de un .EXE tambi n se encuentra la direccin a la que DOS transferir el control una vez cargado el programa. Al contrario que en el caso de los .COM, los programas .EXE pueden comenzar a ejecutarse en cualquier direccin. En algunos assemblers la directiva END sirve para indicar el punto de entrada al programa. Por ejemplo si un programa va a comenzar su ejecucin en la rutina MAIN, debemos terminar el cdigo fuente con END MAIN De ese modo al cargarse el programa, CS ser inicializado por DOS con el valor del segmento de cdigo al cual pertenece MAIN y el registro IP ser inicializado al offset que MAIN tiene dentro de ese segmento. ----------------------------------------------------Tema: : Numeros Hexadecimales ------------------------------------------------------------------------------@PID: RA 1.11 21280

@MSGID: 0:0/0 500f42e2 Notacin Hexadecimal. Un punto de vista computacional. (*) ----------------------------------------------------Las computadoras son mquinas con una capacidad descomunal para manejar largas tiras de 0s y 1s. Para ellas la memoria es un conjunto de celdas que se encuentran en dos estados posibles ON y OFF. Estas celdas se llaman bits. El hombre ha aprendido diferentes m todos para codificar informacin en la memoria de las computadoras. Por ejemplo una imagen en un monitor monocromtico es una larga tira de bits cada uno en correspondencia con un pixel del video. Un bit en estado ON significa que el pixel asociado est iluminado y uno en OFF que est apagado. Como vemos, las tiras de bits no siempre representan nmeros y en realidad los programas interpretan solamente una pequea porcin de la memoria como datos num ricos. Ahora bien, independientemente del significado que pueda tener una tira de bits, una cosa es cierta: es malo para la vista trabajar con cosas como: 1001101110010110 Se puede mejorar un poco el aspecto de este tipo de engendros introduciendo una simple separacin: 1001 1011 1001 1110 Pero mejor an es si ponemos un nombre a cada conjunto de 4 bits. Estos conjuntos se llaman nibbles: 0000: 0001: 0010: 0011: 0 1 2 3 0100: 0101: 0110: 0111: 4 5 6 7 1000:8 1001:9 1010:A 1011:B 1100:C 1101:D 1110:E 1111:F

Con estos nuevos smbolos, la tira anterior se escribe: 9 B 9 E Ahora los espacios son ms molestos que tiles y podemos poner 9B9E que no es otra cosa que la representacin hexadecimal de la tira de bits. Para resaltar el hecho de que estamos usando la codificacin hexadecimal, en assembler se usa el sufijo H. Por ejemplo: 9B9EH. Esta convencin es especialmente til cuando la representacin hexadecimal no posee letras como en el caso de 91H. Resumen: ------1) Cada BYTE tiene 8 BITS. El primer bit (el de la izquierda) es el bit 7, el ltimo es el 0. Ejemplo: 10011011 | | | +------- bit 0 +-------------- bit 7

2) Cada byte tiene 2 NIBBLES. El primer nibble se llama ALTO el otro, BAJO. Ejemplo: 1001 1011 ---- ---| | | +-------- nibble bajo +------------- nibble alto 3) Una CIFRA HEXADECIMAL es uno de los siguientes smbolos: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. 4) Cada nibble se puede representar por una cifra hexadecimal. Ejemplos: 1001 se representa por 9 y 1011 por B. 5) Si se necesita interpretar una cifra hexadecimal como un nmero, entonces A corresponde a 10, B a 11, C a 12, D a 13, E a 14 y F a 15. 6) Si se necesita interpretar un byte como una cantidad se debe multiplicar el nibble alto por 16. Ejemplo: 9B corresponde al nmero 9*16+11=155. || |+------------- nibble bajo = 11 +-------------- nibble alto * 16 = 144 --interpretacin num rica de 9BH = 155 7) Tambi n se usan las PALABRAS (o WORDS) que constan de 2 bytes. El primer byte se llama byte ALTO, el segundo BAJO. Ejemplo: 9B9E | | | +------------ byte bajo +-------------- byte alto 8) En el caso en que dos o ms bytes deban ser interpretados como valores num ricos cada uno de ellos se deber multiplicar por una potencia de 256. Ejemplo: 019B9E | | | | | +------------- byte bajo = 158 | +--------------- byte medio * 256 = 39680 +----------------- byte alto * 256^2 = 65536 ----105374 (*) Basado en el captulo "Digression: A Notation System for Bit Patterns" del minicurso de Assembler "An Assembly Language Primer (C) 1983 by David Whitman". Leandro y Valeria

Tema: : Trucos en assembler ------------------------------------------------------------------------------@PID: RA 1.11 21280 @MSGID: 0:0/0 500c7dd0

;--------------------------------; ; Unos pocos TRICKS en Assembler ; ;--------------------------------; ; ; ; ; ; ; ; Los trucos en general *no* son convenientes porque pueden comprometer la claridad o la generaldiad del cdigo. Sin embargo algunos trucos son tan utilizados que conviene conocerlos de antemano para no trabarse cuando se examina el desensamble de una rutina o se lee un archivo fuente que no los explica claramente. Otros solucionan de una manera elegante y consisa problemas tpicos de la programacin en assembler.

Ingenuos pero muy difundidos: ---------------------------TRICK_0: XOR AX,AX ; Equivale a MOV AX,0 pero es ms rpido SUB AX,AX ; Otra variante de lo anterior ... TRICK_1: OR AX,AX AND AX,AX ... ; Usado para ver si AX es cero, par o positivo ; Otra variante de lo mismo

Sencillos pero piolas: --------------------TRIC_2: CMP AL,SALIDA_OK ; Para salir con CY en caso STC ; que AL sea diferente de SALIDA_OK JNE RET ; ... TRICK_3: JMP RUTINA_FINAL ; En lugar de: ; CALL RUTINA_FINAL RET

Ingeniosos: ---------TRICK_4: SUB AL,PPIO ; Sirve para salir con NC CMP AL,FIN - PPIO + 1 ; en caso en que AL cumpla la condicin CMC ; PPIO <= AL <= FIN y con CY en caso contrario RET Peligrosos: ---------TRICK_5: CALL VER_SI_SIGO L1: ... CLC RET VER_SI_SIGO: ... JNC RET POP AX RET

; Chequear posible error ; Proceder como si no hubiera habido error ; Finalmente salir con NC como seal de OK ; ; ; ; ; ; Rutina que chequea el error Cdigo de chequeo propiamente dicho Si todo OK, salir (vuelve a L1) Si error, quita direccin de retorno del stack Ahora RETorna a la rutina que llam a TRICK_5, no a L1. Adems vuelve con la seal CY.

Simplifican el cdigo: --------------------TRICK_6: CALL EVALUAR ; Decidir qu hay que poner en AH JE L1 ; Si hay que poner VAL_2, salto a L1 MOV AH,VAL_1 ; Si hay que poner VAL_1, hacerlo DB 0A9 ; El valor 0A9 es el cdigo de TEST AX,... L1: ; donde ... son los 2 bytes ensamblados por MOV AH,VAL_2 ; la instruccin MOV AH,VAL_2 ... ; Si se lleg por L1, entonces AH = VAL_2 ; Si se pas por DB 0A9, entonces AH = VAL_1 TRICK_7: PUSH SEGUIR CMP AH,OPCION_1 JE ATENDER_1 CMP AH,OPCION_2 JE ATENDER_2 ... CMP AH,OPCION_n JE ATENDER_n SEGUIR: ... ; ; ; ; ; ; ; ; ; ; ; ; ; ; Debido a este PUSH, la prxima instruccin RET har que el flujo del programa contine en la direccin rotulada SEGUIR. Esta es una buena manera atender diferentes opciones antes de continuar con otras cosas. Si no fuera por el PUSH, habra que haber escrito algo como: CMP AH,OPCION_1 JNE > L1 CALL ATENDER_1 JMP SEGUIR L1: CMP AH,OPCION_2 ....

------------------------------------------------Tema: : Lost Interrupts ------------------------------------------------------------------------------@PID: RA 1.11 21280 @MSGID: 0:0/0 501af2ce COMUNICACION A BAJO NIVEL CON LOS PORTS PARALELOS ------------------------------------------------Exiten 3 registros por lo cuales acceder a un port paralelo, estos son: REGISTRO DIRECCION ------------------------------------------------------Data Output BASE Status BASE + 1 Control BASE + 2 Donde BASE = 03BCH (MDA, H rcules) 0378H (LPT1) 0278H (LPT2). Un programa puede determinar la direccin de BASE consultando la memoria baja de la BIOS en las direcciones: DIRECCION BASE ----------------0:0408 LPT1 0:040A LPT2 El cdigo para hacer esto es el siguiente:

GET_BASE: MOV AX,040H MOV ES,AX MOV DX,ES:[8] RET

; Estas dos instruciones sirven para que en la ; tercera el MOV lea la direccin 0040:0008 ; DX=BASE para LPT1 (poner 10 para LPT2)

Descripcin de los registros: ---------------------------DATA OUTPUT: Este es el registro que se usa para mandar los datos a la impresora. STATUS: Bits 7 6 5 4 3 2 1 0 | | | | | | | | | | | | | | | +-| | | | | | +---| | | | | +-----| | | | +-------| | | +---------| | +-----------| +-------------+---------------CONTROL: Bits 7 6 5 4 3 2 1 0 | | | | | | | | | | | | | | | +-| | | | | | +---| | | | | +-----| | | | +-------| | | +---------| | +-----------| +-------------+---------------0=estado normal, 1=salida de 1 byte (*) 0=estado normal, 1=Automatic LF after CR 0=inicializar port, 1=estado normal 0=desactivar printer, 1=estado normal 0=inhibir interrupciones, 1=habilitarlas \ > No se usan / \ > No se usan / 0=Printer error, 1=no error 0=Printer off line, 1=on line 0=Hay papel, 1=Out of paper 0=ACK (recibo de datos), 1=estado normal 0=Busy, 1=Printer not busy

(*) En los manuales de las impresoras hay un grfico que muestra cmo despu s de mandar un byte de datos al port, se debe mantener baja la seal STROBE (que normalmente est alta) durante un lapso que suele ser de 1/2 microsegundo como mnimo. El cdigo para inicializar el port sera: INIT_PORT: INC DX INC DX MOV AL,08H OUT DX,AL L1: MOV AX,01000H DEC AX JNZ L1 MOV AL,0CH OUT DX,AL RET ; ; ; ; ; ; ; ; ; ; Se supone que DX contiene la direccin BASE Control Registre = BASE + 2 018H si se desea habilitar las interrupciones Enviar el valor de inicializacin al port Bucle de espera 4096 veces Inicializar contador decrementar en 1 Jump Not Zero a L1 01CH si se desea habilitar las interrupts Enviar el valor normal al port.

Como su nombre lo indica el registro de Status sirve para checkear el funcionamiento de la impresora.

Ejemplo: Ver si la impresora est On Line y tiene papel: TEST: IN AL,DX TEST AL,BIT 4 JZ OFF_LINE TEST AL,BIT 5 JNZ OUT_OFF_PAPER RET ; ; ; ; ; Se supone que DX = BASE + 1 Consultar bit 4 del Status Register Jump if Zero a tratar el error Consultar bit 5 del Status Register Jump if Zero a tratar el error

Ejemplo: Enviar un byte de datos al port paralelo. PRINT_AL: OUT DX,AL INC DX INC DX MOV AL,0DH DEC AL OUT AL,0CH RET ; ; ; ; ; ; Se supone que DX = BASE y que AL = dato Pasar al port con direccin BASE + 2 para acceder al Control Register Bit 0 = 1 : Strobe bajo. 1DH si interrupts Bit 0 = 0 : Strobe alto. Enviar al port fin de seal "ah fue el dato"

Mensaje #11635 - Assembler 80x86 Fecha: 02-08-92 21:37 De : Leandro Caniglia Para: All Tema: : lost interrupts ------------------------------------------------------------------------------@PID: RA 1.11 21280 @MSGID: 0:0/0 501af302 Hola a todos. Como en los ltimos mensajes nos enganchamos con el tema de las interrupciones de la impresora, les paso la documentacin que encontr al respecto. Los que no tienen familiaridad con el assembler no arruguen que es fcil! Otra cosa: la informacin y las rutinas de este mensaje constituyen ejemplos de programacin a bajo nivel. OJO porque he visto como en la FCEyN hay [... no se que t rmino emplear... bue' digamos] docentes que le llaman programacin de bajo nivel a cualquier engendro escrito en assembler. Aclaro esto para que nadie crea que para imprimir un simple string por la impresora, un programador en assembler debe manejar toda esta informacin. No, para imprimir el string PIRULO lo nico que hay que hacer es esto: DATOS DB 'PIRULO' LONG EQU $ - OFFSET DATOS PRINT_PIRULO: MOV BX,OFFSET DATOS MOV CX,LONG JMP PRINT PRINT: ; String a imprimir ; LONG es una constante = Long(string) ; BX = Direccin del string ; CX = Long del string ; A la rutina de impresin

MOV AH,05 L1: MOV DL,[BX] INT 021 INC BX LOOP L1 RET

; Servicio nro 5 del DOS ; ; ; ; DL = caracter a imprimir imprimirlo apuntar al prximo caracter El bucle se repite CX veces

------------------------------------------------------------------------------Tema: : Segmentacin ------------------------------------------------------------------------------@PID: RA 1.11 21280 @MSGID: 0:0/0 50303aa6 Segmentacin -----------Este mensaje es una digresin de los mensajes sobre registros de segmento que estn bajo el subject "Lo primero que hay que saber..." Para ms detalles sobre segmentacin ver las partes quinta y siguientes. ----------------------------En el 8088 las direcciones absolutas de memoria se obtienen como un OFFSET o desplazamiento de hasta 64 Kilo bytes (65535 bytes) dentro de un SEGMENT. Esta es una caracterstica del direccionamiento *segmentado* que no se encuentra en otros micros. Por ejemplo la COMMODORE 128 tiene (esencialmente) el mismo micro que la 64 y sin embargo puede direccionar ms memoria. La t cnica que se us en la C128 es un poco diferente a la del 8088. En la 128 las direcciones se componen de 4 cifras hexa (16 bits). Esto, al igual que en la 64, da un espacio de direccionamiento de 64 Kbytes: la primera direccin es la 0000H y la ltima la FFFFH = 65535 decimal. Lo que se hizo en la 128 es agregar un chip llamado MMU (Memory Managment Unit) que sirve para elegir una configuracin de bancos. Fsicamente existen 128 Kbytes de RAM y se puede programar la MMU para que la CPU "vea" 64 de esos 128 K en cada momento. Esto se logra mediante una particin lgica de la memoria disponible en porciones ms pequeas. La MMU "elige" algunas de estas porciones y forma los 64 K que le hace ver a la CPU. El programador maneja esto programando la MMU para indicar la configuracin elegida. Si en un momento necesita acceder a datos que se encuentran en una porcin que est fuera de los 64 K activos, puede cambiar la configuracin para que ese sector de memoria ingrese en el campo visual de la CPU, dejando otro temporalmente fuera del mismo. Esto que dicho as, mal y pronto, puede parecer complicado es en realidad muy sencillo de manejar y cuando uno comprende los detalles lo incorpora rpidamente a su forma de pensar. LLegado ese punto deja de ser un problema y uno puede programar concentrado en lo realmente importante. La diferencia entre lo que acabamos de describir y la segmentacin de memoria del 8088 es que mientras en la C128 las configuraciones posibles (por bancos) son unas pocas (debido a limitaciones propias de la MMU), en el 8088 el comienzo de un segmento puede elegirse con absoluta libertad. La nica restriccin es que su direccin absoluta sea mltiplo de 16 (010H). De este modo se logra un manejo mucho ms flexible de la

memoria. Aqu no tenemos configuraciones rgidas de bancos predefinidos, tenemos una gran libertad de elegir el inicio del segmento de 64 Kbytes. Y una vez elegido un segmento, podemos movernos en l con direcciones de 16 bits como si se tratara de una computadora de 64 Kbytes. Ms an, las caractersticas fundamentales de la segmentacin son varias: 1) El origen de los segmentos se fija desde la CPU a trav s de los registros de segmento (y no mediante el manejo de chips especiales como el MMU de la C128) 2) En cada momento uno tiene 4 bloques de 64 Kbytes visibles por el 8088: uno con origen en cada uno de los registros de segmento DS, ES, CS, SS. 3) Cada segmento puede ser elegido con total libertad con la nica restriccin de que su direccin absoluta (de 20 bits) termine con la cifra hexadecimal 0 (es decir, sea mltiplo de 16) 4) La segmentacin no impide que paralelamente se puedan manejar bancos de memoria a trav s de chips especiales tipo MMU. De hecho hay zonas altas de memoria en las que chips de RAM y de ROM comparten el mismo direccionamiento (Shadow RAM) y el swapping entre uno y otro banco se maneja a trav s de la programacin de esos dispositivos. Hoy en da, la segmentacin puede parecer una limitacin frente a otras formas de direccionamiento directas en las que la CPU mantiene en su campo visual toda la memoria disponible. Sin embargo hay que entenderla como lo que realmente es: un avance con respecto a micros que manejaban espacios de direcciones de 64 Kbytes. Ya en la C64 hay en realidad ms de 64 Kbytes de memoria y un programador puede configurar el espacio de direccionamiento para que la CPU vea RAM o CHAR ROM o SYSTEM ROM o BASIC ROM o una combinacin de ellas. Los que entendieron esto en su oportunidad lo aprovecharon y cuando pasaron a la C128 puedieron ver las ventajas del manejo de bancos que esta ofreca. Del mismo modo, es posible aprovechar la experiencia adquirida en mquinas como la C128 para comprender y valorar la "solucin" de la segmentacin que permite un manejo sumamente flexible de 1 Mb de memoria en un micro cuyos registros son de 16 bits. Hoy en da la segmentacin conserva su vigencia por la inercia producida por millones de programas que corren bajo DOS, pero es una forma de trabajo en extincin. Sin embargo, puede ser importante entenderla y manejarla para no pasar por alto otra etapa de la evolucin de la computacin. ------------------------------------------------------------------------------Tema: : Alguna cosita sobre instrucciones no docuentadas ------------------------------------------------------------------------------@PID: RA 1.11 21280 @MSGID: 0:0/0 503ec330 Hola a todos. Charlando con Ricardo Pesce surgi el tema de las instrucciones no documentadas del 80x86. Supongo que todas las CPU son capaces de hacer cosas que nunca se vuelcan en la documentacin final. Gracias a las personas que trabajan en el desarrollo del microcdigo, muchas veces

podemos conocer algunas de esos misterios. El microprocesador 80286 tiene dos modos de funcionamiento: Real y Protegido. En el modo Real la 286 es 8086 rpido con unas pocas instrucciones ms, que tambi n existen en el 80186. El DOS trabaja en modo real. La gran diferencia entre el 80286 y sus antecesores 8088, 8086 y 80186 es la habilidad que tiene de pasar al modo protegido que posee facilidades para la multitarea y tiene un modo de direccionamiento que le permite direccionar 16 Mb (contra 1 Mb del modo real). Hay un servicio de la INT 15H de la BIOS de la AT que permite pasar al modo protegido. Oficialmente la nica manera de volver del modo protegido al real es reseteando. Sin embargo Mauricio Taslik me cont que existe una instruccin no documentada que permite volver del protegido al real. Parece ser que ciertos programas que necesitan manejar grandes volmenes de datos en memoria la utilizan. En el manual del A86, el autor comenta en el captulo 5 dos instrucciones que si bien son conocidas, tienen variantes no documentadas muy interesantes. Estas variantes estn disponibles en el A86-D86. Las instrucciones son AAM y AAD. Segn la documentacin estas instrucciones hacen lo siguiente: AAM AH <- AL DIV 10 AL <- AL MOD 10

donde AL DIV 10 es el valor entero de la divisin de AL por 10 y AL MOD 10 es el resto de esa divisin. AAD AL <- AH*10 + AL AH <- 0

La primera sirve para convertir un valor binario a decimal. La segunda hace el trabajo inverso. Por ejemplo: Si AL = 1BH, despu s de ejecutar la instruccin AAM queda: AH = 02H y AL = 07H, ya que 1BH = 16 + 11 = 27. Esto es comodsimo para mostrar el valor decimal de AL. Ejecutando: MOV AL,1BH AAM OR AX,3030H RET A la salida de la rutina, AH contiene el valor 32H, que es el ASCII de '2' y AL el valor 37H, que es el ascii de '7'. Con AAD podemos hacer el trabajo contrario. Convertir una cifra decimal a su valor hexadecimal: MOV AH,'2' MOV AL,'7'

AND AX,0F0FH AAD RET Y a la salida de la rutina, AH contendr el valor 1BH. Una vez ensambladas, la instrucciones AAM y AAD ocupan 2 bytes de memoria. AAM produce los bytes D4 0A y AAD los bytes D5 0A. Esto puede verse con ayuda del DEBUG. En el ensamble de toda instruccin del lenguaje mquina aparecen tantos bytes como sean necesarios para contener toda la informacin de la instruccin y sus operandos. En el caso de AAM y AAD no tenemos operandos (segn la documentacin oficial). Por lo tanto es innecesario que ocupen 2 bytes cada una. El byte que estara de ms es 0A. Pero 0A es justamente el valor 10 con el que estas instrucciones operan. Si fuera una constante para las operaciones no aparecera ah. Bueno ocurre que si uno reemplaza 0A por otro valor, las instrucciones hacen las cuentas con ese valor. Lo que se explica en el manual del A86 es que este ensamblador permite escribir AAM operando y AAD operando, donde "operando" es un operando de 1 byte. El A86 se encarga de ensamblar este operando a continuacin de D4 y D5. Si uno prueba a ensamblar desde el DEBUG un operando a continuacin de las instrucciones AAM y AAD obtiene un mensaje de error. Una aplicacin interesante de estas extensiones no documentadas es cuando el "operando" es 16. En ese caso AAM se ensambla los bytes D4 10 y AAD los bytes D5 10 (estoy escribiendo en hexa, 10 es 16 decimal). Si uno no quiere usar el A86 puede ir al DEBUG, ingresar el comando A(ssemble) y a continuacin escribir AAM o AAD. Luego de dar enter 2 veces, mirar los bytes ensamblados con el comando D(ump). Ah uno comprueba lo que dije del D4 0A y el D5 0A. Despu s con el comando E(dit) uno puede cambiar 0A por 10 (16 decimal). Desensamblando con U(nenssamble), se obtiene: ????:0100 D410 ????:0102 D510 AAM AAD 10 10

Esto significa que el DEBUG sabe desensamblar los operandos aunque no los sabe dessensamblar. Bien. Y por qu el valor 16 es un reemplazo interesante del 0A? Por lo siguiente. La rutina: MOV AL,1BH AAM 10H RET devuelve AH = 01H el nibble alto de AL AL = 0BH el nibble bajo de AL Sin el uso de esta instruccin deberamos haber hecho el siguiente despliegue para obtener el mismo resultado: MOV SHR SHR SHR SHR AH,AL AH,1 AH,1 AH,1 AH,1 ; Salvar AL en AH

AND AL,0FH RET En una 186, 286, 386 o 486 podemos poner SHR AH,4 en lugar de los 4 SHR AH,1 pero igual es ms complicado. En el 8088 no exite el SHR AH,4. Esto es muy til para imprimir en pantalla el contenido del registro AL ya que para mostrar las dos cifras hexadecimales que lo forman uno debe aislar cada uno de los nibbles, convertirlos a ASCII y despu s emitirlos. (En un mensaje de la serie "Lo primero que hay que saber..." esto est explicado con detalle. Con respecto al AAD, el operando 16 sirve para hacer el camino inverso: convertir dos nibbles en AH y AL en un byte en AL: MOV AH,01H MOV AL,0BH AAD 16 RET y a la salida AH = 0 AL = 1BH Sin este truco tendramos que haber escrito: MOV AH,01H SHL AH,1 SHL AH,1 SHL AH,1 SHL AH,1 OR AL,AH XOR AH,AX RET Como antes, en una 186+, podramos haber usado SHL AH,4 que no existe en el 8088/86. El autor del A86 advierte sin embargo que AAD no adimite el cambio de operando en los microprocesadores NEC V20 y V30 pero que AAM s funciona. Yo prob en un NEC V20 y lo comprob . Bueno, esto es todo lo que se hacerca de instrucciones no documentadas, aunque no es mucho me tom la libertad de explayarme como una escusa para repasar otras cositas. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instrucciones del Procesador 8088 Intel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AAA Ajuste ascii para la suma AAD Ajuste ascii para la diision AAM Ajuste ascii para la multiplicacion AAS Ajuste ascii para la resta ADC Suma con arraste ADD Suma AND Y logico CALL Llamada CBW Convierte byte a palabra CLC Borra el indicador de arrastre CLD Borra el indicador de interrupcion

CLI borra el inidicador de interrupcion CMC Complementa el inidicador de arrastre CMP Compara CMPS Compara byte o palabra (de cadena) CMPSB Compara cadena e bytes CMPSW Compara palabra o doble palabra CWD Convierte palabra a doble palabra DAA Ajuste decimal para la suma DAS Ajuste decimal para la resta DEC Decrementa DIV Divide ESC Escape HLT Alto IDIV Divide enteros IMUL Multiplica enteros IN Entre byte o palabra (de un port) INC Incrementa INT Interrupcion (ejecuta) INTO Interrumpe por desbordamiento IRET Vuelta de una interrupcion JA Salta si superior JAE Salta si superior o igual JB Salta si inferior JBE Salta si inferior o igual JC Salta si arrastre JCXC Salta si CX=0 JE Salta si igual JG Salta si mayor JGE Salta si mayor o igual JL Salta si menor que JLE Salta si menor que o igual JMP Salta JNA Salta si no superior JNAE Salta si no superior o igual JNC Salta si no arrastre JNE Salta si no igual JNG Salta si no mayor JNGE Salta si no mayor o igual JNL Salta si no menor JNLE Salta si no menor o igual JNO Salta si no desbordamiento JNP salta si no paridad JNS Salta si no signo JNZ Salta si no cero JO Salta si desbordamiento JP Salta si paridad JPE Salta si paridad par JPO Salta si paridad impar JS Salta si signo JZ Salta si cero LAHF Carga AH con indicadores LDS Carga puntero en DS LEA Carga direccion efectiva LES Carga puntero en ES LOCK Bloquea BUS LODS Almacena byte o palabra (de cadena) LODSB Almacena byte (cadena) LODSW almacena palabra (cadena) LOOP Bucle LOOPE Bucle mientras igual

LOOPNE Bucle mientras no igual LOOPNZ Bucle mientras no cero LOOPZ Bucle mientras cero MOV Mueve MOVS Mueve byte o palabra (de cadena) MOVSB Mueve byte (de cadena) MOVSW Mueve palabra (cadena) MUL Multiplicar NEG Niega NOP No operacion NOT Negacion logica OR O logico OUT Salida de byte o palabra (por un port) POP Desapila POPF Desapila indicadores PUSH Apila PUSHF Apila inidicadores RCL Rotacion a la izquierada contando con el arrastre RCR Rotacion a la derecha contando con el arrastre REP Repite RET Retorno RDL Rotacion izquierda ROR Rotacion Derecha SAHF Almacena AH en los indicadores SAL Desplazamiento aritmetico a la izquierda SAR Desplazamiendo aritmetico a la derecha SSB Resta con acarreo SCAS Busca byte o palabra (de cadena) SCASB Busca byte (cadena) SCASW Busca palabra (cadena) WAIT Espera SLAT Traslada SHL Desplazamiento a la izquierda SHR Desplazamiento a la derecha STC Pone indicador de arrastre STD Pone indicador de direccion STI Pone indicador de interrupcion STOS Almacena byte o palabra (de cadena) STOSB Almacena byte (cadena) STOSW Almacena palabra (cadena) SUB Resta TEST Test XCHG Intercambio (Swap) XOR O exclusivo Logico 2 Libros para leer: - Guia del programador para el IBM PC. - Guia del programador en ensamblador para el IBM PC. Ambos de Peter Norton.. la editorial es ANAYA, pero.. los conseguir por todos lados, en fotocopias (el de assembler, sale 120$, por eso, fotocopialo) Despues de leerte esos 2 libros.... vas a entender todo.. De cualquier manera, lista de interrupciones, te conviene conseguirte la q/ronda por varios bbs/s (Esta en Esabb) q/tiene interrupciones NO DOCUMENTADAS... Es decir, q/ni Peter Norton, te las dice... y q/con esas, les podes sacar bastante jugo a las Pcs... ------------------------------------------------------------------------------Tema: : Algo es algo

------------------------------------------------------------------------------; Este programa, as como est, debe ser compilado usando el A86. ; El paquete del A86 est en el area de files de assembler. ; El programa solamente anda en una AT. En una XT no hace nada. ;-----------------------------------------------------; ; Si tiene alguna duda mande un mensaje. ; ; Buena suerte ; ;-----------------------------------------------------; JMP MAIN ; Saltar por encima de los datos

MSG: DB 'FASTKBD V1.0 (1991) - por Leandro Caniglia',10,13 DB ' Con FASTKBD el cursor se mueve MUY RAPIDO.',10,13 DB '$' MAIN: CALL SHOW_MSG MOV AH,03 MOV AL,05 MOV BH,00 MOV BL,00 INT 22 RET SHOW_MSG: MOV DX,MSG MOV AH,09 INT 33 RET ; Algunas Explicaciones: ; ; ; ; ; ; ; ; ; ; ; El el de se JMP MAIN del principio es obligatorio porque cuando DOS transfiere control a un programa .COM lo hace mediante un salta al principio ese programa. Uno quiere que se ejecute la ruitna MAIN y no que interprete el mensaje como cdigo ejecutable. ; ; ; ; ; ; ; ; ; ; ; Imprimir mensaje en pantalla Nmero de funcin de la BIOS Nmero de subfucin Retardo mnimo: 250 milisegundos Ratio mnimo de typematic: 30 caracters/seg Interrucpin de la ROM BIOS Fin del programa, vuelta al DOS DS:DX apunta al comienzo del string Funcin Print String del DOS Imprimir el string Vuelta al punto de llamada

La instruccin INT 22 es una llamada a la BIOS. A trav s de ella se puede acceder a diferentes funciones de control del teclado. La nmero 03 sirve para establecer la velocidad de respuesta de las teclas. El valor en BH (retardo) sirve para determinar el tiempo que hay que mantener apretada una tecla para que entre en el modo de repeticin. El valor en BL (Typematic Ratio) es la velocidad de repeticin de la tecla que se mantiene apretada.

; La instruccin INT 33 es una llamada al DOS. Es la puerta de ; acceso a todas las funciones del DOS (abrir y cerrar archivos, ; formatear discos, etc, etc) ; La funcin 09 sirve para imprimir un string en pantalla. El string ; debe terminar con el caracter '$' (que no es mostrado en pantalla). ------------------------------------------------------------------------------Tema: : Programacion a bajo nivel ------------------------------------------------------------------------------;------------------------------------------------------; ; DESENSAMBLAR LA BIOS ES UNA BUENA MANERA DE APRENDER ; ;------------------------------------------------------;

; ** TODOS LOS NUMEROS ESTAN ESCRITOS EN BASE HEXADECIMAL ** ; ** ENSAMBLAR CON EL A86, EL PAQUETE COMPLETO ESTA EN LA SECCION FILES ; DE ESTA AREA ** ;INTRODUCCION: ; ; ; ; El TypeMatic es el proceso por el cual despu s de mantener pulsada una tecla durante un cierto perodo (Delay), el teclado entra en un modo de repeticin en el que *simula* que el operador pulsa y suelta la tecla a una velocidad dada (Repeat Rate).

; La BIOS de la AT contiene el cdigo necesario para programar el chip ; del teclado y ajustar los valores del Delay y del Repeat Rate. ; Si bien este cdigo puede invocarse fcilmente mediante la funcin 03 ; de la INT 016 con el subservicio 05, es interesante ver los detalles ; que lleva a cabo dicho subservicio. ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Como regla general, los valores que se envan al port 064 corresponden a comandos que sirven para programar el chip del teclado. Si estos comandos necesitan de datos adicionales, los valores son enviados al port 060. El comando para definir el comportamiento del TypeMatic tiene el nmero 0F3. Los datos que necesita son el Delay y el Repeat Rate. Esos datos se combinan en un byte (que se manda la port 060) de la siguiente manera: bits 7 : reservado (siempre 0) 6,5 : Delay: 00=250ms, 01=500ms, 10=750ms, 11=1seg 4,3,2,1,0 : Repeat Rate: de los 32 posibles valores los ms importantes son los que siguen (los dems salen por interpolacin) 0=30.0 0A=10.0 1=26.7 0D= 9.2 2=24.0 010= 7.5 4=20.0 014= 5.0 8=15.0 01F= 2.0 repeticiones/seg

; El comando 0F4 se llama Enable Keyboard. Al enviarlo el teclado ; devuelve una seal de ACKnowledge, limpia sus buffers internos y ; reinicia el scanneado de la matriz del teclado. ; Otra cosa a tener en cuenta es la variable de la BIOS ubicada en la ; direccin 040:097. Es una variable de 1 byte que contiene en sus bits ; la siguiente informacin del teclado: ; ; ; ; ; ; bits 7 6 5 4 3 2,1,0 : : : : : : 1=Error Mode Indicator Update (?) Resend Receive Flag (?) Received ACKnowledge Reservado Estado de los LEDs del teclado

;-------------------------------------; ; PROGRAMACION DEL TYPEMATIC ;

; Entran: BH = Delay = (0..3) ; ; BL = Repeat Rate = (0..01F) ; ;-------------------------------------; SBT_08C16: STI ; Habilitar interrupciones de Hardware PUSH DS,BX,CX ; Salvar registros estropeados PUSH 040 ; DS = 040 (Zona de variables de la BIOS) POP DS ; a traves del stack CMP BL,01F ; BL = Repeat Rate debe estar entre 0 y 01F JA > L1 ; Rechazar si invlido CMP BH,03 ; BH debe tener nicamente 2 bits JA > L1 ; Rechazar si invlido ROR BH,3 ; Manda bits 0,1 a 5,6 para obtener Delay OR BL,BH ; BL = Delay y Repeat Rate CALL SBT_08AA4 ; Mandar Comando 0AD (?) al port 064 STI ; Habilitar interrupciones por Hardware MOV AL,0F3 ; Valor para definir el TypeMatic CALL SBT_08B9B ; Mandar comando 0F3 al port 060 MOV AL,BL ; AL = datos de TypeMatic CALL SBT_08B9B ; Mandar los datos al port 060 CALL SBT_08A9B ; Mandar comando 0AE (?) al port 064 STI ; Habilitar interrupciones por Hardware MOV AL,0F4 ; Valor para Enable Keyboard (Habilita Teclado) CALL SBT_08B9B ; Mandar el comando L1: POP CX,BX,DS ; Restaurar registros RET ;----------------------------; ; ATENCION! ; ; La rutina que sigue inhibe ; ; el teclado al mandar el ; ; valor 0AD al port 064 ; ;----------------------------; SBT_08AA4: CALL SBT_08AB1 ; Leer port 064 MOV AL,0AD ; Enviar comando 0AD (?) OUT 064,AL ; al port 064 CALL SBT_08AB1 ; Leer port 064 IN AL,060 ; Leer port 060 RET SBT_08AB1: CLI XOR CX,CX L1: JMP > L2 L2: IN AL,064 TEST AL,02 LOOPNZ L1 JZ RET RET SBT_08B9B: MOV AH,AL MOV CX,03 L1: PUSH CX CALL SBT_08AB1 ; ; ; ; ; ; ; ; ; Inhibir interrupciones por Hardware CX = contar 65.536 veces como mximo Bucle de espera Salto que sirve para esperar un poco antes de volver a leer el port. Leer port 064 Consultar bit 1 (?) Si apagado continuar esperando Cuando bit 1 apagado, salir (?)

; Salvar dato en AH ; Intentar hasta 3 veces ; Salvar contador de veces (hasta 3) ; Consultar port 064

MOV AL,AH OUT 060,AL AND B[097],04F STI XOR CX,CX L2: TEST B[097],030 JNZ > L4 LOOP L2 L3: TEST B[097],030 LOOPZ L3 L4: CLI POP CX TEST B[097],010 LOOPZ L1 JNZ RET OR B[097],080 RET SBT_08A9B: CALL SBT_08AB1 MOV AL,0AE OUT 064,AL STI RET ; Notas:

; ; ; ; ; ;

Recuperar dato Mandar el dato al port 060 Apagar bits 7 (Error), 5 (Resend Receive Flag) y 4 (Received ACKnowledge) Habilitar interrupciones CX = contar 65.536 veces como mximo

; Consultar Resend Receive Flag & ACK ; Si alguno encendido continuar ; Esperar ; Si no hubo respuesta esperar ; hasta 65.536 veces ms ; ; ; ; ; ; Inhibir interrupciones Recuperar contador de (hasta 3) intentos Se recibi ACKnowledge del teclado? No, nuevo intento si cabe S, volver (OK) Tres intentos fallidos: activar bit de error

; ; ; ;

Consultar port 064 Comando 0AE (?) Enviar el comando Habilitar interrupciones

; 1. Los comentarios marcados con (?) corresponden a cosas que no ; comprendo. Si alguien tiene la informacin que falta para completar ; esto, por favor envela al BBS. ; 2. La BIOS de la XT no posee estas rutinas. Por eso no pueden usarse ; los subservicios de la funcin 03 de la INT 016 para programar la ; velocidad de TypeMatic. Sera bueno que alguien ensamble esto, lo ; pruebe en una XT y luego cuente cmo le fue. Ojo porque puede haber ; diferencias con los ports 060 y 064 entre XT y AT. ; 3. Hay otros subservicios interesantes en la funcin 03 de la INT 016. ; Si alguien hace un estudio de los mismos, hgamelo saber. ; 4. Las nicas diferencias que tienen estas rutinas con las de la BIOS ; son: ; a) Se evitaron los JMPs iniciales propios de las rutinas en ROM ; b) Se elimin la comprobacin de los valores de AH y AL que ; contienen los nmeros de funcin y subservicio de la INT 016 ; c) Se reemplaz el IRET por un RET comn ------------------------------------------------------------------------------Tema: : Otro ejemplito a propsito de un mensaje ------------------------------------------------------------------------------; En un mensaje dirigido a ALL en el rea general, Norberto Olari pidi ; una frmula o un programa en cualquier lenguaje para calcular el da ; de la semana en que cae una fecha dada. ; Bien, el programa que mando a continuacin sirve para calcular el da ; de la semana de una fecha entre 1980 y 2099.

; No usa ninguna frmula para calcular el da ya llama a las funciones ; 2AH y 2BH de la INT 21H (DOS) que hacen el trabajo (de ah la ; limitacin en la amplitud de las fechas. ; ; ; ; ; Existe sin embargo una frmula para calcular el da de la semana de cualquier ao (aguna vez hice ese ejercicio en el BASIC de la COMMODORE). Si alguien tiene ganas y necesita algn dato, le ofrezco mi ayuda. Sera (cmo ste) uno de esos ejercicios que hay que hacer alguna vez en la vida: buenos para escribir pero raramente necesarios. JMP MAIN ANIO DW ? MES DB ? DIA DB ? DIA_DE_LA_SEMANA DB ? MAIN: CALL GET_SYS_DATE PUSH CX,DX CALL SET_SYS_DATE CALL GET_SYS_DATE MOV DIA_DE_LA_SEMANA,AL POP DX,CX CALL SET_SYS_DATE MOV AL,DIA_DE_LA_SEMANA MOV AH,04CH INT 021H GET_SYS_DATE: MOV AH,02AH INT 021H RET SET_SYS_DATE: MOV AH,02BH MOV CX,ANIO MOV DH,MES MOV DL,DIA INT 021H RET ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Poner aqu el ao entre 1980 y 2099 Un byte para el mes Un byte para el da Aqu sale 0=domingo, 1=lunes,... 0FFH=fecha no vlida Leer fecha del sistema salvar en la pila Cambiar fecha de hoy x fecha dada Si fecha vlida leer da de la semana salvar en variable de memoria Recuperar fecha de hoy y restablecerla. Mandar el resultado para que quede en la variable ERRORLEVEL del DOS y salir.

; Funcin del DOS para leer fecha ; LLamada al DOS

; ; ; ; ;

Funcin del DOS para definir fecha CX = ao entre 1980 y 2099 DH = mes DL = da LLamar al DOS

Mensaje #12163 - Assembler 80x86 Fecha: 19-08-92 01:03 De : Leandro Caniglia Para: All Tema: : Otro ejemplito a propsito de un mensaje ------------------------------------------------------------------------------@PID: RA 1.11 21280 @MSGID: 0:0/0 50303b68 ; En un mensaje dirigido a ALL en el rea general, Norberto Olari pidi ; una frmula o un programa en cualquier lenguaje para calcular el da ; de la semana en que cae una fecha dada. ; Bien, el programa que mando a continuacin sirve para calcular el da ; de la semana de una fecha entre 1980 y 2099.

; No usa ninguna frmula para calcular el da ya llama a las funciones ; 2AH y 2BH de la INT 21H (DOS) que hacen el trabajo (de ah la ; limitacin en la amplitud de las fechas. ; ; ; ; ; Existe sin embargo una frmula para calcular el da de la semana de cualquier ao (aguna vez hice ese ejercicio en el BASIC de la COMMODORE). Si alguien tiene ganas y necesita algn dato, le ofrezco mi ayuda. Sera (cmo ste) uno de esos ejercicios que hay que hacer alguna vez en la vida: buenos para escribir pero raramente necesarios. JMP MAIN ANIO DW ? MES DB ? DIA DB ? DIA_DE_LA_SEMANA DB ? MAIN: CALL GET_SYS_DATE PUSH CX,DX CALL SET_SYS_DATE CALL GET_SYS_DATE MOV DIA_DE_LA_SEMANA,AL POP DX,CX CALL SET_SYS_DATE MOV AL,DIA_DE_LA_SEMANA MOV AH,04CH INT 021H GET_SYS_DATE: MOV AH,02AH INT 021H RET SET_SYS_DATE: MOV AH,02BH MOV CX,ANIO MOV DH,MES MOV DL,DIA INT 021H RET ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Poner aqu el ao entre 1980 y 2099 Un byte para el mes Un byte para el da Aqu sale 0=domingo, 1=lunes,... 0FFH=fecha no vlida Leer fecha del sistema salvar en la pila Cambiar fecha de hoy x fecha dada Si fecha vlida leer da de la semana salvar en variable de memoria Recuperar fecha de hoy y restablecerla. Mandar el resultado para que quede en la variable ERRORLEVEL del DOS y salir.

; Funcin del DOS para leer fecha ; LLamada al DOS

; ; ; ; ;

Funcin del DOS para definir fecha CX = ao entre 1980 y 2099 DH = mes DL = da LLamar al DOS

Você também pode gostar