domingo, 13 de abril de 2008

EXPLICANDO ASSEMBLER

Vamos a empezar por algo que quizás muchos ya sepan, pero como es indispensable, igual se lo tengo que contar:


La Memoria de Las PC:


Como ya sabrán, de haber escuchado por ahí, hay varios tipos de memoria, la Memoria Base, la Memoria Expandida (EMS), Memoria Extendida (XMS), Upper Memory Blocks (UMB), High Memory Area (HMA) y un montón mas de referencias a distintos tipos de memoria, lo que les voy a contar es que dirección de memoria le corresponde a un Byte dentro del primer MegaByte, es decir, dentro de la memoria base (0-640K) y en la zona donde están las plaquetas de expansión, Monitor, BIOS ROM, área de la controladora de Disco, etc...


Tratemos de acostumbrarnos a ver los números en Hexadecimal (base 16). Los dígitos que pueden formar un numero en esta base, son: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E y F, tomando valores en ese mismo orden, el 0 vale 0 y la F vale 15.


De esta forma un numero, por ejemplo 0FA3h (la 'h' final indica que esta en 'Hexa'), expresado en decimal valdría (3)+(Ah*16)+(Fh*16*16)+(0*16*16*16), o lo que es lo mismo 3+160+3840+0=4003, pongámonos de acuerdo que cuando un número lo escribimos en Haxa le ponemos la 'h' al final, y si comienza con una letra, le agregamos un '0' adelante para no confundirnos con un nombre de variable.


Esto fue una breve pero necesaria introducción al sistema Hexadecimal de numeración.Ahora si, veamos como se direcciona la memoria de una PC.


Tengamos en cuenta que si queremos direccionar 1 Mb nos hacen falta 5 dígitos hexadecimales, es decir, poder escribir, de alguna forma 0FFFFFh o 100000h-1 que es 1*16^5=1 Mb (1 que se resta, corresponde al byte #0). Como los 80x86 tienen registros de 16 bits (8 bits = 1 Byte, 16 bits=1 Word) o 4 dígitos hexa, solo podríamos llegar a direccionar 0FFFFh bytes, lo que significan 64 Kb solamente (de aquí la limitación de los .COMs).


Lo que se hace es utilizar dos registros a la vez para apuntar a un solo byte, lo que nos amplia el campo a poder diferenciar 0FFFFh:0FFFFh bytes o 1 Mg de Mb muchísimo! Entonces, ¿Que pasa que no se puede tener tanta memoria sin hacer cosas extrañas? Claro, lo que pasa es que para direccionar se usan dos registros, pero no ubicados uno al lado del otro, sino desplazados un dígito. ¿Que? Si, ya se que esta confuso, ahora te cuento:En un Registro, digamos que se llama DS, esta 0040h, y en otro, digamos BS, hay 006Ch.


La dirección a la que apunta DS:BS (así se escribe) es 0040:006C, pero esta dirección, en números de 5 cifras se escribe 0046C, la conversión es simplísima, lo que hay que hacer es sumar los registros de la siguiente forma:BS -> 006CDS -> + 0040---------------DS:BS -> 0046Ch De esta suma, uno se puede dar cuenta que para referirse a una misma posición de memoria puede haber muchas formas distintas. Por ejemplo, para referirse a 0046C, se puede hacer: 000C 046C+0046 +0000------- --------0046C = 0046:000C 0046C=0000:046C 023C 005C+0023 +003F-------- --------0046C = 0023:023C 0046C = 003F:005CEs decir, puede haber muchas formas de nombrar el mismo byte, y tenemos que tratar de acostumbrarnos a darnos cuenta que, por ejemplo, 0F000:1234 y 0F123:0004 son equivalentes.


Tenemos que una dirección de memoria se separa en dos partes, la de la izquierda del : es el famoso SEGMENT o segmento y lo de la derecha es el OFFSET o desplazamiento, de una dirección.Conclusión: Para referirnos a cualquier Posición de Memoria es necesario conocer su Segment y su Offset, ya que sabiendo uno solo, nos podemos estar refiriendo a muchos bytes distintos.


Muy bien, ahora ya sabemos que la máquina tiene memoria y también sabemos como apuntar a una posición en esta memoria, pero bueno, este curso es de Assembler, no de memorias, por lo tanto, lo interesante y apropiado sería que les comentara, así como si de pasada fuera, como se hace en Assembler para cargar o grabar algo desde o hacia la memoria. Pero, primero hay que saber otras cositas... No es tanfácil la cosa... Al "hablar" en Assembler, estamos diciéndole a la máquina en lo mas cercano a su idioma posible (ya que su verdadero idioma es de "1" y "0" totalmente) lo que tiene que hacer.


Estamos hablando directamente, nosotros mismos, sin la ayuda de un traductor (compilador), con el cerebro propiamente dicho de la máquina. Estamos hablando con el vendito 80x86. Entonces para decirle algo tenemos que conocerlo un poco mas. Como es muy probable que muchos tengan 8086/88 o 80286vamos a ver especialmente, la estructura interna de estos "micros" (microprocesadores), por arriba, solo lo necesario para programar en assembler, obviamente también sirve para los iluminados que tengan 386 o 486, o quizás 586 o P-5. Internamente todos los micros (entiendo que todos), tiene unas pocas variables, llamadas REGISTROS que son en donde se procesan los datos, se hacen operaciones; son por las cuales se puede acceder a memoria, etc. Sirven para todo. Pero no todas sirven para lo mismo, y aunque muchas si sean intercambiables, todas están destinadas para algo en especial, y tienen una que otra función propia. Los registros de la familia 80x86 son:

Flags Señalizadores de Estado:

Todos estos registros son de 16 bits, es decir de un Word. Pero los registros que terminan en X (AX, BX, CX, DX) pueden ser manejados, también, como si fueran dos Bytes (Que lo son), por separado, sus nombres son, AH para el Byte mas significativo de AX y AL para el menos significativo, BH y BL para BX, CH y CL para CX y DH y DL para DX (la H y la L hacen referencia a "Hi" y "Lo", es decir Alto y Bajo, alto es sinónimo de mas significativo y bajo de menos. Si por ejemplo:

AX = 437Ah Entonces AH = 43h y AL = 7Ah

BX = 0145h BH = 01h y BL = 45h

CX = 0AABBh CH = 0AAh y CL = 0BBh

DX = 1h DH = 00h y DL = 01h

De igual manera, si:

AL = 10h y AH = 32h AX = 3210h

BL = 08h y BH = 0CAh BX = 0CA08h

CL = 1h y CH=1h CX = 0101h

DL = 00 y DX=00 DX = 0000h

Estos cuatro son los únicos segmentos que se pueden separar en byte alto y byte bajo.

Funciones especificas:

AX, Acumulator: Sirve para hacer todas las operaciones aritméticas, y algunas, como Multiplicar y Dividir, le son exclusivas. AX (y AL por su versión de un solo byte) son los únicos registros que pueden ser multiplicados y divididos por otro registro. La resta de AX y AL, por ejemplo, ocupan un byte menos que la de cualquier otro registro, pero esto no es para preocuparse, un byte, realmente no es nada, por mas que se acumule.

BX, Base Index: Sirve para ser usado como registro de base para un índice o array, es decir, una posición de memoria puede ser apuntada por BX (su offset), igualmente también se lo puede usar para hacer sumas restas y todo tipo de operaciones lógicas. AX, CX y DX no sirven para apuntar a memoria.

CX, Counter: Es el registro reservado para contar, como su nombre lo indica. Para este propósito hay órdenes especiales que lo decrementan o incrementan y hacen algo según el resultado. También hay ciertas órdenes repetitivas que necesitan saber cuanto repetirse, por medio de CX (o CL en su versión Byte) se les indica.

DX, Data: Este registro no tiene definido un uso, en general es utilizado para pasar ciertos parámetros, pero si cumple una función, por ejemplo en la multiplicación, si se multiplica AX=1000h (un Word) por, simplemente 10h (un Byte) el resultado es 00010000h (Un DWord), entonces, el Word Alto del resultado de la multiplicación se deposita en DX, y el Bajo en AX.

SI, Source Index: Puede ser utilizado como índice a posiciones de memoria, es decir se puede poner un número en SI (Offset) y leer el dato de esta posición. Pero a la vez tiene una función específica, la de Registro Fuente para las órdenes de tratamiento de cadenas. Hay ciertas órdenes en Assembler que son para, por ejemplo mover toda una cadena de bytes, de un lugar a otro, la dirección de la cual se leen los bytes se pone en SI antes de decir que lea.

DI, Destination Index: Como SI, puede ser usado como índice. Pero su función específica es la de Registro de Destino para las operaciones de cadena, lo que se lee de el contenido de SI (no de SI mismo, sino de la posición de memoria a la que apunta SI) es depositado en la posición de memoria a la que apunta DI, expresada por [DI]. Al la vez, igual que con SI, se pueden hacer operaciones aritméticas simples (suma y resta), y también todo tipo de operaciones lógicas (AND, OR, XOR).

BP, Base Pointer: Puntero a una posición de memoria, muy parecido a BX, pero generalmente usado para facilitar el pasaje de parámetros en funciones hechas con lenguajes de alto nivel, por una característica propia que ya voy a explicar.

SP, Stack Pointer: Puntero que indica en que Offset termina el Stack, o pila. El Stack, es un área de la memoria principal de la máquina, (no esta dentro del Micro, ni tampoco es fija) que sirve para preservar cosas, la estructura del Stack, que ya explicare mas a fondo, es simple, esta estructura es llamada LIFO (Last In First Out) o lo que es lo mismo, lo último que entra, es lo primero que sale, es como si tuviéramos una pila de cosas, lo último que apoyamos arriba va a ser lo primero que podamos sacar después. Si no esta claro, no se preocupen, ya voy a explicarlo bien, y voy a decir para que se usa, y cuando.

IP, Instruction Pointer: El puntero de instrucción es el que le indica al Micro cual va a ser la próxima instrucción que debe ejecutar (Solo el Offset). El programa en Assembler tiene una estructura lógica, la cual se puede seguir. IP comienza al principio del programa (la próxima orden que se debe ejecutar es la primera del programa), se ejecuta esa orden e IP es incrementado tanto como Bytes ocupe la orden recién ejecutada (no todas las órdenes ocupan un byte) luego sigue con la próxima y así sucesivamente. Si pudiéramos de alguna forma cambiar el contenido de IP lo que estaríamos haciendo seria una desviación, o un Jump (salto) a otro lado del programa, y efectivamente se puede hacer esto, pero no diciendo IP = 1234h, sino haciendo un salto, que es equivalente a esto último: JMP 1234h. Ya lo voy a explicar, esto también.

Se habrán dado cuenta, que siempre que dije que apuntaba a una posición de memoria, hice notar que solo era el Offset lo que estaba comprendido, por ejemplo en DI o SI, BX o SP. Pero entonces, como es posible que con solamente el offset alcance para identificar una posición de memoria? si yo mismo dije:

"Para referirnos a cualquier posición de memoria es necesario conocer su Segment y su Offset, ya que sabiendo uno solo, nos podemos estar refiriendo a muchos bytes distintos."

2 comentarios:

Anónimo dijo...

Debe ser mas especifico en la definicion de sus conceptos, ya que apaerece mucha teoria que en si no dice nada sobre el tema que se tiene que tratar.

Julian Cardenas
Fernando Perez
Lina Cuervo

Fabian Camilo Molina dijo...

buen trabajo, coloca mas ejemplos y un poco mas de graficos, de todas maneras se nota el esfuerzo.