Manual Ensamblador (#1) By Viktor SalEm

Iniciado por ProcessKill, Febrero 24, 2010, 04:01:27 PM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Febrero 24, 2010, 04:01:27 PM Ultima modificación: Febrero 28, 2021, 11:55:07 PM por DtxdF
Bueno, creo que finalmente me decidí a hacer esto, después de pensar, "Cómo comenzar este foro" creo que me he decidido por este señor desconocido y temido. El ensamblador.

Vamos a ver por donde comienzo.... hace no mucho recuerdo que estaba explicando la teoría de los binarios, el por que los sistemas usaban el método binario y no otro sistema más acorde a nosotros como los decimales. Bueno, creo que podría resumir un poco de nuevo esto.

El sistema en el cual trabajan los computadores es el de binarios (0 y 1) por la simple razón de que para un computador es más fácil notar la ausencia o la falta de energía que notar una serie de parámetros... es como decir que en dos personas es más fácil distinguir si una luz está encendida o apagada a distinguir el color real de esa luz.

Ahora, como el binario cuenta solo con dos cifras en lugar de 10 como el sistema decimal, podremos decir que el primer dígito de más a la derecha, valdrá su valor multiplicado por 1, (dos elevado a la cero así como en decimales es 10 elevado a la cero), el siguiente dígito será dos elevado a la uno y asi ir valiendo de manera distinta, 2x2, 2x2x2, 2x2x2x2... etc. Un par de ejemplos.

1011 = 1x8 + 0x4 + 1x2 +1x1 = 8+2+1 = 11 decimal

Un poco más facil para los que no la hayan cogido. Los numeros en paréntesis veanlo como una potencia.

1011 = 1(3) 0(2) 1(1) 1(0)

1x8    el 1 por ser el primer dígito de 1011 y 8 por que son 1(3) = 1 (2x2x2) = 1(8)

0x4    el 0 por ser el segundo digito de 1011 y 4 por que es 0(2) = (2x2) = 0(4)

1x2    el 1 por ser el tercer digito de 1011 y el 2 por que es 1(1) = (2) = 1(2)

1x1    el 1 por ser el cuarto dígito de 1011 y el 1 por que es 1(0) y si nos vamos a las reglas de mate todo numero tiene como cociente 1... bla bla bla, se acuerdan de ese grafico que tenia 1 por todos lados? bueno pues por eso ese elevado a la 0 se vuelve 1.


vale otro ejemplo:

1100 = 1x8 + 1x4 + 0x2 + 0x1 = 8 + 4 = 12


si aplicamos esta regla veremos que si la cifra de la derecha vale 1, al movernos a la izquierda este valor se duplica sobre si mismo, así que por cada cifra que se mueva hacia la derecha sucederá esto:

1 - 2 - 4 - 8 - 16 - 32 - 64 - 128 - 512 - 1024 - 2048 - 4096 - 8192 - 16348 ..... así sucesivamente.


Ahora que vimos un sistema con muchos menos símbolos que lo habitual, veremos otro sistema que tiene más, el hexadecimal tiene 16 símbolos. los números del 0 al 9 y las letras de la A a la F, el sistema para traducir de hexadecimales a decimales es el mismo que lo anterior (Es matemática modular para el que quiera saberlo) solo que en vez de trabajar sobre 2 trabajaremos sobre 16 (la totalidad de los carácteres posibles, es lo mismo que se hace al traducir las direcciones IP que están en base de 256, la base sería 256 y si fueran decimales sería en base de 10) así que el número que esté más a la derecha será multiplicado por 1

Por fin sirve la calculadora de Windows xD!

E07F = 14x4096 + 0x256 + 7x16 + 15x1 = 57344 + 112 + 15 = 57471 en decimales

Una ventaja entre binarios y hexadecimales (y no dudo que por eso es que los dos son usados al programas ensamblador) es que hace fácil la traducción entre ambos, por que cada dos cifras hexadecimales corresponden a un byte (una cadena de 8 bits, u 8 unidades binarias) haciendo un pasaje muy limpio de un lado a otro. Lo demuestro:

0001 = 1
0010 = 2
0011 = 3
0100 = 4
0101 = 5
0110 = 6
0111 = 7
1000 = 8
1001 = 9
1010 = A
1011 = B
1100 = C
1101 = D
1110 = E
1111 = F

De esta forma, tomando el ejemplo anterior, pasado de hexadecimales a binarios E07F = 1110 0000 0111 1111


Es por esto que se usarán estos sistemas, de hecho, el hexadecimal se usará bastante para hacer referencia de memoria o valores, para el ensamblador se usa una h o una b al final para destacar si son hexadecimales o binarios, de esta manera:

E07Fh = 1110000001111111b


Negativos

Con los binarios tenemos un pequeño problema y es que no sabemos representar números negativos, en base de esto se han ideado una serie de posibles soluciones en la cual, la forma más sencilla es usar un símbolo de magnitud.

En este caso, la primera cifra hará que el resto tome valor de positivo o negativo, por eso es que se usa el bit más significativo, es decir, el que se encuentra más hacia la izquierda como simbolo de magnitud. teniendo de esta manera dos formas de representar un número, por ejemplo. (0)00000000 y (1)00000000 lo cual nos dará valores distintos.


10000010b = -2 decimal

00000010b = 2 decimal

10000111b = -7 decimal

00000111b = 7 decimal


El problema es que no se pueden realizar restas con facilidad.



Complementos

Los complementos se ha ideado precisamente para facilitar estas funciones, está el complemento 1 y el complemento 2

En caso del complemento 1, lo que se hace básicamente es invertir los bits que lo forman excepto el primero, que será siendo 1 positivo, 0 negativo.. de esta manera:

11111110 = -1

00000001 = 1 

11111000 = -7

00000111 = 7

como vemos, simplemente se le dieron la vuelta a las ultimas 7 cifras del numero pero conservando el signo de magnitud.

esto lastimosamente trae consigo un problema al hacer operaciones, por que si sumamos +1 y -1 (11111110) y (00000001) el resultado es 11111111b y no 00000000b para ello se considera a todo 11111..... como otra forma de 0, pero tener dos representaciones para el 0 es un problema aritmético.


El complemento 2, es el más aceptado es una mejora al complemento 1, en este caso, se invierten todos los bits que lo forman menos el de la izquierda que marca el signo, y en caso de hacerlo positivo o negativo, sumandole 1 y restandole 1... es un poco más difícil de comprender pero es solo mientras agarramos el tiro:

11111101 = -3 decimal

00000011 = 3 decimal






Representación

En la práctica esto es un poco más complejo, está la representación binaria común, a la que simplemente se le agrega la b al final, la representación hexadecimal común del formato de Intel, la cual inicia con "0" para indicarle al compilador que se trata de un valor numérico y después escribir el hexadecimal con la h al final, de esta forma 0E07Fh. También tenemos la representación hexadecimal alternativa, que es el formato AT&T, en este caso no se escribe una h al final si no que simplemente se coloca un "0x" así el 0E07Fh se escribiría como 0xE07F. Al final estas representaciones dependen totalmente del compilador.


Nota: Antes de continuar, ten en cuenta que debes conocer las especificaciones del procesador de tu sistema, un procesador CISC no actúa igual que un procesador RISC, con RISC no tenemos más que dos saltos, en cambio con CISC (la de los computadores normales) utilizan el lenguaje ensamblador 80x86, en este caso por ejemplo, el AMD de Intel tiene 16 tipos de contenido de saltos y puede extraer comparaciones como "mayor que", "mayor o igual que", "menor o igual que" "menor que" "igual que" etc.
Creo que lo he dicho varias veces, el intentar facilitar las cosas para el hombre ha hecho que los CISC tengan problemas frente a los RISC, así que de momento, enfocados en CISC que es el más común.

Formas de Almacenamiento

También tenemos que tener en cuenta que existen dos maneras de almacenar los resultados, Big Endian y Little Endian.

Big Endian se almacenan los datos tal cual, es decir si yo guardo en la memoria el valor 124679AFh, en la zona quedará guardado como ??,12,36,79,AF,??

Little Endian (Usado en portátiles) es distinto, los valores son almacenados al revés, por eso esta misma cifra, 124679AFh será almacenada como ??,AF,79,46,12,?? esto normalmente no nos va a afectar por que las instrucciones hacen por si mismas las conversiones, pero hay que tenerlo en cuenta cuando si queremos acceder por ejemplo, a un byte en particular y no a todo el conjunto.


Registros de los 80x86

En las arquitecturas tipo 80x86, tenemos una serie de registros comunes compartidos por los fabricantes, con algunos de ellos se pueden realizar operaciones aritméticas, movimientos de memoria, etc etc etc, estos registros son:

EAX: Normalmente se le conoce como "Acumulador" ya que es aquí donde se situarán los resultados de otras operaciones, su tamaño es de 32 bits, se puede dividir en dos sub-registros de 16 bits cada uno y el menos significativo de los dos (es decir, el de la derecha) se puede dividir en dos sub-sub-registros de 8 bits cada uno, para acceder a ellos:

EAX = toma todo el valor de 0 a 31 bits
AX = toma el valor del bit 0 al bit 15
AL = toma el valor del bit 0 al bit 7
AH = toma el valor del bit 8 al bit 15


EBX: Sucede lo mismo que con EAX, se accede a sus datos de esta forma:

EBX = toma todo el valor de 0 a 31 bits
BX = toma el valor del bit 0 al bit 15
BL = toma el valor del bit 0 al bit 7
BH = toma el valor del bit 8 al bit 15

ECX: Este registro tiene las mismas reglas que EAX y EBX, la diferencia es que tiene la función especial de servir como contador de bucles y hacer operaciones con cadenas. Se accede de esta forma:

ECX = toma todo el valor de 0 a 31 bits
CX = toma el valor del bit 0 al bit 15
CL = toma el valor del bit 0 al bit 7
CH = toma el valor del bit 8 al bit 15

EDX: Tiene las mismas reglas que EAX y EBX, la diferencia radica en que aquí es donde se guardarán parte de los resultados de algunas operaciones como multiplicación y división. Aparte, este registro es llamado "Puntero de E/S" ya que tiene funciones de acceso directo a puertos.

EDX = toma todo el valor de 0 a 31 bits
DX = toma el valor del bit 0 al bit 15
DL = toma el valor del bit 0 al bit 7
DH = toma el valor del bit 8 al bit 15

EBP Es un registro de 32 bits, que solo tiene un sub-registro de 16 bits llamado BP. Se representa de esta manera:

EBP = toma todo el valor de 0 a 31 bits
BP = toma el valor del bit 0 al bit 15

ESI: Es un registro de 32 bits más específico, al igual que EBP tiene un sub-registro "SI" de 16 bits, pero no se divide en sub-sub-registros de 8 bits, ESI tiene la propiedad de que nos servirá en algunas funciones como LODSX, MOVSX, y SCASX, que veremos más adelante (Cabe destacar que ESI es operando de origen siempre). Se accede de esta forma:

ESI = toma todo el valor de 0 a 31 bits
SI = toma el valor del bit 0 al bit 15

EDI Tiene las mismas reglas básicas hasta que llegamos a los operandos con otras funciones, ESI siempre será el operador de origen y EDI el operador de destino. Se accede de esta forma:

EDI = toma todo el valor de 0 a 31 bits
DI = toma el valor del bit 0 al bit 15

EIP: Este es importante, es el "Contador de Programa" y no puede ser accedido mediante métodos normales, se almacena a la dirección de la próxima instrucción que se ejecutará en el procesador, Existe también una sub división IP, con los 16 bits de la derecha pero no se tendrá en cuenta ya que en sistemas operativos cono Windows y Linux se usará siempre la combinación

CS:EIP para determinar lo que hay que ejecutar, solo en SI viejos como MS-DOS se usa es

CS:IP para realizar estas acciones.

ESP: Es un registro de pila que indica la dirección a la cual se apunta.

Registros de segmento: Los registros de segmento son registros de 16 bits que se anteponen a los anteriores para formar una dirección completa, hay que tener en cuenta que sea cual sea el caso estamos hablado de direcciones virtuales así que cuando el procesador interpreta un segmento no está operando con nada, combinaciones de un segmento (Como en el caso de EIP) pueden ser [segmento]:[operando] CS:EIP. La función de estos segmentos es de separar datos de código, zonas de acceso restringido... etc, así los dos últimos registros de segmento en Windows indican el ring con el que procesador está corriendo, ring3 en Windows, es con permisos de usuario y se representa con 11... ring0 es administrador con lo cual los últimos registros serán "00" que indican que está corriendo como Administrador.

CS: Es el registro de segmento de ejecución, por lo tanto un registro como CS:EIP indica la dirección completa que se está ejecutando (En otras palabras se coloca CS, refiriéndonos a que estamos apuntando a la dirección EIP en el segmento CS)

SS: Es el registro de segmento de pila y tal como sucede con CS:EIP, la pila estará siendo apuntada por SS:ESP

DS: Normalmente es el registro de datos, tomando como ejemplo la operación ADD (sumar) vemos que una posible forma de usarla es la de tener "ADD EAX,DS:[EBX]" que añadiría al registro EAX el contenido de la dirección del segmento DS y la dirección EBX.

ES, FS y GS: Son segmentos que apuntan a distintos segmentos de datos.


Nota: También están los flags y los registros de coprocesador, pero probablemente en este primer capítulo no vamos a verlos... de echo, los de coprocesador es difícil que los veamos, en si son 8 registros de 80 bits que contienen números representados en una flotante llamados R0..R7, el GDTR e IDTR, registros de control CR0, CR1, CR2,CR3 y CR4.. y algunos otros más, pero es difícil que los usemos, no solo en este manual, de por si, es difícil que los usemos.

EL MOV Y LOS ACCESOS A MEMORIA

Bueno, ahora si comenzamos en serio, MOV es una de las instrucciones más importantes del ensamblador, su función es la transferencia de información, esa transferencia puede darte de un registro a un registro o de un registro a una memoria.. nunca, jamás de memoria a memoria.. también se transfiere información entre valores inmediatos teniendo como destino la memoria o un registro, para ello se tendrán dos operandos, el Primero es el de DESTINO y el Segundo es el que ORIGEN... vale apréndanse eso, el primero es y siempre será el DESTINO y el segundo el ORIGEN... ejemplo:

MOV EAX, EBX

OJO, a pesar de que MOV se parezca tanto a mover, lo que hace es copiar, es decir EBX seguirá valiendo lo mismo que valía antes, EAX si cambia de valor pero EBX no.

Ya, explicado esto, las opciones de trabajar con MOV son las siguientes:

MOV registro1, registro2: Como por ejemplo MOV EAX, EBX que copia el contenido del registro2 en el registro1

MOV registro, valor inmediato(imm): En este caso se le asigna un valor inmediato a cierto registro, por ejemplo MOV EBX, 1479A7F4h asigna directamente ese valor hexadecimal al registro EBX

MOV registro, memoria(mem): Aquí se transfiere el contenido de un registro a una posición en la memoria, la cual se encierra en corchetes, se puede representar la dirección exacta de la memoria o usar un valor de registro encerrado en corchetes, lo que está entre corchetes es una referencia directa a la memoria y cuando es procesada la instrucción el registro toma el valor de la memoria, por ejemplo MOV EDX, [FFFFEDFTh] este es un acceso directo a una dirección en la memoria, en caso de que la dirección esté en un registro se puede hacer así MOV EDX, [EAX]

También hay una variante que usa el valor que existe en un registro más un desplazamiento, así que dentro de los corchetes se le señala una dirección y en base de esto se le suma o se le resta una cantidad. De esta manera MOV ECX,[EBX+25]
De acuerdo a la variante anterior, también podemos unir varios registros, de esta manera. MOV EAX,[EBP+ESI+12]

MOV memoria(mem), registro(reg): Con las mismas reglas que el MOV reg, mem pero al revés, en este caso podemos decir que cogemos un registro y lo copiamos a la memoria, con las mismas reglas pero al contrario. Ejemplo MOV [78FEAA8Dh], EDI

MOV memoria(mem), valor inmediato(imm): El valor inmediato se copia a una posición de la memoria. Ejemplo MOV [EBP], 1357h

El MOV no termina aquí, el ejemplo anterior daría un fallo al compilador ya que no se le está indicando el tamaño, como vemos un EBP tiene 32 bits mientras que la cifra hexadecimal 1257h es de 16 bits (recuerden que cada cifra vale 4 bits), entonces tenemos que especificarle al compilador que escriba los 16 bits que corresponden a EBP, o escribir los 32 bits en la serie hexadecimal. 00001357h

Para solucionar este problema, cuando existen instrucciones dudosas como estas, (esto también varía según el compilador) tenemos el word, dword y el byte en este caso, Word le da un valor de 32 bits, dword le da un valor de 16 bits y byte le da un valor de 8 bits. Ejemplo

MOV dword [EBP], 1357h

MOV word [EBP], 1357h

Finalizando con esto, también tenemos que ver los segmentos, en si no tiene perdida ya que si escribimos en un compilador MOV DS:[EAX], EBX el compilador quitará el DS para ahorrar espacio, ya que actúa de esa manera por defecto, pero nosotros podemos elegir el segmento a usar, no olvidemos colocar los ":" al finalizar el segmento.

Solución de operandos con tamaños distintos

Vale, supongamos que tenemos un BL (16 bits) y quiero moverlo a EAX, puedo simplemente colocar MOV EAX, BL? no, dará un error al compilador por que los tamaños son diferentes para eso existen el MOVZX y el MOVSX

En el MOVZX se realiza la operación añadiendo ceros al operando de destino, es decir, para el ejemplo del caso anterior, suponiendo que BL valga 4578h EAX quedará valiendo 00004578h... bueno y que pasa si BL vale menos? Por ejempl 12h? pues EAX quedará valiendo 00000012h por que el resto se ha rellenado con 0

En el MOVSX se realiza la misma operación solo que el valor puede ser tanto de ceros como de unos (0,1) dependiendo del bit más significativo, es decir, volviendo al ejemplo anterior, si BL vale 10000000b (80h) se va a rellenar con unos el EAX, y en este caso EAX quedará valiendo FFFFFF80h, si el bit más significativo no es 1, por ejemplo que BL valga 01000000b (40h) se va a rellenar con ceros, y EAX tomará el valor de 00000040h.

Operaciones lógicas en Binarios

Las operaciones dentro de los registros se dividen en dos, las lógicas y las aritméticas, las aritméticas debemos tenerla en la cabeza por que las tratamos cada día (Suma, resta, multiplicación, división) y las lógicas... bueno, esas operan a nivel de bit, así que pueden pegarnos un poquito duro jeje, las operaciones lógicas son AND, OR, XORNOT

AND: El AND realiza un bit a bit y da como resultado 1 si LOS DOS BITS CON LOS QUE SE OPERAN SON 1, en caso contrario da 0:

11000101
10110001
-------------
10000001

OR: El OR realiza un bit a bit y da como resultado 1 si ALGUNO DE LOS DOS BITS CON LOS QUE SE OPERAN ES 1, si los dos bits son 0 da como resultado 0:

11000101
10110001
-------------
11110101

XOR: El XOR realiza un bit a bit y da como resultado 1 si SI SOLO UNO DE LOS BITS CON LOS QUE SE OPERAN VALE 1, de lo contrario vale 0:

11000101
10110001
-------------
01110100

NOT: El NOT simplemente invierte los BITS de un OPERANDO, así que solo necesita de uno por que si se colocan DOS OPERANDOS, creará un error. Ejemplo.

10110001
-------------
01001110


***Pequeña interrupción para mensaje personal, solo le interesa a "Eldiabloxico"***

Mirando esto se me pasó por la mente lo de Spinozza... Diablo, tu crees que si este tío hubiera sabido estas reglas de binarios habría logrado analizar al hombre y hacer la fórmula matemática para saber las reacciones y pensamientos de cada persona???

*****Fin de la interrupción*****

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta - [Caído]

Leido y entendido, gracias por el aporte :)

uuhy Mucho Texto, Pero Buena informacion. Buen Aporte.

Salu2!!.