CAPITULO V. DIRECTIVAS Y MÁS DIRECTIVAS.
Conocer las directivas que acepta nuestro ensamblador simplifica mucho el trabajo, pero a fin de no hacer esto soporífero (aunque creo que ya es demasiado tarde) sólo incluiré las típicas. También hay que tener en cuenta que esta parte puede cambiar bastante de un ensamblador a otro. NASM presenta una sintaxis semejante (aunque con importantes diferencias) a TASM o MASM, ensambladores de Borland y Microsoft respectivamente, para MSDOS/Windows. GASM (GNU Assembler, también con un nutrido grupo de adeptos), por otra parte, emplea una notación bien distinta (y en muchos casos odiada por los que no la usamos), conocida como AT&T. Con esto digo que aunque las instrucciones sean las mismas, puede cambiar mucho la manera de escribirlas.
Y quizá sea esta la parte más ingrata del ensamblador, pero bueno, la ventaja es que muy fácil, y no hace falta conocer todas las directivas para realizar un código eficiente.. Simplemente nos harán la vida más fácil. Empecemos.
Cada línea de ensamblador tiene (salvando las excepciones de las macros y cosas similares) la misma pinta:
etiqueta: instruccion operandos ;comentario
Todos los campos son optativos (menos, evidentemente, cuando la instrucción requiera operandos); así pues es posible definir etiquetas en cualquier sitio, o simplemente colocar un comentario. Si la línea fuese demasiado larga y quisiéramos partirla en varias (cosa un tanto rara, pero que viene bien saber), se puede usar el carácter '\'. Si la línea termina en \, se colocará la siguiente a continuación.
El ensamblador admite operadores de multiplicación, suma, resta, paréntesis.. Cuando toca ensamblar, él solito opera y sustituye. Por ejemplo:
MOV AX,2*(2+4)
es exactamente lo mismo que
MOV AX,12
Las directivas básicas para definir datos son las siguientes:
Ejemplos:
db 'Soy una cadena' | ;es cierto, es una cadena |
dw 12,1 | ;0C00h 0100h (ordenación Intel, no se te olvide) |
dt 3.14159265359 | ;representación en coma flotante de 10 bytes PI |
Lo de definir números reales se puede hacer con DD (precisión simple, 32 bits), DQ (precisión doble, 64 bits) y DT (a veces llamado real temporal, una precisión de la leche, 80 bits; uno para el signo, 64 para la mantisa y 15 para el exponente o_O'). Los caracteres ascii van entrecomillados con ''; cuando el ensamblador encuentre un caracter entre comillas simples lo sustituye por el valor numérico del código ascii correspondiente.
Para reservar zonas amplias de memoria se emplean RESB y familia, que lo que hacen es dejar hueco para N bytes, palabras (RESW), palabras dobles (RESD), palabras cuádruples (RESQ) o bloques de 10 bytes (REST).
resb 10 | ;deja un hueco de 10 bytes sin inicializar |
resq 4*4 DUP (1) | ;espacio para una matriz 4x4 (16 reales de precisión doble) |
Una directiva muy peculiar, de uso muy esporádico pero muy interesante, es INCBIN; lo que hace es incluir dentro del código el contenido binario de un archivo dado. Puede ser útil, por ejemplo, para incrustar pequeñas imágenes o logotipos dentro del ejecutable:
incbin "datos.dat" | ;ensambla introduciendo datos.dat en este punto |
incbin "datos.dat",100 | ;incluye datos.dat, excepto los primeros 100 bytes |
incbin "datos.dat",100,200 | ;incluye los 200 bytes siguientes a los 100 primeros de datos.dat |
El uso es tan simple como org numero_de_bytes
Siglo equ 21
MOV AX,Siglo ;AX ahora vale 21
cpu 8086
cpu 186
cpu 286
cpu 386
cpu 486
cpu 586
cpu 686
cpu IA64
Si no se selecciona ninguna, por defecto se admiten todas las instrucciones.
%define h2(a,b) ((a*a)+(b*b))
mov ax,h2(2,3)
es equivalente a
mov ax,(2*2)+(3*3)
es decir
mov ax,13
En los ejemplos del tema anterior vimos las llamadas al sistema para mostrar una cadena
en pantalla. Si tuviéramos que repetir esa llamada varias veces, sería más cómodo y legible usar una macro
como la siguiente:
%macro imprime 2
mov edx,%1
mov ecx,%2
mov ebx,1
mov eax,4
int 80h
%endmacro
con lo que cada vez que cada vez que quisiéramos imprimir una cadena sólo tendríamos que escribir
imprime longitud,cadena
Aunque es tentador hacerlo, no hay que abusar de las macros porque el código engorda que da gusto. Las macros son más rápidas que las subrutinas (pues ahorran dos saltos, uno de ida y uno de vuelta, más los tejemanejes de la pila), pero a menudo no compensa por muy bonitas que parezcan: el que la pantalla de bienvenida de un programa cargue un par de milisegundos antes o después no se nota. Claro que ahora la memoria es cada vez menos problema... En cualquier caso, su uso es deseable siempre que se favorezca la legibilidad del código.
Una aplicación que da idea de la potencia de las macros es la de emplear macros con el nombre de instrucciones. Es así posible definir una macro llamada push que reciba dos argumentos, por ejemplo, y que ejecute las instrucciones push %1 y push %2. Cuando se escriba push eax, al recibir un número de argumentos distintos a los de la definición de la macro, sabrá que se refiere a una instrucción (aunque dará un warning por ello), mientras que al escribir push eax,ebx desarrollará la macro como push eax seguido de push ebx.
Para especificar que una etiqueta dentro de la macro es local (a fin de que no se duplique la etiqueta al usar dos veces la misma macro), hay que poner %% justo delante de la etiqueta.
%macro macroboba 0
%%EtiquetaInutil:
%endmacro
Si llamamos a esta macro varias veces, cada vez que se la llame usará un nombre distinto para la etiqueta (pero igual en toda la macro, claro).
...y eso es todo en cuanto a directivas. Hay muchas más. Existen deficiones de condicionales para que ensamble unas partes de código u otras en función de variables que definamos con EQU, comparadores, operadores lógicos.. Son de uso muy sencillo todas ellas, por lo que basta con echar un ojo de vez en cuando a la documentación del ensamblador para tenerlas presentes.