Mostrando entradas con la etiqueta compilador. Mostrar todas las entradas
Mostrando entradas con la etiqueta compilador. Mostrar todas las entradas

martes, 17 de agosto de 2021

Introduccion a Arquitectura x86 y x64


CPU

Se encarga de procesar todas las instrucciones del dispositivo, que son interpretadas en lenguaje maquina (binarios), para facilitar el entendimiento de estas instrucciones es necesario traducirlo a un lenguaje mnemotécnico llamado Assembly (ASM).

 

Los assembler más populares son:

    NASM (Netwide Assembler).

    MASM (Microsoft Macro Assembler).

    GAS (GNU Assembler).

    FASM (Flat Assembler).

 Este post será muy interesante para entender cómo se compila programas.

 

Diferencias entre AT&T e Intel

A medida que se bajando de nivel, se va notando algunas cosas que no se necesitaba preocupar al momento de trabajar con lenguajes de alto nivel. Al trabajar con sistemas basados en Unix, tradicionalmente se tiene una sintaxis AT&T, en muchas herramientas se tiene la opción de obtener la sintaxis Intel. Ahora voy a nombrar algunas de las diferencias más significativas para identificar la sintaxis del ensamblador.

En algunas instrucciones donde se especifica el origen y el destino se invierten, mientras que para AT&T va primero el origen y luego el destino, en Intel es lo contrario primero el destino y luego en origen

AT&T

<instrucción> <origen><destino>

Intel

<instrucción> <destino><origen>

Un indicador de que se está trabajando con AT&T es que cada operando es precedido por un "%", mientras que Intel no lo utiliza.

 

Arquitectura 32

Endians:

Describe como es el orden de la secuencia de bytes se guardará en la memoria. Cuando se quiere representar un numero decimal con 2 bytes, por ejemplo, el 6, aquí entra el concepto del byte más significativo y el menos significativo. En este ejemplo el byte (0000) es el más significativo (MSB) y el byte (0110) menos significativo (LSB), y esto está dado porque si agregáramos un byte al primer byte (0001 0110 = 22) variaría más que si se lo agregáramos al segundo byte (0000 0111 = 7).  En Big Endian, el byte más significativo es el primero, en cambio en Little Endian el ultimo byte es el más significante.

Conocer la Endian es importante a la hora de interpretar los registros.

                                Binario                  Hex          Decimal

Big Endian           0101 0010            0x52           82

Little Endian       0101 0010            0x25            37

 

En procesadores modernos existe, registros de propósito general, registro de segmento, registros de banderas y un registro de puntero.


Registros GPR

Los registros GPR(General Purpose Register) almacenan direcciones de memoria o datos, que serán utilizados durante la ejecución del programa.

Abr Nombre Descripción
EAX Extended Accumulator
Register
Es responsable de manejar el resultado de
operaciones de llamada al sistema
EBX Extended Base
Register
Es usada para almacenar la dirección de
los datos en el segmento de registro DS
ECX Extended Counter
Register
Sirve como contador para strings y
operaciones cíclicas
EDX Extended Data
Register
Es usado para almacenar direcciones para
operaciones de entrada y salida
EBP Extended Base
Pointer
Apunta a la dirección de la base del Stack
EDI Extended Destination
Index
Es usado como el puntero de destino para
las operaciones de strings
ESI Extended Source
Index
Es usado como puntero de origen para las
operaciones de strings
ESP Extended Stack
Pointer
Apunta a la dirección de la parte superior
del Stack
EIP Extended Instruction
Pointer
Tiene 32 bits de ancho, apunta a la dirección
de la siguiente instrucción

 

 Estos registros tiene un tamaño específico que se pueden dividir en registros con tamaño mucho más pequeño, por ejemplo EAX tiene un tamaño de 32 bits, y a su vez puedo tener 2 registros AX de tamaño de 16 bits, un registro AX de 16 bits puede tener un 2 registros de 8 bits, llamados AH(high bit) y AL(low bit). En arquitecturas mas modernas como x64, se duplicó el espacio de memoria, por ejemplo un registro RAX es equivalente a 2 registros EAX.



Los registros EFLAGS tienen un tamaño de 32 bits y contiene varios diferentes flags que son usadas para varias operaciones, algunos de estos bits están reservados como lo son 1,3,5,15, 22 al 31. En arquitectura x64 son denominados RFLAGS.

Algunos EFLAGS:

Carry Flag (CF): Se encuentra en el bit 0 y este flag indica una condición de desbordamiento para operaciones aritmética de enteros sin signo.

Parity Flag (PF): Se encuentra en el bit 2 y se coloca si el bit más significativo contiene incluso un número de un solo bit.

Auxiliary Carry Flag (AF): Se encuentra en el bit 4 y es usado en operaciones matemáticas si la operación resultante es llevar y pedir prestado.

Zero Flag (ZF): Se encuentra en el bit 6,  tiene el valor de 1 y si es colocado 0 despejados. 

Sign Flag (SF): Se encuentra en el bit 7 y es usado para demarcar el signo de un entero.

Direccion Flag (DF): Se encuentra en el bit 10 y se usa para determinar la dirección de operaciones de string, si se pasa un 1, las instrucciones de string operan desde la dirección más alta a la más baja y si se pasa 0 viceversa.

Overflow Flag(OF): Se encuentra en el bit 11 y se establece si el resultado entero es un número positivo demasiado grande o pequeño para caber en el operando de destino o una condición de desbordamiento en una operación matemática.


domingo, 22 de noviembre de 2020

PROCESO DE COMPILACION

Un lenguaje de programación es un tipo de lenguaje, que permiten generar un conjuntos de órdenes para el ordenador y que de este modo pueda realizar tareas. 

El primer lenguaje es el codigo maquina o binario que crean un grupo de instrucciones en base de 0's y 1's y es el lenguaje

Existen 2 grupos grandes de lenguajes.

Lenguajes de alto nivel:

Es el lenguaje mas compresible para el desarrollador pero en terminos de optimizacion no es lo mas adecuado al trabajar con hadware.

lenguajes de alto bajo nivel

Es el lenguaje as cercano al lenguaje maquina, en algunas ocaciones se suele utilizar directamente con el hardware, por ser el mas adecuado en terminos de rendimiento.

Ademas existen lenguajes que son considerados en un estado intermedio como es C, aunque otros los llaman de bajo nivel, su uso de punteros y rutinas en codigo ensamblador.

 

Sin importar como se considere un lenguaje de alto o bajo nivel, si es un lenguaje compilado necesitara forzosamente un compilador,que es una especie de traductor que transforma en este código en instrucciones binarias que la computadora pueda entender. Dependiendo de la arquitectura y el sistema operativo, los compiladores más usados son: gnu compiler collection para linux, clank para mac y Msvc para windows. En mi caso voy a utilizar OS linux asi que debo usar para compilar gcc.

Para convertir este código fuente en algo que nuestra computadora pueda interpretar y ejecutar tenemos que compilarlo en 4 pasos:

  •  Pre-procesamiento 

  • Compilación 

  • Ensamble 

  • Enlace 

En el pre procesamiento, el pre procesador identificando las directivas incluidas en el código fuente, haciendo que algunas como puede ser #define #include #ifdef, sean sustituidas, por el valor en donde han sido invocadas.

Con el comando: 

gcc -E edad.c > edad.pp 

Se vera un archivo con extencion .pp Durante este proceso se buscará el contenido del archivo que se indica como directivas y se reemplaza por completo de modo recursivo posiblemente haciendo llamado a otras dependencias. Asi es como un pequeño codigo de una pocas lineas termina con un poco mas de 700 lineas. 

La segunda etapa llamada compilación lo que hace es convertir esas 700 líneas en unas cuantas instrucciones en assembler.

Con el comando:

 gcc -S edad.c 

 Se obtendra un archivo con extension .s, si lo abre, obtendra algo similar a lo anterior, un archivo en codigo en lenguaje esamblador, aunque puede variar dependiendo la arquitectura es de x64 o x86. 

 

La tercera etapa en el proceso de compilación es el ensamblado,

independiente del sistema o la arquitectura, en este punto, el ensamblador tomara el archivo con el codigo assembler y lo conviete en codigo binario. 

Con el comando: 

gcc -c edad.c 

Se obtendra un archivo objeto, con extencion .o . Aunque no es tan frecuente hacer pasar por las etapas anteriores y tener un archivo en codigo assembler, se puede convertir a un archivo objeto con el comando:

 as -o edad.o edad.s 

Al abrir el archivo con extencion .o se puede aprenciar que es ilegible para los humanos. 

La última etapa es el enlazado, el compilador en cadena los archivos.o ademas creando los enlcaes de lsa distintas librerias que se invocan desde estos archivos. El enlazador se denomina ld, por motivos practicos no voy a ejemplificarlo, por tener que especificar las rutas de las librerias que seran consumidas por el programa.

En cambio, gcc con el siguiente comando, permite hacer este enlace con los archivo con extension .o:

gcc -o edad edad.o 

Para hacer los 4 pasos con un solo comando: 

gcc -o edad edad.c 

Por defecto cuando se crea un enlace dinamico, esto quiere decir que el ejecutable carga desde la memoria del equipo las liberias necesarias para funcionar correctamente, mientras que el enlace estatico hace que el ejecutable sea mas independiente por tener sus librerias incorporadas, pero esto hace que el archivo sea mas pesado. 

Para especificar el enlace estatico con el flag -static: 

gcc -static -o edad edad.c

 

ref: https://gcc.gnu.org/onlinedocs/ 

ref: man gcc