¡Esta es una revisión vieja del documento!


Guía de supervivencia de ensamblador

under_construction.jpg

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 

Referencias