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 0x25int
0x26, etc.

Así como las instrucciones:
int 0x10int 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 ebxecxedx, 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