¡Esta es una revisión vieja del documento!
Tabla de Contenidos
Guía de supervivencia de ensamblador
Descargo
La redacción de una guía que permita aprender el lenguaje ensamblador es una tarea que no escapa al principio de incertidumbre de Heisenberg
Si el lenguaje se enseña rápidamente, no se comprenderá como aplicarlo, mientras que si se detalla exhaustivamente, la enseñanza será extremadamente lenta.
Para los menos amantes de la cuántica se puede decir que una guía de supervivencia de lenguaje ensamblador, es como resumir El Señor de los Anillos en la historia de un tipo que perdió un anillo y quería recuperarlo porque lo hacia sentir más fuerte.
Por lo expresado de manera irónica en los párrafos anteriores, esta guía no pretende ser un texto que trate de forma exhaustiva los por menores del lenguaje ensamblador detallando el basto conjunto de instrucciones de la arquitectura Intel, así como la sintaxis del NASM, sino que empleará un enfoque meramente práctico para la resolución de los ejercicios requeridos por la materia, haciendo incapie en algunos conceptos útiles que deben ser comprendidos para la formación del futuro ingeniero.
Introducción
Esta guía solo cubre la sintaxis NASM y el conjunto de instrucciones IA32
Registros de la familia IA
Instrucciones básicas
- Asignación: Guardan un valor en memoria o registros
mov ;Move
- Comparación: Utilizadas para comparar un valor con otro, solo afectan las banderas.
cmp ;Compare Two Operands test ;Logical Compare
- Salto:
- Condicionales en función del estado de un bit de bandera particular
jxx ;Jump short if ...
- Absolutos
jmp ;Jump near/long
- Binarias: operan bit a bit
and ;Logical AND or ;Logical Inclusive OR xor ;Logical Exclusive OR not ;One's Complement Negation
- Aritméticas: operaciones matemáticas
add ;Add adc ;Add with Carry sub ;Subtract sbb ;Integer Subtraction with Borrow inc ;Increment by 1 dec ;Decrement by 1 clc ;Clear Carry Flag mul ;Unsigned Multiply div ;Unsigned Divide
- Stack: operan con la pila, introduciendo y recuperando valores
pop ;Pop a Value from the Stack push ;Push Word, Doubleword or Quadword onto the Stack popf ;Pop Stack into FLAGS Register pushf ;Push FLAGS Register onto the Stack popad ;Pop All General-Purpose Registers
- Desplazamiento: operan desplazando los bits en un sentido u otro.
shr ;Shift shl ;Shift ror ;Rotate rol ;Rotate
Constantes, variables y tipos
Constantes
La definición de un simbolo asociado a una constante se realiza mediante la pseudo instrucción EQU
SEL_CODE EQU 0x08 ;El simbolo SEL_CODE se reemplazara en cada uso por el valor 0x08
Variables
En ensamblador el concepto de variable es meramente figurativo, ya que esta no es más que un espacio identificado en memoria. Analicémoslo mediante los siguientes ejemplos:
- Tomando la analogía con el bien conocido lenguaje C, si se quiere definir un char, el ensamblador brinda la pseudo instrucción resb, la cual reserva un Byte no inicializado. El problema es que no se dispone de una identificación de esa memoria que se acaba de reservar, para ello se debe etiquetar dicha posición utilizando la siguiente sintaxis
Mi_var_char resb 1 ;Variable no inicializada
Si además de definir la variable se quiere inicializar, la pseudo instrucción a utilizar es db.
Mi_var_char db 0xA5 ;Variable inicializada
- Aplicando el mismo criterio se utilizan resw, resq, rest, dw, dq, dt
Mi_var_short resw 1 ;Variable no inicializada Mi_var_short resb 2 ;Otra opcion de lo anterior Mi_var_long resq 1 ;Variable no inicializada Mi_var_long dq 0xAA55AA55 ;Variable inicializada
Tipos
En ciertas ocasiones es conveniente disponer de un arreglo de memoria con un formato específico, es decir que se pueda reservar memoria en forma más ágil que un simple dx. Para tal fín el preprocesador del ensamblador provee el par de macros struc y endstruc, las cuales permiten crear tipos de datos complejos, como por ejemplo una descriptor de GDT.
struc gdtd_t ;Definicion de la estructura denominada gdtd_t, la cual contiene los siguientes campos .limite: resw 1 ;Limite del segmento bits 00-15. .base00_15: resw 1 ;Direccion base del segmento bits 00-15. .base16_23: resb 1 ;Direccion base del segmento bits 16-23. .prop: resb 1 ;Propiedades. .lim_prop: resb 1 ;Limite del segmento 16-19 y propiedades. .base24_31: resb 1 ;Direccion base del segmento bits 24-31. endstruc
Al igual que una variable, una estructura puede ser instanciada con o sin inialización, como se muestra a en los siguientes ejemplos, en ambos casos se debe utilizar istruct e iend para la instanciación.
Gdt_desc_nulo: istruc gdtd_t ;Instancia Gdt_desc_nulo de la estructura gdtd_t at gdtd_t.limite, dw 0 ;Observar que para inicializar at gdtd_t.base00_15, dw 0 ;cada una de los campos se at gdtd_t.base16_23, db 0 ;se utiliza la macro AT junto at gdtd_t.prop, db 0 ;al nombre del tipo y el campo at gdtd_t.lim_prop, db 0 ; at gdtd_t.base24_31, db 0 ; iend ;Observar el uso de istruc e iend Gdt_desc_extra: istruc gdtd_t ;Instancia Gdt_desc_extra de la estructura gdtd_t. iend ;En este caso se reserva el espacio en memoria pero no se inicializa
Debido a que la carga de un valor en un campo de una estructura requiere cierta lógica, la misma se explica a continuación mediante un simple ejemplo, utilizando la estructura instanciada previamente.
mov [Gdt_desc_extra+gdtd_t.base00_15], bx
Como se explicara anteriormente, una variable no es mas que un espacio identificado en memoria. Este concepto también se aplica a las estructuras, por lo cual como se viera en Variables, la identificación se realiza mediante una etiqueta, que en este caso es el propio nombre de la instancia de la estructura, es decir Gdt_desc_extra. Sabiendo que el inicio del espacio en memoria que se quiere cargar se encuentra Gdt_desc_code, si desea aaceder al campo base00_15, habrá que desplazarse hasta dicha etiqueta. Este desplazamiento es calculado internamente por el ensamblador, pero el programador debe expresarlo como se indico en el ejemplo.
Macros
En los apartados precedentes se indicó que el preprocesador utilizaba macros para realizar ciertas definiciones, sin embargo el uso de las mismas no está restringido solo para dichos casos, sino que el programador también puede utilizar macros propias. A modo de recordatorio se define como macro una secuencia de instrucciones que pueden ser invocadas mediante una única sentencia. A diferencia de las funciones la utilización de macros genera un incremento en el tamaño del código y por ende en el binario final, sin embargo tienen la ventaja de aumentar la legibilidad del código y por lo general aumentar la velocidad de ejecución respecto al mismo código implementado por función. Por lo general existen dos tipos de macros
- Linea simple: es el caso más básico de macros, el cual permite realizar la definición en una única línea utilizando la directiva %define.
- Lineas múltiples: permite realizar secuencias más complejas y emplea las directivas %macro, %endmacro. Se ejemplificará este caso por ser el más complejo.
Como es costumbre se explicará la utilización de macros mediante un ejemplo, el cual tiene por objetivo inicializar la instancia Gdt_desc_extra del ejemplo visto en estructuras. El primer paso es definir la macro
- gdtdescini: nombre de la macro
- Argumento 1: Direccion de la base del segmento. Valor maximo soportado 2^32.
- Argumento 2: Tamaño del segmento. Valor maximo soportado 2^20.
- Argumento 3: Propiedades P;DPL;S;TIPO.
- Argumento 4: Propiedades G;D/B;L;AVL
- Argumento 5: Direccion de inicio del descriptor a ser inicializado.
%macro gdtdescini 5 ;Nombre y numero de argumentos push ebx ;Se almacena el valor del registro xor ebx, ebx ;y se borrar para operar con el mismo mov ebx, %1 ;Se carga EL PRIMER ARGUMENTO (la direccion) en ebx mov [%5+gdtd_t.base00_15], bx ;y carga en la instancia indicada por EL QUINTO ARGUMENTO shr ebx, 16 mov [%5+gdtd_t.base16_23], bl xor ebx, ebx mov ebx, %2 ;Se carga EL SEGUNDO ARGUMENTO EN ebx mov [%5+gdtd_t.limite],bx ;del tamaño shr ebx, 16 ;Se adapta el restante nibble y mov [%5+gdtd_t.lim_prop],bl ;se carga en el descriptor xor bl, bl mov bl, %3 ;Se carga el primer byte mov [%5+gdtd_t.prop],bl ;de propiedades EL TERCER ARGUMENTO xor bl, bl mov bl, %4 ;Se carga el nibble EL CUARTO ARGUMENTO shl bl, 4 ;Se desplaza al nibble mas significativo or [%5+gdtd_t.prop],bl ;Se carga en el descriptor pop ebx ;Se restatura el valor del registro %endmacro
La utilización es extremadamente sencilla, por ejemplo si se quisiera inicializar el descriptor mencionado anteriormente para direccionar la memoria de video, bastaría con el siguiente codigo
gdtdescini 0B8000h, 4000, 10010011b, 0b, Gdt_desc_extra