Programación
IA32 en linux Basado en el documento de Armando Falcón Tirado (la Nena) "Laboratorio de Sistemas Digitales en UNIX mini-HOWTO" de 2002. |
Introducción Este mini-HOWTO trata de explicar
como utilizar herramientas como un ensamblador, un enlazador y
un depurador bajo sistemas operativos UNIX/Linux.
La idea de escribir este documento surgió debido a que en el Laboratorio de Sistemas Digitales uno de sus objetivos es aprender a programar en ensamblador bajo plataformas IA-32 (x86), utilizando el sistema operativo MS-DOS y la herramienta MASM. Como yo en lo personal prefiero utilizar UNIX a otros sistemas operativos, y principalmente software libre a software propietario, decidí utilizar herramientas como Linux y GNU para realizar las prácticas de este laboratorio, y fomentar su uso como una mejor opción con la realización de este mini-HOWTO . Como podrán ver más adelante yo no soy un buen programador :), pero el objetivo de este mini-HOWTO no es que ustedes aprendan a crear buen código en ensamblador para x86, sino que aprendan a utilizar herramientas de software libre como una mejor opción al software propietario. Este documento comienza explicando primero el software necesario, las diferencias entre programar en UNIX y en MS-DOS, las diferencias entre la sintaxis AT&T e Intel. Se muestran códigos sencillos de ejemplo. |
Software necesario UNIX/Linux Se recomienda Linux y los distintos
BSD, pero también se pueden utilizar UNIX comerciales como
Solaris para x86. Este
mini-HOWTO se basa en Linux, pero los ejemplos propuestos
aquí se
pueden realizar en otros UNIX sin aplicar casi ningún cambio.
GNU Assembler El ensamblador GNU (GNU Assembler, gas) o as es parte de la colección binutils de GNU. Muchas distribuciones Linux ya vienen con este paquete. http://sources.redhat.com/binutils/ Binutils se puede obtener en: ftp://ftp.gnu.org/gnu/binutils/ ftp://ftp.azc.uam.mx/mirrors/gnu/binutils/ El manual de as se puede obtener en: http://www.gnu.org/manual/gas-2.9.1/as.html http://mirrors.azc.uam.mx/mirrors/gnu/Manuals/gas-2.9.1/as.html GNU Linker El enlazador GNU (GNU Linker) o ld es también parte de la colección binutils de GNU. El manual de ld se puede obtener en: http://www.gnu.org/manual/ld-2.9.1/ld.html http://mirrors.azc.uam.mx/mirrors/gnu/Manuals/ld-2.9.1/ld.html GNU Debugger El depurador GNU (GNU Debugger) o gdb. Muchas de las distribuciones Linux ya vienen también con este paquete: http://www.gnu.org/software/gdb/gdb.html |
MSDOS vs Linux Es importante mencionar que
UNIX/Linux es un sistema operativo que trabaja en modo protegido (en plataformas IA-32
UNIX opera al microprocesador en modo protegido). Lo cual significa
que procesos ordinarios que operan en modo usuario no pueden realizar
ciertas cosas como utilizar los IRQs o DMA en forma directa.
Nota.- Los módulos para el kernel (que operan en modo kernel) pueden utilizar en forma directa los IRQs o DMA. Llamadas al sistema Muchos programas requieren la entrada y salida de datos, así como de otros servicios del sistema operativo. En MS-DOS para tener acceso a estos servicios se utilizan las instrucciones: int 0x21, int 0x25, int 0x26, etc. Así como las instrucciones: int 0x10, int 0x16, etc. para tener acceso a los servicios del BIOS. En Linux se utiliza la instrucción: int 0x80 para hacer una llamada al sistema. El número de llamada al sistema se pasa por medio del registro eax, y los parámetros de la llamada al sistema se pasan por medio de los registros ebx, ecx, edx, esi y edi. En el archivo /usr/include/asm/unistd.h se encuentran los números de las llamadas al sistema. Si se quiere tener información acerca de una llamada al sistema específica, por ejemplo write(), se ejecuta: $man 2 write en el shell (la sección 2 de los man pages de Linux cubre las llamadas al sistema). Se puede observar que en el archivo /usr/include/asm/unistd.h aparece la siguiente línea: #define __NR_write 4 ésto indica que el registro eax debe contener un 4 para poder llamar a write(). Ahora, del man page de write() se observa: ssize_t write(int fd, const void *buf, size_t count); ésto indica que el
registro ebx debe
ser igual al descriptor de archivo del archivo al cual queremos
escribir, ecx es
una apuntador a la cadena
que queremos escribir y edx es
el tamaño de la cadena. Si esta llamada tuviera dos parámetros
más, éstos se colocarían en esi y edi respectivamente.
El descriptor de archivo para la entrada estándar (stdin) y la salida estándar (stdout) es 0 y 1 respectivamente. Ésto se puede comprobar al ejecutar: $ls -l /dev/stdin $ls -l /dev/stdout en el shell. |
Sintaxis AT&T vs Intel as utiliza
sintaxis AT&T. Las principales diferencias entre la sintaxis
AT&T e Intel son las
siguientes:
Los nombres de los registros tienen el prefijo %; por ejemplo, se utiliza %eax en lugar de eax. El orden de los operandos es el fuente primero y el destino al último, en lugar de la convención de Intel de destino primero y fuente al último. Por ejemplo AT&T: mov %edx, %eax Intel: mov eax, edx El tamaño de los operandos es especificado como un sufijo al nombre de la instrucción. El sufijo es b para byte (8 bits), w para palabra (word, 16 bits) y l para doble palabra (long, 32 bits). Por lo tanto, la sintaxis correcta para la instrucción de arriba sería: movl %edx, %eax Aunque as no requiere una sintaxis AT&T estricta, ya que el sufijo es opcional cuando el tamaño se puede inferir de los operandos, y sino por defecto es 32 bits (con un warning). Los operandos inmediatos tienen el prefijo $; por ejemplo: addl $5, %eax Si el operando tiene un prefijo significa que es una dirección de memoria; por ejemplo: movl $foo, %eax pone la dirección de la variable foo en el registro %eax, y movl foo, %eax pone el contenido de la variable foo en el registro %eax. |
Compilación, elazado y depuración Archivo objeto Para ensamblar el archivo
fuente invocamos a as de
la siguiente forma:
$ as -o holaMundo.o holaMundo.s ésto crea el archivo objeto holaMundo.o. Archivo ejecutable El segundo paso es crear el archivo ejecutable del archivo objeto invocando ld $ ld -s -o holaMundo holaMundo.o y para ejecutar el archivo ejecutable desde el shell $ ./pract2 Depurando con gdb Antes de empezar a depurar con gdb primero es necesario volver a ensamblar el archivo fuente con la opción --gstabs de as, para que en el archivo objeto se genere información necesaria para el depurador: $ as --gstabs -o holaMundo.o holaMundo.s y creamos el archivo ejecutable con ld de la siguiente forma: $ ld -O0 -o holaMundo holaMundo.o Para empezar a depurar el programa con gdb se ejecuta la siguiente línea: $ gdb holaMundo Desde el prompt de gdb '(gdb) ' se pueden ejecutar comandos de gdb. objdump
objdump despliega información sobre uno o más archivos objeto, y es parte de la colección binutils de GNU. Para desplegar información sobre los encabezados, tabla de símbolos, etc. $ objdump -x holaMundo objdump también puede ser utilizado para desensamblar: $ objdump -d holaMundo la opción -d sólo desensambla las secciones que tienen instrucciones. Para desensamblar todas las secciones se utiliza la opción -D $ objdump -D holaMundo size size lista el tamaño de las secciones y el tamaño total del archivo objeto $ size holaMundo strace strace es una herramienta para
depurar que imprime el seguimiento de las llamadas al sistema hechas
por otro programa
$ strace ./holaMundo |