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

Por nmt
numit_or@cantv.net

================================
CONSIDERACIONES PRELIMINARES II
================================

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

  - Consideraciones necesarias sobre el hardware

  - La maldicin del lenguaje ensamblador

  - Dispositivos relevantes
	 Nombres y direcciones
	 Rutinas y subrutinas
	 Ejecucin

  - Dos tipos de programador

  - Registros internos del CPU

  - Nota sobre las interrupciones I

  - Uso de DEBUG de DOS I

  - Sistemas de numeracin

  - Uso de DEBUG II: Trazar un Programa

  - Uso de DEBUG III: Escribir un Programa

  - Observaciones


---------------------------------------------
Consideraciones necesarias sobre el hardware
---------------------------------------------
Antes  de entrar en el calor de la batalla,  son  indispensables algunas 
consideraciones sobre la arquitectura del hardware de la PCs. 

Haba quedado pendiente explicar que es un registro y cuntos tienen los 
procesadores ix86. Tambin ha quedado pendiente qu es una interrupcin. 
A continuacin intentar explicar estas cuestiones.

--------------------------------------
La maldicin del lenguaje ensamblador
--------------------------------------
Vimos en el ejemplo anterior que necesariamente cada paso de un algoritmo 
casi siempre exige la ejecucin de varias instrucciones del CPU.  Lo  que 
podra considerarse una maldicin del lenguaje  ensamblador  es  que  sus 
trminos y expresiones estn en relacin uno a  uno con las instrucciones 
de  cada  CPU:  es  un  lenguaje,   si  as  puede  llamarse,  totalmente 
dependiente del hardware.

A  esta  situacin se agrega que no existe un estndar para los lenguajes 
ensambladores,  por  lo  que  la  primitiva  sintaxis  de estos lenguajes 
tambin depende del ensamblador usado.

Slo  aquellos ensambladores que suministren  un  preprocesador  poderoso 
pueden salvar estas  circunstancias  otorgando  facilidades  que  podran 
hacer ms portable  los programas escritos para estos ensambladores.  Eso 
lo demostraremos pronto.

La maldicin de los lenguajes ensambladores condena a los programadores a 
un conocimiento estricto de hardware de su mquina, sin el cual se podra 
terminar escribiendo programas que resultaran ms  lentos  que  los  que 
podramos haber escrito en un lenguaje  de  alto  nivel.  No  siempre  un 
programa escrito en lenguaje ensamblador  es  necesariamente ms rpido y 
eficiente que otro escrito en un  lenguaje  de  alto  nivel,  que  en  la 
actualidad generan cdigo mquina  optimizado.  La  eficiencia del cdigo
escrito en ensamblador se subordina a dicho conocimiento del hardware.

Curiosamente los lenguajes ensambladores se encuentran a la sombra de una 
paradoja. Cuando escribo cdigo en esta forma:

  main  proc    far
        _init
        _display string
        _pause
        _exit
  main  endp

puedo entender a primera vista qu est ocurriendo: es un algorritmo que 
dice: iniciar, desplegar una cadena de caracteres,  esperar y salir.  No 
se  necesita saber mucho de programacin.  Pero si la misma secuencia la 
escribo as:

  main  proc    far
        mov     ax, @data 
        mov     ds, ax
        lea     dx, cadena
        mov     ah, 9
        int     33 
        mov     ah, 16
        int     22
        mov     ax, 0
        ret
  main  endp

necesito de un entrenamiento especial para entender qu pasa y an as 
no es fcil comprender el cdigo. La paradoja del lenguaje ensamblador 
es  que  hace menos comprensible un  programa  debido  a  que  muestra 
literalmente lo que corre en una  mquina:  mientras ms muestra,  ms 
incomprensible hace lo que ocurre.  Posiblemente esta maldicin,  como 
la he llamado, sea una bendicin,  vista desde otro ngulo:  cuando se 
quiere un control estricto sobre el hardware del sistema.

Ahora que nuestra tarea es programar en ensamblador, necesitamos dicho 
entrenamiento  y un conocimiento del  hardware.  Estamos  obligados  a 
representar nuestras ideas sobre acciones (como iniciar, desplegar) en 
los trminos de una mquina.


------------------------
Dispositivos relevantes
------------------------
De  los  dispositivos del hardware del PC,  son  ms  mportantes para 
nuestra labor de programadores dos de ellos:

	El procesador (CPU: Central Processor Unit)
	La memoria principal (RAM: Random Acces Memory)

Esto no quiere decir que otros  dispositivos,  como  el  monitor,  el 
teclado o el ratn,  no  sean  importantes.  Lo  que pasa es que para 
comunicarnos con ellos debemos pasar por la memoria  y  el  CPU.  Hay 
posiblidades de una comunicacin directa con ciertos dispositivos  de 
hardware, pero son casos especiales.

La memoria principal es una especie de gran array o vector: una serie
de  casillas  dispuestas  en  serie  lineal  cuyo contenido puede ser 
actualizado con cierta libertad.  Debido  a  que el contenido de esta
memoria es dinmico, se actualiza con mucha frecuencia,  se  le llama
memoria de acceso aleatorio (RAM: Random Access Memory), en oposicin
a otro tipo de memoria que slo permite  acceso  para  lectura  (ROM: 
Read Only Memory).


Nombres y direcciones
---------------------
Cada casilla de la memoria se identifica con un nmero que llamamos su 
direccin.  En  lenguaje ensamblador esa casilla la podemos marcar con
un trmino que  la identifique, como en nuestro ejemplo ponamos en el 
segmento de datos:

string  db      "Hola gente", 36

o en el segmento de cdigo:

  _display:
        lea     dx, string

aqu  la  palabra "string" identifica un dato, y "display" identifica 
el inicio de  una  secuencia  en  el  cdigo.  Estos  marcadores  son 
traducidos a direcciones de  memoria,  a  un  nmero  que  identifica
la casilla donde se encuentra  un  dato o una instruccin y que es lo 
que entiende la mquina: el lenguaje de los nmeros.


Rutinas y subrutinas
--------------------
Cuando  un  programa,  o  mejor  dicho  su  imagen  binaria en cdigo 
mquina, va a ejecutarse,  es montado por el sistema en algn espacio 
disponible  de  la  memoria,  todo  junto:  cdigo  y  datos,  aunque 
generalmente en segmentos distintos.

Una  vez montado,  el sistema debe localizar el punto de entrada  del
cdigo del programa.  El punto de entrada es la  primera  instruccin
del programa.  Una vez ubicada la instruccin,  se le pasa el control
del sistema al programa y empieza a ejecutarse.

En  nuestro  programa,  el  punto  de  entrada  est  indicado con la 
expresin:

  main  proc    far

En esta expresin, "main" es un nombre, "proc" es el objeto nombrado,
en ese caso un procedimiento de cmputo, y "far" es una propiedad del 
objeto (un procedimiento) con el nombre indicado (proc): la propiedad 
de ser  lejano (far),  lo que dice al ensamblador que debe considerar 
esta situacin e  incluir el cdigo correspondiente a  procedimientos 
de este tipo.  Tenemos un objeto de tipo procedimiento llamado "main" 
y que tiene como propiedad el ser lejano (far).

Un  procedimiento  de  cmputo  no  es  ms  que  una  secuencia  de 
instrucciones que realiza alguna operacin.

Nuestro  programa  slo tiene un procedimiento:  main, que es "far", 
lejano. Pero un programa puede tener ms de un procedimiento:  donde 
se encuentra la entrada del programa se considera  el  procedimiento 
principal,  los dems se  consideran  procedimientos  secundarios  o 
subrutinas.  Si  los  procedimientos  secundarios  estn en un mismo 
segmento se deben declarar NEAR;  en caso de  que  puedan  accederse 
desde otros segmentos, deben declararse FAR. Sobre los 
procedimientos 
ahondaremos luego. Ahora estamos tratando de revisar la memoria y el 
procesador.

Ejecucin
---------
Cada  instruccin  que va leyendo el sistema es puesta en  un  lugar 
llamado bus de datos. Un bus simplemente es un espacio que  comunica 
un dispositivo con otro.  As  que  lo  que  se coloca en el bus  es 
puesto  de  inmediato  en  una cola de instrucciones que van  siendo 
ledas una por una,  de  forma  secuencial,  por  el  procesador. En 
realidad esta lectura es un  poco  ms  compleja  a  partir  de  los 
procesadores  +486  de  Intel,  cuya  serie  es  nuestro  objeto  de 
estudio. Pero asumamos ahora que es simplemente secuencial.

Una  a una las instrucciones van siendo ledas por el procesador que 
las va tomando desde la cola de instrucciones y las  va ejecutando a 
su turno. Lo que va leyendo el procesador es una combinacin  lineal 
de  unos   y  ceros  que  le  dice  qu  debe  hacer.  La  serie  de 
combinaciones que puede entender el procesador constituye su  cdigo 
de operacin.  Despus  explicaremos esto mejor,  por qu deben  ser 
unos y ceros, slo dos valores.

Para realizar las diversas operaciones,  el  procesador dispone  de, 
adems del bus mencionado y otro, el bus de direcciones, un conjunto 
de pequeos  almacenes  de  datos  llamados  registros.  En  nuestro 
ejemplo, instrucciones como:

        mov     ds, ax

es  una operacin en la que se involucran dos  registros:  ds  (data 
segment: segmento de datos) y ax (acumulador).  La  palabra "mov" al 
comienzo indica la operacin que se realiza entre los dos registros, 
en este caso es  "MOVER"  lo  que est en el registro acumulador del 
procesador a otro registro en el procesador llamado DS.

[Despus nos extenderemos sobre la sintaxis del lenguaje ensamblador.]

Cada uno de estos almacenes internos del  procesador,  que  llamamos 
registros,  tiene  una capacidad que puede variar de un procesador a 
otro.  Esa  capacidad s e llama su tamao y se mide generalmente  en 
bits, la unidad mnima de informacin que puede ser memorizada en un 
sistema digital, es decir, basado en dos valores:  1 y 0. En nuestro 
ejemplo,  hemos usado registros de 16 bits, que pueden guardar hasta 
2 bytes. Un byte es una unidad formada de 8 bits.

Otros  dispositivos  sern  descritos  en  su  momento. Revisemos el 
procesador con ms detalle, especialmente sus registros.


-------------------------
Dos tipos de programador
-------------------------
Para comprender mejor la serie ix86 de procesadores,  es importante
distinguir entre el programador de aplicaciones y el programador de
sistema. El primero se dedica a disear aplicaciones nivel usuario.
As que su mbito se reduce al conocimiento de

	 los  registros internos del procesador,  necesarios para 
	  manipular datos y direcciones de memoria

	 el repetorio bsico de instrucciones del procesador

	 modos de direccionamiento

El programador  de sistemas tiene a su haber otra misin:  el diseo
de sistemas lo ms ptimo posible de explotacin de los recursos del
hardware para dar soporte a las aplicacions previstas. Para realizar
su tarea, debe conocer

	 registros denominados registro del sistema,
	  indispensables para gestionar aspectos como el modo de
	  operacin del procesdor, etc.



---------------------------
Registros internos del CPU
---------------------------
El Intel 8086, el primero de la serie x86, dispone de varios registros
que pueden clasificarse en tres grupos:

	 Registros de propsito general (8 registros):
		- De datos:
			AX: Acumulador
			BX: Base
			CX: Contador
			DX: Datos

		- Punteros:
			SP: Puntero de pila (Stack Pointer)
			BP: Puntero de base

		- ndices:
			SI: ndice de origen (Source Index)
			DI: ndice de destino (Destiny Index)


 	 Registros de puntero de instrucciones y registro de sealizadores 
	  (2 registros)
			IP: Puntero de Instrucciones (Instruction Pointer)
			Sealizadores (Flags)
		

	 Registros de segmento (3 registros)
			CS: Segmento de cdigo
			DS: Segemento de datos
			SS: Segemento de pila
			ES: Segmento extendido

En  el 8086 estos  registros tienen un tamao de 16 bits. En el 80386,
el  tamao de los  registros generales,  del puntero de  instrucciones 
y  del de  sealizadores se  extendi a 32 bits.  Tambin se agregaron 
tres registros de segmentos adicionales:

			FS
			GS

Estos registros constituyen el nivel del programador de aplicaciones.
Esto quiere decir que son totalmente visibles a este programador. Hay
otros  registros que ya no son  transparentes para el  programador de
aplicaciones: los registros del sistema. Dado la complejidad de estos
registros, aplazamos su discusin para el momento oportuno.

Al  extenderse  a  32  bits,  los registros de propsito general y al 
puntero  de  sealizaciones  se  le  prefij  una  "E"  (extended) al 
comienzo. De manera que AX ahora se llamara EAX, SP se llamara ESP, 
etc. Los registros de segmento quedaran  iguales:  registros  de  16 
bits.

De todos modos  los  registros  de 32 bits an pueden ser usados como 
registros de 16 bits.  Simplemente  hacemos  referencia  a ellos como 
tales, sin el prefijo "E".

Los  registros  de datos  pueden ser usados tambin como registros de
8 bits.  Cada registro de 16 bits,  digamos AX, puede ser dividido en
dos partes,  una alta (AH,  H=high) y una baja (AL,  L=low).  Esto es 
vlido para todos los registos de datos.

Con   frecuencia   se  encontrar  que  se  habla  de  la  parte  ms 
significativa de  un registro general para referirse a su parte alta, 
y de la menos  sigificativa para referirse a su parte baja.  La razn 
de esto la veremos luego.

					 -------------------------------
				    AX: |    AH: 8bits	|   AL: 8bits	|
	 ---------------------------------------------------------------
  EAX:	|				32 bits				|
	 ---------------------------------------------------------------

La  estructura  mostrada  en  la  figura  de  arriba  es  comn a los 
registros EBX, ECX y EDX.

Es  posible  realizar  operaciones  sobre  la parte alta o baja de un 
registro general de datos con idependencia.  Por ejemplo,  es posible 
la siguiente instruccin:

	mov	al, bh

Mover el contenido de la parte alta de BX a la parte baja de AX.  Lo 
mismo  no  puede  hacerse con los dems registros que no sean los de 
datos de propsito general.

Luego  hablaremos  sobre  el  uso  que  se  reserva  a  estos cuatro 
registros.

Los registros punteros estn destinados a  guardar  direcciones.  SP 
guarda la direccin donde se encuentra el ltimo dato introducido en 
la pila.

BP,  puntero  base,  se  emplera  para  guardar direcciones bases en 
operaciones con direccionamiento indirecto.

Direccionar, en programacin, es hacer referencia a una direccin en 
la memoria.  Un  direccionamiento  puede constar de dos partes:  una 
base y un ndice o desplazamiento.  El ndice o desplazamiento es un 
puntero o una direccin relativa a la base. 

Los registros ndices se  emplean en direccionamiento indirecto pero 
en  combinacin  con  los  registros  de  segmento, especialmente en 
operaciones con cadenas de caracter.

En dichas operaciones,  SI  apunta  a  una  direccin  dentro de  un 
segmento  ubicado  donde  lo  especifica el registro del segmento de 
datos DS. DI apunta en cambio a una direccin ubicada en un segmento 
de memoria  ubicado donde lo indica el registro de segmento ES.  Los 
trminos Source (fuente) y  Destiny (destino) refieren al papel  que 
juegan dichos registros en operaciones de movimiento de  cadenas  de 
caracteres desde una direccin de memoria a otra. 

El registro IP es el puntero de instrucciones.  En  l se guarda  la 
direccin de la instruccin que se est ejecutando actualmente.

El registro de sealizadores o banderas (flags) es un registro donde 
cada uno de los bits tiene un significado muy  preciso  respecto  al 
resultado de una operacin. Debido a su complejidad lo  explicaremos 
luego. Slo diremos que es la esencia de las estructuras de  control 
en  lenguaje  ensamblador,   ya  que   las  operaciones  lgicas  de 
comparacin  activan  o  no  algunos  de  sus  bits  para indicar el 
resultado.  Muchas  instrucciones, especialmente las que suponen una 
desicin sobre la base de una condicin, revisan alguno de los  bits 
de este registro para ejecutarse.

Los registros de segmento son usados para guardar las direcciones de 
los segmentos  de nuestro  programa.  DS  se  usa  para  guardar  la 
direccin del segmento de datos;  CS  para  guardar la direccin del 
segmento de cdigo y SS  para  guardar la direccin del segmento  de 
pila.

			      --------------------
			     |			  |
			     |			  |
			     |			  |
			      --------------------
	    -----------      |                    |
	SS | direccin |---->| Segmento de pila   |
	    -----------      |                    |
	DS | direccin |-     --------------------
	    -----------  +-->|                    |
	CS | direccin |-    | Segmento de datos  | 
	    -----------  |   |                    |
			 |    --------------------
	     Regitros	 |   |			  |
		de	  -->| Segmento de cdigo |
	     segmento	     |			  |
			      --------------------
			     |			  |
			     |			  |
			     |			  |
			     |			  |
			     |			  |
			      --------------------

				     Memoria

La segmentacin de la memoria permite reubicar  objetos.  Para  ello
slo basta con establecer sus propiedades y su direccin.

El  uso  de  los  registro  de  datos  de propsito general es mejor 
revisarlo directamente en los programas que vayamos escribiendo.

Detengo momentneamente mi explicacin sobre los registros ya que lo 
mejor es verlo en la prctica.


-------------------------------
Nota sobre las interrupciones I
-------------------------------
A nivel de hardware,  el  sistema  est  controlado generalmente por 
interrupciones.

Supongamos que inicias el PC en DOS.  Llegars a un puntero de lnea 
de rdenes:

	C:\>

Parecier que el sistema se ha quedado dormido, esperando las rdenes 
del usuario. Supongamos ahora que pulsamos una tecla  Cmo  sabe  el 
sistema  que  ha  tenido  lugar ese evento?  Posiblemente  el  evento 
produce algn tipo de perturbacin  que  _interrumpe_  el  sueo  del 
sistema.  ste despierta y revisa el motivo de la perturbacin, donde 
ocurri y qu  la  caus.  Dependiendo  de  donde  ocurri  y  de  su 
importancia, el sistema deber atender esa interrupcin. La  atencin 
consiste en una suspensin de lo que en ese momento est haciendo  el 
sistema, y en el paso del control a una rutina llamada manipulador de 
interrupcin, que se encuentra en un sitio determinado de la memoria.

Ms o menos es as como funcionan las cosas en  la  mquina:  a  cada 
dispositivo  se le  asigna  una  lnea  de  interrupcin.  Cuando  el 
dispositivo entra en funcionamiento,  activa  la  lnea,  produciendo 
seguramente un cambio de voltaje que es captado por el procesador.  A 
esa lnea a veces se le conoce como IRQ (Interrupt ReQuest: Solicitud 
de Interrupcin). Para cada lnea no slo se asigna un  nmero,  sino 
tambin un manipulador (handler) de la interrupcin. Este manipulador 
consiste en una rutina que se ejecuta para atender la interrupcin y, 
luego, despus de atenderla,  devuelve el control a la rutina que fue 
interrumpida.

Un mecanismo semejante ha sido adoptado para el software. Se escriben
o disean una serie de rutinas bsicas, los manipuladores  (handlers) 
de las interrupciones, y se les asigna a cada una de ellas un nmero. 
Todas estas rutinas pueden ser  montadas en  memoria  en  direcciones 
especficas al iniciarse el sistema. Sus direcciones pueden colocarse 
en serie,  una despus de otra,  para formar una tabla en la memoria, 
generalmente llamada tabla de servicios de interrupciones.

Aqu  vector  significa  una  serie  de casillas de memoria del mismo 
tamao con informacin del mismo tipo.  En  este  caso,  el vector de 
interrupciones es una serie de casillas del  mismo  tamao  (aqu  32 
bits o 4 bytes) cada una conteniendo una direccin. A cada casilla le 
hacemos corresponder el nmero  del  correspondiente  manipulador  de 
interrupcin. Supongamos que elegimos para manejar las pulsaciones de 
teclado la interrupcin 1.  Entonces  cuando  pulsemos una tecla,  se 
activar una lnea que har que el procesador pase el  control  a  la 
direccin que se encuentra en la casilla 1 de la tabla de servicios.

Generalmente, cada manipulador incluye varios servicios.  Para  saber 
cul servicio se requiere puede revisar  el  contenido  en  registros 
especficos  del  procesador,  generalmente  en  la  parte  alta  del 
registro acumulador (AH).  El  nmero que encuentre en este  registro 
indica cul servicio est requierindose.

Ms o menos, as funcionan las interrupciones de software,  a  travs 
de  las  cuales  el  sistema  satisface  los  requerimientos  de  sus 
servicios desde los programas.

As que, en principio, tenemos dos tipos de interrupciones:

	 Interrupciones de software o internas
	 Interrupciones de hardware o externas

Estas interrupciones pueden subdividirse en otros tipos, pero dejamos
su estudio para luego. Basta por ahora hacer observar que en  nuestro 
ejemplo  hemos  usado   varias   interrupciones.   En  los  ix86,  la 
interrrupcin de software se provoca a travs de un operador unario:

	int XX

donde  XX es el nmero de la entrada en el vector de interrupcin con 
la direccin donde se halla  la rutina que trabaja como manipulador o 
manipulador de  la  interrupcin.  El  servicio  a  solicitar  de  la 
interrupcin  se  pasa  generalmente  a  travs  de la parte alta del 
registro Acumulador (AX, la parte alta es AH). As que para solicitar 
el servicio 9 de la interrupcin 33,  que  despliega  una  cadena  de 
caracteres, cuya direccin debe ponerse en el registro de  datos  DX, 
debemos escribir algo como:

	lea	dx, cadena
	mov	ah, 9
	int	33

La instruccin "lea dx, cadena" pone en dx la direccin del marcador
"cadena" (lea = Load Efective Address), donde se debe encontrar  una 
cadena de caracteres cuyo final debe ser marcado con el smbolo "$".

La instruccin "mov ah, 9", mueve a la parte alta del acumulador  el
nmero 9, el servicio requerido.

La instruccin "int 33" dispara una interrupcin que pasa el control 
a  la direccin  que  se  encuentra  en  la entrada 33 del vector de 
interrupciones del sistema.

En los PCs, la tabla con los vectores de interrupcin del sistema se 
ubica al comienzo de la memoria, ocupando 1024 bytes,  para soportar 
256 interrupciones (256 entradas de 4 bytes = 1024 bytes).

En  DOS,  hay dos fuentes principales de interrupciones.  Las que se 
incluyen en el chip ROM que sirve de BIOS del sistema y las  propias 
del sistema operativo.  Las entradas para los servicios del BIOS son 
31, ocupando los primeros 124 bytes de la tabla.

Las entradas de los servicios de DOS  inician  con  la  32.  Arriba, 
"int 33" designa un servicio de interrupcin de DOS.

En la mayora de los textos y cdigos fuente encontraremos que  esta 
misma interrupcin se designa con la cifra 21. Lo que pasa es que en 
mi  ejemplo  he  usado  el  cdigo  decimal,  el  que  los  mortales  
occidentales  manejamos  a  diario.  En  cambio  los   programadores 
iniciados usan el  sistema de numeracin llamado cdigo hexadecimal,
ms cercano al cdigo con el que trabaja la mquina.

En  Linux,  a  los  servicios  el  sistema  se accede a travs de la 
interrupcin 108 (80 en hexadecimal).  Es  la nica que se emplea en 
este sistema operativo. Linux corre en modo protegido  e  implementa 
memoria virtual: existe una diferencia entre las direcciones que  ve 
el programador y las reales de memoria, donde corren los  programas. 
Este no es el caso tradicional de DOS donde slo se trabaja  con  la 
memoria real. Lo cierto es que, as como no se  accede  directamente 
a la memoria fsica,  en Linux tampoco se accede directamente a  los 
servicios del sistema.  La  interrupcin  80  (en hexadecimal) es la 
interface que conecta la aplicaciones con los servicios.  As que un 
cdigo como

	lea dx, string
	mov ah, 9
	int 33

que en DOS despliega la cadena cuya direccin hemos cargado en  edx, 
en Linux debera ser:

	mov eax, 4	; nmero de servicio o llamada de sistema: 
			; 4 = sys_write
	mov ebx, 1	; descriptor de archivo: 1 = stdout
	lea ecx, string	; direccin de la cadena a escribir
	mov edx, len	; tamao de la cadena
	int 0x80	; llamada al ncleo

Vemos  que  Linux  trabaja con registros de 32 bits:  los  registros 
extendidos. En el registro extendido del acumulador  EAX  ponemos el 
nmero del servicio, 4 para el servicio de escritura en ficheros. En 
el registro extendido de la  base  EBX  ponemos  el  descriptor  del 
fichero; como queremos que la cadena se despliegue  en  la  cnsola, 
ponemos 1, que indica la salida estndar. La direccin de la  cadena 
la ponemos  en  ecx,  el  registro  extendido  del contador.  En  el 
registro extendido de datos  ponemos el tamao de  la  cadena.  Esta 
cadena debe terminar en cero. Luego pasamos el control al ncleo  de 
Linux, produciendo la interrupcin 108, que en hexadecimal es 0x80 u 
80h.

Windows,  como  Linux,  corre  en  modo protegido. Igual incluye una 
interrupcin  para  comunicar  entre las aplicaciones  que corren en 
modo usuario y los servicios delsistema,  que corren en modo ncleo. 
En Windows  95 es la interuupcin 32 (o 20 en hexadecimal);  en  NT, 
2000 y XP es la interrupcin 46 (o 2E en hexadecimal).


----------------------
Uso de DEBUG de DOS I
----------------------
DOS y la cnsola de Windows incluyen un programa llamado DEBUG.  La
palabra  "debug"   podramos  traducirla   por  depurador,   aunque 
realmente sta no  sea  su traduccin  literal.  Supuestamente,  un 
depurador est destinado a la eliminacin de  "bichos",  cucarachas 
en nuestros programas.  Es una especie de insecticida. En  terminos 
de programacin, podemos decir que se trata de un programa para  la 
localizacin de errores que afectan sensiblemente el funcionamiento 
de nuestros programas.

Con el depurador podemos revisar paso  por  paso  la  ejecucin  de 
nuestro programa, revisar como va modificndose el contenido de los 
registros hasta ubicar donde est el "bicho" y aplastarlo.

Imagino  que el  nombre  se debe a que los primeros desperfectos en 
las primeras mquinas de cmputo electrnicas se deba generalmente 
a la presencia de cucarachas u otros insectos en  los circuitos  de 
tubos.

El  debug de  DOS  permite,  entre  otras  cosas,  editar  archivos  
ejecutables y hasta escribir programas en lenguaje ensamblador.

Ahora  vamos a hacerlo: escribir un programa en ensamblador  con el
DEBUG. Antes hay que hacer unas consideraciones sobre los  sistemas
de numeracin con los que se trabaja.


-----------------------
Sistemas de numeracin
-----------------------
Los occidentales trabajamos a diario con el sistema de  numeracin 
decimal o base 10. En este sistema, se usan diez smbolos:

	0 1 2 3 4 5 6 7 8 9 

cada nmero es el resultado de una suma de potencias con base  10. 
Me explico con un ejemplo. El nmero 4501.56 es la suma de:

(4 x 10^3) + (5 x 10^2) + (0 x 10^1) + (1 x 10^0) + (5 x 10^-1) + 
   (6 x 10^-2) = 4000 + 500 + 10 + 1 + 0.5 + 0.06 = 1501.56

Las PCs trabajan sobre otro sistema numrico: el sistema  binario, 
con base 2. Se usan dos smbolos:

	0 1

cada nmero  es  el resultado de una suma de potencias con base 2. 
El nmero 1101, por ejemplo, sera:

	(1 X 2^3) + (1 x 2^2) + (0 x 2^1) + (1 x 2^0) =
 	   8 + 2 + 0 + 1 = 11 decimal

Hay varias circunstancias que favorecen el uso del sistema binario 
en las computadoras.  Por  ahora slo har mencin de una de orden 
prctico.

Las  investigaciones  que  dieron  inicio  a la era digital de las 
computadoras determinaron que los sistemas electrnicos  digitales 
superaban  una  serie  de  problemas  presentes  en  las  mquinas 
mecnicas  y  analgicas.  Electrnicamente  hablando,  un  cdigo 
basado en el sistema binario parece simple de implementar:

	 se asigna el  valor 1 a la presencia de voltaje X en  un 
	  terminal de un circuito.

	 se asigna el valor 0 a la ausencia del voltage X  en  el 
	  terminal.

Supongamos  entonces un dispositivo con dos conjuntos definidos de 
terminales de este tipo, el conjunto I  de terminales de entrada y 
el conjunto O de terminales de salida. Podemos disear un circuito 
que compute una funcin  que asigne valores especficos de salida 
para valores especficos de entrada. De esta forma podemos disear 
un cdigo de operaciones para un  sistema:  para  una  combinacin 
especfica de unos y ceros  el sistema realizar una operacin , 
que dar como salida una combinacin de unos y ceros .

Desde  este  punto  de  vista,  podemos  definir  un  registro  de 
procesador  como  una  clula  dentro  del  procesador  que  puede 
almacenar informacin en forma de una secuencia de unos  y  ceros, 
que  tendr  el  significado  que  le  asigne  nuestro  cdigo  de 
operaciones.  Cada terminal del registro puede almacenar un  uno o 
un cero,  es decir,  puede  almacenar  un  bit de informacin.  El 
nmero de combinaciones que pueden  establecerse  en  un  registro 
depende de este nmero de terminales: 2 ^ No.de terminales.

Los  registros  del  i386 tienen un ancho de 32 bits, por lo tanto 
pueden almacenar una de  2 ^ 32 = 4 GB  combinaciones  de  unos  y 
ceros.

Una representacin ms adecuada del cdigo mquina,  es el sistema 
de numeracin hexadecimal, de base 16, de dieciseis smbolos:

	0 1 2 3 4 5 6 7 8 9 A B C D E F

  [A es 10 en decimal, B es 11, C es 12,... y F es 15.]

y donde cada nmero es el resultado de la suma de  potencias  base 
16.

El nmero 4A7B sera, en decinal:

	(4 x 16^3) + (10 X 16^2) + (7 x 16^1) + (B x 16^0) =
	   16384 + 2560 + 112 + 11 = 19067

Por qu se escogi la notacin hexadecimal? Por que la unidad con 
la que generalmente trabajan las PCs es el  byte=8  bits.  Con  un 
smbolo hexadecimal puedo representar 16 valores, es decir, 4 bits 
(2 ^ 4 = 16). As que con slo dos hexdecimales puedo  representar 
un byte.

Una secuencia de un byte de informacin, como 1111 1111,  tiene la
siguiente representacin hexadecimal.

	1	1	1	1      -   1	1	1	1
	8	4	2	1 = 15 -   8	4	2	1 = 15
	                            F  -                            F

Es decir FF = 15x16^1 + 15x16^0 = 15x16 + 15 = 255.

En  el  lenguaje  C  los  valores  en  hexadecinal se  representan 
antecediendo el valor con "0x".  Por ejemplo el ABC8 se expresara 
en C as:

	0xABC8

La misma notacin es  empleada  en algunos ensambladores,  pero en 
TASM y en  MASM la  misma cantidad se expresara  en   hexadecimal  
haciendo seguir la cantidad con una 'h' mayscula o minscula.  Si 
la cifra  comienza con una letra, hay que prefijarle un cero.  As 
que  la cifra anterior habra que escribirla:

	0ABC8h

Hay  ocasiones  en  las  que podemos necesitar escribir valores en 
binario.  En  ese  caso  debemos  sufijar  la  expresin  con  'b' 
minscula o mayscula:

	1101b = 0Dh = 13

En cambio:

	1101h = 4096+256+0+1= 4353

Ahora podramos reescribir nuestro  primer  programa  especificando 
los nmeros en hexadecimal:

; --------------------------------------------------------
  TITLE	PRIMER.ASM: Primer programa escrito en ensamblador
; --------------------------------------------------------
	.MODEL SMALL
; --------------------------------------------------------
	.stack 40h
; --------------------------------------------------------
	.data
  cadena        db      'Hola gente!', 24h
; --------------------------------------------------------
	.code
  init	proc 	far
	mov	ax, @data 
	mov	ds, ax
  _display:
	lea	dx, cadena
	mov	ah, 1001b
 	int	21h 
  _pause:
	mov	ah, 16
	int	16h
  _exit:
	mov	ax, 0
	ret
  init	endp
; --------------------------------------------------------
  end 	init
; -------------------------------------------------------

Si lo ensamblas, dar el mismo resultado que antes.  La instruccin 
"mov ah, 9" la reemplac a propsito por "mov ah, 1001b", cambiando 
el 9 decimal por su equivalente binario, como un ejemplo.


------------------------------------
Uso de DEBUG II: Trazar un Programa
------------------------------------
DEBUG,  como  muchos  otros programas auxilares de la programacin,
trabaja con el sistema hexadecimal.

Para  ejecutar DEBUG escribimos DEBUG en el puntero de DOS o en  el 
cuadro de ejecucin (Inicio-Ejecutar) de Windows.

Aparecer slo un guin que es el apuntador del DEBUG.

Las diversas operaciones del DEBUG se indican con letras:

	A: assemble (ensamblar)
	C: compare (comparar)
	D: display (desplegar)
	E: enter (ingresar)
	F: full (llenar)
	G: go (ir)
	H: hexadecimal
	I: in (entrada)
	L: load (cargar)
	M: move (mover)
	N: name (nombrar)
	O: out (salida)
	P: proceed (proceder)
	Q: quit (quitar)
	R: register (registro)
	S: search (buscar)
	T: trace (trazar o rastrear)
	U: unassemble (desensamblar)
	W: write (escribir)

Ahora no las usaremos todas.

Antes de escribir un programa con DEBUG, primero revisemos  el  que 
escribimos al comienzo. Cargamos el programa en el DEBUG:

	C:\tasm\works\primer>DEBUG primer.exe

Ahora ingresamos la orden T, y obtendremos:

-t

AX=0E00  BX=0000  CX=0020  DX=0000  SP=0040  BP=0000  SI=0000  DI=0000
DS=0DEF  ES=0DEF  SS=0E01  CS=0DFF  IP=0003   NV UP EI PL NZ NA PO NC
0DFF:0003 8ED8          MOV     DS,AX

Se  trata  de  la  segunda  instruccin  de  nuestro  programa.  La 
instruccin MOV  es  una instruccin  de  asignacin,  parecida  al 
operador "=" de C.  Lo que hace es mover el contenido  del  segundo 
operando,  aqu AX,  que  para  el momento  tiene la  direccin del 
segmento de datos, al primer operando, DS. Antes de la ejecucin de 
la instruccin DS=0DEF y AX=0E00; son valores hexadecimales.

Ingresemos de nuevo t:

-t

AX=0E00  BX=0000  CX=0020  DX=0000  SP=0040  BP=0000  SI=0000  DI=0000
DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0005   NV UP EI PL NZ NA PO NC
0DFF:0005 BA0400        MOV     DX,0004

Ahora DS=0E00. Se ha movido lo que estaba en AX a DS.

La siguiente instruccin mueve a DX la direccin de nuestra cadena.
Habamos escrito "LEA DX, string";  DEBUG  lo ha interpretado  como 
"mover  a DX lo que est 4 bytes por encima del inicio del segmento
de datos".

Si se quiere verificar escribimos:  D DS:4, desplegar lo que est 4 
bytes despus del inicio del segmento de datos:

-d ds:4
0E00:0000              48 6F 6C 61-20 67 65 6E 74 65 21 24       Hola gente!$
0E00:0010  C7 06 0E 96 3E 2B 2E C7-06 10 96 3D 3B E8 83 09   ....>+.....=;...
0E00:0020  73 13 B8 FF FF 53 26 8B-1D 26 3A 0F 73 03 B8 02   s....S&..&:.s...

En  0E00+4  vemos el cdigo hexadecimal correspondiente  a  nuestra 
cadena, que en la columna derecha podemos ver en ASCII. Podemos ver 
que el smbolo "$", que originalmente habamos introducido como 36, 
y que en la columna del medio aparece en hexadecimal: 24.

Sigamos trazando:

-t

AX=0E00  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000
DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0008   NV UP EI PL NZ NA PO NC
0DFF:0008 B409          MOV     AH,09

Ahora moveremos a la parte superior de AX un nueve:

-t

AX=0900  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000
DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=000A   NV UP EI PL NZ NA PO NC
0DFF:000A CD21          INT     21

Vemos  ahora  que  "AX=0900",  que confirma que hemos movido 9 a la 
parte alta de AX.

Se va a ejecutar la interrupcin 21h, servicio  9;  recurdese  que 
anteriormente  habamos   escrito  "int  33".   DEBUG   trabaja  en 
hexadecimal, as que despliega la versin  hexadecimal  de  lo  que 
escribamos.

Para trazar por encima de la interrupcin,  no es bueno usar T,  ya 
que esto nos llevar a la rutina de la interrupcin.  As que mejor 
ejecutamos P:

-p
Hola gente!
AX=0924  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000
DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=000C   NV UP EI PL NZ NA PO NC
0DFF:000C B410          MOV     AH,10

Esto despliega nuestra cadena en la cnsola.  Observa que todos los 
registros se han mantenido  iguales  despus  de  la  interrupcin, 
excepto AX; vemos que en la parte baja de AX, que es AL, aparece un 
24h. Se trata del caracter "$".  Quiere decir que el servicio  9 de 
la interrupcin 21h usa el registro AL para revisar la cadena.

Ahora moveremos 16 a AH. DEBUG lo presenta como "MOV AH, 10"; 10h = 
16 decimal.

Hacemos T:

AX=1024  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000
DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=000E   NV UP EI PL NZ NA PO NC
0DFF:000E CD16          INT     16

Se va a ejecutar el servicio 16 o 10h de la interrupcin 22 o  16h. 
Lo  que  hace  este  servicio es esperar a que el usuario  pulse un 
tecla, cuyo cdigo devuelve en AH el cdigo  de rastreo y en  AL el 
cdigo de la tecla.  Para  teclas de funcin  extendida  (F1 - F12) 
ms importante es el cdigo de rastreo, ya que como cdigo de tecla 
se pasar 00, para las teclas F1-F12, E0h para para otras teclas de 
control como Inicio o RePag.

Hacemos P y se ejecuta nuestra pausa;  pulsamos cualquier tecla, en 
mi caso "enter":

-p

AX=1C0D  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000
DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0010   NV UP EI PL NZ NA PO NC
0DFF:0010 B80000        MOV     AX,0000

Cdigo de rastreo en AH=1Ch y cdigo de tecla en AL=0Dh.

Ahora vamos a salir. Devolvemos el valor FALSE (=0) en AX a DOS. Un
simple gesto de educacin.

AX=0000  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000
DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0013   NV UP EI PL NZ NA PO NC
0DFF:0013 CB            RETF

Por  ltimo regresamos a DOS.  La instruccin RETF es una operacin
ceroaria (sin operandos) que significa RETURN FAR,  regresar lejos.
Recordemos que hemos declarado nuestro procedimiento as:

	main	proc	far

"main" es el nombre del procedimiento;  proc  es  una directiva que 
indica inicio de una rutina o  funcin (en  ensamblador  se  llaman 
procedimientos a las funciones) y far indica  que el  procedimiento 
puede ser accedido desde otros  segmentos.  La instruccin RETF nos 
saca del procedimiento a la lnea de rdenes.

Hay otras formas ms limpias de salir de un programa, pero con esta
basta por ahora.

Nota  tambin  que  a  la  derecha  de cada  instruccin aparece la 
direccin en memoria, en sistema hexadecimal, de la instruccin,  y 
luego es seguida por otra cifra en hexadecimal.  sta  ltima es la 
versin en hexadecimal del cdigo de operacin  de la  instruccin. 
Si no fuera por el sistema hexadecimal,  ah  apareceran una serie 
de unos y ceros.

Con DEBUG tambin  podemos obtener el cdigo fuente en  ensamblador 
de un fichero ejecutable.  Esto  se  hace  cargando  el  fichero  y 
ejecutando la orden U,  seguida por la direccin  a  partir  de  la 
cual queremos que se inicie el desensamblado y el nmero de  lneas 
a desensablar:

-u cs:00
0CE2:0000 B8E30C        MOV     AX,0CE3
0CE2:0003 8ED8          MOV     DS,AX
0CE2:0005 BA0400        MOV     DX,0004
0CE2:0008 B409          MOV     AH,09
0CE2:000A CD21          INT     21
0CE2:000C B410          MOV     AH,10
0CE2:000E CD16          INT     16
0CE2:0010 B80000        MOV     AX,0000
0CE2:0013 CB            RETF

Tememos aqu un desensamblado de nuestro  cdigo.  Tambin  podemos 
obtener este desensamblado en un archivo de texto aparte:

DEBUG primer.exe<U CS:00 14>prim.asm

Esta orden volcar en prim.asm  un desensablado de  los primeros 20 
(14h) bytes de primer.exe, que podemos revisar con un editor.

Podramos escribir un fichero disasm.bat:

cls
@echo off
@echo U CS:00 13>t_$
@echo.>>t_$
@echo Q>>t_$
2echo.>>t_$
@echo Desensamblando....
DEBUG %1.exe<t_$>%1_.asm
EDIT %1_.asm

Luego ejecutamos:

	disasm primer

y ya! Podramos mejorarlo  para  que  desensamble  exactamente  las 
instruccciones del segmento de cdigo;  e incluso hacer que nuestro 
.BAT cambie algunos valores hexadecimales en el .EXE,  pero  no  es 
este el lugar para explicarlo.

	
---------------------------------------
Uso de DEBUG III: Escribir un Programa
---------------------------------------
Escribamos ahora un programa con el DEBUG.  Antes  una  observacin 
sobre el formato de los ejecutables en DOS.  Existen dos  formatos: 
.COM y .EXE.  El primero, .COM,  es el formato  original.  En  este 
formato, todo, cdigo y datos, es puesto en un nico segmento  cuyo 
tamao no debe exeder los 64KB.

En el formato .EXE,  que es posterior,  se reserva un segmento para 
datos, uno para cdigo y uno para la pila.

Nuestro programa fue escrito en  formato  .EXE.  Con  el  DEBUG  se 
pueden  escribir  programas en formato .COM,  que  son bastante ms 
pequeos.

El programa debe comenzar a ejecutarse en la direccin 256  [100h], 
ya que los ejecutables de DOS reservan los primeros 256 bytes  para 
colocar  ah una estructura de datos conocida como PSP,  cuando  es 
cargado en la memoria. El PSP (Program Segment Prefije:  Prefijo de 
Segmento del Programa)  contine  informacin que ser utilizada por 
el cargador de DOS.

As que comenzamos escribiendo A 100 [enter]

-a 100
0DAB:0100 jmp 108
0DAB:0102 db "hola$"
0DAB:0107 nop
0DAB:0108 mov dx,102
0DAB:010B mov ah,9
0DAB:010D int 21
0DAB:010F mov ah,10
0DAB:0111 int 16
0DAB:0113 int 20
0DAB:0115

La  primera  instruccin ,"jmp 108",  es  saltar a la localidad 108. 
Necesitamos un espacio para  nuestra  cadena.  Como  no  sabemos  el 
tamao del programa y no podemos determinar al comienzo donde estar 
la  cadena si la ponemos  al  final  del  programa,  la  ponemos  al 
comienzo, y para evitar  que el programa comience a ejecutarse en la 
cadena (lo que dara error)  le pasamos por encima.  La  instruccin 
jmp 108 tiene dos bytes,  reservamos  6  bytes  para  la cadena. Nos 
sobra un byte, as que escribimos "nop" en 107, un operador ceroario 
que significa No OPeracin.

Vemos  que  la  direccin de la cadena es 102,  as que ponemos este 
valor en  DX;  luego  llamamos  al  servicio  9  (mov ah, 9)  de  la 
interrupcin 21h (int 21) para desplegar la cadena. Luego ejecutamos 
el servicio 10h de la interrupcin 16h (que detiene la ejecucin del 
programa) y por ltimo regresamos a DOS con la interrupcin 20h.

Pulsamos dos veces enter para salir de la orden A.

Podemos probar de inmediato nuestro programa con la orden G:

-g
hola

Debes pulsar ENTER para salir del programa. Se desplegar:

"El programa ha finalizado con normalidad" 

Ahora debemos escribir nuestro programa a un archivo .COM en  disco.
Para ello empleamos la orden W [write]. Para realizar esta escritura
correctamente,  primero  elegimos  un  nombre para el programa, como 
pr.com. La extensin debe ser .COM, porque W no puede crear ejecuta-
bles con formato .EXE; la orden es:

-n pr.com

Tambin debemos especificar el tamao de nuestro ejecutable. W crea-
r un archivo con el tamao indicado en  el  registro  CX;  as  que 
deberamos  poner en CX el tamao de nuestro ejecutable empleando la
orden R:

-r cx
CX 0000
:15

Finalmente, escribimos el archivo en disco:

-w
Escribiendo 00015 bytes

Eso es todo. Luego lo ejecutamos. Salimos de DEBUG:

-q

y ejectamos pr.com:

hola

Todo bien?

Observa el tamao del archivo: 21 bytes (15h = 21 decimal)


--------------
Observaciones
--------------
Estas  ltimas  explicaciones  muestran  un poco las exigencias del
lenguaje ensamblador,  la cantidad de conocimientos que se requiere
para escribir un simple programa.  Por  eso  se dice que si alguien
programa en ensamblador es un buen programador. 

A estas alturas del partido,  el  lenguaje  ensamblador  ha quedado
bastante excluido del mundo de la  programacin,  precisamente  por
su nivel de exigencia.  Sin  embargo,  aunque  no  lo  parezca,  en 
ocasiones es la mejor opcin; no slo donde se necesite velocidad o 
ahorro en el tamao del programa. Tambin es la mejor opcin cuando 
nos interesa un contacto estrecho, a muy bajo nivel, con cualquiera 
de los componentes del sistema.  La programacin de sistema ser su 
medio ambiente por antonomacia,  por  ejemplo,  en  la escritura de 
controladores   (drivers)  de  dipositivos  o  de  algunos  mdulos 
esenciales de los sistemas operativos.

Como es la mejor  opcin para velocidad,  es  muy  empleado  en  la 
programacin  grfica,  en  la creacin de animaciones  en  tercera
dimensin, por ejemplo.

El ensamblador  se requiere donde son exigentes las  condiciones de
operacin.  No imaginamos hoy a nadie invirtiendo los recursos  que
exige la programacin  en ensamblador para escribir  programas  que
slo digan "Hola gente".  A  esto se agrega  que,  en  general,  la
interface de programacin (API) de los sistemas operativos modernos
est pensada para lenguajes de alto o medio nivel.

Trataremos de ser consecuentes y de concentraremos  entonces en  la 
programacin  de  sistema,  en  aspectos  poco  documentados de  la 
programacin en lenguaje ensamblador.

Por  supuesto,  todava  tenemos  que revisar la sintaxis y  alguna
semntica  del  lenguaje  ensamblador.  Tambin  en  los  cabos que
hemos dejado sueltos. Esto lo haremos sobre la marcha,  escribiendo 
programas: nuestro enfoque intenta ser prctico.

Ahora unas indicaciones sobre la sintaxis del lenguaje ensamblador, 
antes de escribir nuestra primera utilidad.


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

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




