Professional Documents
Culture Documents
Sistemas numéricos
1.1.1. Sistema decimal
El sistema decimal es el sistema numérico natural humano, en el que todos
pensamos naturalmente. Pero, ¿qué es?. Es un sistema numérico en el que la
base es el número 10 (bastante redondo). Veámoslo con un ejemplo.
Supongamos que tenemos el número 5013. Vamos a ver cuál es su estructura
en base 10:
1101 = 1 * 23 + 1 * 22 + 0 * 21 + 1 * 20 = 8 + 4 + 0 + 1 = 13
1101 = 23 + 22 + 01 + 20 = 8 + 4 + 1 = 13d
1011 = 23 + 02 + 21 + 20 = 8 + 2 + 1 = 11d
------ ---- +
11000 = 24 + 23 = 16 + 8 = 24d
Explicación:
1 más 1 son 2 (en decimal), que en binario se representa con un 10, por lo tanto,
ponemos un cero y nos llevamos una.
0 más 1 es 1, más 1 que nos llevábamos de antes, son 2 (en decimal), que de
nuevo, en binario se representa por un 10, por lo que ponemos un cero y nos
llevamos una.
1 más 0 es 1, más 1 que nos llevábamos de antes, son 2 (en decimal), que en
binario es 10, con lo que ponemos un cero y nos llevamos 1.
1 más 1 son 2, más 1 que nos llevábamos antes son 3 (en decimal), que en
binario se representa por un 11, que es lo que ponemos, et voilà.
1101 = 23 + 22 + 20 = 8 + 4 + 1 = 13d
1011 = 23 + 21 + 20 = 8 + 2 + 1 = 11d
------ --- -
0010 = 21 = 2 = 2d
Explicación:
Como su nombre indica el sistema hexadecimal tiene por base al número 16.
Aquí tenemos un problema, pues los números que nosotros conocemos van del
0 al 9, que son 10, ¿cómo llegar al 16?; fácil, le añadimos las letras A (10), B
(11), C (12), D (13), E (14), F (15) a partir del 10, con lo cuál ya tenemos 16
dígitos para representar números.
Para convertir un número hexadecimal en binario tan sólo tenemos que utilizar
esta tabla, y a cada dígito hexadecimal sustituirlo por sus cuatro dígitos
decimales. Supongamos que tenemos el número 0ABCDh y lo queremos pasar
a binario:
0 A B C D
0000 1010 1011 1100 1101
Es así de sencillo.
1.1.7.1. Bit
1.1.7.2. Nibble
1.1.7.3. Byte
7 6 5 4 3 2 1 0
1.1.7.4. Palabra
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
Si para medir la capacidad de los registros internos del procesador se usan las
unidades indicadas anteriormente, cuando se trata de indicar la capacidad de un
bloque de memoria o de ciertos dispositivos como los CD-ROM, discos duros
y similares, se emplean siempre múltiplos del byte. Todos ellos, al operarse con
la base 2 en lugar de la decimal, usan como multiplicador no el millar, sino el
1024, que es el resultado de elevar 2 a 10.
Hasta ahora hemos tratado sólo los números binarios positivos, pero ¿qué pasa
con los negativos?.
Propiedad asociativa: a + (b + c) = (a + b) + c
Existe el elemento neutro (0): 0 + a = a + 0 = a
Existe el elemento simétrico (-a): a + (-a) = (-a) + a = 0
Propiedad conmutativa : a + b = b + a
Existen más, pero estos son los más usados. Todos ellos están pensados
naturalmente para albergar números enteros positivos hasta su límite, pero
¿cómo podemos almacenar números negativos?. Realmente tenemos que usar
un truco que es establecer la mitad de su capacidad para los números positivos
y la otra mitad para los negativos.
Por ejemplo:
Supongamos que cogemos un número de magnitud byte, esto significa que sólo
puede contener 8 dígitos, ceros o unos.
Vemos en este gráfico que esta disposición que hemos elegido para nuestro
sistema numérico es como el pez que se muerde la cola.
1111 1111
1
-------------- +
1 0000 0000
La cifra que obtenemos excede de tamaño byte, puesto que tiene 9 dígitos, por
lo tanto el dígito de la extrema izquierda se pierde, es como si intentaras llenar
una botella con vasos de agua y el último no cupiese, simplemente se
desbordaría y se perdería. De esta forma ¿qué es lo que obtenemos?, el número
0b, con lo que obtenemos este círculo vicioso del que hablábamos antes. Esto
ocurre por la ya mencionada limitación de contención de cualquier número de
las computadoras y a este suceso se le llama desbordamiento. La única solución
para que no ocurran desbordamientos (puesto que obtenemos resultados
imprevisibles) es elegir una botella lo suficientemente grande como para que
quepan todos los vasos de agua que le vamos a echar.
Este subconjunto de Z, en el caso de los bytes de -128 a 127, con esta operación
suma en complemento a 2 que hemos definido es un subgrupo:
Es cerrado respecto de la suma. Si notamos al subconjunto como B, b y b' dos
elementos suyos, b+b' también es de B por la propiedad del desbordamiento.
1000 0001b
0111 1111b
------------ +
1 0000 0000b
Contiene al elemento neutro. Claramente es el cero.
Cada elemento tiene su simétrico. Dado g en G, su simétrico será: NOT(g) + 1.
NOT cambiará cada bit a su contrario, y luego le sumamos 1. Únicamente el
128 se escapa de esta propiedad porque -128 es un byte, pero 128 ya es un word.
Así ([-127,127], suma complemento a 2) sería un subgrupo de (Z,+), pero el -
128 también cabe dentro de un byte.
Importante
Antes de realizar operaciones con números con signo es preciso cerciorarse de que todos tengan el mismo
número de bits. Si esto no es así hay que alargar el tamaño de los más cortos al tamaño del mayor. Para ello
se repite la cifra a la izquierda tantas veces como sea necesario, es decir, a los números negativos se les añade
1 repetidas veces a la izquierda y 0 a los positivos. Esto ya se consigue con los operadores CBW, CWD, por
ejemplo.
Otra cosa son los enteros sin signo. Por ejemplo, de tipo byte, que son 8 bits
serían 28=256 elementos, que van desde el 0 hasta el 255. Aquí ya no tenemos
la suma en complemento a 2, sino la suma normal, pues no existen los
negativos, pero el desbordamiento sigue existiendo, lo cuál provoca que el
número resultante de la suma siga siendo de tipo byte, pero el resultado sería
distinto al esperado. Por ejemplo:
Obsérvese que un byte sólo puede tener dos dígitos hexadecimales, pues FFh =
255d.
Es bien sencillo, tan sólo hay que fijarse que estar en base 2 significa que todos
los números son combinaciones lineales de potencias de 2 (igual que si estamos
en base decimal serían combinaciones lineales de potencias de 10).
39.5625 = 3x101+9x100+5x10-1+6x10-2+2x10-3+5x10-4
En binario tenemos:
39.5625 = 32+4+2+1+0.5+0.0625 =
1x25+0x24+0x23+1x22+1x21+1x20+1x2-1+0x2-2+0x2-3+1x2-4
39.5625d = 100111.1001b
39d = 100111b
Obsérvese, asimismo que este algoritmo es válido para cualquier base a la que
queramos convertir, utilizando su base para multiplicar.
Vamos a seguir las especificaciones dadas por el IEEE-754. Para guardar algo
tendremos que reservar espacio y esto influirá decisivamente en nuestra forma
de guardar el número binario decimal, si bien el método estructural es el mismo.
Ax2B
aEb
Veamos ahora cuál es la estructura para cada uno de los tipos de números reales:
El bit más significativo tanto de los 32 de real corto, como de los 64 del real
largo, como de los 80 del Real extendido está reservado para almacenar el signo
del número: 0 para positivo, 1 para negativo.
Los siguientes 8 bits del real corto, 11 para el real largo y 15 para el Real
extendido, están reservados para guardar el exponente del número, y para
trabajar con exponentes positivos y negativos, éste está discriminado por una
suma de 127 (27-1) en el caso de reales cortos, 1023 (210-1) para los reales largos
y 16383 (214-1) para los Reales extendidos.
El resto de los bits se reservan para la mantisa del número exponencial. Es
importante decir que el 1 de la parte real de la mantisa no se guarda puesto que
se asume que siempre es así. En caso del número real 0, toda la estructura que
comentamos tendrá todos sus bits a cero.
S EXP MANTISA
63 62 52 51 0
Tabla 01-10 - Real largo
S EXP MANTISA
79 78 64 63 0
Tabla 01-10 - Real extendido
El signo es positivo
La mantisa 001111001
Algunos ejemplos:
La parte entera:
101b=5d.
La parte decimal:
0*2-1+1*2-2+1*2-3 = 1/4d+1/8d=3/8d=0,375d
RealCM01 RealCJ01 RealCN01 RealCF01
Código Código Código Código
[bin] [bin] [bin] [bin]
Resultado en pantalla
>RealCF01
.0004998
Lo que hacemos en este ejemplo es ir desmenuzando cada parte del número real
guardado según las especificaciones anteriormente comentadas. Una vez que
tenemos la parte entera y la decimal, hay que convertirlas a ASCII para
imprimirlas.
x = 0 01111011 10011001100110011001100...
x' = 0 01111011 10011001100110011001100
x''= 0 01111011 10011001100110011001101 = 3DCCCCCD
x'' está más próximo a x que x', por lo que la representación de x será x''.
EEE MMMMM
3 5
Si el exponente y la mantisa tienen todos sus bits a 1, el número más grande que
podremos representar será 1,11111x27 = 11111100b = 252d.
Por otra parte usando el formato de 8 bits para guardar un entero, tenemos que
el número más pequeño que puede representar es 0 y el mayor es 28-1 = 255.
Para empezar, ya estamos viendo que el número de enteros que abarca es mayor
que el de coma flotante, si bien éste puede representar números decimales que
el otro no puede, claro.
Vamos a ver ahora una curiosa pérdida de datos mediante este método de
representar números en coma flotante. Si el mayor número representable es 252,
¿cuál es su inmediato anterior?. Sería 1,11110 x 27 = 11111000b = 248d.
Es decir que antes del 252 va el 248, ¡nos hemos comido el 249, 250, 251!, que
la representación entera sí posee.
El montante del error es mayor para los valores más altos, puesto que la pérdida
de precisión en la mantisa es amplificada por el exponente. Cuanto más pequeño
es el valor, menor es el error en la precisión.
1,00000 x 21 = 2d
Big Endian actúa justo al revés, es decir, el byte más alto de la estructura
multibyte se almacena en la dirección de memoria más pequeña y el byte más
bajo en la dirección más grande. La estructura multibyte anterior, se
almacenaría así:
Comentario
La distribución Little Endian la utilizan microprocesadores tales como el de Intel, y la del Big Endian
microprocesadores como los de Motorola, como los que usan los ordenadores "Apple". Obsérvese que con la
primera estructura, que va a ser la que nosotros utilizamos, vamos a ver que los pares de dígitos
hexadecimales que forman un byte se guardan en "orden inverso" al que posee la estructura multibyte. Esta
característica quizás nos desconcierte un poco al principio, pero enseguida se le coge el manejo.
No vamos a entrar aquí en el agrio debate que suscitan los pros y contras de
cada uno de ellos, en cambio vamos a ver un ejemplo ilustrativo de cómo se
guarda un entero de tipo LongInt (4 bytes) usando la distribución Little Endian.
Para ello vamos a utilizar el depurador AFD, que es sencillo de utilizar y
gratuito.
Vemos, pues, que el valor que toman los registros DX:AX = AB CD : EF 01,
que es justamente el valor de la variable miLongInt, además, vemos que su valor
almacenado en memoria es 01 EF CD AB, siguiendo el método Little Endian,
por tanto.
1.2. Registros
Dentro del procesador 8086 existen 14 registros de 16 bits, su misión es
almacenar datos o direcciones de memoria de forma temporal a lo largo del
programa. Hay que utilizarlos siempre que se pueda porque los accesos a
memoria son mucho más lentos que los accesos a los registros. Además, hay
ciertas operaciones que sólo se pueden realizar sobre los registros. Hay registros
que tienen características especiales, los hay que están especializados en una
determinada acción y no todos sirven para realizar algún proceso en concreto.
Por ejemplo:
AX, BX, CX, DX pueden utilizarse como registros de 16 bits o el byte superior
e inferior por separado como sendos registros de 8 bits. No hay ningún otro
registro con esta característica.
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
OF DF IF TF SF ZF AF PF CF
Tabla 01-12 - Estructura de la Palabra de Banderas
IN reg-acum, puerto
OFFSET = SEGMENT * 16
SEGMENT = OFFSET / 16 (perdemos los 4 bits inferiores)
dirección = segmento * 16 + desplazamiento
SEGMENT 0010010000010000----
OFFSET ----0100100000100010
Dirección de 20 bits 00101000100100100010
Por ejemplo, con DS:SI
====== DS ======
====== SI ======
Esta es la forma DS:SI con la que accedemos a 20 bits con dos registros de 16
bits. El segmento en DS y el desplazamiento en SI. Obsérvese que DS y SI se
solapan, esto implica que existe más de una forma de acceder a la misma
dirección de memoria, por ejemplo, 3D00h:0300h es equivalente a
3D30:0000h. Veamos por qué:
Puesto que la pila va a ser muy utilizada, tanto por el programador como por
operaciones internas, es muy necesario ir desapilando todo lo apilado para
evitar una pérdida de control sobre el ordenador por desbordamiento de ésta.
La rutina de BIOS que se inicia en FFFF0h verifica los diferentes puertos para
identificarlos e inicializa los dispositivos que están conectados a la
computadora. Después el BIOS establece dos áreas de datos. En la Fig. 01-06
vemos un mapa de la memoria convencional.
Codigo SEGMENT
MOV AX, Codigo ; Coloca en AX el valor del
segmento
Codigo ENDS ; donde se encuentra el código
Sin embargo, esta forma de utilizar el nombre del segmento de código sólo es
posible en programas de tipo .EXE, en programas de tipo .COM ni si quiera es
necesario, puesto que sólo cuenta con un segmento.
1.4.6.1. Alineación
Se utiliza para que el segmento comience en una dirección de memoria que sea
múltiplo de un valor determinado. Sus posibles valores son los siguientes:
Alineación Múltiplo de
BYTE 1
WORD 2
DWORD 4
PARA 16
PAGE 256
MEMPAGE 4096
Tabla 01-29 - Alineación
Obsérvese que el 8086 y 80286 tienen un bus de datos de 16 bits (una palabra),
por lo que trabajan más rápido si acceden a datos con tamaño de palabra.
Supongamos el valor 8A9Bh que se encuentra en la dirección de memoria 912-
913, el procesador los podrá recuperar en una sola pasada metiéndolos, por
ejemplo en el registro AX; sin embargo, si este mismo valor se encontrase en
911-912 tendría que recuperarlo en dos pasos: primero leería 910-911,
metiendo 9B en AL y luego leería 912-913 metiendo 8A en AH. Veámoslo
gráficamente:
912 913
9B 8A
AL AH
Tabla 01-30 - Primer caso
910 911 912 913
?? 8A 9B ??
AH AL
Tabla 01-31 - Segundo caso
Contamos con la directiva ALIGN para informar al ensamblador que nos alinee
elementos en límites. Por ejemplo "ALIGN 2" alinea en un límite de palabra.
De forma que en un programa con muchos datos resulta interesante su uso para
acelerar el proceso de carga. Se emplea inmediatamente antes del dato que
queramos alinear. Tenemos el código de ejemplo en "AlignC?1.asm".
Evidentemente con esta directiva se ocupa más espacio, compruébese
eliminándolo del código y volviendo a compilarlo; sin embargo, para una
colección de varias palabras, sólo necesitamos usarlo para la primera.
Especifica cómo debe combinarse el segmento con otros que tengan el mismo
nombre y que se encuentren en otros módulos del programa.
1.4.6.3. Clase
Ejemplos:
datos SEGMENT PARA PUBLIC 'data' ; Abre el segmento de
datos
msg db 'Hola, mundo!$' ; Mensaje a imprimir
datos ENDS
...
ASSUME CS: Codigo, DS: Datos
MOV AX, SEG datos
MOV DS, AX
datos1 SEGMENT PARA PUBLIC 'data' ; Abre el segmento
de datos
msg1 DB '¡Hola, $' ; Mensaje a imprimir
datos1 ENDS
datos2 SEGMENT PARA PUBLIC 'data' ; Abre el segmento
de datos
msg2 DB 'mundo!$' ; Mensaje a imprimir
datos2 ENDS
...
datos GROUP datos1, datos2
ASSUME CS: codigo, DS: datos
Los dos códigos fuente completos se pueden ver en el directorio Cap2 en los
archivos MundoXM1.asm y SegtosM1.asm. Ambos ejemplos tienen, a efectos
prácticos, el mismo resultado, lo veremos en el siguiente capítulo.
Codigo SEGMENT
ASSUME CS:Codigo
Los modelos Small, Medium, Compact, Large y Huge son los modelos de
memoria tradicionalmente reconocidos por la mayoría de lenguajes.
Símbolo Descripción
@code Devuelve el nombre del segmento de código
@CodeSize Devuelve un entero que representa la distancia por defecto al código
@CurSeg Devuelve el nombre del segmento actual
@data Expande hasta el DGROUP
@DataSize Devuelve un entero que representa la distancia por defecto a los datos
@fardata Devuelve el nombre del segmento definido por .FARDATA
@fardata? Devuelve el nombre del segmento definido por .FARDATA?
@Model Devuelve el modelo de memoria seleccionado
@stack Expande al DGROUP para pilas cercanas o al STACK para pilas lejanas
@WordSize Proporciona el atributo de tamaño del segmento actual
Tabla 01-33 - Símbolos de Segmento
Símbolo Descripción
@Cpu Contiene una máscara de bits especificando el modo de procesador
@Environ Devuelve valores de las variables de entorno durante el ensamblaje
@Interface
Contiene información acerca de los parámetros del lenguaje
Representa el texto equivalente del número de versión del MASM. P.e. para MASM 6.1., lo
@Version
expande a 610.
Tabla 01-34 - Símbolos de ámbito
Símbolo Descripción
@FileCur Nombre y extensión del fichero actual
@FileName Da el nombre del fichero principal que está siendo ensamblado
@Line Da el nº de línea del código fuente del fichero actual
Tabla 01-36 - Símbolos de Información de Fichero
Símbolo Descripción
@CatStr Devuelve la concatenación de dos cadenas
@InStr Devuelve la posición inicial de una cadena dentro de otra
@SizeStr Devuelve la longitud de una cadena
@SubStr Devuelve una subcadena dentro de otra
Tabla 01-37 - Símbolos de Manipulación de Macros
Aparte de los ficheros batch, sólo son los archivos .COM y .EXE, llamados así
porque esta es su extensión. La diferencia básica entre los dos es que los .COM
son una imagen exacta del programa cargado en memoria mientras que los
EXE, su estructura final en memoria se la da el DOS, y que los COM no pueden
superar los 64K de tamaño, mientras que los EXE no tienen esta restricción.
Los programas COM son más rápidamente cargados en memoria porque no
necesitan que el DOS les de su estructura y son más pequeños, pero esto es
inapreciable en los ordenadores actuales. Los ficheros COM son una reliquia
del pasado, cuando la memoria era escasa y los programas no pasaban de los 64
Kb y se han ido manteniendo por compatibilidad; si bien, para crear programas
residentes en memoria son muy adecuados y su modelo de memoria es parecido
al flat de windows.
En los 64 Kb que pueden tener los programas COM como máximo, se han de
incluir el código de programa, los datos y el stack. Esto tiene como
consecuencia que los registros de segmento durante el inicio del programa y
durante la ejecución del mismo apunten al inicio del semgneto de memoria de
64K que el DOS ha reservado antes de cargar el programa. En los programas
EXE, el código, datos y stack se guardan en segmentos diferentes que pueden
estar repartidos en diferentes segmentos dependiendo de su tamaño.
1.5.1. El PSP
Realmente es una reliquia del DOS, mantenido por compatibilidad. Contiene
numerosos datos que el DOS necesita para la gestión de un programa [bin],
aunque para un programador, la mayoría de esta información, carece de interés,
aunque sí hay algo de mucha utilidad con la que podremos gestionar los
parámetros de entrada en la línea de comandos. Veamos esquemáticamente la
estructura del PSP:
Dirección Contenido
00h-01h Llamada de la interrupción 20h
02h-03h Dirección final de la memoria ocupada por el programa. Por ejemplo A0000 se pone como 00A0h
04h Reservado
05h-09h FAR-CALL a la interrupción 21h
0Ah-0Dh Dirección de terminación (dirección del segmento para INT 22h)
0Eh-11h Dirección de salida de Ctrl+Break (dirección de segmento para INT 23h)
12h-15h Dirección de salida de error crítico (dirección de segmento para INT 24h)
16h-17h Segmento del PSP del proceso padre
18-2Bh Tabla de manejadores de archivo por omisión
2Ch-2Dh Dirección de segmento de las variables de entorno
2Eh-31h Reservado
32h-33h Longitud de la tabla de manejadores de archivo
34h-37h Apuntador lejano a la tabla de manejadores
38h-4Fh Reservado
50h-51h Llama a la función DOS (INT 21h y RETF)
52h-5Bh Reservado
5Ch-6Bh FCB #1
6Ch-7Fh FCB #2
80h Número de caracteres en la línea de comandos
81h-FFh Línea de comandos
Tabla 01-38 - Esquema del PSP
Como ya hemos dicho, un programa COM puede tener una longitud máxima de
64 Kb (65.536 bytes), de los que se ha de descontar la longitud del PSP (256
bytes) y al menos 1 word (2 bytes) para la pila, aunque es recomendable un
mínimo de 256 bytes.
Aunque la longitud de un programa COM nunca puede pasar los 64 Kb, el DOS
siempre reserva la memoria RAM completa para ellos, con lo que no se podría
llamar a otro programa con "EXEC"; esto se puede solucionar liberando en el
programa COM la memoria que no necesita, con ayuda de una función del DOS,
para su utilización.
Un programa COM se puede terminar con un RET ("near return"), que provoca
que el programa continúe en la dirección que está como valor superior en la
pila, donde el DOS colocó un cero antes de iniciar el programa COM, por tanto
saltamos a la dirección de memoria CS:0000; pero aquí se encuentra el inicio
del PSP donde está la interrrupción 20h, que termina la ejecución de un
programa. Sin embargo, lo más recomendable es terminar mediante la función
4Ch de la interrupción 21h, mediante la cuál podemos pasar además un mensaje
a su invocador en el registro AL, estableciendo el valor 0 como terminación de
programa normal.
Ya hemos dicho que un programa COM se guarda en disco como una imagen
directa del código máquina y además, el DOS no lo carga en RAM en una
dirección fija, sino en una divisible por 16, con lo que los segmentos del
programa son desconocidos hasta la propia ejecución del programa. Debido a
todo ello este tipo de programas no puede tener direcciones de segmento
explícitas, es decir, comandos FAR. Sólo se permiten los comandos NEAR, que
sólo contienen una dirección de offset, no de segmento. De esta forma, estos
comandos siempre se refieren al segmento actual en el registro CS, DS, ES o
SS, y no a una dirección de segmento determinada. Esto implica que no están
permitidos los saltos lejanos ni las llamdas lejanas, así como instrucciones LDS
o LES. Si se usa algo de esto, se podrá compilar, pero no se podrá pasar a
archivo COM. Sin embargo no está prohibido cargar valores de segmentos
constantes, como por ejemplo, la dirección de segmento de la RAM de vídeo
en un registro de segmento.
Veremos ahora cómo se puede crear un archivo de tipo COM usando la sintaxis
del MASM, NASM y FASM:
En la línea 11 provocamos que el compilador salte los primeros 100h bytes para
dejar espacio al PSP.
De esta forma, cada vez que abras el símbolo del sistema se abrirá en tu carpeta.
>EsqComM1 [INTRO]
Verás que no produce ningún resultado porque el programa no hace nada salvo
salir al DOS. Pero nos sirve para saber que lo hemos hecho todo bien, pues el
compilador no ha dado ningún mensaje de error.
Versión NASM
En la línea 1 tenemos org 100h , que sería equivalente a resb 100h , que ya
sabemos que deja espacio para el PSP.
Para ensamblar dicho código usaremos uno de estos dos métodos, el más
sencillo es el primero:
Lanzamos una ventana del "Símbolo del sistema" como ya sabemos y ponemos
lo siguiente:
C:\Trabajo\AOE\Codigos\Cap01>debug ESQCOMM1.COM
-u
0D85:0100 EB00 JMP 0102
0D85:0102 B8004C MOV AX,4C00
0D85:0105 CD21 INT 21
0D85:0107 7365 JNB 016E
0D85:0109 61 DB 61
0D85:010A 206361 AND [BP+DI+61],AH
0D85:010D 6D DB 6D
0D85:010E 62 DB 62
0D85:010F 69 DB 69
0D85:0110 61 DB 61
0D85:0111 7220 JB 0133
0D85:0113 61 DB 61
0D85:0114 6C DB 6C
0D85:0115 206469 AND [SI+69],AH
0D85:0118 7265 JB 017F
0D85:011A 63 DB 63
0D85:011B 746F JZ 018C
0D85:011D 7269 JB 0188
0D85:011F 6F DB 6F
-q
C:\Trabajo\AOE\Codigos\Cap01>
Aquí está todo nuestro programa. Lo único que hace es devolver la ejecución al
sistema operativo.
Vamos a dar a continuación los otros dos esqueletos posibles para construir un
archivo COM con MASM:
EsqCmM1b EsqComM2 EsqComM3 Resultado en pantalla
Código Código Código
[bin] [bin] [bin]
Source 01-03 - EsqCmM??.asm
EsqCmM1b.asm
Ya hemos dicho que los archivos COM no usan diferentes segmentos puesto
que todo está dentro del mismo y que tampoco se define una pila puesto que es
el propio DOS el que se encarga de definirsela al final del programa. Sin
embargo vemos en esta estructura que se definen diferentes segmentos y
además una pila. Pero todo esto es virtual porque al final todo ello se engloba
dentro del mismo segmento llamado grupo mediante la directiva de la línea 4:
Y luego, en la siguiente línea le decimos que asuma que todos los registros de
segmento apunten a nuestro segmento grupo.
EsqComM2.asm
.model tiny indica al compilador que el modelo de datos que vamos a usar va
a ser el más pequeño de todos, apropiado para archivos COM.
.code indica que aquí empieza el segmento de código y termina donde empieza
el siguiente.
EsqComM3.asm
Podemos ver la mezcla de sintaxis clásica con simplificada.
Evidentemente esto no se hace en un archivo COM puesto que como todo está
dentro de un rango de 64K (el mismo segmento), no es necesario.
Veremos ahora cómo se puede crear un archivo de tipo EXE con MASM,
NASM y FASM.
> ml EsqExeM1.asm
EsqExeM3.asm
EsqExeN1.asm
La única diferencia existente aquí con los otros esqueletos vistos es que se
indica dónde empiezan los segmentos pero no donde terminan, pues esto ocurre
donde empieza el siguiente segmento.
En la línea 11 tenemos resb 256 que equivale al código MASM "db 256 dup
(?)".
Después de definir el segmento de pila definimos una etiqueta "InicioPila:" que
está situada en la cima de la pila y nos sirve para indicar donde se va a situar el
registro SP.
Alink es un linkador gratuito y necesitamos que esté también dentro del PATH.
EsqExeF1.asm
Para ensamblar dicho código usaremos uno de estos dos métodos, el más
sencillo es el primero:
También podemos ver claramente las etiquetas con las que definimos la pila y
que ahora nos muestran ésta muy visualmente.
C:\Trabajo\AOE\Codigos\Cap01>debug EjPilaN1.exe
-d 100
0D18:0100 3C 2D 20 41 71 75 69 20-65 6D 70 69 65 7A 61 20 <-
Aqui empieza
0D18:0110 6C 61 20 70 69 6C 61 20-79 20 20 61 71 75 69 20 la
pila y aqui
0D18:0120 74 65 72 6D 69 6E 61 20-00 00 00 00 00 00 00 00
termina ........
0D18:0130 B8 28 0D 8E D0 BC 2A 00-B8 28 0D 8E D8 B4 4C CD
.(....*..(....L.
0D18:0140 21 C4 12 F3 A4 1F 03 E2-5F 5E 5A 59 5B 89 26 C2
!......._^ZY[.&.
0D18:0150 12 2E 8E 16 40 05 8B E0-A1 04 15 C3 59 8B 0E EE
....@.......Y...
0D18:0160 14 8B D1 C1 E1 04 C1 EA-0C E8 61 1F 73 20 A3 D4
..........a.s ..
0D18:0170 14 C1 E9 04 8A C2 C0 E0-04 0A E8 C1 EA 04 74 03
..............t.
-q
Vamos a utilizar aquí el editor hexadecimal gratuíto "HexIT" que posee una
opción que nos va a ofrecer una información muy valiosa respecto a la cabecera
de nuestro archivo "EXE":
Fig. 01-15 - Cabecera de Archivo EXE
Como vemos en el recuadro de fondo azul, tras pulsar la tecla "F6", nos da
información de todo el contenido de la cabecera del programa EXE.
>cv EjPilaN1.exe
Los dos primeros bytes contienen una llamada a la interrupción 20h: "CD 20",
que produce un retorno al DOS.
En el byte 80h nos indica el número de bytes que hemos ingresado por línea de
comandos sin contar el retorno de carro: "0C", es decir, 12 en decimal.
Nasm reserva espacio con la directiva RESB, Fasm lo hace con RB o también
con ?, por ejemplo:
Var1 DB ?
Var2 RB 1
En el coprocesador matemático, NASM define el primer registro con ST0,
mientras que FASM lo hace con ST
Fasm no define espacios especiales con etiquetas como ".bss". En el caso de
variables sin inicializar lo hace como ya hemos indicado anteriormente.
Fasm tiene su propio lenguaje de macros