=======================================================
Introduccin a la programacin en lenguaje ensamblador
para procesadores Intel serie x86 y compatibles (IV)
=======================================================

Por nmt
numit_or@cantv.net

=======================================
MACROS, ESTRUCTURAS DE CDIGO Y CADENAS
======================================= 

--------------------------------------------------------
  CONTENIDO

  - Anlisis el programa anterior: Introduccin a los arrays

  - Macros y subrutinas: 
	      Inicializacin de un programa
	      Directiva PROC
              El PSP
		NOTA SOBRE LOS SEGMENTOS
		NOTA SOBRE LAS DIRECTIVAS SIMPLIFICADAS PARA SEGMENTO
	      Despliegue de cadenas de caracteres
	 Macros: primera aproximacin
	 Subprogramas
		 Directiva PROC y la instruccin call

 - Estructuras de Control en Lenguaje Ensamblador
	 Comparaciones: segunda aproximacin

 - Instrucciones de salto o ramificacin: 2da, aproximacin
	 Instrucciones para bucles

 - Estructuras de control estndar en ensamblador

 - Tratando con cadenas de caracteres

 - Lnea de rdenes
	 Anlisis



--------------------------------------
Anlisis el programa anterior: arrays
--------------------------------------
Antes de proceder a la simplificacin de nuestro programa,  hagamos 
su anlisis:

; --------------------------------------------------------
  TITLE TEST1.ASM: Programa para probar direccionamientos
; --------------------------------------------------------
	.model small
; --------------------------------------------------------
space	EQU	32
; --------------------------------------------------------
	.stack 32

 - Inicializacin -
En este pasaje indicamos  el  ttulo,  en la primera lnea,  usando
la  directiva  TITLE,  que comenta el resto de la lnea,  como ";". 
Luego indicamos el modelo de memoria, SMALL en este caso,  que como 
ya dijimos indica que asignamos un segmento para cdigo,  uno  para 
datos y uno para la pila.  La  directiva  ".model"  se  emplea para 
indicar  el  modelo  de  memoria  y  que  debemos  usar  directivas 
simplificadas para indicar los segmentos.  Aclararemos  esto  en su 
oportunidad. El detalle nuevo es la instruccin "space EQU 32";  lo 
que hace esta lnea es asignar al trmino "space" el valor 32. Esto 
significa que el trmino "space" puede usarse  como  equivalente al 
nmero 32 Por qu no usar mejor 32?  porque  "space" ilustra mejor 
el uso para el cul est destinado este valor en nuestro  programa: 
32 es el equivalente ASCII del caracter espacio.

Finalmente, declaramos el tamao de la pila con la directiva .stack
y su tamao: 32 bytes.


 - Cadenas terminadas en cero -
A continuacin se declaran los datos del programa:

; --------------------------------------------------------
	.data
string1	db	'Hola gente', 0
string2 db	'Programa_TEST1.ASM', 0
; --------------------------------------------------------

Como  puede  observarse,  se  trata  de dos cadenas de caracteres,
declaradas como ya lo hemos especificado:  usando  la  instruccin
"db"; lo interesante ahora es que terminamos  las cadenas  con  el
nmero "0", y ya no con el signo "$".


 - Directiva PROC -
Sigue ahora el cdigo, un poco ms complejo, y encerrado entre las 
directivas "main proc" y "main endp". Estas directivas sealan los 
lmites o el mbito de un procedimiento,  en  esta ocasin llamado 
"main".

	.code
main	proc
_init:
	mov	ax, @data
	mov	ds, ax

	; ...

main	endp

Las instruciones de una rutina de un programa es "empacada"  en un 
bloque que llamamos procedimiento.  Para indicar el comienzo de un 
procedimiento, usamos la directiva PROC y usamos ENDP para indicar 
el final. El formato es:

	nombre_del_procedmiento	PROC	distancia

La distancia puede ser FAR o NEAR. Se usa FAR en  el  procedimieto 
principal de un programa .EXE, el punto de entrada principal de la 
aplicacin.

La distancia NEAR se reserva para los procedimientos que se hallan 
en un mismo segmento.  Cuando  se omite el indicador de distancia, 
por defecto, el ensamblador interpreta el procedimiento como NEAR.



 - El PSP -
Es importante explicar estas lneas. Cuando el programa se inicia,
el  propio sistema operativo le antepone un rea de 256 bytes con
datos  relevantes sobre el programa. Esta rea se conoce como PSP 
(Prefijo de Segmento del Programa), se ubica en el desplazamiento
cero desde la base del programa en memoria.  As  que el programa
propiamente dicho se ubicar en el desplazamiento 256 o 100h.  El
PSP incluye infomacin valiosa, como un buffer donde DOS pone  el
texto que introdujo el usuario para ejecutar el programa y  desde
donde se pueden extraer los parmetros pasados al programa.

Cuando el programa es cargado,  los  registros  DS y ES poseen la
direccin del PSP, es decir, la direccin de la base del programa
en la memoria. Todava el registro DS no tiene  la  direccin del
segmento de datos. Las lneas anteriores se encargan de eso: como
no se puede mover directamente datos en memoria a  los  registros 
de segmento, entonces se pasa primero la direccin  del  segmento 
de datos a AX y luego desde AX a DS. Ntese que la direccin  del
registro de datos est contenida en la variable especial "@data",
vlida solamente cuando se usan las  directivas  simplificadas de
segmento (.model, .stack, .data, .code).

	*NOTA SOBRE LOS SEGMENTOS*
	Para declarar un segmento se emplea la directiva SEGMENT.
	Recordemos  que  los i386+ permiten segmentar la memoria: 
	dividir  el  espacio disponible en bloques de diferentes 
	tamaos, llamados segmentos.

	El  formato .EXE  de  los ejecutables de DOS se adapta a 
	esta posibilidad:  la informacin que contiene se divide  
	en segmentos cuyo contenido puede variar.

	Hemos visto que los segmentos pueden ser reservados para 
	datos (el segmento de datos),  cdigo  (el  segmento  de 
	cdigo),  o  para ser empleado como pila (el segmento de  
	pila).  Tambin  habamos mencionado que la segmentacin 
	del .EXE aporta una primera clasificacin de los objetos 
	de un programa.

	Para definir un segmento en un programa,  se emplean dos 
	directivas solidarias,  una  para  indicar  comienzo del 
	segmento,  SEGMENT, y otra para indicar el final,  ENDS. 
	El formato sera el siguiente:

		NOMBRE	OPERACIN  OPERANDO  COMENTARIO
		nombre	SEGMENT	  [opciones] ; inicio del segmento
		
		
		
		nombre	ENDS

	En DOS, el tamao mximo de un segmento es 64KB, excepto 
	en modelo de memoria HUGE, donde puede ser mayor.  En el 
	modelo FLAT, usado en sistemas de 32 bits, el ejecutable 
	est constituido por un nico segmento que puede ser, en 
	teora, de hasta 4 GB.  Son  tres  los tipos de opciones 
	posibles para SEGMENT:

	  sintaxis:  nombre  SEGMENT  alineacin  combinar  'clase'

	alineacin: indica  el lmite de inicio del segmento. El 
		segmento iniciar en una direccin  inmediata  a 
		la indicacin, que puede variar:

			BYTE: Inicio en la siguiente direccin.
			WORD: Siguiente direccin par. Divisible 
			      entre 2.
			DWORD: Siguiente  direccin  de  palabra 
			      doble. Divisible entre 4.
			PARA: Siguiente  prrafo.   Un   prrafo 
			      equivale a 16 bytes.
			PAGE: Siguiente  direccin   de  pgina. 
			      Divisible entre 256.

	combinar: esta opcin se incluye si se  quiere  combinar 
		un  segmento  con  otros  en tiempo de enlazado, 
		despus de haber  sido  ensamblados.  Los  tipos 
		combinar son:

			NONE: Segmento  separado  lgicamente de 
			      los otros.  El  segmento tendr su 
			      propia direccin base.

			PUBLIC: el segmento ser cargado junto a 
			      otros  del  mismo  nombre  y clase 
			      adyacente.  Todos  estos segmentos 
			      tendrn una  sola  direccin  base 
			      comn para todos.

			STACK: LINK  de  Microsoft  trata  STACK
			       como  PUBLIC.   Indica   que   el 
			       segmento se reserva para la pila.

			COMMON: El  segmento  ser  agrupado con 
			       los que tienen el mismo nombre  y 
			       la misma clase. Todos tendrn una 
			       misma direccin de  base.  En  la 
			       ejecucin   del   programa,   los 
			       segmentos se traslaparn y el ms 
			       grande determinar el tamao  del 
			       rea comn.

			AT direccin: Permite  definir etiquetas 
			       y  variables  en  desplazamientos 
			       fijos en reas de memoria como la 
			       tabla de interrupciones o el rea
			       de datos del BIOS. Lo que hace el 
			       ensamblador, es crear un segmento
			       mudo   o   ficticio    que   slo 
			       proporciona  una  imagen  de  las 
			       localidades de memoria.  

	clase: la clase de un segmento se pone entre  apstrofos 
		e  indica al  enlazador cmo deben agruparse los 
		segmentos  en  tiempo  de  enlace.  Los  nombres 
		recomendados son 'code', 'data' y  'stack'  para 
		cdigo, datos y pila, respectivamente.


	*NOTA SOBRE LAS DIRECTIVAS SIMPLIFICADAS PARA SEGMENTO*
	TASM  y  MASM  proporcionan  la  directiva  .MODEL  para 
	simplificar la declaracin de segmentos. Su formato es:

			.MODEL modelo_de_memoria

	Al usar  .MODEL  ya  no tiene que emplearse la directiva 
	SEGMENT,  ya que .MODEL  har que el ensamblador acomode 
	los segmentos de acuerdo a los modelos preestablecidos:

	        TINY    reservado para generar ejecutables .COM: 
			1 segmento  para datos y cdigo, todo en 
			un nico segmento.

		SMALL	1 segmento de datos, 1 seg. de cdigo.

	        FLAT    1  segmento  para  datos  y  cdigo.  El 
			segmento puede ser,  en teora, de hasta 
			4 GB.

	        MEDIUM  Varios  segmentos  de  datos,  1 seg. de 
			cdigo.

	        COMPACT 1 seg. de datos, varios de cdigo.

	        LARGE   Varios segs. de datos, varios de cdigo.

		HUGE	Varios segmentos para datos y cdigo. Los 
			segmentos pueden ser superiores a los  64 
			KB.

	Despus de indicarla, slo hay que sealar los  segmentos 
	con sus respectivas directivas simplificadas:

		.MODEL modelo_de_memoria 


		.STACK tamao

	
		.DATA

			[elementos]

		.CODE

			[instrucciones]
		
		END

	Las directivas simplificadas para segmentos son:

		.STACK	define la pila.

		.CONST	definicin  de  un  segmento de datos de 
			clase 'const'.
		
		.DATA	un  segmento   para   inicializar  datos 
			cercanos.

		.DATA?	define un segmento para  datos  cercanos 
			no inicializados.

		.FARDATA define un segmento para  datos  lejanos 
			inicializados.

		.FARDATA? define un segmento para  datos lejanos 
			no inicializados.

		.CODE	define un segmento de cdigo



 - Despliegue de cadenas de caracteres -
El siguiente fragmento despliega la cadena "string1"

	mov	si, 0
_print_string1:
		lea 	bx, string1
		mov	al, [bx][si]
		test	al, al
		jz	_print_string21

		mov	bx, 000Fh                                                   
		mov	ah, 0Eh                                                     
		int	10h
		inc	si
	jmp	_print_string1 

Aqu  ya  no  usamos el servicio 9 de la interrupcin 21h de DOS 
para  desplegar cadenas de caracteres. Usamos el servicio 0Eh de
la interrupcin 10h del BIOS  que  despliega  un  caracter,  que 
ponemos en AL, en el monitor.

En  la rutina utilizamos  SI como un ndice al array "string1" y 
lo inicializamos con cero,  para  que apunte al primer caracter. 
La  instruccin  "lea  bx,  string1"  pone en BX la direccin de 
"string1". En la instruccin "mov al, [bx][si]", donde empleamos 
direccionamiento indirecto de base-ndice (base del array en BX, 
ndice en SI), movemos un caracter del array "string1" a AL.

La instruccin "test al, al" prueba para verificar si  AL  tiene 
cero. La operacin TEST somete a los operandos a  una  operacin 
lgica de conjuncin AND, que resulta en 0 si sus  operandos son 
cero, lo que activara la bandera ZF.  La siguiente instruccin, 
"jz _print_string21", revisa la bandera ZF y si est activada se 
pasa  el  control  a  la  direccin  indicada  por  la  etiqueta 
"_print_string21".  Por  lo  tanto,  se  trata  de  un bucle que 
terminar de ejecutarse cuando en el array se encuentre un cero.

Luego, las siguentes tres lneas, despliegan  el contenido en AL
aprovechando  el  servicio 0Eh (mov ah,  0Eh) de la interrupcin
10h. Ntese que el servicio de interrupcin siempre se indica en
la parte alta del registro AX, es decir, en AH. 

La instruccin  "mov bx, 000Fh",  antes de la interrupcin  10h, 
debera indicar el color del caracter a desplegar y el color  de  
su  fondo,  de  acuerdo  a  un  cdigo  preestablecido que en su 
oportunidad explicaremos.

Despus  de  ejecutada  la  interrupcin  10h  y  desplegado  el 
caracter, se incrementa SI (inc si),  para apuntar al  siguiente 
caracter en el array "string1", y se obtiene este nuevo caracter 
en AL para desplegarlo, proceso que se repetir hasta que,  como 
sealamos, se ubique un cero que seala el final de la cadena.

Luego se despliegan cuatro espacios:

_print_string21:
	mov	cl, 4
	_next1:
		push	cx
		mov	al, space

		mov	bx, 000Fh                                                   
		mov	ah, 0Eh                                                     
		int	10h
	
		pop	cx
		dec	cx
	jne	_next1

El  nmero  de  espacios  se  indican  con  "mov  cl, 4".  Luego 
guardamos el contenido original de CX en la pila  (push cx),  ya 
que  al  ejecutarse  la interrupcin puede cambiarse el valor en 
CX.  Se despliega el espacio,  como cualquier otro caracter,  se
recupera el valor original de CX guardado en la  pila  (pop cx), 
decrementamos su valor y si se ha llegado a cero, se activar la 
bandera ZF; mientras esta bandera no se active la rutina volver 
a ejecutarse, desplegando de nuevo  un  espacio.  La instruccin 
"jne _next1", pasa el control a "_next1" siempre  que la bandera 
ZF no est activada.

El siguiente fragmento es medio bizarro.  Se incluye simplemente
para mostrar un direccionamiento indirecto usando base, ndice y 
escalar.

_print_space:
	mov	si, 0
	_next2:
		mov	bx, offset string2
		mov	al, [bx+si+2]

		mov	bx, 000Fh
		mov	ah, 0Eh                                                     
		int	10h

		mov	ah, 10h
		int	16h

		inc	si
		cmp	si, 2
	jnz	_next2


De  nuevo  usamos  SI  como  ndice,  inicializndolo en cero, y 
cargamos  en BX la direccin de "string2".  Ntese que no se usa 
la instruccin LEA sino  MOV,  con el operador  "OFFSET".  Luego 
movemos a AL el segundo caracter en el array  "string2" (mov al, 
[bx+si+2], bx tiene la base del array,  si  un ndice y 2  es un 
escalar),  que ser desplegado usando  el  servicio  0Eh  de  la 
interrupcin 10h.  Luego  usamos  el  servicio 10h de la int 16h 
para  detener momentneamente el programa en  espera  a  que  el 
usuario pulse una tecla. Cuando se hace,  se  incrementa SI y se 
revisa luego si contiene 2 (cmp si, 2).  Si  SI  contiene  2, se 
activar la bandera ZF, en caso contrario se ejecutar  un salto 
que har que se ejecute de nuevo el bucle (jnz _next2),  pasando 
el  control  de  nuevo  a  _next2.  Cuando  el  bucle  vuelve  a  
ejecutarse,   se   desplegar  el  tercer  caracter;  cuando  se 
incremente SI contendr 2 y ya no se ejecutar un salto a _next2.

Las siguientes lneas despliegan un espacio:

	mov	al, space
	mov	bx, 000Fh                                                   
	mov	ah, 0Eh                                                     
	int	10h

Luego  desplegamos toda la cadena "string2". Esta vez lo hacemos 
de  manera que cada caracter se vaya desplegando uno a uno, cada 
vez que el usuario pulse una  tecla,  operacin  que  continuar 
hasta alcanzar el final de la cadena, indicado por un cero:

_print_string22:
	lea	si, string2
	_next3:
		mov	al, [si]
		test	al, al
		jz	_end_

		mov	bx, 000Fh                                                   
		mov	ah, 0Eh                                                     
		int	10h

		mov	ah, 10h
		int	16h

		inc	si
	jmp	_next3

En  este  caso,  para mover a AL el caracter  indicado,  se  usa 
direccionamiento  indirecto  por  ndice:  se  carga  en  SI  la 
direccin de "string2" (lea si, string2), as que SI contiene en 
este  momento  la  base  del  array  "string2";  luego   movemos 
directamente el caracter a AL  (mov al, [si]).  Se revisa si ese 
caracter  es  cero  (test al, al)  y  si lo es salimos del bucle 
(jz _end_). Si no se ha alcanzado an el final de la cadena,  se 
despliega el caracter usando de nuevo  el  servicio  0Eh  de  la 
interrupcin 10h, se detendr la ejecucin hasta que el  usuario 
pulse  una  tecla  y  se  incrementar  SI  para  que  apunte al 
siguiente caracter.


 - Salir del programa -
Cuando  se  alcanza el final de la cadena,  en  AL debe haber un 
cero  y la instruccin "test al, al"  activar  la  bandera  ZF, 
haciendo  que  la  siguiente  instruccin, "jz _end_",  pase  el 
control a la rutina final:

_end_:
	mov	ah, 10h
	int	16h

	mov	ax, 4C00h
	int	21h
main	endp
end	main

Ahora  se  detiene  la  ejecucin hasta que el usuario pulse una 
tecla y, cuando lo haga, se ejecutar la rutina de salida a DOS, 
usando el servicio 4Ch de la interrupcin  21h.  La  instruccin 
"mov ax, 4C00h", pone 4Ch en AH y 00 en AL.

Como puede observarse,  se  trata  de  un  primer  programa  que 
manipula arrays y cadenas de caracteres.  El  8086 incluye en su 
repertorio instucciones que facilitan este trabajo  que  an  no 
usamos pero que ya veremos.

Para  sealar  el  final del programa se indica con la directiva 
END que indica el final del programa. Su formato es:

		END [direccin del inicio del cdigo]

Si se escribe un programa en un mdulo secundario,  donde  no se 
halla el punto de entrada, entonces debe terminarse el  programa 
escrito en este fichero con END, sin indicar nombre de inicio.




====================
Macros y subrutinas
====================

-----------------------------
Macros: primera aproximacin
-----------------------------
Despus  de  ejecutada  la  interrupcin  10h  y  desplegado  el 
Cualquier programador entrenado en lenguaje ensamblador,  notar 
de inmediato que el programa anterior puede mejorarse. En primer 
lugar, hay muchos pasajes que hacen lo  mismo y que bien podran 
ser reunidos en un slo bloque de cdigo.  Por ejemplo,  siempre 
se usa el mismo cdigo para desplegar un caracter:

		mov	bx, 000Fh                                                   
		mov	ah, 0Eh                                                     
		int	10h

El servicio 0Eh de la interrupcin 10h imprime  sobre el monitor 
el caracter que est en AL.

Una manera de aligerar el trabajo puede ser escribir  una  macro 
para esta rutina:

		Display_char 	macro
			mov	bx, 000Fh                                                   
			mov	ah, 0Eh                                                     
			int	10h
				endm

Luego slo escribimos algo como:

		mov	al, "A"
		Display_char

Las  directivas "macro" - "endm" nos permiten encapsular  varias 
lneas  de  cdigo  que  sern desplegadas por el ensamblador en 
tiempo  de  ensamblado  cada vez que encontramos el nombre de la 
macro. La directiva macro tiene el siguiente formato:

		nombre		macro
			...
			; contenido de la macro
			...
				endm

Con  las macros se abren posibilidades  muy  interesantes,  pero 
tienen un problema, si su contenido es  muy  amplio  y  se  usan 
mucho, el cdigo se agrandar ms de  lo  deseado.  Asi  que  en 
algunos casos es mejor otra opcin.

Lo ideal sera capturar mentalmente lo comn que encontramos  en 
diversas rutinas y buscar cmo sintetizarlas en una sola rutina, 
en un concepto, en una funcin.

Una vez apresado un fragmento que puede ser encapsulado,  que ya 
podramos hacerlo mediante macros,  lo mejor sera colocarlo  en 
una pare aparte del programa y pasarle el control cada  vez  que 
sea necesario.

Esto puede hacerse usando subrutinas.  Para ello, escribimos las 
rutinas aparte  usando  la  directiva  "proc - endp".  Luego  le 
pasamos el control cuando  sea necesario  usando  la  intruccin 
"call".


-------------
Subprogramas
-------------
Un subprograma es una unidad independiente  de  cdigo que puede 
ser usada desde diferentes  partes  de  un  programa.  En  otras 
palabras, un subprograma es como una funcin en lenguaje C. Para 
invocar subprogramas se podra usar algn salto, pero el regreso 
a la rutina que hizo la invocacin representa  un  problema. As 
que la direccin de  regreso debe ser indicada. 

El  8086  tiene  dos  instrucciones  para facilitar la llamada a 
subprogramas:   la  instruccin  CALL,   que  realiza  un  salto 
incondicional a un subprograma y mete en la pila  la direccn de 
la siguiente instruccin; otra instruccin,  RET saca de la pila 
la direccin y salta a  esa  direccin.  Cuando  se  usan  estas 
instrucciones, es importante que uno maneje bien  la  pila  para 
que  el  valor  correspondiente  a  la direccin de regreso, sea 
sacado de ella por la instruccin RET.

El uso de CALL y RET tene varias ventajas:

	 Es ms simple
	 Permite  que  las  llamadas a subprogramas sea anidada 
	  fcilmente.



Directiva Proc y la instruccin call
------------------------------------
La directiva PROC se emplea para encapsular rutinas.  Indica un 
"procedimiento" que debe realizar el sistema:  un  programa  en 
ensamblador, como los escritos en C, generalmente consta de una 
rutina o procedimiento principal ("main ()" en C)  y  una serie 
de subrutinas o procedimientos secundarios.

Tenemos entonces que las instruciones que  conforman una rutina 
de  un  programa  es  "empacada"  en  un  bloque  que  llamamos 
procedimiento.  Para indicar el comienzo  de  un procedimiento, 
usamos la directiva PROC y usamos ENDP para indicar  el  final. 
El formato es:

	nombre_del_procedmiento	PROC	distancia

La distancia puede ser FAR o NEAR.  Se  usa FAR para indicar el 
procedimieto principal de un programa .EXE,  que es el punto de 
entrada principal de la aplicacin.

La  distancia  NEAR  se  reserva para los procedimientos que se 
hallan en un mismo segmento.

Para pasar el control de un procedimiento a otro,  se emplea la 
instruccin unaria "CALL" (Llamar),  la cual tiene el siguiente 
formato:

		CALL	nombre_del_procedimiento

La  instruccin  CALL  guarda  en  la  pila  la direccin de la 
instruccin  que  le  sigue  y  pasa  el control a la drieccin 
indicada por el nombre_del_procedimiento.  El  valor en la pila 
ser  usado  por  el  procedimiento  llamado  para  devolver el 
control al procedimiento que hizo la  llamada.  La  instruccin 
CALL equivale a:

		PUSH 	offset return_here		; Meter en la pila
							; la direccin de la
							; instruccin que sigue
							; a la llamada.
		JMP	nombre_del_procedimiento	; Pasar el control
							; a otro procedimiento
return_here:						; y regresar aqu


Para  devolver  el  control  al   procedimiento   original,  un 
procedimiento  llamado  debe  usar la instruccin RET  (return: 
regresar). Esta instruccin devuelve el control a la  direccin 
indicada  por  el puntero de pila (SP).  Ah  debera  estar el 
valor puesto por la intruccin CALL que pas el  control  a  la 
subrutina actual.

Usando estas instrucciones podemos reescribir nuestro programa:


; --------------------------------------------------------
  TITLE TEST1.ASM: Programa para probar direccionamientos
; --------------------------------------------------------
	.model small
; --------------------------------------------------------
space	EQU	32
; --------------------------------------------------------
	.stack 32
; --------------------------------------------------------
	.data
string1	db	'Hola gente', 0
string2 db	'Programa_TEST1.ASM', 0
; --------------------------------------------------------
	.code
main	proc
_init:
	mov	ax, @data
	mov	ds, ax

	mov	si, 0
_print_string1:
		lea 	bx, string1
		mov	al, [bx][si]
		test	al, al
		jz	_print_string21

		call	_display_char

		inc	si
	jmp	_print_string1 

_print_string21:
	mov	cl, 4
	_next1:
		push	cx
		mov	al, space
		call	_display_char	
		pop	cx
		dec	cx
	jne	_next1

_print_space:
	mov	si, 0
	_next2:
		mov	bx, offset string2
		mov	al, [bx+si+2]

		call	_display_char
		call	_pause

		inc	si
		cmp	si, 2
	jnz	_next2

	mov	al, space
	call	_display_char

_print_string22:
	lea	si, string2
	_next3:
		mov	al, [si]
		test	al, al
		jz	_end_

		call	_display_char
		call	_pause

		inc	si
	jmp	_next3
_end_:
	call	_pause
	mov	ax, 4C00h
	int	21h
main	endp

	_display_char proc NEAR
		mov	bx, 000Fh                                                   
		mov	ah, 0Eh                                                     
		int	10h
		ret
	_display_char endp


	_pause	proc NEAR
		mov	ah, 10h
		int	16h
		ret
	_pause	endp

end	main

; ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


Este programa tendr un tamao menor que el anterior, aunque no 
tanto: nuestras subrutinas son tan pequeas que igual vale usar 
macros, pero igual vale para ilustrar el uso de subrutinas.

Veamos otra posibilidad. El pasaje: 

	mov	si, 0
	lea 	bx, string1
_print_string1:
		mov	al, [bx][si]
		test	al, al
		jz	_print_string21

		call	_display_char

		inc	si
	jmp	_print_string1 

_print_string21:

es una rutina que despiega una cadena de caracteres que termina 
en cero. Se carga en BX la direccin efectiva  de  string1,  se 
pone  en  AL  el  caracter  apuntado  por BX+SI. La instruccin 
"test al, al" revisa si ese caracter es cero:  si este fuera el 
caso, se activa la  bandera  ZF, lo que indicara que se  lleg
al final de la cadena. La instruccin "jz _print_string21" pasa 
el control a "_print_string21" si encuentra activada la bandera
ZF, en caso contrario  se  ejecuta  "call  _display_char",  que
pasa el control a la funcin "_display_char" que  despliega  el 
caracter en pantalla; al regreso de la llamada,  se  incrementa
en uno el valor de SI ("inc si") y se repite de nuevo la rutina 
para el siguiente caracter.

Tenemos as una rutina que puede  ser  muy  til,  que  no  usa 
interrupciones  del sistema operativo. La llamaremos _szDisplay 
y despliega cadenas de caracteres que terminan en cero:

	_szDisplay proc near
		mov	si, 0
		_print_string:
			mov	al, [bx][si]
			test	al, al
			jz	_exit_szDisplay

			call	_display_char

			inc	si
			jmp	_print_string 
		_exit_szDisplay:
			ret
	_szDisplay endp


El procedimiento espera que BX posea la direccin de la  cadena 
a desplegar.  Es un buen reemplazo para el servicio 9 de la int 
21h, especialmente cuando no tenemos DOS a nuestra disposicin. 



	*NOTA*
	El empleo de "PROC" para designar bloques de rutinas es 
	significativo del estilo de programacin favorecido por 
	el lenguaje ensamblador: programacin procedimental. El 
	paradigma de este estilo es "decidir qu procedimientos 
	quieres y usar los mejores argumentos que consigas". El 
	programador debe concentrarse en la implementacin  del 
	algoritmo  necesario  para realizar el cmputo deseado. 
	Para  soportar  este  paradigma,  los  lenguajes  deben 
	suministrar facilidades para el paso  de  parmetros  a 
	subrutinas o funciones y para regresar valores de stas. 
	La discusin reposa en las maneras de pasar argumentos, 
	las diferentes maneras de distinguirlos, los diferentes 
	tipos de funciones. 


===============================================
Estructuras de Control en Lenguaje Ensamblador
===============================================

------------------------------------
Comparaciones: segunda aproximacin
------------------------------------
Las  estructuras  de control deciden qu hacer basndose en una 
comparacin de datos.  Vimos que en ensamblador el resultado de 
la comparacin se almacena en el registro de banderas para  ser 
usado luego.  El  registro  de  banderas  hace  las veces de un 
registro de estado cuyos bits  informan  sobre  el  estado  del 
procesador.  En  este  caso,  estos  bits indican el estado del 
procesador despus de realizada alguna operacin.

Ya mostramos que para hacer comparaciones, el 8086  proporciona 
la instruccin CMP. Esta operacin realiza una substraccin  y, 
de acuerdo al resultado,  se activa alguna de los indicadores o 
banderas del registro FLAGS sin que el resultado sea almacenado 
en alguno de los operandos. Otra instruccin hace casi lo mismo 
que CMP, pero guardando el resultado en el operando de destino: 
SUB. En ambos casos, si los operadores son iguales el resultado 
ser cero,  por  lo  que  se activar la bandera de cero ZF del 
registro FLAGS. Otras  operaciones  revisarn esta bandera para 
tomar alguna decisin.

Para operaciones con enteros sin signo, hay dos banderas:  Cero 
(ZF) y carro (CF). ZF se activa (se pone en 1)  si el resultado 
de la diferencia es cero;  CF  se  usa  para indicar que en una 
substraccin que se ha intentado substraer a una cantidad  otra 
mayor; si este es el caso, entonces se activa la bandera CF.

Un ejemplo:

	cmp v1, v2

Al  computarse  la  diferencia,  se  activa  o no la bandera en 
correspondencia al resultado.  Si el resultado es cero,  porque 
v1 = v2, ZF se activa y CF se desactiva.  Si v1 < v2,  entonces 
se activar CF y  ZF  se  desactivar.  Pero si v1 > v2,  ambas 
banderas se desactivarn.

Para  enteros  con  signo hay tres banderas significativas,  de 
nuevo ZF, la de desbordamiento (overflow) OF y la de signo  SF. 
Para indicar que un entero es negativo, el bit ms alto,  en la 
extrema izquierda, considerado el bit de signo, est activo. En 
su   momento   hablaremos  sobre  operaciones  aritrmticas  en 
ensamblador.

La bandera de desbordamiento OF se activa cuando  el  resultado 
de una operacin  de  comparacin  produce  un  desbordamiento, 
condicin que se  da  cuando  la cantidad supera los lmites de 
espacio de un operando; por ejemplo, cuando se intenta poner en 
un  registro  de  8  bits  (como AL), cuyo mximo es 0FFh = 256
(0FFh = 11111111),   una  cantidad  como  257  o  superior,  se 
activar la bandera de desbordamiento.

La  bandera  de  signo  SF  se activa cuando el resultado de la 
operacin es negativo. 

Si v1 = v2, se activa ZF, como en el caso de enteros sin signo. 
Si v1 > v2, ZF se  desactiva  y  SF = OF.  Si  v1 < v2,  ZF  se 
desactiva y  SF difiere de OF. 

Por  qu  SF = OF  si v1 > v2?  si  no  hay desbordamiento, la 
diferencia tendr el valor correcto y debe  ser  positivo.  As 
que SF = OF = 0. Pero si hay un desbordamiento, el resultado no 
ser correcto, y ser negativo. As que SF = OF = 1.


---------------------------------------------------------
Instrucciones de salto o ramificacin: 2da, aproximacin
---------------------------------------------------------
Estas instrucciones transfieren el control a puntos arbitrarios 
del programa.  Es decir, actan como  "goto".  Hay  dos  tipos: 
incondicionales y condicionales.

Las   incondicionales    siempre   efectan   el   salto;   las 
condicionales  lo   efectan  dependiendo  del  estado  de  las 
banderas. Si no se efecta el salto,  el control es pasado a la 
siguiente instruccin.

La instruccin JMP  se  usa  para  saltos  incondicionales.  Es 
unaria, de un nico operando que generalmente es la etiqueta de 
una instruccin a donde se pasar el control.  El ensamblador o 
el enlazador reemplazar la etiqueta por la  direccin correcta 
de la direccin. La instruccin siguiente,  no ser ejecutada a 
no ser que alguna otra instruccin le pase el control.

Hay variaciones para la instruccin JMP:

Salto corto (SHORT) 
Este salto es muy limitado en rango. Slo puede moverse hasta o 
por debajo de 128 bytes en memoria.  La ventaja de este tipo es 
que usa menos memoria que los otros. Usa un byte con signo para 
almacenar  el  desplazamiento del salto respecto a la direccin 
de la propia instruccin. (El desplazamiento es agregado a IP). 
Para especificar un salto corto, se usa la palabra clave  SHORT 
inmediatamente antes de la etiqueta en la instruccin JMP.

Salto cercano (NEAR)
Este  el  tipo  por  defecto  para  saltos  incondicionales  y 
condicionales,  puede  ser  usado  para  saltar   a  cualquier 
localidad  en  un  segmento.  Realmente,  el 80386 soporta dos 
tipos de instrucciones cercanas.  Una  usa  dos  bytes para el 
desplazamiento.  Esto  permite  moverse  hacia  arriba o hacia 
abajo 32,000 bytes.  El  otro  tipo  usa  cuatro bytes para el 
desplazamiento, que permite moverse  a  cualquier direccin en 
el segmento de cdigo.  El tipo de cuatro bytes es el tipo por 
defecto en el modo protegido 386.

Salto lejano (FAR)
Este  salto permite pasa el control a otro segmento de cdigo. 
Es muy raro en modo protegido 386. 

Las etiquetas de cdigo  siguen  las  mismas  reglas  que  los 
nombres de los datos:  se  definen colocndolas en el segmento 
de cdigo frente de la expresin que etiquetan.  Pero  hay una
diferencia: hay que colocar dos puntos  inmediatamente despus 
de la etiqueta. 

A continuacin un ejemplo de salto incondicional:

	jmp	etiqueta

		; aqu puede haber cualquier cdigo

etiqueta:		; el control es pasado a esta direccin
	mov	ax, 0	; primera instruccin ejecutada despus 
			; del salto

Hay varias instrucciones para saltos condicionales. Ya dijimos 
que  estas  instrucciones revisan si se da cierta condicin de 
la cual depende la realizacin del salto: el procesador revisa 
el registro de banderas en busca de las banderas activas  para
decidir si hay que saltar.  A continuacin una lista de saltos 
condicionales simples y una descripcin de cmo se ejecutan:

	JZ 	salta slo si ZF se activa
	JNZ 	salta slo si ZF se desactiva
	JO 	salta slo si OF se activa
	JNO 	salta slo si OF se desactiva
	JS 	salta slo si SF se activa
	JNS 	salta slo si SF se desactiva
	JC 	salta slo si CF se activa
	JNC 	salta slo si CF se desactiva
	JP 	salta slo si PF se activa
	JNP 	salta slo si PF se desactiva

(PF  es  la  bandera de paridad, que indica si el resultado de 
 una operacin es par o impar.)

Las  instrucciones de control SON complejas:  una  instruccin
revisa algn dato; el resultado de la revisin activa o no una 
o ms banderas dependiendo del  resultado  de  la  evaluacin; 
luego  la  instruccin  de salto condicional efectuar o no un 
salto dependiendo  de  las  banderas  activadas. Como ejemplo, 
escribamos en ensamblador el siguiente pseudo-cdigo:

	if ( AX == 0 )
		BX = 1;
	else
		BX = 2;

(si AX es igual a cero, poner 1 en BX, sino poner 2 en BX) 

Este pseudo-cdigo podra escribirse as en ensamblador:

		cmp ax, 0 	; activar banderas: ZF se activa si ax - 0 = 0)
		jz thenblock 	; si ZF se activ, pasar el control a thenblock
		mov bx, 2 	; Parte ELSE: sino mover 2 a BX
		jmp next 	; salir hacia next 
	thenblock:
		mov bx, 1 	; Parte THEN: entonces mover 1 a BX
	next:

Otras  comparaciones  no  son tan fcil de implementar con los 
saltos condicionales simples presentados en la  pasada  lista. 
Veamos el siguiente pseudo-cdigo:

	if ( AX >= 5 )
		BX = 1;
	else
		BX = 2;

(Si AX es mayor o igual que cinco, poner 1 en BX, sino poner 2 en BX)

Si AX es mayor o igual que cinco, ZF puede activarse o no y SF 
ser igual que OF.  A  continuacin  el cdigo en ensamblador, 
asumiendo que AX tiene signo:

		cmp ax, 5
		js signon 	; ir a signon si SF = 1
		jo elseblock 	; ir a elseblock si OF = 1 y SF = 0
		jmp thenblock 	; ir a thenblock si SF = 0 y OF = 0
	signon:
		jo thenblock 	; ir a thenblock si SF = 1 y OF = 1
	elseblock:
		mov ebx, 2
		jmp next
	thenblock:
		mov ebx, 1
	next:

Es un cdigo complejo y confuso para el cual, afortunadamente, 
el 8086 suministra instrucciones que facilitan  estos tipos de 
pruebas,  pensadas  para  datos  sin  signo o con signo. Estas
instrucciones generalmente revisan las  banderas  en busca del 
resultado de un operacin de comparacin com CMP, pero tambin 
responden al resultado de operaciones aritmticas, que tambin 
activan las banderas de acuerdo a su resultado. Suponiendo que 
ejecutamos una instruccin que compara dos  valores  lv  y  rv 
(lv = left_value o valor izquierdo;  rv = right_value  o valor 
derecho):

	cmp	lv, rv

los saltos  condicionales  se efectuarn segn los siguientes 
criterios:

Saltos sin signo:
JE		salta si lv es igual a rv
JNE		salta si lv no es igual a rv
JB, JNAE	salta si lv es menor que rv
JLE, JNG	salta si lv no es menor que rv 
JG, JNLE	salta si lv es mayor que rv
JGE, JNL	salta si lv no es mayor que rv

Saltos con signo:
JE		salta si lv es igual a rv
JNE 		salta si lv no es igual a rv
JL, JNGE	salta si lv es menor que rv
JBE, JNA	salta si lv no es menor que rv 
JA, JNBE	salta si lv es mayor que rv
JAE, JNA	salta si lv no es mayor que rv


El salto igual (equal) y no igual (not equal),  JE  y JNE, son 
los mismos para enteros con signo y sin signo. En realidad son 
instrucciones idnticas a JZ y JNZ, respectivamente.  Cada una 
de las otras instrucciones de salto tienen dos sinnimos.  Por 
ejemplo, JL (jump less than: saltar si es menor  que)  y  JNGE 
(jump not greater than or equal to: saltar si no es ms grande 
que). Estas instrucciones son las mismas porque:

	x < y = not(x > y)

Las  saltos sin signo usan A para "above" (encima)  y  B  para 
"below" (debajo) en vez de L y G. 

Gracias a estas nuevas instrucciones, nuestro pseudo-cdigo de
arriba puede escribirse en un cdigo ensamblador  ms  claro y 
simple:

		cmp ax, 5
		jge thenblock
		mov bx, 2
		jmp next
	thenblock:
		mov bx, 1
	next:


Instrucciones para bucles
-------------------------
El  8086  incluye  en su repertorio algunas instrucciones para 
bucles estilo "for",  y que toman una etiqueta como  operando. 
El formato de estas instrucciones es:

	instruccin	etiqueta

La  etiqueta  indica  donde la instruccin devuelve el control 
hasta que se cumpla una condicin que finaliza  el  bucle.  La 
instruccin usa el registro CX como operando implcito:  en l 
debe incluirse el nmero de veces que se ejecutar el bucle de 
acuerdo a las siguientes reglas:  

LOOP		Decrementa CX,  si  CX  difiere  de 0, y salta 
		donde indica la etiqueta.

LOOPE, LOOPZ 	Decrementa  CX  (El  registro  de  bandera  es 
		modificado); si CX no vale 0 y ZF = 1, efecta 
		un salto.

LOOPNE, LOOPNZ 	Decrementa  CX  (El  registro  de  bandera  es 
		modificado); si CX no vale 0 y ZF = 0, efecta 
		un salto.

Las ltimas dos instrucciones son tiles en bucles de bsqueda
secuencial. El siguiente pseudo-cdigo:

	sum = 0;
	for ( i=10; i>0; i)
		{
		sum+=i;
		--i;
		}

puede escribirse as en lenguaje ensamblador:

		mov eax, 0 ; eax is sum
		mov ecx, 10 ; ecx is i
  	loop_start:
		add eax, ecx
		loop loop_start

A partir del 80486, estas instrucciones son poco recomendables 
debido a que la nueva arquitectura hace inconveniente su uso,: 
requieren ms tiempo de ejecucin que  otras instrucciones ms 
complejas que hacen lo mismo, pero en menos tiempo.


-----------------------------------------------
Estructuras de control estndar en ensamblador
-----------------------------------------------
Hemos  estado viendo un poco cmo empleamos las estructuras de 
control en lenguaje ensamblador. Podemos concluir que se trata 
de  ejecutar  una operacin de prueba (hemos usado CMP y TEST) 
que "pregunta" si se ha satisfecho una condicin;  el programa 
nos dice sobre el cumplimiento de esa condicin activando o no 
un bit especial del registro de  banderas,  como ZF,  al  cual 
responde una instruccin de salto condicional.

Teniendo esto encuenta, podemos disear algunas estructuras de 
control estndar usadas en lenguajes de alto nivel, como C:

	Constructo IF-ELSEIF-THEN
	-------------------------
El siguiente pseudo-cdigo:

	if ( condicin )
	then 
		bloque1 ;
	else 
		bloque2 ;

(Si  se cumple una condicin entonces ejecutar bloque1,  sino 
 ejecutar bloque2)

podra ser implementado as:

	;
	; Activar banderas
	;
	cmp v1, v2	; (puede ser otra instruccin de prueba 
			;  como test)
	;
	; Revisar banderas activadas
	;
	jxx else_block	; selecciona xx para que haya un salto 
			; si se activ o no la bandera 
			; consultada
		;
		; Bloque "then"
		;
	jmp endif

	else_block:
		;
		; Bloque "else"
		;
	endif:

Si no hay bloque "else", puede reemplazarse  el  salto a este 
bloque por un salto a "endif".

	;
	; Activar banderas
	;
	cmp v1, v2	; (puede ser otra instruccin de prueba como test)
	jxx endif	; selecciona xx para que haya un salto 
		;
		; cdigo para el bloque else
		;
	endif:


	Constructo WHILE
	----------------
El bucle while es una estructura con prueba en el comienzo del 
bloque:

	while( condicin ) {
		cuerpo del bucle;
	}

Esto podra traducirse as:

	while:
	;
	; Activar banderas de acuerdo a la comparacin 
	;
	cmp v1, v2	; (puede ser otra instruccin de prueba como test)
	jxx endwhile	; seleccionar xx para saltar si la condicin es falsa
	;
	; cuerpo del bucle: debe cambiar los valores de v1 o v2.
	;
	jmp while
	endwhile:


	Constructo DO WHILE
	-------------------
Un bucle do while es una estructura con prueba en el final del 
bloque:

	do {
		cuerpo del bucle;

	} while( condicin );

Podra traducirse como:
	do:
		;
		; cuerpo del bucle
		;
		;
		; cdigo para poner las banderas de acuerdo a una condicin
		;
		jxx do  ; seleccionar xx para saltar si la condicin es verdadera

Luego veremos cmo usar macros para simular  estas estructuras
de alto nivel en lenguaje ensamblador.


----------------------------------
Tratando con cadenas de caracteres
----------------------------------
El ix86+ dispone de instrucciones para  facilitar  el  trabajo
con cadena de caracteres:

	MOVS		MOVe String:
	CMPS		CoMPare String: comparar cadenas
	SCAS		SCAn String: comprar cdenas
	LODS		LOaD String: Cargar cadenas
	STOS		STOre String: Almacenar cadenas

Veamos una por una estas instrucciones.

En  realidad el  formato de estas instrucciones es un poco ms 
complejo, ya que necesitan un sufijo  que  indica el tamao de 
los datos. Ya veremos cmo es esto.

Todas estas instrucciones trabajan con los registros de ndice 
SI y DI, usando SI (Source Index = ndice al origen) para  una 
direccin donde deberan estar los  datos que sirven de origen 
para la operacin,  y  DI  (Destiny Index = ndice al destino) 
para la direccin de destino de los datos.  Estos registros se 
usan en combinacin con los registros de segmento de datos  DS 
y el extra ES.

Adems, algunas de estas instrucciones usan  como  prefijo  el 
operador REP que indica la repeticin de una instrucin tantas 
veces como se indique en el registro CX. Em adicin a REP, hay 
otros operadores semejantes que no trabajan  con  CX  sino que 
repiten  la operacin hasta que se satisfaga una condicin que
ser indicada por la bandera de cero ZF. Ya las revisaremos.

Otra situacin que observar es que una operacin  con  cadenas 
siempre tiene una direccin: se puede realizar de  izquierda a 
derecha (desde el comienzo al final de la cadena) o de derecha 
a izquierda (desde el final al comienzo).  Para  especifiar la 
direccin hay que activar o desactivar la bandera de direccin 
DF. En condiciones normales, esta bandera est en cero para el 
proceso de cadenas de izquierda a derecha. Si vamos a procesar 
cadenas de derecha a izquierda,  debemos  activar esta bandera 
con la operacin ceroaria (sin operandos) STD  (SeT Df).  Para 
restablecer la bandera DF usaremos CLD (CLear Df),  que limpia
la banderam es decir, la pone en cero.

	- MOVS -
	Esta operacin se usa en combinacin con el prefijo REP 
	para mover caracteres desde una direccin indicada  por 
	DS:SI  hasta  otra  direccin  indicada  por  ES:DI. El 
	nmero de carateres que sern movidos debe indicarse en 
	el registro contador CX. El formato es:

		REP MOVSn 

	Automticamente se movern los caracteres  desde  DS:SI 
	hasta ES:DI. La "n" que hemos usado de sufifo en  MOVS, 
	indica el tamao del dato a mover: b para byte,  w para 
	word, d para dword, etc. As que una instruccin como:

		lea	si, str1
		lea	di, str2
		mov	cx, size_of_str1
	here:	mov	al, [si]
		mov	[di], al
		inc	di
		inc	si
		dec	cx
		jne	here

	que  mueve  los caracteres desde str1 hasta str2 puede 
	escribirse as:


		lea	si, str1
		lea	di, str2
		mov	cx, size_of_str1
		rep movsb


	- LODS -
	Esta operacin arga en AL, AX o EAX,  dependiendo  del 
	sufijo (B para byte, W para word o D para  dword)  los 
	caracteres que se encuentran en una direccin indicada 
	por DS:SI. La instruccin  incrementa  el valor en SI, 
	dependiendo del sufij en LODS. As que una instruccin 
	equivalente para LODSW, sera:

		lea	si, str1
		mov	ax, [si]
		inc	si
		inc	si

	- STOS -
	La operacin almacena (store) en la direccin indicada 
	por ES:DI el contenido en AL, AX o en EAX, dependiendo 
	del sufijo. Incrementa el  valor  de  DI de acuerdo al 
	tamao  indicado por el sufijo.  Un cdigo equivalente 
	para STOSB podra ser:

		mov	al, "A"
		lea	di, str2
		mov	[si], al
		inc	di

	La instruccin mueve a str2 la letra A.

	Usando LODS y STOS se puede  mover  con  facilidad  un 
	cadena de caracteres de un array a otro, sin necesidad 
	de saber su tamao, dato necesario para el uso de MOVS:

		str1	db	"hola gente", 0
		str2	db	20 DUP (?)

		lea	si, str1	; en DS:SI la direccin de str1
		lea	di, str2	; en ES:DI la direccin de str2

	otro:	lodsb			; poner en AL un caracter en DS:SI
		test	al, al		; es cero este caracter?
		je	ya		; si es cero, salir del bucle
		stosb			; sino, poner el caracter en ES:DI
		jmp	otro
	ya:


	- REP -
	Como vimos, REP es un prefijo para repeticin  de  una 
	operacin con cadenas de caracteres: dice al procesador 
	que repita una operacin de este tipo tantas veces como 
	se indique en el registro contador CX. Por ejemplo,

		MOV CX, 4
		REP MOVSB

	repetir  4  veces  MOVSB,  moviendo cuatro caracteres 
	desde DS:SI hasta DS:DI.

	Hay variaciones de REP:

		 REPE o REPZ: repite la operacin mientras la  
		  ZF  est  activa;  la  operacin se detendr
		  cuando se desactive ZF o CX sea cero.

		 REPNE o REPNZ:  repite la operacin mientras 
		  la  ZF  est  desactivada;  la  operacin se 
		  detendr cuando se sactive ZF o CX sea cero. 


	- CMPS -
	Esta instruccin compara el contenido en  DS:SI con el
	de ES:DI.  Dependiendo  de la bandera de direccin DF, 
	se incrementar o  disminuir  el valor en SI y DI con 
	cada operacin:

		.data
		string1	db "hola", 0
		string2 db "hole", 0

		.code
		; ....
		mov	cx, 4
		lea	si, string1
		lea	di, string2
		repe cmpsb
		test	cx, cx
		je are_equals
		mov	ax, 0
		jmp	_exit_
	are_equals:
		mov	ax, 1
	_exit_:

	El  cdigo  compara las  cadenas  de cuatro caracteres 
	string1 y string2, si son iguales CX debe ser cero, ya 
	que cada comparacin decrementa el  valor  de  CX.  Se 
	revisa si es el caso, si no lo es se pone 0 en ax,  si 
	es el caso se pone 1 en ax.

	- SCAS -
	Similar a CMPS, SCAS busca uno o ms caracteres en una 
	localidad indicada por ES:DI,  donde  debe  haber  una 
	cadena de caracteres. El caracter a buscar  se  indica 
	en AL.  Si se buscan una pareja de caracteres se usar 
	AX; si es una secuencia de cuatro caracteres, se usar 
	EAX.  Cada  ejecucin  de  la instruccin incrementa o 
	disminuye el valor de DI, dependiendo de la bandera de 
	direccin DF.

		.data
		string db "hola amigo", 0

		.code
		; ....
		push	edi
		mov	cx, -1		; cx = infinito
		mov	al, "m"
		lea	di, string
		repne scasb
		neg	cx
		dec	cx
		mov	ax, cx
		pop	edi

	El cdigo anterior devuelve  en  AX dnde se encuentra 
	el caracter "m" en  la  cadena  "hola amigo".  Primero 
	indicamos  en  CX que la revisin sea infinita;  luego 
	buscamos en la cadena repetidas veces  hasta hallar el 
	caracter buscado; luego negamos  del  contenido  de CX 
	para  obtener  el  nmero de comparaciones realizadas. 
	Finalmente restamos 1  al contenido en CX para obtener 
	el lugar del caracter de la cadena. Aqu hay que tener 
	cuidado,  porque  si la cadena no contiene el caracter
	buscado,  la  bsqueda  se  extender  ms  all de lo 
	necesario,  realizando  lecturas  sobre  segmentos  de 
	memoria  que  posiblemente  estn  protegidos   contra 
	lectura, producindose un grave error. Pero valga como 
	un ejemplo.



-----------------
Lnea de rdenes
-----------------
Para ejercitarnos con las cadenas de caracteres, escribamos un 
pequeo intrprete de rdenes. Pensemos en  tres instrucciones 
arbitrarias:

	 clean: limpia el monitor
	 quit: sale del programa
	 exit: reinicializar el sistema

El intrprete consiste en un bucle que slo debe terminar  por 
indicacin del ususario.  Una  vez  ejecutado,  espera por una 
indicacin del usuario; cuando el  usuario  escribe  algo,  el 
intrprete lo lee,  determina si es  alguna de las rdenes que 
maneja y si lo es, pasa el control a la rutina correspondiente 
a la accin requerida por el usuario.

La secuencia de acciones para un programa as debera ser algo 
como:

	1. Desplegar mensaje de inicio del programa
	2. Inicio del bucle:  Desplegar puntero que indica que 
	   el programa est en espera
	3. Leer lnea
	4. Interpretar la lnea:
		Si es "clean":
			Limpiar el monitor
		Si es "quit":
			Salir del programa
		Si es "exit":
			Reiniciar el sistema
		Si no es uno de los anteriores:
			Desplegar mensaje de aviso
	5. Final del bucle: Volver a 2
	
El cdigo en ensamblador:


; ====================================================================
  TITLE	CONSOLE.ASM: Pequea cnsola para procesor rdenes
; ====================================================================
	.model small
; ================================================================================
	.stack	64
; ================================================================================
	.data
	msg1	db	"Int",82h,"rprete de ",0A2h,"rdenes", 10, 13, 0
	msg2	db	10, 13, "No es una orden del programa!", 10, 13, 0
	msg3	db	10, 13, "Introdujo m",0A0h,"s de 12 caracteres", 10, 13
	puntero	db	10, 13, "> ", 0

	_clean_	db	"clean", 0
	_quit_	db	"quit", 0
	_exit_	db	"exit", 0

	command	db	12 dup (?)
; ================================================================================
	.code
main	proc far
_init:
	mov	ax, @data
	mov	ds, ax
	mov	es, ax

	call	clean_

	lea	si, msg1
	call	_szDisplay
_loop:
	lea	si, puntero
	call	_szDisplay

	lea	di, command
	call	_get_command
_is_clean:
	lea	si, _clean_
	call	_compare
	test	ax, ax
	je	_is_exit
	call	clean_
	jmp	_loop	
_is_exit:
	lea	si, _exit_
	call	_compare
	test	ax, ax
	je	_is_quit
	int 	19h
_is_quit:
	lea	si, _quit_
	call	_compare
	test	ax, ax
	je	_no_order
_quit:
	mov	ax, 4C00h
	int	21h
_no_order:
	lea	si, msg2
	call	_szDisplay
	jmp	_loop
main	endp
; ================================================================================
	_szDisplay proc near
		push	si
		_print_string:
			lodsb
			test	al, al
			jz	_exit_szDisplay

			call	__display_char

			jmp	_print_string 
		_exit_szDisplay:
		pop	si
			ret
	_szDisplay endp

	__display_char proc NEAR
		mov	bx, 000Fh                                                   
		mov	ah, 0Eh                                                     
		int	10h
		ret
	__display_char endp
; --------------------------------------------------------------------
	_get_command proc near
		push	di
		mov	cx, 12
	_loop_:
		mov	ah, 10h
		int	16h
	filt_extended_function_keys:
		test	al, al
		je _loop_
		cmp	al, 0E0h
		je _loop_
	is_enter_key:
		cmp	al, 0Dh
		je _exit_get_command
		dec	cx
		test	cx, cx
		je _excess
	get_character:
		stosb
		call	__display_char
		jmp _loop_
	_exit_get_command:
		xor	al, al
		stosb
		pop	di
		ret

	_excess:
		lea	si, msg3
		call	_szDisplay
		mov	cx, 12
		pop	di
		push	di
		jmp _loop_
	
	_get_command endp
; --------------------------------------------------------------------
	_compare proc near
		push	si
		push	di
	get_string_lenght:		
		xchg	di, si
		call	_strlen
		xchg	si, di
	_compare_strings:
		repe cmpsb
		test	cx, cx
		jne	_not_equals
		mov	ax, 1
		jmp	_exit_compare
	_not_equals:
		mov	ax, 0
	_exit_compare:
		pop	di
		pop	si
		ret	
	_compare endp

	_strlen	proc near
		push	di
		mov	al, 0
		mov	cx, -1	; cx = -1 = FFFFh = 65535 = infinito
		repne scasb
		neg	cx
		dec	cx
		dec	cx
		pop	di
		ret
	_strlen	endp
; --------------------------------------------------------------------
	clean_ proc near
		mov	dx, 0
		call	set_cursor
		mov	cx, 80*25
	_print:
		mov	al, 32	; 32 = 20h = espacio
		call	__display_char
		loop	_print

		mov	dx, 0
		call	set_cursor
		ret
  	clean_ endp

	set_cursor:
		mov	ah, 2
		int	10h
		ret
; --------------------------------------------------------------------
  end main
; --------------------------------------------------------------------
; Para ensamblar:
; 	tasm  console
; 	tlink console
; ====================================================================



Anlisis
--------
La verdad que este programa es un reto: como pienso usarlo luego como
shell para un pequeo sistema operativo, se evita usar interrupciones
de DOS: slo se usa la int21 para salir del programa, ya que pensamos
correrlo en DOS para probarlo.

El programa responde, por ahora, a cuatro rdenes:

	clean: limpia la pantalla
	time: despliega el tiempo
	quit: sale a DOS
	exit: reinicia el sistema

Si se introduce una orden ajena, se despliega el mensaje:

	"No es una orden del programa!"

Ahora  veamos  cmo  trabaja el programa.  Despus de inicilizar los
registros de los segmentos de datos, ES y DS:

	mov	ax, @data
	mov	ds, ax
	mov	es, ax

Se limpia el monitor:

	call	clean_

El cdigo de esta subritina es:

	clean_ proc near
		mov	dx, 0
		call	set_cursor
		mov	cx, 80*25
	_print:
		mov	al, 32
		call	__display_char
		loop	_print

		mov	dx, 0
		call	set_cursor
		ret
  	clean_ endp

Las  primeras  tres  lneas colocan el cursor en la esquina izquierda
superior del monitor:

		mov	dx, 0
		call	set_cursor

llamando a set_cursor:

	set_cursor:
		mov	ah, 2
		int	10h
		ret

que ejecuta el servicio 2 de la interrupcin 10h, que  pone el cursor 
en la columna indicada en DL y en la fila o lnea indicada por DH.

Luego se escriben espacios (20h) por todo el monitor.  Para ello,  se 
ponen espacios desde el comienzo todas las localidades del monitor. 

		mov	cx, 80*25
	_print:
		mov	al, 32
		call	__display_char
		loop	_print

En modo de texto,  que es el usado,  el monitor ofrece 80 espacios en 
cada lnea y 25 lneas.  As  que  lo  que  hacemos es escribir 80*25 
espacios  sobre  el  monitor  comenzando  desde  la  esquina superior 
izquierda del monitor.  Para ejecutar 80*25 veces la llamada (call) a 
__display_char, ponemos esta cantidad en CX,  y  ejecutamos  un bucle 
controlado por la instruccin "loop",   que va decrementando en uno a 
CX cada vez que se ejecuta. Cuando CX llega a cero,  termina el bucle 
y se coloca de nuevo el cursor en la esquina superior  izquierda  del 
monitor, llamando de nyevo a set_cursor con DX=0. 

Luego se despliega el mensaje inicial

	lea	si, msg1
	call	_szDisplay

se coloca el puntero que seala que ya pueden ingresarse las rdenes

_loop:
	lea	si, puntero
	call	_szDisplay

y se espera por la orden, que ser puesta en el buffer "command"

	lea	di, command
	call	_get_command

Analicemos _get_command. Primero esperamos por que el usuario ingrese
una tecla:

	_loop_:
		mov	ah, 10h
		int	16h

El  servicio  10h  de la interrupcin 16h espera que el usuario pulse
una tecla. Ingresada la tecla,  su contenido  est  en  AL.  Entonces 
revisamos si este valor es cero:

	filt_extended_function_keys:
		test	al, al
		je _loop_

si es 0E0h,  que  es  el cdigo de rastreo generado por las teclas de 
funcin extendida  (inicio, fin, repg, avpg, insert, supr, flechas, 
etc.):

		cmp	al, 0E0h
		je _loop_

En el caso que sean estos valores, se regresa al inicio del bucle. Si 
ese no es el caso, se revisa si el usuario tecleo ENTER:

	is_enter_key:
		cmp	al, 0Dh
		je _exit_get_command

si este es el caso,  se  sale  de  la rutina,  sino  se revisa cuntos 
caracteres han sido ingresados, para evitar que se sobrepase el tamao 
del buffer.

Al comienzo de la rutina habamos indicado el tamao del buffer en CX:

		mov	cx, 12

Para revisar si ya se han ingresado los 12 caracteres:

		dec	cx
		test	cx, cx
		je _excess

se decrementa en uno el contenido en CX,  luego  se  comprueba  si ha 
llegado a cero el decremento, si este es el caso se pasa el control a 
la rutina "_excess":

	_excess:
		lea	si, msg3
		call	_szDisplay
		pop	di
		push	di
		mov	cx, 12
		jmp _loop_


que despliega la cadena en msg3, restaura el valor original en DI, que es 
la direccin de inicio del buffer "command"; se pone en CX el tamao  del
buffer, 12, y se vuelve a ejecutar el bucle.

Si  no  se  ha  presionado  la  tecla  ENTER o no se han escrito los doce 
caracteres para la orden, entonces se coloca el caracter en el buffer, en 
este caso "command":

	get_character:
		stosb

se usa "stosb" que pone en la direccin indicada por DI el caracter en AL.
Luego desplegamos el caracter introducido:

		call	__display_char

y ejecutamos de nuevo el bucle, 

		jmp _loop_

el cual se repetir hasta que el usuario teclee ENTER, como hemos visto:

Antes de salir colocamos al final de la cadena introducida un cero, que 
ser nuestro marcador de final de cadena:

	_exit_get_command:
		mov	al, 0
		stosb
		pop	di
		ret
	_get_command endp

Luego revisamos si el usuario ha escrito alguna de las rdenes que debe
ejecutar nuestro programa. El esquema general es el siguiente:

	lea	si, _clean_
	call	_compare
	test	ax, ax
	je	_is_exit
	call	clean_
	jmp	_loop	

ponemos en SI la direccin de la cadena que queremos revisar. En DI debe
estar la otra que se va a comparar, en este caso es la introducida por el 
usuario. Luego llamamos a "compare", que compara las cadenas y si son
iguales devuelve en AX un valor distinto de cero, en caso contrario
devuelve cero. Si devuelve cero se pasa el control al cdigo que revisa
si se introdujo alguna otra orden. Si no se devuelve cero, entonces se
ejecuta la orden correspondiente, "clean" en este caso, que ya hemos
analizado, y ejecutamos de nuevo el bucle.

Analicemos la funcin "_compare". Primero obtenemos el tamao de la
cadena en DI, que en este caso ser la orden que se va a probar:

	_compare proc near
		push	si
		push	di
	get_string_lenght:		
		xchg	di, si
		call	_strlen

Las primeras dos instrucciones salvan el contenido de SI y DI en la pila. 
La rutina que cuenta los caracteres es _strlen, que lee en la direccin 
en DI en busca del caracter cero. Como la direccin de la orden a verificar
est en SI, tenemos que pasarla a DI, pues es en DI donde debe pasarse la
direccin de la cadena cuyo nmero de caracteres queremos contar. Para 
hacer esto, hemos usado la instruccin "xchg" (eXCHanGe: intercambiar)
que mueve al primer operando el contenido del segundo y el del segundo
operando al primero.

Analicemos el procedimiento _strlen. Para la bsqueda, colocamos en AL el 
caracter a buscar, cero en este caso:

		mov	al, 0

Ponemos en CX -1:

		mov	cx, -1	; cx = -1 = FFFFh = 65535 = infinito

que es igual a 0FFFFh = 65535, el nmero ms grande que puede colocarse 
en un dato de tamao de una palabra (word). Luego efectuamos la bsqueda:

		repne scasb

La instruccin repetir la bsqueda mientras no se encuentre 0, y con
cada lectura se debe redudir el valor de CX. Para recuperar el nmero
exacto de caracteres cambiamos el signo del valor en CX y restamos
dos a ese valor:

		neg	cx
		dec	cx
		dec	cx

Restauramos los valores originales de los registros:

		xchg	si, di

y comparamos las dos cadenas en un nmero de caracteres indicado en CX:

	_compare_strings:
		repe cmpsb

Si esta instruccin encuentra un caracter diferente o si CX llega a cero
antes, entonces termina la comparacin. Si las cadenas son iguales, al
finalizar la comparacin, CX debe ser cero y ponemos 1 en ax, en caso
contrario se pone 0 en AX. 

		test	cx, cx
		jne	_not_equals
		mov	ax, 1
		jmp	_exit_compare
	_not_equals:
		mov	ax, 0
	_exit_compare:

Las otras dos rdenes son "quit" y "exit". Primero revisamos si el
usuario introdujo la orden "exit":

_is_exit:
	lea	si, _exit_
	call	_compare
	test	ax, ax
	je	_is_quit
	int	19h

Si es el caso, se ejecuta la interrupcin 19h que reinicializa el sistema.
Por supuesto, esta interrupcin no funcionar en Windows.

    *NOTA*
    Pude haber usado otra instruccin para reinicializa  como:
    
      push 0ffffh          ; Meter el segmento en la pila
      push 0000h           ; Meter el desplazamiento en la pila
      retf                 ; Saltar ah

    o como

    reboot:
            db 0EAh        ; Cdigo de operacin para JMP
            dw 0000h       ; Saltar a FFFF:0000h, que reinicia
            dw 0FFFFh      ; el sistema
            
    pero son instrucciones de mucha complejidad que requieren
    explicaciones adicionales.

Luego se revisa si se trata de la orden "quit":

_is_quit:
	lea	si, _quit_
	call	_compare
	test	ax, ax
	je	_no_order
_quit:
	mov	ax, 4C00h
	int	21h

si es el caso, se ejecuta el servicio 4Ch de la int21, que sale del programa
a DOS. Si no es el caso, se pasa el control a una rutina que despliega el 
mensaje "No es una orden del programa!" y ejecuta de nuevo el bucle.

Es necesario aclarar algunas cosas todava. Hemos declarado dos cadenas de
caracteres, en el segmento de datos, as:

	msg2	db	10, 13, "No es una orden del programa!", 10, 13, 0
	msg3	db	10, 13, "Introdujo m",0A0h,"s de 12 caracteres", 10, 13
	puntero	db	10, 13, "> ", 0

Hemos antecedido ambas declaraciones con dos nmeros separados por una coma:
"10, 13,". Ambos nmeros son interpretados por nuestra rutina de despliegue
como caracteres de control. El nmero 13 indica retorno de carro (CR) y es 
interpretado como un restablecimiento del cursor a la posicin de la extrema
izquierda. El nmero 10 indica avance de lnea (LF), que se interpreta como
avance a la lnea siguiente. Al colocar estas indicaciones al comienzo de
la cadena, esta se desplegar al comienzo de la siguiente lnea desde donde
estaba originalmente el cursor. Obsrvese que no se coloc un cero al final
de la cadena msg3; se hizo as para que cuando se ordene desplegar esta
cadena, tambin se desplegar un par de lneas por debajo el indicador del
puntero de rdenes.

En la declaracin de la cadena "Int",82h,"rprete de ",0A2h,"rdenes", se han
reemplazado los caracteres "" y "" por 82h y 0A2h respectivamente, ya que
usar directamente estos caracteres provoca un despliegue errado.

Para declarar un buffer de 12 caracteres usamos la instruccin:

	command db 12 dup (?)

Aqu el operador DUP DUPLica el contenido entre parnteses que le sigue en
la cantidad indicada por el nmero entero que le antecede.

Otra observacin importante tiene que ver con unas instrucciones que hemos
agregado al comienzo y al final de muchas subrutinas: algunas comienzan
salvando en la pila los registros SI y DI:

		push	si
		push	di

Estas subrutinas tambin incluyen en su salida las instrucciones:

		pop	di
		pop	si
		ret

que restauran los valores originales en los registros SI y DI en el momento
que se llam a la rutina. Este va a ser una convencin que vamos a seguir en
nuestras subrutinas: siempre preservarn el contenido en SI, DI y BX cada
vez que la subrutina haga uso de ellos.


----------------------------------------------- TO BE CONTINUED ----------->

emailme: numit_or@cantv.net
webZ: http://mipagina.cantv.net/numetorl869/
      http://oberon.spaceports.com/~tutorial/aks/


