Manual Completo de Programación en Ensamblador

1. Introducción al Ensamblador

El lenguaje ensamblador es un lenguaje de programación de bajo nivel que tiene una correspondencia casi directa con el código máquina que ejecuta el procesador. Cada familia de procesadores tiene su propio conjunto de instrucciones y, por lo tanto, su propio lenguaje ensamblador.

Nota: Este manual se enfoca en ensamblador para arquitectura x86 (32 bits) que es una de las más comunes.

1.1 Ventajas del Ensamblador

1.2 Desventajas del Ensamblador

2. Herramientas Necesarias

Para programar en ensamblador necesitamos:

2.1 Instalación en Linux

sudo apt-get update
sudo apt-get install nasm gdb make

2.2 Instalación en Windows

Descargar NASM desde nasm.us y agregarlo al PATH.

3. Estructura Básica de un Programa

Un programa básico en ensamblador tiene la siguiente estructura:

section .data       ; Sección para variables inicializadas
    mensaje db 'Hola Mundo!', 0   ; Cadena terminada en null

section .bss        ; Sección para variables no inicializadas
    buffer resb 100 ; Reserva 100 bytes

section .text       ; Sección de código
    global _start   ; Punto de entrada para el linker

_start:
    ; Código del programa aquí
    
    ; Terminar programa
    mov eax, 1      ; Número de llamada al sistema para exit
    mov ebx, 0      ; Código de retorno 0
    int 0x80        ; Llamada al kernel

3.1 Secciones del Programa

Sección Propósito
.data Contiene datos inicializados (variables con valores)
.bss Contiene datos no inicializados (reserva espacio)
.text Contiene el código ejecutable del programa

4. Registros del Procesador

Los registros son pequeñas ubicaciones de almacenamiento dentro del CPU. En x86 tenemos varios tipos:

4.1 Registros de Propósito General

Registro Descripción
EAX Acumulador - usado para operaciones aritméticas y retorno de valores
EBX Base - usado como puntero a datos
ECX Contador - usado en bucles
EDX Datos - extensión para EAX en operaciones grandes
ESI Índice fuente - puntero para operaciones con cadenas
EDI Índice destino - puntero para operaciones con cadenas
ESP Puntero de pila (Stack Pointer)
EBP Puntero de base (Base Pointer)

4.2 Registros de Segmento

Registro Descripción
CS Segmento de código
DS Segmento de datos
ES Segmento extra
FS, GS Segmentos adicionales
SS Segmento de pila

4.3 Registros de Estado (EFLAGS)

Contiene banderas que indican el estado del procesador:

5. Instrucciones Básicas

5.1 Movimiento de Datos

mov eax, 10      ; EAX = 10
mov ebx, eax    ; EBX = EAX (10)
mov [var], eax  ; Mueve EAX a la dirección de memoria de var

5.2 Operaciones Aritméticas

add eax, ebx    ; EAX = EAX + EBX
sub eax, ecx    ; EAX = EAX - ECX
inc edx         ; EDX = EDX + 1
dec esi         ; ESI = ESI - 1
mul ebx         ; EAX = EAX * EBX (resultado en EDX:EAX)
div ecx         ; EAX = EDX:EAX / ECX (cociente en EAX, resto en EDX)

5.3 Operaciones Lógicas

and eax, ebx    ; EAX = EAX AND EBX
or eax, ecx     ; EAX = EAX OR ECX
xor edx, edx    ; EDX = EDX XOR EDX (forma rápida de poner EDX a 0)
not eax         ; EAX = NOT EAX
shl eax, 2      ; Desplazamiento a la izquierda 2 bits (multiplica por 4)
shr ebx, 1      ; Desplazamiento a la derecha 1 bit (divide entre 2)

5.4 Comparaciones y Saltos

cmp eax, ebx    ; Compara EAX con EBX (establece flags)
je etiqueta     ; Salta si igual (ZF=1)
jne etiqueta    ; Salta si no igual (ZF=0)
jg etiqueta     ; Salta si mayor (signed)
jl etiqueta     ; Salta si menor (signed)
ja etiqueta     ; Salta si mayor (unsigned)
jb etiqueta     ; Salta si menor (unsigned)
jmp etiqueta    ; Salto incondicional

6. Manejo de la Pila

La pila es una estructura LIFO (Last In, First Out) usada para:

6.1 Instrucciones de Pila

push eax    ; Guarda EAX en la pila (ESP disminuye)
pop ebx     ; Saca valor de la pila a EBX (ESP aumenta)
pushad      ; Guarda todos los registros de propósito general
popad       ; Restaura todos los registros de propósito general

Importante: La pila crece hacia direcciones de memoria más bajas. ESP siempre apunta al último elemento agregado.

6.2 Ejemplo de Uso de Pila

mov eax, 10
push eax     ; Pila: [10]
mov eax, 20
push eax     ; Pila: [20, 10]
pop ebx      ; EBX = 20, Pila: [10]
pop ecx      ; ECX = 10, Pila: []

7. Llamadas al Sistema

Las llamadas al sistema permiten interactuar con el sistema operativo. En Linux se usan a través de la interrupción 0x80.

7.1 Llamadas al Sistema Comunes

Número Nombre Descripción
1 sys_exit Termina el programa
3 sys_read Lee de un descriptor de archivo
4 sys_write Escribe a un descriptor de archivo
5 sys_open Abre un archivo

7.2 Ejemplo: Escribir en Pantalla

section .data
    msg db 'Hola Mundo!', 0xA  ; Cadena con salto de línea
    len equ $ - msg            ; Longitud de la cadena

section .text
    global _start

_start:
    ; sys_write
    mov eax, 4      ; Número de llamada al sistema (sys_write)
    mov ebx, 1      ; Descriptor de archivo (1 = stdout)
    mov ecx, msg    ; Dirección de la cadena
    mov edx, len    ; Longitud de la cadena
    int 0x80        ; Llamada al kernel
    
    ; sys_exit
    mov eax, 1      ; Número de llamada al sistema (sys_exit)
    mov ebx, 0      ; Código de retorno
    int 0x80

8. Funciones y Procedimientos

En ensamblador, las funciones son bloques de código que pueden ser llamados y retornan al punto de llamada.

8.1 Estructura de una Función

; Función que suma dos números
; Entrada: EAX = primer número, EBX = segundo número
; Salida: EAX = suma
sumar:
    add eax, ebx    ; Suma los dos números
    ret             ; Retorna al llamador

; Llamada a la función
mov eax, 10
mov ebx, 20
call sumar          ; EAX ahora contiene 30

8.2 Convención de Llamada cdecl

Es una convención común donde:

; Función que suma dos números (cdecl)
; int sumar(int a, int b)
sumar:
    push ebp        ; Guarda el base pointer anterior
    mov ebp, esp    ; Establece nuevo base pointer
    
    mov eax, [ebp+8]  ; Primer parámetro (a)
    add eax, [ebp+12] ; Suma segundo parámetro (b)
    
    pop ebp         ; Restaura base pointer
    ret             ; Retorna (EAX contiene el resultado)

; Llamada a la función
push dword 20      ; Segundo parámetro (b)
push dword 10      ; Primer parámetro (a)
call sumar
add esp, 8         ; Limpia la pila (2 parámetros * 4 bytes cada uno)

9. Arreglos y Cadenas

9.1 Arreglos

En ensamblador, los arreglos son bloques contiguos de memoria.

section .data
    arreglo dd 10, 20, 30, 40, 50  ; Arreglo de 5 enteros (32 bits cada uno)

section .text
    global _start

_start:
    mov eax, [arreglo]      ; EAX = primer elemento (10)
    mov ebx, [arreglo+4]    ; EBX = segundo elemento (20)
    mov ecx, [arreglo+8]    ; ECX = tercer elemento (30)
    
    ; Acceso con índice (ESI)
    mov esi, 3              ; Índice 3 (cuarto elemento)
    mov edx, [arreglo + esi*4]  ; EDX = 40 (3*4 bytes por elemento)

9.2 Cadenas

Las cadenas son arreglos de bytes terminados usualmente con 0 (null-terminated).

section .data
    cadena db 'Ensamblador', 0  ; Cadena terminada en null

section .text
    global _start

_start:
    ; Recorrer cadena
    mov esi, cadena  ; ESI apunta al inicio de la cadena
    
bucle:
    mov al, [esi]    ; AL = carácter actual
    cmp al, 0        ; ¿Es el terminador null?
    je fin_cadena    ; Si es así, terminar
    
    ; Procesar carácter (aquí podríamos imprimirlo, etc.)
    inc esi          ; Mover al siguiente carácter
    jmp bucle
    
fin_cadena:
    ; Continuar con el programa...

10. Macros y Directivas

Las macros permiten definir bloques de código reutilizables que se expanden durante el ensamblado.

10.1 Definición de Macros

%macro imprimir 2  ; Macro con 2 parámetros
    mov eax, 4    ; sys_write
    mov ebx, 1    ; stdout
    mov ecx, %1   ; Primer parámetro: cadena
    mov edx, %2   ; Segundo parámetro: longitud
    int 0x80
%endmacro

section .data
    msg1 db 'Hola', 0xA
    len1 equ $ - msg1
    msg2 db 'Mundo', 0xA
    len2 equ $ - msg2

section .text
    global _start

_start:
    imprimir msg1, len1  ; Expande a las instrucciones de la macro
    imprimir msg2, len2
    
    mov eax, 1
    mov ebx, 0
    int 0x80

10.2 Directivas Comunes

Directiva Descripción
%define Define una constante simbólica
%include Incluye otro archivo fuente
equ Define una constante
times Repite una instrucción o dato

11. Depuración con GDB

GDB (GNU Debugger) es una herramienta esencial para depurar programas en ensamblador.

11.1 Comandos Básicos de GDB

Comando Descripción
break Establece un punto de interrupción
run Inicia la ejecución del programa
stepi Ejecuta una sola instrucción (entra en llamadas)
nexti Ejecuta una sola instrucción (no entra en llamadas)
info registers Muestra el contenido de los registros
x/ Examina memoria (ej: x/10x $esp para ver la pila)
print Evalúa e imprime una expresión
quit Sale de GDB

11.2 Ejemplo de Sesión de Depuración

# Ensamblar con información de depuración
nasm -f elf -g programa.asm -o programa.o
ld programa.o -o programa

# Iniciar GDB
gdb ./programa

# Dentro de GDB:
(gdb) break _start      # Punto de interrupción al inicio
(gdb) run               # Ejecutar programa
(gdb) stepi             # Ejecutar instrucción por instrucción
(gdb) info registers    # Ver registros
(gdb) x/10x $esp        # Examinar pila
(gdb) quit              # Salir

12. Optimización en Ensamblador

La optimización en ensamblador busca maximizar el rendimiento y minimizar el uso de recursos.

12.1 Técnicas de Optimización

12.2 Ejemplo: Multiplicación Eficiente

; Multiplicación lenta (usando mul)
mov eax, 10
mov ebx, 5
mul ebx         ; EDX:EAX = EAX * EBX

; Multiplicación rápida (usando desplazamientos y sumas)
mov eax, 10
mov ebx, eax    ; EBX = 10
shl eax, 2      ; EAX = 10*4 = 40
add eax, ebx    ; EAX = 40 + 10 = 50 (10*5)

Nota: Las técnicas de optimización pueden hacer el código menos legible. Siempre hay que equilibrar entre rendimiento y mantenibilidad.

13. Ejemplos Completos

13.1 Suma de dos números ingresados por usuario

section .data
    msg1 db 'Ingrese primer numero: ', 0
    len1 equ $ - msg1
    msg2 db 'Ingrese segundo numero: ', 0
    len2 equ $ - msg2
    msg3 db 'La suma es: ', 0
    len3 equ $ - msg3
    newline db 0xA
    
section .bss
    num1 resb 5
    num2 resb 5
    res resb 5

section .text
    global _start

_start:
    ; Pedir primer número
    mov eax, 4
    mov ebx, 1
    mov ecx, msg1
    mov edx, len1
    int 0x80
    
    ; Leer primer número
    mov eax, 3
    mov ebx, 0
    mov ecx, num1
    mov edx, 5
    int 0x80
    
    ; Pedir segundo número
    mov eax, 4
    mov ebx, 1
    mov ecx, msg2
    mov edx, len2
    int 0x80
    
    ; Leer segundo número
    mov eax, 3
    mov ebx, 0
    mov ecx, num2
    mov edx, 5
    int 0x80
    
    ; Convertir y sumar
    mov eax, [num1]
    sub eax, '0'     ; Convertir ASCII a número
    mov ebx, [num2]
    sub ebx, '0'
    add eax, ebx
    add eax, '0'     ; Convertir número a ASCII
    mov [res], eax
    
    ; Mostrar resultado
    mov eax, 4
    mov ebx, 1
    mov ecx, msg3
    mov edx, len3
    int 0x80
    
    mov eax, 4
    mov ebx, 1
    mov ecx, res
    mov edx, 1
    int 0x80
    
    ; Nueva línea
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 1
    int 0x80
    
    ; Salir
    mov eax, 1
    mov ebx, 0
    int 0x80

13.2 Factorial de un número (recursivo)

section .data
    msg db 'Factorial de 5 es: ', 0
    len equ $ - msg
    newline db 0xA

section .bss
    result resb 1

section .text
    global _start

; Función factorial recursiva
; Entrada: EAX = número
; Salida: EAX = factorial
factorial:
    cmp eax, 1
    jg calcular
    mov eax, 1
    ret
    
calcular:
    push eax        ; Guarda el valor actual
    dec eax         ; n-1
    call factorial  ; factorial(n-1)
    pop ebx         ; Recupera el valor original
    mul ebx         ; EAX = EAX * EBX
    ret

_start:
    mov eax, 5      ; Calcular factorial de 5
    call factorial
    
    ; Convertir resultado a ASCII
    add eax, '0'
    mov [result], eax
    
    ; Mostrar mensaje
    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, len
    int 0x80
    
    ; Mostrar resultado
    mov eax, 4
    mov ebx, 1
    mov ecx, result
    mov edx, 1
    int 0x80
    
    ; Nueva línea
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 1
    int 0x80
    
    ; Salir
    mov eax, 1
    mov ebx, 0
    int 0x80

14. Conclusión

Este manual ha cubierto los conceptos fundamentales de la programación en ensamblador para arquitectura x86. Aunque el lenguaje ensamblador puede parecer intimidante al principio, su comprensión proporciona un conocimiento profundo de cómo funcionan realmente las computadoras a bajo nivel.

Para continuar aprendiendo:

Recuerda: La programación en ensamblador es un arte que requiere paciencia y práctica. ¡Sigue experimentando y aprendiendo!

15. Recursos Adicionales

Libros

En Línea

Herramientas