DevTools

Cheatsheet Assembly

Linguagem de baixo nível com acesso direto ao hardware (x86/NASM)

Volver a los lenguajes
Assembly
80 tarjetas encontradas
Categorías:
Versiones:

Básico


10 cards
Registros x86-64
; Registros de 64 bits (GPR):
; RAX RBX RCX RDX RSI RDI RSP RBP
; R8  R9  R10 R11 R12 R13 R14 R15

; Sub-registros:
; EAX (32b) | AX (16b) | AH/AL (8b)

; Propósito:
; RAX - retorno de funciones
; RCX - contador (loops)
; RSP - stack pointer
; RBP - base pointer (frame)
; RSI/RDI - source/destination

Los registros son la memoria más rápida de la CPU. RAX es el acumulador (retorno). RSP apunta a la cima de la stack. R8-R15 son exclusivos de 64 bits. Acceder a EAX pone a cero los 32 bits superiores.

Directivas NASM
; Definir datos:
db  0x41            ; 1 byte ('A')
dw  1000            ; 2 bytes (word)
dd  3.14            ; 4 bytes (float)
dq  123456789       ; 8 bytes (qword)

; Reservar espacio:
resb 16             ; 16 bytes (bss)
resw 8              ; 8 words
resq 1              ; 1 qword

; Constantes:
MAX: equ 100
TAM: equ $ - inicio

; Alineación:
align 16

db/dw/dd/dq definen datos de 1/2/4/8 bytes. resb/resw/resq reservan espacio en BSS. equ crea constantes. align alinea para rendimiento.

Flags (EFLAGS/RFLAGS)
; Flags principales:
; ZF - Zero Flag (resultado = 0)
; CF - Carry Flag (overflow unsigned)
; SF - Sign Flag (bit de signo)
; OF - Overflow Flag (overflow signed)
; PF - Parity Flag (paridad baja)

; Instrucciones que afectan flags:
add rax, rbx    ; ZF, CF, SF, OF
sub rax, rbx    ; ZF, CF, SF, OF
cmp rax, rbx    ; como sub, descarta resultado
test rax, rax   ; como and, descarta (ZF/SF)

; Ver flags:
pushfq          ; push flags a la stack
pop rax         ; flags en rax

Las Flags son bits que indican resultados. ZF=1 si cero. CF=1 si carry/borrow. SF=1 si negativo. OF=1 si overflow signed. CMP y TEST solo afectan flags.

Estructura NASM
; fichero.asm — estructura básica NASM
section .data       ; datos inicializados
    msg: db "Hola", 10
    len: equ $ - msg

section .bss        ; datos no inicializados
    buffer: resb 64

section .text       ; código
    global _start

_start:
    ; instrucciones aquí
    mov rax, 60     ; sys_exit
    xor rdi, rdi    ; código 0
    syscall

.data tiene datos con valor inicial. .bss reserva espacio (cero). .text contiene el código. global _start exporta el punto de entrada. section organiza el binario.

Modos de direccionamiento
mov rax, [rbx]          ; indirecto (contenido de rbx)
mov rax, [rbx + 8]      ; desplazamiento
mov rax, [rbx + rcx*4]  ; base + índice*escala
mov rax, [rbx + rcx*4 + 16] ; completo

; RIP-relative (64-bit):
mov rax, [rel variable]

; Inmediato:
mov rax, 42             ; valor directo

; Registro:
mov rax, rbx            ; registro → registro

Direccionamiento: [reg] = indirecto, [reg+N] = desplazamiento, [base+idx*escala] = arrays. RIP-relative es el estándar en 64-bit para datos globales. Escalas: 1, 2, 4, 8.

Comentarios y convenciones
; Comentario de línea (punto y coma)

/* Comentario multi-línea
   (soportado por NASM) */

; Convenciones de nombres:
; _start      → punto de entrada (Linux)
; main        → punto de entrada (C)
; snake_case  → funciones y labels
; CONSTANTES  → equ en mayúsculas

; Labels:
loop_inicio:    ; label local
.fin:           ; label local (punto)
@@:             ; label anónimo
jmp @B          ; salta al @@ anterior

; inicia un comentario. Los labels terminan con :. .nombre son locales (scope entre labels). @@ es anónimo (@F = forward, @B = backward). Convención: snake_case para símbolos.

Compilar y ejecutar (NASM)
; Ensamblar (assembly → objeto):
nasm -f elf64 programa.asm -o programa.o

; Enlazar (objeto → ejecutable):
ld programa.o -o programa

; Ejecutar:
./programa

; Con GCC (si usa funciones C):
nasm -f elf64 -g -F dwarf main.asm
gcc main.o -o app -no-pie

; Ver código máquina:
objdump -d programa

nasm -f elf64 genera un objeto 64-bit Linux. ld enlaza sin libc. gcc si necesitas printf/malloc. -g -F dwarf añade debug info. objdump -d desensambla.

MOV y transferencia
mov rax, 42         ; inmediato → registro
mov rax, rbx        ; registro → registro
mov rax, [mem]      ; memoria → registro
mov [mem], rax      ; registro → memoria

; Variantes con extensión:
movzx eax, byte [x]  ; zero-extend (8→32)
movsx eax, byte [x]  ; sign-extend (8→32)
movsxd rax, eax      ; sign-extend (32→64)

; LEA (dirección, no contenido):
lea rax, [rbx + rcx*4 + 10]

MOV copia datos (¡no mueve!). movzx extiende con ceros. movsx extiende con signo. LEA calcula una dirección sin acceder a la memoria — útil para aritmética rápida.

Hello World
section .data
    msg: db "¡Hola, Mundo!", 10
    len: equ $ - msg

section .text
    global _start

_start:
    mov rax, 1          ; sys_write
    mov rdi, 1          ; stdout (fd=1)
    mov rsi, msg        ; puntero a string
    mov rdx, len        ; longitud
    syscall

    mov rax, 60         ; sys_exit
    xor rdi, rdi        ; exit code 0
    syscall

sys_write (rax=1) imprime. rdi=fd (1=stdout), rsi=buffer, rdx=tamaño. syscall invoca al kernel. sys_exit (rax=60) termina. $ - msg calcula la longitud.

Tamaños y sufijos
; Tamaños explícitos:
mov byte [x], 0x41     ; 1 byte
mov word [x], 0x1234   ; 2 bytes
mov dword [x], 0xABCD  ; 4 bytes
mov qword [x], 0xFF    ; 8 bytes

; En un registro (implícito):
mov al, 1     ; 8-bit
mov ax, 1     ; 16-bit
mov eax, 1    ; 32-bit (¡pone a cero upper 32!)
mov rax, 1    ; 64-bit

; NASM usa byte/word/dword/qword ptr
; MASM usa PTR: mov [x], DWORD PTR 0

Sufijos: byte=1, word=2, dword=4, qword=8 bytes. Escribir en EAX pone a cero automáticamente los 32 bits superiores de RAX. En NASM, el tamaño viene del registro o es explícito.

Dados e Memória


10 cards
Definir variables
section .data
    ; Strings:
    nombre: db "Ana", 0        ; null-terminated
    msg:    db "Hola", 10      ; con newline

    ; Numéricos:
    edad:   db 30              ; byte
    anio:   dw 2024            ; word (2 bytes)
    pop:    dd 7000000         ; dword (4 bytes)
    big:    dq 9999999999      ; qword (8 bytes)

    ; Float:
    pi:     dd 3.14159         ; float 32-bit
    e:      dq 2.718281828     ; double 64-bit

    ; Arrays:
    vector: dd 1, 2, 3, 4, 5

db = byte/string, dw = word, dd = dword/float, dq = qword/double. Los strings son arrays de bytes. 0 al final = null-terminated. Los arrays son datos contiguos.

Structs en Assembly
; Simular un struct con offsets:
; struct Persona { char nombre[32]; int edad; }
STRUC Persona
    .nombre: resb 32
    .edad:   resd 1
    .size:
ENDSTRUC

section .bss
    p: resb Persona.size

section .text
    ; Acceso:
    mov dword [p + Persona.edad], 30
    lea rdi, [p + Persona.nombre]

    ; Offset manual:
    ; nombre = offset 0
    ; edad   = offset 32

STRUC/ENDSTRUC definen structs en NASM. Los campos tienen offsets automáticos (Persona.edad). Persona.size da el tamaño total. Acceso vía [base + offset]. Equivale a structs en C.

Aritmética de punteros
; Puntero en rdi, recorrer array:
mov rdi, arr        ; rdi = &arr[0]
mov rcx, 5          ; 5 elementos

.loop:
    mov eax, [rdi]  ; leer elemento actual
    add eax, 1      ; procesar
    mov [rdi], eax  ; escribir de vuelta
    add rdi, 4      ; siguiente (dword = +4)
    dec rcx
    jnz .loop

; Alternativa con índice:
xor rcx, rcx
.next:
    mov eax, [arr + rcx*4]
    inc rcx
    cmp rcx, 5
    jl .next

Los punteros avanzan por el tamaño del tipo: +4 para dword, +8 para qword. Alternativa: índice con [base + idx*escala]. Ambos equivalentes. Un puntero es más natural en Assembly.

BSS (reservar espacio)
section .bss
    ; Reservar sin inicializar (cero):
    buffer:   resb 256        ; 256 bytes
    contador: resd 1          ; 1 dword (4 bytes)
    matriz:   resq 100        ; 100 qwords (800 bytes)
    array:    resw 10         ; 10 words

; Ventajas del BSS:
; • No ocupa espacio en el binario
; • Inicializado a cero por el OS
; • Más rápido que .data para buffers

; Acceso:
mov byte [buffer], 'A'
mov dword [contador], 0

BSS reserva espacio sin valor inicial (cero por el OS). resb/resw/resd/resq reservan 1/2/4/8 bytes por elemento. No aumenta el binario. Ideal para buffers y arrays grandes.

Pila (push/pop)
; PUSH: decrementa RSP, escribe
push rax         ; RSP -= 8; [RSP] = RAX
push rbx
push rcx

; POP: lee, incrementa RSP
pop rcx          ; RCX = [RSP]; RSP += 8
pop rbx
pop rax

; Ver la cima sin quitar:
mov rax, [rsp]   ; peek

; Reservar espacio local:
sub rsp, 32      ; 32 bytes para locales
; ... usar [rsp], [rsp+8], etc.
add rsp, 32      ; liberar (epilogue)

PUSH coloca en la pila (RSP -= 8). POP quita (RSP += 8). LIFO: último en entrar, primero en salir. La pila crece hacia abajo (direcciones menores). sub rsp, N reserva espacio para variables locales.

Datos alineados
section .data
    align 16            ; alinear a 16 bytes
    vec4: dd 1.0, 2.0, 3.0, 4.0

    align 32            ; para AVX
    vec8: dd 1.0, 2.0, 3.0, 4.0
          dd 5.0, 6.0, 7.0, 8.0

section .bss
    alignb 16
    buffer: resb 128

; Alinear la pila (obligatorio en llamadas):
and rsp, -16        ; alinear RSP a 16 bytes
; ... llamar funciones ...

; Por qué: SIMD requiere alineación
; ¡movaps falla si no está alineado!

align 16 alinea datos a 16 bytes (SSE). align 32 para AVX. and rsp, -16 alinea la pila antes de llamadas. movaps falla con segfault si está desalineado. Rendimiento: cache lines de 64 bytes.

Arrays e indexación
section .data
    arr: dd 10, 20, 30, 40, 50

section .text
    ; Acceso por índice:
    mov rax, [arr]            ; arr[0] = 10
    mov rax, [arr + 4]        ; arr[1] = 20
    mov rax, [arr + 8]        ; arr[2] = 30

    ; Con índice variable (i en rcx):
    mov rcx, 2
    mov eax, [arr + rcx*4]   ; arr[2] = 30

    ; Escritura:
    mov dword [arr + rcx*4], 99  ; arr[2] = 99

    ; Tamaño:
    len: equ ($ - arr) / 4   ; 5 elementos

Los arrays son memoria contigua. [arr + idx*4] para dword (4 bytes por elemento). Escala: *1 (byte), *2 (word), *4 (dword), *8 (qword). $ - arr da el tamaño en bytes.

Acceso a memoria
; Leer de memoria:
mov rax, [variable]        ; 8 bytes
mov eax, [variable]        ; 4 bytes
mov al, byte [variable]    ; 1 byte

; Escribir en memoria:
mov [variable], rax
mov byte [buffer + 5], 'X'

; Direccionamiento RIP-relative (64-bit):
mov rax, [rel contador]    ; posición-independiente

; Con desplazamiento negativo:
mov rax, [rbp - 8]        ; variable local
mov rax, [rbp + 16]       ; argumento

Los corchetes [] = acceso a memoria. Sin corchetes = valor inmediato o registro. [rel var] es RIP-relative (código posición-independiente). [rbp-N] para locales, [rbp+N] para argumentos.

LEA (cargar dirección)
; LEA calcula la dirección SIN acceder a la memoria:
lea rax, [rbx + rcx*4 + 8]

; Uso para aritmética (¡rápido!):
lea rax, [rax + rax*4]    ; rax = rax * 5
lea rax, [rax*8 + rax]    ; rax = rax * 9
lea rax, [rax + rax*2]    ; rax = rax * 3

; vs MOV (accede a memoria):
mov rax, [rbx + rcx*4]    ; carga VALOR
lea rax, [rbx + rcx*4]    ; carga DIRECCIÓN

; Puntero a una variable:
lea rdi, [rel msg]        ; dirección de msg

LEA calcula la dirección sin leer memoria. Más rápido que MOV para aritmética (1 ciclo). Multiplica por constantes: [rax+rax*4] = ×5. No afecta flags. Muy usado para &variable.

Constantes y EQU
; Constantes (no ocupan memoria):
MAX:     equ 100
NEWLINE: equ 10
NULL:    equ 0
STDIN:   equ 0
STDOUT:  equ 1
STDERR:  equ 2

; Cálculo en compilación:
TAM:     equ 1024 * 4
OFFSET:  equ 8 + 16

; Uso:
mov rcx, MAX
cmp rax, TAM

; %define (macro, redefinible):
%define VERSION 2

equ define constantes en tiempo de ensamblado (sin memoria). Sustituidas textualmente. %define es redefinible (macro). Ideal para syscalls, tamaños y offsets. No confundir con variables en .data.

Aritmética e Lógica


10 cards
ADD y SUB
add rax, 10         ; rax += 10
add rax, rbx        ; rax += rbx
sub rax, 5          ; rax -= 5
sub rax, rcx        ; rax -= rcx

; Flags afectadas: ZF, CF, SF, OF
add rax, rbx
; ZF=1 si resultado=0
; CF=1 si overflow unsigned
; SF=1 si bit 63=1 (negativo)
; OF=1 si overflow signed

; ADD con memoria:
add dword [contador], 1
sub qword [saldo], rax

ADD suma, SUB resta. Ambos afectan flags: ZF (cero), CF (carry), SF (signo), OF (overflow). ADD reg, mem y ADD mem, reg son válidos.

AND, OR, XOR, NOT
and rax, 0xFF       ; máscara (mantener 8 bits)
and rax, -16        ; alinear a 16 (limpiar 4 bits)

or rax, 0x01        ; establecer bit 0
or rax, rbx         ; combinar bits

xor rax, rax        ; ¡poner a cero (más rápido que mov!)
xor eax, eax        ; pone a cero el RAX entero (32→64)

not rax             ; invertir todos los bits

; Probar un bit:
test rax, 0x04      ; ¿está el bit 2 establecido?
jnz .bit_set        ; ZF=0 si bit=1

AND = máscara, OR = establecer bits, XOR = toggle/poner a cero, NOT = invertir. xor rax,rax es la forma más rápida de poner a cero (1 byte, 1 ciclo). TEST = AND sin guardar el resultado.

ADC y SBB (carry)
; ADC: suma + carry flag
; Para suma de 128 bits:
mov rax, [num1]
add rax, [num2]       ; suma low
mov rdx, [num1+8]
adc rdx, [num2+8]     ; suma high + carry

; SBB: resta + borrow
mov rax, [num1]
sub rax, [num2]       ; sub low
mov rdx, [num1+8]
sbb rdx, [num2+8]     ; sub high - borrow

; Uso: aritmética de precisión arbitraria
; (números mayores que 64 bits)

ADC = ADD + Carry Flag. SBB = SUB + Borrow. Usados para aritmética multi-precisión (128+ bits). Patrón: ADD en el low, ADC en el high. Esencial para criptografía y big numbers.

MUL e IMUL
; MUL (unsigned): resultado en RDX:RAX
mov rax, 100
mov rbx, 200
mul rbx         ; RDX:RAX = RAX * RBX

; IMUL (signed, más versátil):
imul rax, rbx           ; rax *= rbx
imul rax, rbx, 10       ; rax = rbx * 10
imul rax, rcx           ; rax = rax * rcx

; 32-bit: resultado en EDX:EAX
mov eax, 50000
mov ebx, 60000
mul ebx         ; EDX:EAX = EAX * EBX

; Overflow: CF=OF=1 si el resultado no cabe

MUL = unsigned, resultado en RDX:RAX (128 bits). IMUL = signed, más flexible (2 o 3 operandos). Para multiplicar por potencias de 2, usa SHL (más rápido).

Shifts (SHL, SHR, SAR)
shl rax, 1      ; rax *= 2 (shift left)
shl rax, 4      ; rax *= 16
shr rax, 1      ; rax /= 2 (unsigned)
shr rax, 3      ; rax /= 8

sar rax, 1      ; shift aritmético (preserva signo)
; sar -8, 1 = -4 (correcto para negativos)
; shr -8, 1 = valor enorme (¡incorrecto!)

; Rotación:
rol rax, 1      ; rotate left (bit sale → entra)
ror rax, 4      ; rotate right

; SHLD/SHRD: shift doble
shld rax, rbx, 4  ; shift left con bits de rbx

SHL = ×2^n, SHR = ÷2^n (unsigned). SAR preserva el signo (para negativos). ROL/ROR rotan bits. Los shifts son mucho más rápidos que MUL/DIV para potencias de 2.

BSR, BSF, POPCNT
; Bit Scan:
bsf rcx, rax      ; índice del primer bit 1 (low)
bsr rcx, rax      ; índice del último bit 1 (high)
; ZF=1 si input=0

; Popcount (contar bits 1):
popcnt rcx, rax   ; rcx = nº de bits a 1
; Requiere: -mpopcnt o CPUID check

; Contar ceros:
lzcnt rcx, rax    ; leading zeros
tzcnt rcx, rax    ; trailing zeros (= bsf)

; Extraer un bit:
bt rax, 5         ; CF = bit 5 de rax
bts rax, 5        ; establece bit 5
btr rax, 5        ; limpia bit 5
btc rax, 5        ; toggle bit 5

BSF/BSR encuentran el primer/último bit 1. POPCNT cuenta bits activos. LZCNT/TZCNT cuentan ceros. BT/BTS/BTR manipulan bits individuales. Útiles para bitmaps y flags.

DIV e IDIV
; DIV (unsigned):
; Divide RDX:RAX por el operando
; Cociente → RAX, Resto → RDX
mov rax, 100
xor rdx, rdx       ; ¡PONER A CERO RDX primero!
mov rbx, 7
div rbx            ; rax=14, rdx=2

; IDIV (signed):
mov rax, -100
cqo                ; sign-extend RAX → RDX:RAX
mov rbx, 7
idiv rbx           ; rax=-14, rdx=-2

; 32-bit: cdq antes de div
; ¡Cuidado: div por 0 = excepción #DE!

DIV divide RDX:RAX por el operando. Cociente en RAX, resto en RDX. Siempre poner a cero RDX (xor rdx,rdx) o sign-extend (cqo). La división por 0 causa una excepción.

CMP y TEST
; CMP: resta sin guardar (solo flags)
cmp rax, 10
; ZF=1 si rax==10
; CF=1 si rax<10 (unsigned)
; SF≠OF si rax<10 (signed)

cmp rax, rbx
je  .igual      ; ZF=1
jl  .menor      ; SF≠OF (signed)
ja  .mayor      ; CF=0 y ZF=0 (unsigned)

; TEST: AND sin guardar (solo flags)
test rax, rax   ; ¿es rax cero?
jz  .nulo       ; ZF=1 si rax=0

test rax, 1     ; ¿es impar?
jnz .impar      ; bit 0 = 1

CMP compara (hace SUB sin guardar). TEST verifica bits (hace AND sin guardar). Ambos solo afectan flags. Seguidos de saltos condicionales. test rax,rax + jz = verificar null/cero.

INC, DEC y NEG
inc rax         ; rax++ (¡no afecta CF!)
dec rcx         ; rcx-- (¡no afecta CF!)

neg rax         ; rax = -rax (complemento a 2)
; neg: CF=0 si input=0, CF=1 en caso contrario

; Alternativas que afectan CF:
add rax, 1      ; como inc, pero afecta CF
sub rax, 1      ; como dec, pero afecta CF

; Uso típico:
mov rcx, 10
.loop:
    ; procesar
    dec rcx
    jnz .loop   ; ZF=1 cuando rcx=0

INC/DEC incrementan/decrementan sin afectar CF (útil tras ADC/SBB). NEG niega (complemento a 2). En loops, DEC + JNZ es el patrón. ADD/SUB 1 si necesitas CF.

Conversiones de tipo
; Extensión con signo:
movsx eax, byte [x]    ; int8 → int32
movsx rax, dword [y]   ; int32 → int64
movsxd rax, eax        ; int32 → int64
cwde                   ; AX → EAX (sign)
cdqe                   ; EAX → RAX (sign)

; Extensión con cero:
movzx eax, byte [x]    ; uint8 → uint32
movzx rax, word [y]    ; uint16 → uint64

; Preparar división:
xor rdx, rdx           ; poner a cero RDX (unsigned)
cqo                    ; RDX:RAX = sign-extend (signed)
cdq                    ; EDX:EAX = sign-extend (32-bit)

MOVSX extiende con signo (correcto para negativos). MOVZX extiende con ceros (unsigned). CQO/CDQ preparan RDX para la división. Usa siempre la extensión correcta para evitar bugs.

Controlo de Fluxo


10 cards
JMP (incondicional)
; Salto incondicional:
jmp .fin            ; label local
jmp inicio          ; label global
jmp rax             ; indirecto (dirección en rax)
jmp [rax]           ; indirecto vía memoria

; Labels:
inicio:
    ; código
.fin:
    ; código

; Corto vs largo:
jmp short .cerca    ; -128 a +127 bytes (2 bytes)
jmp near .lejos     ; ±2GB (5 bytes)
; NASM elige automáticamente

JMP salta incondicionalmente (como goto). jmp label = directo. jmp reg = indirecto (tablas de salto, vtables). short = 2 bytes (limitado). Labels locales con prefijo ..

Instrucción LOOP
; LOOP: decrementa RCX, salta si != 0
mov rcx, 5
.loop:
    ; cuerpo (ejecuta 5 veces)
    mov rax, rcx
    loop .loop      ; rcx--; if rcx!=0 jmp

; Variantes:
loope .loop     ; loop si RCX!=0 Y ZF=1
loopz .loop     ; sinónimo de LOOPE
loopne .loop    ; loop si RCX!=0 Y ZF=0
loopnz .loop    ; sinónimo de LOOPNE

; Nota: ¡LOOP es lento en CPUs modernas!
; Preferir: dec rcx / jnz .loop

LOOP = DEC RCX + JNZ implícito. LOOPE/LOOPNE verifican ZF también. En CPUs modernas, LOOP es más lento que DEC+JNZ (microcode). Usar solo por legibilidad en código educativo.

Ternario (CMOV)
; rax = (rbx > 10) ? 1 : 0

; Con jumps:
    cmp rbx, 10
    jle .cero
    mov rax, 1
    jmp .fin
.cero:
    xor rax, rax
.fin:

; Con CMOV (sin branch, más rápido):
    xor rax, rax        ; rax = 0 (default)
    mov rcx, 1
    cmp rbx, 10
    cmovg rax, rcx      ; si >, rax = 1

; CMOVcc: move si condición (¡sin salto!)
; cmovl, cmovg, cmove, cmovne, cmova...

CMOVcc = move condicional sin branch. Evita la misprediction del branch predictor. cmovg = move if greater. Ideal para ternarios simples. Sin branch = el pipeline no se detiene. Prefiérelo para expresiones simples.

Saltos condicionales (igualdad)
cmp rax, 10

je  .igual      ; jump if equal (ZF=1)
jne .diferente  ; jump if not equal (ZF=0)

; Sinónimos:
jz  .cero       ; jump if zero (ZF=1) = JE
jnz .no_cero    ; jump if not zero (ZF=0) = JNE

; Tras TEST:
test rax, rax
jz  .nulo       ; rax == 0?
jnz .válido     ; rax != 0?

; Verificar un bit:
test rax, 0x04
jnz .bit2_set   ; ¿bit 2 establecido?

JE/JNE = igual/diferente (tras CMP). JZ/JNZ = cero/no-cero (tras TEST). Son sinónimos (mismo opcode, ZF). Usa el nombre que tenga más sentido en el contexto.

IF/ELSE en Assembly
; if (rax > 10) { rbx = 1 } else { rbx = 0 }

    cmp rax, 10
    jle .else       ; si rax <= 10, va a else

.then:
    mov rbx, 1
    jmp .fin        ; ¡salta el else!

.else:
    mov rbx, 0

.fin:
    ; continúa aquí

; Patrón: la condición NEGADA salta a else
; jmp .fin en then evita ejecutar el else

IF/ELSE: niega la condición y salta a .else. En el bloque then, JMP .fin para no caer en el else. Los labels con . son locales. Equivale a if (cond) {...} else {...} en C.

SETcc (byte condicional)
; SETcc: pone un byte a 0 o 1
cmp rax, rbx
sete al         ; al = 1 si igual, 0 si no
movzx rax, al   ; extender a 64 bits

setl cl         ; cl = 1 si signed less
setg dl         ; dl = 1 si signed greater
flecha bl         ; bl = 1 si unsigned above
setne sil       ; sil = 1 si no igual

; Booleano → entero:
xor eax, eax
cmp rcx, 0
setnz al        ; al = (rcx != 0) ? 1 : 0

; Combinar con aritmética:
; rax += (rbx > 5)
cmp rbx, 5
setg cl
movzx rcx, cl
add rax, rcx

SETcc pone un byte (AL, CL, etc.) a 0 o 1 según las flags. Seguido de MOVZX para usarlo como entero. Sin branches. Útil para contar condiciones y booleanos. Variantes: SETE, SETL, SETG, SETA.

Saltos condicionales (comparación)
; SIGNED (tras CMP):
jl  .menor      ; jump if less (SF≠OF)
jle .menor_igual ; less or equal (ZF=1 o SF≠OF)
jg  .mayor      ; jump if greater (ZF=0 y SF=OF)
jge .mayor_igual ; greater or equal (SF=OF)

; UNSIGNED (tras CMP):
jb  .debajo     ; jump if below (CF=1)
jbe .debajo_igual ; below or equal (CF=1 o ZF=1)
ja  .encima     ; jump if above (CF=0 y ZF=0)
jae .encima_igual ; above or equal (CF=0)

; Mnemónicos: L/G = signed, B/A = unsigned

Signed: JL/JG/JLE/JGE. Unsigned: JB/JA/JBE/JAE. L/G para negativos correctos. B/A para direcciones/tamaños. Siempre tras CMP.

Switch/Case (tabla de saltos)
section .data
    jump_table: dq .case0, .case1, .case2, .case3

section .text
    ; rax = índice (0-3)
    cmp rax, 3
    ja  .default        ; fuera del rango

    jmp [jump_table + rax*8]  ; salto indirecto

.case0:
    mov rbx, 10
    jmp .fin
.case1:
    mov rbx, 20
    jmp .fin
.case2:
    mov rbx, 30
    jmp .fin
.default:
    mov rbx, 0
.fin:

Switch usa una tabla de direcciones + JMP [tabla + idx*8]. Verifica el rango primero (CMP + JA). Cada case termina con JMP .fin (evita fall-through). O(1) para cualquier case. Como una vtable.

Loops con DEC/JNZ
; Loop N veces (patrón):
mov rcx, 10         ; contador
.loop:
    ; cuerpo del loop
    mov rax, rcx    ; usar contador
    dec rcx         ; rcx--
    jnz .loop       ; repite si rcx != 0

; Loop con índice creciente:
xor rcx, rcx        ; i = 0
.next:
    mov rax, [arr + rcx*4]
    ; procesar
    inc rcx
    cmp rcx, 10
    jl .next        ; mientras i < 10

; Loop infinito:
.inf:
    jmp .inf        ; o: jmp $

Patrón: DEC + JNZ (countdown). Alternativa: INC + CMP + JL (countup). JMP $ = loop infinito (salta a sí mismo). El countdown es más eficiente (menos instrucciones).

Cortocircuito (&amp;&amp;, ||)
; if (a > 0 && b > 0) { ... }
    cmp rax, 0
    jle .fin        ; si a<=0, FALSO (cortocircuito)
    cmp rbx, 0
    jle .fin        ; si b<=0, FALSO
    ; ambos verdaderos:
    ; cuerpo del if
.fin:

; if (a == 1 || b == 2) { ... }
    cmp rax, 1
    je  .verdadero  ; si a==1, VERDADERO (cortocircuito)
    cmp rbx, 2
    je  .verdadero  ; si b==2, VERDADERO
    jmp .fin
.verdadero:
    ; cuerpo del if
.fin:

&&: salta al fin en la primera condición falsa. ||: salta al cuerpo en la primera verdadera. Cortocircuito: la segunda condición solo se evalúa si es necesario. El mismo patrón que generan los compiladores C.

Funções e Stack


10 cards
CALL y RET
; CALL: push RIP, salta a la función
call mi_funcion       ; llama
; ... continúa aquí tras RET

; RET: pop RIP, vuelve al caller
mi_funcion:
    mov rax, 42       ; valor de retorno
    ret               ; vuelve

; CALL indirecto:
call rax              ; dirección en registro
call [rax]            ; dirección en memoria

; RET con limpieza (stdcall, raro en x64):
ret 8                 ; pop + quita 8 bytes

CALL guarda la dirección de retorno en la pila y salta. RET hace pop de esa dirección y vuelve. RAX es el registro de retorno. En x86-64, CALL es siempre relativo (±2GB).

Variables locales
func:
    push rbp
    mov rbp, rsp
    sub rsp, 48       ; 6 qwords locales

    ; Locales (offsets negativos de RBP):
    mov [rbp-8], rdi      ; local1 = arg1
    mov [rbp-16], 0       ; local2 = 0
    mov qword [rbp-24], 100 ; local3 = 100

    ; Array local:
    ; [rbp-32] a [rbp-48] = 2 qwords

    ; Acceso:
    mov rax, [rbp-8]      ; leer local1
    add rax, [rbp-24]     ; local1 + local3

    leave
    ret

Las variables locales viven en [rbp-N] (offsets negativos). Reservar con sub rsp, N (¡múltiplo de 16!). El compilador aloca todo de una vez. El acceso es relativo a RBP (estable incluso con push/pop).

Funciones variádicas
; printf(fmt, ...) — args variables
; RAX = nº de registros XMM usados (0-8)

section .data
    fmt: db "x=%d, y=%f", 10, 0

section .text
    extern printf

    mov rdi, fmt        ; formato
    mov esi, 42         ; int arg
    movq xmm0, [pi]    ; double arg
    mov eax, 1          ; ¡1 registro XMM usado!
    call printf

; AL (RAX low) = conteo de args XMM
; ¡Si lo olvidas → crash o basura!
; Para enteros puros: xor eax, eax

Las funciones variádicas (printf, scanf) exigen AL = el número de args en XMM. Olvidarlo causa un crash. Enteros en RDI, RSI, RDX... Floats en XMM0-7. Siempre xor eax,eax si no hay floats.

Stack frame (prologue/epilogue)
mi_funcion:
    ; Prologue:
    push rbp          ; guardar frame anterior
    mov rbp, rsp      ; nuevo frame pointer
    sub rsp, 32       ; espacio para locales

    ; Cuerpo:
    mov [rbp-8], rdi   ; local 1 (arg1)
    mov [rbp-16], rsi  ; local 2 (arg2)

    ; Epilogue:
    mov rsp, rbp      ; liberar locales
    pop rbp           ; restaurar frame
    ret

; Alternativa (leave = mov rsp,rbp + pop rbp):
    leave
    ret

Prologue: push rbp + mov rbp, rsp + sub rsp, N. Epilogue: leave + ret. RBP es estable durante la función. Locales en [rbp-N], args en [rbp+16] y superiores.

Recursión
; factorial(n): si n<=1 retorna 1, si no n*factorial(n-1)
factorial:
    cmp rdi, 1
    jle .base           ; caso base

    push rdi            ; guardar n (¡caller-saved!)
    dec rdi             ; n - 1
    call factorial      ; recursión
    pop rdi             ; restaurar n

    imul rax, rdi       ; n * factorial(n-1)
    ret

.base:
    mov rax, 1
    ret

; ¡Cuidado: stack overflow con N grande!
; Cada llamada usa ~16+ bytes de stack

Recursión: guarda los registros necesarios con PUSH antes del CALL (¡son caller-saved!). Restaura con POP después. El caso base evita la recursión infinita. Cada llamada consume stack — límite práctico ~8MB.

Llamar funciones C (extern)
; Declarar una función externa:
extern printf
extern malloc
extern strlen

section .data
    msg: db "Valor: %d", 10, 0

section .text
    global main

main:
    push rbp
    mov rbp, rsp

    ; printf("Valor: %d", 42)
    mov rdi, msg        ; arg1: formato
    mov esi, 42         ; arg2: valor
    xor eax, eax        ; 0 xmm args
    call printf wrt ..plt  ; PIC (Linux)

    ; return 0
    xor eax, eax
    pop rbp
    ret

; Enlazar: gcc main.o -o app -no-pie

extern declara funciones C. Llama con argumentos en RDI, RSI, RDX... wrt ..plt para código PIC (position-independent). Enlaza con gcc (no ld). xor eax,eax antes de variádicas.

Argumentos (System V AMD64)
; Convención Linux/macOS (System V):
; Enteros/punteros: RDI, RSI, RDX, RCX, R8, R9
; Floats: XMM0-XMM7
; Retorno: RAX (int), XMM0 (float)
; Extras: en la pila (derecha → izquierda)

; Ejemplo: func(a, b, c, d, e, f, g)
; a=RDI, b=RSI, c=RDX, d=RCX, e=R8, f=R9
; g → [rsp] (en la pila)

; Windows (Microsoft x64):
; RCX, RDX, R8, R9 (¡solo 4 en registros!)
; Shadow space: 32 bytes obligatorios

Linux: 6 args enteros en RDI, RSI, RDX, RCX, R8, R9. Retorno en RAX. Windows usa RCX, RDX, R8, R9 + shadow space. Floats en XMM0-7.

Leaf function (sin CALL)
; Leaf = no llama a otras funciones
; ¡No necesita un prologue completo!

; Versión mínima:
suma:
    lea rax, [rdi + rsi]  ; rax = a + b
    ret

; Con red zone (128 bytes libres):
; Debajo de RSP, sin push, seguro en leaf
leaf_func:
    mov [rsp-8], rdi    ; ¡red zone!
    mov [rsp-16], rsi
    ; procesar sin sub rsp
    ret

; Red zone: 128 bytes debajo de RSP
; Solo seguro si NO llama a funciones
; (los signal handlers pueden corromperla)

Una leaf function no hace CALL — puede omitir el prologue. Red zone: 128 bytes debajo de RSP son seguros en un leaf (System V). Sin push/pop = más rápido. Ideal para funciones simples (getters, cálculos).

Callee-saved vs Caller-saved
; Callee-saved (¡preservar si los usas!):
; RBX, RBP, R12, R13, R14, R15
; (también RSP implícitamente)

; Caller-saved (pueden ser alterados):
; RAX, RCX, RDX, RSI, RDI, R8-R11

; Ejemplo: función que usa RBX
func:
    push rbx          ; ¡preservar!
    mov rbx, rdi      ; usar rbx
    call otra_func    ; rbx seguro
    mov rax, rbx      ; aún válido
    pop rbx           ; restaurar
    ret

; ¡Si no preservas → bug intermitente!

Callee-saved: RBX, RBP, R12-R15 — la función llamada DEBE preservarlos. Caller-saved: RAX, RCX, RDX, RSI, RDI, R8-R11 — pueden ser destruidos por un CALL. Siempre push/pop de los callee-saved.

Alineación de stack
; El ABI exige RSP alineado a 16 bytes
; ANTES de un CALL (tras CALL, RSP%16 = 8)

; Verificar:
; rsp % 16 == 0 antes de call → correcto

; Si está desalineado:
sub rsp, 8          ; alinear (si es necesario)
call func
add rsp, 8          ; restaurar

; Al entrar en la función (tras CALL):
; RSP % 16 == 8 (el CALL hizo push de 8)
; push rbp → RSP % 16 == 0 ✓

; Por qué: movaps/movdqa requieren alineación
; SSE/AVX fallan con una stack desalineada

La stack DEBE estar alineada a 16 bytes antes de un CALL. Tras CALL, RSP%16=8. El prologue (push rbp) la realinea. Si haces sub rsp, N, N debe ser múltiplo de 16. movaps causa un segfault si está desalineado.

System Calls


10 cards
Syscall (visión general)
; Syscalls Linux x86-64:
; RAX = número de la syscall
; RDI, RSI, RDX, R10, R8, R9 = argumentos
; syscall → invoca al kernel
; Retorno en RAX (negativo = error)

; Syscalls comunes:
; 0  = read
; 1  = write
; 2  = open
; 3  = close
; 39 = getpid
; 60 = exit
; 63 = uname

; Tabla completa:
; /usr/include/asm/unistd_64.h

syscall invoca al kernel Linux. Número en RAX, args en RDI, RSI, RDX, R10, R8, R9. Retorno en RAX (negativo = errno). Destruye RCX y R11.

sys_open y sys_close
section .data
    filename: db "/tmp/prueba.txt", 0

section .text
    ; open(filename, O_WRONLY|O_CREAT, 0644)
    mov rax, 2              ; sys_open
    lea rdi, [rel filename]
    mov rsi, 0x241          ; O_WRONLY|O_CREAT|O_TRUNC
    mov rdx, 0644o          ; permisos (octal)
    syscall
    ; rax = file descriptor (o -errno)

    mov rdi, rax            ; guardar el fd

    ; ... escribir en el fichero ...

    ; close(fd)
    mov rax, 3              ; sys_close
    ; rdi ya tiene el fd
    syscall

; Flags: O_RDONLY=0, O_WRONLY=1, O_RDWR=2
; O_CREAT=0100, O_TRUNC=01000, O_APPEND=02000

sys_open (rax=2): path, flags, modo. Retorna fd (≥0) o -errno. sys_close (rax=3): fd en rdi. Flags: O_RDONLY=0, O_WRONLY=1, O_CREAT=0100. ¡Siempre cerrar fds!

sys_execve
section .data
    path: db "/bin/echo", 0
    arg0: db "echo", 0
    arg1: db "¡Hola!", 0

section .text
    ; execve(path, argv, envp)
    mov rax, 59             ; sys_execve
    lea rdi, [rel path]     ; filename

    ; argv = {"echo", "¡Hola!", NULL}
    lea rsi, [rel argv]     ; puntero al array

    ; envp = NULL (sin entorno)
    xor rdx, rdx

    syscall
    ; Si retorna → ¡ERROR! (execve no vuelve)

section .data
    argv: dq arg0, arg1, 0  ; array de punteros

sys_execve (59) reemplaza el proceso por otro. rdi=path, rsi=argv (array de punteros, NULL-terminated), rdx=envp. Si retorna, es un error. Nunca vuelve en caso de éxito. Combinar con fork.

sys_write (imprimir)
section .data
    msg: db "¡Hola, Mundo!", 10
    len: equ $ - msg

section .text
    global _start
_start:
    ; write(1, msg, len)
    mov rax, 1          ; sys_write
    mov rdi, 1          ; fd = stdout
    lea rsi, [rel msg]  ; buffer
    mov rdx, len        ; tamaño
    syscall

    ; rax = bytes escritos (o -errno)
    cmp rax, 0
    jl  .error          ; verificar error

    mov rax, 60         ; sys_exit
    xor rdi, rdi
    syscall
.error:
    mov rax, 60
    mov rdi, 1          ; exit code 1
    syscall

sys_write (rax=1): rdi=fd (1=stdout, 2=stderr), rsi=buffer, rdx=tamaño. Retorna bytes escritos. Verificar rax < 0 para errores. No añade un newline automáticamente.

sys_mmap (asignar memoria)
; mmap(NULL, size, PROT, FLAGS, fd, offset)
mov rax, 9              ; sys_mmap
xor rdi, rdi            ; addr = NULL (el kernel elige)
mov rsi, 4096           ; size = 4KB
mov rdx, 3              ; PROT_READ|PROT_WRITE
mov r10, 0x22           ; MAP_PRIVATE|MAP_ANONYMOUS
mov r8, -1              ; fd = -1 (anónimo)
xor r9, r9              ; offset = 0
syscall
; rax = dirección de memoria (o -errno)

; Usar:
mov [rax], byte 'A'    ; escribir

; Liberar: munmap(addr, size)
mov rax, 11             ; sys_munmap
mov rsi, 4096
syscall

; PROT: READ=1, WRITE=2, EXEC=4
; MAP: SHARED=0x01, PRIVATE=0x02, ANON=0x20

sys_mmap (rax=9) asigna memoria. MAP_ANONYMOUS = sin fichero. Retorna una dirección o -errno. sys_munmap (rax=11) la libera. Equivale a malloc (que usa mmap internamente). Páginas de 4096 bytes.

Manejo de errores
; Una syscall retorna -errno en RAX
; (valores negativos: -1 a -4095)

mov rax, 2          ; sys_open
lea rdi, [rel path]
xor esi, esi        ; O_RDONLY
syscall

cmp rax, 0
jl  .error          ; negativo = error
mov rdi, rax        ; fd válido
jmp .continuar

.error:
    neg rax         ; rax = errno positivo
    ; errno comunes:
    ; 2  = ENOENT (el fichero no existe)
    ; 13 = EACCES (permiso denegado)
    ; 9  = EBADF (fd inválido)

    ; Escribir el error en stderr:
    mov rax, 1      ; sys_write
    mov rdi, 2      ; stderr
    lea rsi, [rel errmsg]
    mov rdx, errmsg_len
    syscall

Las syscalls retornan -errno en RAX (negativo = error). Verificar con cmp rax, 0 + jl. neg rax da el errno positivo. Comunes: 2=ENOENT, 13=EACCES, 9=EBADF. ¡Siempre verificar en producción!

sys_read (leer input)
section .bss
    buffer: resb 256

section .text
    ; read(0, buffer, 256)
    mov rax, 0          ; sys_read
    mov rdi, 0          ; fd = stdin
    lea rsi, [rel buffer]
    mov rdx, 256        ; max bytes
    syscall

    ; rax = bytes leídos (incluye \n)
    ; rax = 0 → EOF
    ; rax < 0 → error

    ; Quitar el newline:
    lea rdi, [rel buffer]
    add rdi, rax        ; posición del \n
    dec rdi
    mov byte [rdi], 0   ; reemplazar por NUL

sys_read (rax=0): rdi=fd (0=stdin), rsi=buffer, rdx=máximo. Retorna bytes leídos (incluye \n). rax=0 = EOF. Buffer en BSS. Quita el newline reemplazándolo por NUL.

sys_getpid y sys_getuid
; getpid() → PID del proceso
mov rax, 39         ; sys_getpid
syscall
; rax = PID (siempre positivo)

; getppid() → PID del padre
mov rax, 110        ; sys_getppid
syscall

; getuid() → user ID
mov rax, 102        ; sys_getuid
syscall
; rax = UID (0=root)

; getgid() → group ID
mov rax, 104        ; sys_getgid
syscall

; gettid() → thread ID
mov rax, 186        ; sys_gettid
syscall

; Útil para: logs, nombres de ficheros temp,
; verificación de permisos, debugging

sys_getpid (39) retorna el PID. sys_getuid (102) retorna el UID (0=root). Sin argumentos — solo RAX con el número. Útiles para logs, ficheros temporales y verificación de permisos.

sys_exit
; Terminar el programa:
mov rax, 60         ; sys_exit
mov rdi, 0          ; exit code (0=éxito)
syscall

; Exit codes convencionales:
; 0   = éxito
; 1   = error genérico
; 2   = uso incorrecto (bash)
; 126 = sin permiso
; 127 = comando no encontrado
; 128+N = matado por la señal N
; 139 = segfault (128+11)

; Alternativa (libc):
; call exit (no retorna)
; Nunca retorna — el código posterior es inalcanzable

sys_exit (rax=60): rdi = código de salida. 0=éxito, 1=error. El kernel termina el proceso y libera recursos. El código tras un syscall de exit nunca se ejecuta. En C: equivale a exit(code).

sys_fork y sys_wait4
; fork() → crea un proceso hijo
mov rax, 57         ; sys_fork
syscall
; rax = 0 → proceso hijo
; rax > 0 → proceso padre (rax = PID del hijo)
; rax < 0 → error

test rax, rax
jz  .hijo

.padre:
    ; esperar al hijo
    mov rax, 61         ; sys_wait4
    mov rdi, -1         ; cualquier hijo
    xor rsi, rsi        ; status (NULL)
    xor rdx, rdx        ; options
    xor r10, r10        ; rusage (NULL)
    syscall
    jmp .fin

.hijo:
    ; código del hijo
    mov rax, 60
    xor rdi, rdi
    syscall
.fin:

sys_fork (57) duplica el proceso. Retorna 0 en el hijo, el PID en el padre. sys_wait4 (61) espera al hijo. Patrón: verificar rax==0 para la rama hijo/padre. El hijo debe hacer exit o execve.

Strings


10 cards
REP MOVSB (copiar)
; Copiar N bytes de RSI → RDI
section .data
    src: db "Hello, World!", 0

section .bss
    dst: resb 64

section .text
    lea rsi, [rel src]   ; origen
    lea rdi, [rel dst]   ; destino
    mov rcx, 14          ; nº de bytes
    rep movsb            ; copia RCX bytes

; REP: repite hasta RCX=0
; MOVSB: mueve byte [RSI]→[RDI], inc ambos
; Dirección: DF=0 (cld) → creciente
;            DF=1 (std) → decreciente

; Variantes: rep movsw (word), rep movsq (qword)

REP MOVSB copia RCX bytes de [RSI] a [RDI]. REP decrementa RCX hasta 0. CLD = dirección creciente (por defecto). Equivale a memcpy. Variantes: MOVSW (2B), MOVSQ (8B).

strlen manual
; strlen: contar bytes hasta NUL
; Input: RDI = puntero a la string
; Output: RAX = longitud
my_strlen:
    xor eax, eax        ; contador = 0
.loop:
    cmp byte [rdi + rax], 0
    je  .fin            ; encontró NUL
    inc rax
    jmp .loop
.fin:
    ret

; Alternativa con REPNE SCASB:
my_strlen2:
    push rdi
    mov al, 0
    mov rcx, -1
    cld
    repne scasb
    pop rax
    sub rax, rdi        ; negativo
    neg rax             ; positivo
    dec rax             ; -1 (NUL)
    ret

strlen cuenta bytes hasta NUL. Método manual: loop con CMP byte, 0. Método rápido: repne scasb (optimizado por hardware). Input en RDI, retorno en RAX. No incluye el NUL en el conteo.

printf con argumentos
extern printf

section .data
    fmt1: db "Nombre: %s, Edad: %d", 10, 0
    fmt2: db "Pi = %.2f", 10, 0
    nombre: db "Ana", 0

section .text
    ; printf("Nombre: %s, Edad: %d", "Ana", 30)
    lea rdi, [rel fmt1]     ; formato
    lea rsi, [rel nombre]   ; %s → puntero
    mov edx, 30             ; %d → entero
    xor eax, eax            ; 0 xmm args
    call printf wrt ..plt

    ; printf("Pi = %.2f", 3.14)
    lea rdi, [rel fmt2]
    movq xmm0, [pi_val]    ; %f → XMM0
    mov eax, 1              ; ¡1 xmm arg!
    call printf wrt ..plt

section .data
    pi_val: dq 3.14

printf: formato en RDI, args en RSI, RDX, RCX... Floats en XMM0-7. EAX = nº de args XMM (¡obligatorio!). %s = puntero, %d = int. String con NUL al final.

LODSB / STOSB
; LODSB: carga byte de [RSI] → AL, RSI++
lea rsi, [rel texto]
lodsb               ; al = [rsi], rsi++
lodsb               ; al = siguiente byte

; STOSB: escribe AL → [RDI], RDI++
lea rdi, [rel buffer]
mov al, 'A'
stosb               ; [rdi] = 'A', rdi++

; REP STOSB: rellenar memoria (¡memset!)
lea rdi, [rel buffer]
mov al, 0           ; byte de relleno
mov rcx, 256        ; 256 bytes
rep stosb           ; memset(buffer, 0, 256)

; REP STOSQ: más rápido (8 bytes/iter)
mov rax, 0
mov rcx, 32         ; 32*8 = 256 bytes
rep stosq

LODSB lee un byte de [RSI] a AL. STOSB escribe AL en [RDI]. REP STOSB = memset. REP STOSQ rellena 8 bytes por iteración (más rápido). Siempre CLD antes.

strcpy manual
; strcpy: copiar hasta NUL (inclusive)
; Input: RDI = destino, RSI = origen
my_strcpy:
    push rdi            ; guardar el inicio (retorno)
.loop:
    lodsb               ; al = [rsi], rsi++
    stosb               ; [rdi] = al, rdi++
    test al, al         ; ¿es NUL?
    jnz .loop           ; si no, continúa
    pop rax             ; retorno = inicio de dst
    ret

; Alternativa con REP:
; (requiere strlen primero)
my_strcpy2:
    push rdi
    push rsi
    call my_strlen      ; rax = len (rsi)
    inc rax             ; incluir NUL
    mov rcx, rax
    pop rsi
    pop rdi
    rep movsb           ; copiar todo
    ret

strcpy copia bytes hasta NUL (inclusive). LODSB+STOSB en un loop. Retorna el puntero del destino (convención C). Alternativa: strlen + rep movsb. ¡Cuidado: el buffer destino debe ser suficiente!

Strings multibyte (UTF-8)
section .data
    ; UTF-8: "Hola" = 4 bytes (á = 2 bytes)
    msg: db "Ol", 0xC3, 0xA1, 0
    ; 0xC3 0xA1 = 'á' en UTF-8

    ; Emoji: "😀" = 4 bytes
    emoji: db 0xF0, 0x9F, 0x98, 0x80, 0

    ; Tamaño en bytes vs caracteres:
    len_bytes: equ $ - msg - 1  ; bytes
    ; ¡caracteres ≠ bytes para acentos!

section .text
    ; write no conoce UTF-8 — solo bytes
    mov rax, 1
    mov rdi, 1
    lea rsi, [rel msg]
    mov rdx, 5          ; 5 BYTES (no chars)
    syscall
    ; El terminal interpreta UTF-8 correctamente

Assembly trabaja con bytes, no caracteres. UTF-8: acentos = 2 bytes, emoji = 4 bytes. sys_write envía bytes — el terminal interpreta UTF-8. strlen cuenta bytes, no caracteres. ¡Cuidado con la indexación!

SCASB (buscar byte)
; SCASB: compara AL con [RDI], RDI++
; Usado para strlen y strchr

; strlen: contar hasta NUL
lea rdi, [rel string]
mov al, 0           ; buscar NUL
mov rcx, -1         ; máximo (infinito)
cld                 ; dirección creciente
repne scasb         ; repite mientras != AL

; RCX = -(strlen+2)
not rcx             ; invertir bits
dec rcx             ; strlen = ~rcx - 1
; o: neg rcx; sub rcx, 2

; strchr: buscar un carácter
mov al, 'o'         ; buscar 'o'
repne scasb
jnz .no_encontrado
; rdi-1 = posición del carácter

SCASB compara AL con [RDI]. REPNE = repite mientras NO igual. Para strlen: buscar NUL con repne scasb. Resultado: NOT RCX - 1 = longitud. REPE = mientras igual.

strcmp manual
; strcmp: comparar strings
; Retorno: 0=iguales, <0 si s1<s2, >0 si s1>s2
my_strcmp:
.loop:
    mov al, [rdi]       ; byte de s1
    mov bl, [rsi]       ; byte de s2
    cmp al, bl
    jne .diff           ; diferentes
    test al, al
    jz  .equal          ; ambos NUL → iguales
    inc rdi
    inc rsi
    jmp .loop
.diff:
    movzx eax, al
    movzx ebx, bl
    sub eax, ebx        ; negativo/positivo
    ret
.equal:
    xor eax, eax        ; 0 = iguales
    ret

; Uso:
; lea rdi, [rel str1]
; lea rsi, [rel str2]
; call my_strcmp

strcmp compara byte a byte hasta una diferencia o NUL. Retorna 0 (iguales), negativo (s1s2). movzx a unsigned antes de sub. Loop con inc en ambos punteros.

CMPSB (comparar strings)
; CMPSB: compara [RSI] con [RDI], ambos++
; Usado para strcmp/memcmp

; Comparar N bytes:
lea rsi, [rel str1]
lea rdi, [rel str2]
mov rcx, 10         ; comparar 10 bytes
cld
repe cmpsb          ; repite mientras iguales

; Resultado:
jz  .iguales        ; todos iguales (RCX=0)
; RSI-1 y RDI-1 = posición de la diferencia
; Flags: CF/SF indican el orden

; strcmp completo (hasta NUL):
lea rsi, [rel str1]
lea rdi, [rel str2]
.cmp_loop:
    mov al, [rsi]
    cmp al, [rdi]
    jne .diferentes
    test al, al
    jz  .iguales    ; ambos NUL
    inc rsi
    inc rdi
    jmp .cmp_loop

CMPSB compara [RSI] con [RDI]. REPE = repite mientras iguales. Para strcmp: loop hasta una diferencia o NUL. Las flags tras la última comparación indican el orden (JB=str1memcmp.

Imprimir número (itoa)
; Convertir entero → string decimal
; Input: RAX = número, RDI = buffer
itoa:
    push rdi
    mov rcx, 0          ; contador de dígitos
    mov rbx, 10
.div_loop:
    xor rdx, rdx
    div rbx             ; rax /= 10, resto en rdx
    add dl, '0'         ; ASCII del dígito
    push rdx            ; guardar (¡orden inverso!)
    inc rcx
    test rax, rax
    jnz .div_loop
; Escribir dígitos (invertidos):
    pop rax
    mov [rdi], al
    inc rdi
    dec rcx
    jnz .write_loop-1   ; (loop)
; NUL:
    mov byte [rdi], 0
    pop rdi
    ret

itoa convierte un entero a string. Divide por 10 repetidamente. Resto + "0" = dígito ASCII. Los dígitos salen invertidos → usar la stack para reordenar. Terminar con NUL. Alternativa: printf vía libc.

Dicas e Técnicas


10 cards
Optimizaciones básicas
; Poner a cero un registro (más rápido):
xor eax, eax        ; 1 byte, 1 ciclo
; vs: mov rax, 0    ; 7 bytes, más lento

; Multiplicar por una constante:
lea rax, [rax + rax*4]  ; *5 (1 ciclo)
shl rax, 3              ; *8 (1 ciclo)
; vs: imul rax, 5       ; 3 ciclos

; Intercambiar registros:
xchg rax, rbx       ; 1 instrucción
; vs: push/pop o temp

; NOP (alineación):
nop                 ; 1 byte
; NOP multi-byte para alineación:
nop dword [rax]     ; 4 bytes (no ejecuta)

; Branch hint (documentación):
; likely: jmp corto hacia adelante
; unlikely: jmp hacia atrás

xor eax,eax es la forma más rápida de poner a cero (1 ciclo, 2 bytes). LEA para multiplicar por constantes. XCHG intercambia sin temporal. NOP para alineación. Evita branches innecesarios.

Macros NASM
; Macro sin parámetros:
%macro newline 0
    mov rax, 1
    mov rdi, 1
    lea rsi, [rel .nl]
    mov rdx, 1
    syscall
    jmp %%fin
    .nl: db 10
    %%fin:
%endmacro

; Macro con parámetros:
%macro print_str 2
    mov rax, 1
    mov rdi, 1
    lea rsi, [%1]
    mov rdx, %2
    syscall
%endmacro

; Uso:
    newline
    print_str msg, msg_len

; %% = label local a la macro (evita conflicto)

%macro nombre N define una macro con N parámetros. %1, %2 = argumentos. %%label = label local (evita duplicados). Expandida en ensamblado (sin overhead). Ideal para código repetitivo (prints, asserts).

Buenas prácticas
; 1. Siempre CLD antes de string ops
cld

; 2. Preservar callee-saved
push rbx            ; si usas rbx
; ...
pop rbx

; 3. Stack alineada a 16 antes de CALL
and rsp, -16        ; si es necesario

; 4. Verificar valores de retorno de syscalls
syscall
test rax, rax
js  .error

; 5. Usar LEA para aritmética
lea rax, [rdi + rsi*4]

; 6. XOR para poner a cero (no MOV 0)
xor eax, eax

; 7. Labels locales con un punto
.loop:
.fin:

; 8. Comentarios en cada bloque lógico

Reglas: CLD antes de string ops. Preservar callee-saved (RBX, R12-15). Stack alineada a 16. Verificar errores de syscall. LEA para aritmética. XOR para poner a cero. Labels locales con .. Comentar bloques.

Branchless programming
; abs(x) sin branch:
mov rax, rdi        ; x
mov rcx, rdi
sar rcx, 63         ; rcx = 0 o -1 (máscara)
xor rax, rcx        ; invierte si negativo
sub rax, rcx        ; +1 si negativo

; min(a, b) sin branch:
mov rax, rdi        ; a
mov rcx, rsi        ; b
cmp rax, rcx
cmovg rax, rcx      ; si a>b, rax=b

; max(a, b):
mov rax, rdi
cmp rax, rsi
cmovl rax, rsi      ; si a<b, rax=b

; Ventaja: sin misprediction de la CPU
; El branch predictor falla con datos aleatorios

Branchless evita JMPs condicionales (penalización de misprediction ~15 ciclos). CMOV para min/max. Máscara con SAR 63 para abs. Ideal para datos impredecibles. Los branches son buenos cuando son predecibles (loops).

Debugging con GDB
; Compilar con debug info:
; nasm -f elf64 -g -F dwarf prog.asm
; gcc prog.o -o prog -no-pie -g

; GDB:
; gdb ./prog
; (gdb) break _start
; (gdb) run
; (gdb) info registers
; (gdb) x/10xw $rsp    ; 10 words en la stack
; (gdb) x/s $rdi       ; string en rdi
; (gdb) stepi           ; 1 instrucción
; (gdb) nexti           ; 1 instrucción (skip call)
; (gdb) display $rax    ; mostrar siempre

; NASM: breakpoints con int3
int3                ; breakpoint por software
; (gdb) continue → para aquí

; Ver flags:
; (gdb) print $eflags

Compilar con -g -F dwarf para debug. GDB: stepi = 1 instrucción, info registers = registros. x/10xw $rsp examina la stack. int3 = un breakpoint en el código. display $rax lo muestra en cada paso.

x86 vs ARM vs RISC-V
; x86-64 (Intel/AMD):
; • CISC: instrucciones complejas, tamaño variable
; • 16 registros GPR (RAX-R15)
; • Flags (EFLAGS) afectadas por ops
; • Little-endian

; ARM64 (Apple M, Raspberry Pi):
; • RISC: instrucciones simples, tamaño fijo (4B)
; • 31 registros (X0-X30)
; • Sin flags (comparación explícita)
; • Bi-endian (generalmente little)

; RISC-V:
; • RISC open-source, modular
; • 32 registros (x0-x31, x0=zero)
; • Extensiones: M(mult), F(float), V(vector)

; Los conceptos son transferibles:
; stack, call/ret, loops, syscalls

x86 = CISC (instrucciones complejas, tamaño variable). ARM = RISC (simple, 4 bytes fijos). RISC-V = open-source modular. Los conceptos (stack, registros, loops) son universales. x86 domina servers/desktops. ARM domina mobile.

SSE (operaciones vectoriales)
; SSE: 128-bit (XMM0-XMM15)
; 4 floats o 2 doubles por registro

; Cargar (requiere alineación 16):
movaps xmm0, [datos]    ; 4 floats alineados
movups xmm0, [datos]    ; no alineado (lento)

; Operaciones:
addps xmm0, xmm1       ; suma 4 floats
mulps xmm0, xmm1       ; multiplica 4 floats
subps xmm0, xmm1       ; resta 4 floats

; Scalar (1 float):
addss xmm0, xmm1       ; solo el float bajo

; Comparar:
cmpps xmm0, xmm1, 0   ; equal (4 comparaciones)

; Convertir:
cvtsi2ss xmm0, eax     ; int → float
cvtss2si eax, xmm0     ; float → int

SSE procesa 4 floats simultáneamente (SIMD). XMM0-15 = 128 bits. movaps requiere alineación 16. Sufijos: ps = packed (4), ss = scalar (1). 4x más rápido que scalar para vectores.

Linking y ELF
; Ensamblar:
nasm -f elf64 main.asm -o main.o

; Enlazar (sin libc):
ld main.o -o main

; Enlazar (con libc):
gcc main.o -o main -no-pie

; Múltiples ficheros:
nasm -f elf64 a.asm -o a.o
nasm -f elf64 b.asm -o b.o
gcc a.o b.o -o app -no-pie

; Ver símbolos:
nm main             ; listar símbolos
readelf -s main.o   ; tabla de símbolos
objdump -d main     ; desensamblar

; Declarar externo:
extern printf       ; viene de otro fichero
global main         ; exportar al linker

nasm -f elf64 genera un objeto ELF. ld = link sin libc (solo syscalls). gcc = link con libc (printf, malloc). extern importa, global exporta. -no-pie evita PIE (más simple).

AVX (256-bit)
; AVX: 256-bit (YMM0-YMM15)
; 8 floats o 4 doubles por registro

; Operaciones (prefijo V):
vaddps ymm0, ymm1, ymm2   ; suma 8 floats
vmulps ymm0, ymm1, ymm2   ; mult 8 floats
vsubps ymm0, ymm1, ymm2   ; sub 8 floats

; Cargar:
vmovaps ymm0, [datos]     ; alineado (32 bytes)
vmovups ymm0, [datos]     ; no alineado

; Broadcast (1 valor → todos):
vbroadcastss ymm0, [val]  ; 1 float → 8

; IMPORTANTE: ¡limpiar los upper bits!
vzeroupper                  ; antes de código SSE
; Evita la penalización de transición SSE↔AVX

; Alinear los datos:
align 32
datos: dd 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0

AVX usa YMM (256 bits) = 8 floats. Prefijo V en las instrucciones. vzeroupper es OBLIGATORIO al mezclar con SSE (evita una penalización). Alinear datos a 32 bytes. 8x throughput vs scalar.

Performance y profiling
; RDTSC: timestamp counter (ciclos de CPU)
rdtsc               ; EDX:EAX = ciclos
shl rdx, 32
or rax, rdx         ; rax = 64-bit counter
mov [start], rax

; ... código a medir ...

rdtsc
shl rdx, 32
or rax, rdx
sub rax, [start]    ; rax = ciclos gastados

; Consejos de performance:
; 1. Evitar branches (cmov, setcc)
; 2. Alinear loops a 16 bytes
; 3. Unroll de loops pequeños
; 4. Prefetch de datos (prefetcht0 [rax])
; 5. Evitar dependencias (ILP)

; Herramientas: perf stat, perf record
; perf stat ./programa

RDTSC lee el contador de ciclos de la CPU. Medir antes/después para profiling. Consejos: evitar branches, alinear loops, unroll, prefetch. perf stat muestra IPC, cache misses. ILP: instrucciones independientes en paralelo.