LinuxParty

NUESTRO SITIO necesita la publicidad para costear hosting y el dominio. Por favor considera deshabilitar tu AdBlock en nuestro sitio. También puedes hacernos una donación entrando en linuxparty.es, en la columna de la derecha.

Ratio: 5 / 5

Inicio activadoInicio activadoInicio activadoInicio activadoInicio activado
 

Es el momento para cambiar al fabuloso GNU as. Vamos a olvidarnos de DEBUG durante algún tiempo. Gracias depuración. GNU as, Gas, o GNU Assembler, es obviamente el ensamblador utilizado por el Proyecto GNU. Es parte del paquete Binutils, y actúa como el defecto de fondo de gcc. Gas es muy potente y puede apuntar a varias arquitecturas informáticas. Todo un programa, entonces.

Como la mayoría de los ensambladores, Gas 'se compone de directivas (también conocido como Pseudo Ops), comentarios, y por supuesto, las instrucciones. Las instrucciones son muy dependientes de la arquitectura de la computadora de destino. A la inversa, las directivas que tienden a ser relativamente homogénea.

1 Sintaxis

Originalmente, este ensamblador sólo aceptaba la sintaxis del ensamblador AT&T, incluso para las arquitecturas Intel x86 y x86-64. La sintaxis de AT&T es diferente a la incluida en la mayoría de las referencias de Intel. Hay varias diferencias, la más memorable es que las instrucciones tienen dos operandos que tienen el origen y los destinos en el orden inverso. Por ejemplo, la instrucción mov ax, bx se expresaría en AT&T sintaxis como movw %bx,%ax , es decir, el operando de la derecha es el destino y el de más a la izquierda es la fuente. Otra distinción es que los nombres de registro utilizados como operandos deben ser precedidos por el signo del porcentaje (%). Sin embargo, desde la versión 2.10, Gas admite la sintaxis de Intel por medio de la directiva intel_syntax.. Pero en el siguiente ejemplo vamos a utilizar la sintaxis AT&T.

2 Nuestras Metas

 Ahora voy a utilizar la función de C printf. En C, el programa de saludo sería

int main()
{
    printf("hello, world!\n");
    return 0;
}

Hemos omitido la inclusión de la cabecera stdio.h. Podríamos recurrir a una sola frase: return printf("hello, world!\n") - 14; pero creo que al utilizar dos frases que vamos a obtener un código más claro. Guardamos nuestro programa en un archivo llamado "hello.c", y compilamos como

gcc -o hello.exe hello.c

  Si estamos trabajando en Windows, con el port MinGW de la colección de compiladores de GNU. Me gusta MinGW mucho, especialmente su capacidad para proporcionar funcionalidad nativa a través de llamadas a la API de Windows directamente, lo cual es bueno para el rendimiento de nuestras aplicaciones. Trabajando en Windows significa que nuestros ficheros ejecutables (código objeto y DLLs también) siguen el formato PE / COFF. El formato de archivo Portable Ejecutable (PE)  es un contenedor de toda la información que requiere el cargador de Windows para ejecutar el código. PE es una versión modificada del formato de archivo de Unix COFF (de ahí la referencia PE / COFF.) Otro popular formato de archivo para el código ejecutable es ELF (Executable and Linkable Format), que es utilizado por Linux, la Nintendo Wii y DS, y la PlayStation 3. Por el momento, sólo tenemos que saber que el comportamiento de GNU as varía en función del formato de archivo de destino (en nuestro caso, el PE / COFF).

gcc también nos puede proporcionar el archivo de ensamblador x86 utilizado. He escrito gcc -S hello.c y este fue el resultado que obtuve:

.file    "hello.c"
    .def    ___main;    .scl    2;    .type 32;    .endef
    .section .rdata,"dr"
LC0:
    .ascii "hello, world!\12\0"
.text
.globl _main
    .def    _main;    .scl    2;    .type    32;    .endef
_main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    andl    $-16, %esp
    movl    $0, %eax
    addl    $15, %eax
    addl    $15, %eax
    shrl    $4, %eax
    sall    $4, %eax
    movl    %eax, -4(%ebp)
    movl    -4(%ebp), %eax
    call    __alloca
    call    ___main
    movl    $LC0, (%esp)
    call    _printf
    movl    $0, %eax
    leave
    ret
    .def    _printf;    .scl    2;    .type    32;    .endef

3 Explicaciones Código

Desde un punto de vista general, podemos identificar tres elementos en el listado superior. En primer lugar, tenemos directivas, que son símbolos que comienzan con un '.' (punto.) Como se ha dicho, las directivas son generalmente válidas para cualquier ordenador. Si el símbolo se inicia con una letra se tratará de instrucción de lenguaje ensamblador, es decir, que se convertirá en una instrucción en lenguaje de máquina, y seguramente serán diferentes entre las arquitecturas informáticas. Por último, las etiquetas son los símbolos inmediatamente seguido por un ':' (colon o dos puntos.) Podemos pensar en las etiquetas como "direcciones" de los datos o código. Ahora vamos a hacer una revisión superficial de unas pocas directivas, así que ten paciencia.

.file string

Esta directiva identifica el inicio del archivo lógico (y la cadena debe ser el nombre del archivo.) En realidad, la directiva se ignora y sólo está allí para la compatibilidad con versiones anteriores. Podemos quitarlo.

.def name … .endef

Este par de directivas adjuntan información de depuración para el nombre del símbolo, y sólo se observan cuando Gas está configurado para la salida de formato PE / COFF. Pero nosotros no lo necesitamos para un programa tan simple como Hello World.

.section name

Esta directiva indica que el código se tiene que montar en una sección llamada nombre. Para los objetivos PE / COFF, la directiva .section es usada en una de las maneras siguientes:

.section name [, "flags"]
.section name [, subsegment]

La salida de gcc recurre a la forma de flags, y, específicamente, dos flags (carácter) se utilizan para indicar los atributos de la sección:  d (sección de datos) y r (. Sección de sólo lectura) Pero de nuevo, no es necesario para señalar explícitamente atributos de sección en nuestro simple programa.

.ascii “string”

Define uno o más literales de cadena (separados por comas). Cada cadena se monta en direcciones consecutivas (sin carácter nulo al final.)

.text subsection

Indica a Gas ensamblar las siguientes sentencias sobre el extremo de la subsección texto numerado subsection. Si se omite subsection (como es nuestro caso), el número que utiliza subsection es cero. Claramente, esta directiva es obligatoria, o gas no reunirá el código para imprimir nuestro mensaje "Hello World"

.global symbol (or .globl symbol)

.global hace symbol visible al enlazador. En nuestro caso, queremos informar al enlazador sobre la función  _main que se esperaba. Para la compatibilidad con otros ensambladores, ambas grafías ( .global o .globl ) son válidas.

Ahora, las directivas están hecha. Después ded la etiqueta _main sólo tenemos el código ensamblador hasta la instrucción ret. Parte de este código debería quedar claro si tiene experiencia previa en programación en ensamblador. Sin embargo, vamos a revisar estas instrucciones. Tenga en cuenta que la "l" al final de cada mnemónico le indica a  Gas que se quiere utilizar la versión de la instrucción que trabaja con operandos "largos" (32-bits).

Las Primeras 3 instrucciones son el código típico para la inicialización de la pila:

pushl   %ebp
movl    %esp, %ebp
subl    $8, %esp

Al restar 8 bytes de ESP estamos reservando el espacio en la pila para contener variables locales (la pila Intel "crece" desde posiciones de memoria alta a las inferiores.) Luego tenemos el raro

andl	$-16, %esp

Recuerde que en hexadecimal, -16 se expresa como 0xfffffff0. Por lo tanto, esta and alinea la pila con la siguiente más baja 16-byte de dirección. Las razones de esta alineación no están muy claras para mí. Puede ser una elección de gcc con el fin de acelerar los accesos de punto flotante, o puede ser para la compatibilidad con una arquitectura particular. Cualquiera de ellos, no requieren tal alineación para mostrar hola, mundo!

El código siguiente es principalmente una manera muy artificial de almacenar un valor en EAX:

movl	$0, %eax
addl	$15, %eax
addl	$15, %eax
shrl	$4, %eax
sall	$4, %eax
movl	%eax, -4(%ebp)
movl	-4(%ebp), %eax

Es evidente que el código no está optimizado, ya que hay un montón de líneas innecesarias. Por otra parte, el valor final de EAX también se almacena en la memoria antes reservado a la pila. Parece que el valor en EAX es un parámetro para la invocación _alloca en las dos líneas siguientes:

call	__alloca
call	___main

Estos dos llamadas son innecesarias para nuestra aplicación. No vamos a ahondar en detalles, pero voy a decir la alloca() es una función que se utiliza para asignar memoria en la pila. Y si los binarios PE / COFF se utilizan, y nuestra aplicación tiene una función int main(), una función void __main() debe ser llamada lo primero después de entrar en main() . Vamos a dejarlo así por ahora. Más información se puede encontrar en este excelente artículo de OSDevWiki e instructivo .

Por último, nos encontramos con el código útil

movl    $LC0, (%esp)
call    _printf

Se mueve la dirección de la cadena ascii en la pila, e invoca printf . Ahora, ¿dónde está la definición de printf ? Bueno, vamos a tomar de la biblioteca de C, por supuesto. El enlazador (ld) es responsable de la asociación de nuestro código con la definición de printf .

Por último, encontramos

movl    $0, %eax
leave
ret

Estas instrucciones constituyen el "código de retorno." Almacenar el valor de retorno (0 == éxito!) En EAX, destruir la pila, y el puntero de instrucción pop salvada la pila con el fin de devolver el control al procedimiento de llamada o programa.

Si le quitamos todas las líneas innecesarias, nuestra Hola, mundo! adquiriría este formulario:

.data
LC0:
    .ascii "hello, world!\n\0"
.text
    .global _main
_main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    $LC0, (%esp)
    call    _printf
    movl    $0, %eax
    leave
    ret

Más corto y más claro. He assambled, paso a paso:

as -o hello.o hello.s

ld -o hello.exe
/mingw/lib/crt2.o
C:/MinGW/bin/../lib/gcc/mingw32/3.4.5/crtbegin.o
-LC:/MinGW/bin/../lib/gcc/mingw32/3.4.5
-LC:/MinGW/lib hello.o
-lmingw32 -lgcc -lmsvcrt -lkernel32
C:/MinGW/bin/../lib/gcc/mingw32/3.4.5/crtend.o


Pero se, que es más rápido escribir:
gcc -o hello.exe hello.s

No estás registrado para postear comentarios



Redes:



   

 

Suscribete / Newsletter

Suscribete a nuestras Newsletter y periódicamente recibirás un resumen de las noticias publicadas.

Donar a LinuxParty

Probablemente te niegues, pero.. ¿Podrías ayudarnos con una donación?


Tutorial de Linux

Formulario de acceso

Filtro por Categorías