Registros y Movimiento de Datos
Visión de Registros
x86-64 tiene 16 registros de propósito general de 64 bits. Escribir en un subregistro de 32 bits (p. ej. eax) extiende con ceros los 32 bits superiores; escribir en subregistros de 16/8 bits deja los bits superiores sin cambios.
; General-purpose 64-bit registers (x86-64)
; rax, rbx, rcx, rdx — main accumulators / scratch
; rsi, rdi — source/destination index (string ops)
; rbp, rsp — base pointer / stack pointer
; r8 .. r15 — extended registers
; 32-bit: eax, 32-bit: ecx, 16-bit: ax, 8-bit: al/ah
mov rax, 42 ; load immediate into 64-bit register
mov eax, 0x1F ; 32-bit move (zero-extends to rax)mov — Mover Datos
mov copia datos entre registros, memoria e inmediatos. No se permiten movimientos memoria-a-memoria — use un registro intermedio. Especifique siempre el tamaño (byte/word/dword/qword) cuando sea ambiguo, p. ej. mov dword [rsp], 1.
mov rax, 60 ; immediate -> register
mov rbx, rax ; register -> register
mov qword [rsp-8], 7 ; immediate -> memory (qword = 8 bytes)
mov rcx, [rsp-8] ; memory -> register
; INVALID: mov [rsp-8], [rsp-16] (memory-to-memory not allowed)
; Use two steps: mov rax, [src]; mov [dst], raxlea — Cargar Dirección Efectiva
lea calcula la dirección de un operando de memoria sin acceder a ella. Se usa frecuentemente como instrucción aritmética rápida (multiplicación por 5/3/9 vía escala+índice) y para tomar direcciones de variables de pila o etiquetas.
; Compute address without dereferencing
lea rax, [rbx + rcx*8 + 16] ; rax = rbx + rcx*8 + 16
; Common idiom: fast arithmetic (no memory access)
lea rax, [rax + rax*4] ; rax *= 5 (rax = rax + rax*4)
; Pointer into a buffer
lea rsi, [buffer] ; rsi = address of buffer
lea rdi, [rsp + 32] ; rdi = address of stack slotpush / pop — Operaciones de Pila
push decrementa rsp en 8 y almacena el valor; pop carga e incrementa rsp. En el ABI System V AMD64, los primeros 6 argumentos enteros van en rdi, rsi, rdx, rcx, r8, r9 — push se usa principalmente para guardar registros callee-saved (rbx, rbp, r12-r15).
push rax ; rsp -= 8; [rsp] = rax
push qword 42 ; push immediate
pop rbx ; rbx = [rsp]; rsp += 8
; Save/restore callee-saved registers
push rbx
push r12
; ... function body ...
pop r12
pop rbx
ret
; Push arguments in reverse (C calling convention)
push 3
push 2
push 1xchg / xadd — Intercambio Atómico
xchg con un operando de memoria es siempre atómico (bloqueo implícito). xadd combina intercambio y suma. Junto con lock cmpxchg (compare-and-swap) forman la base de las primitivas de sincronización sin bloqueos.
; xchg swaps two operands (implicitly LOCKed with memory)
xchg rax, rbx ; swap register/register
xchg [counter], rcx ; swap memory/register (atomic)
; xadd: swap then add (returns old value in src)
; lock xadd [counter], rax ; atomic fetch-and-add
; Spinlock idiom
spin:
xor eax, eax
lock cmpxchg [lock_var], 1 ; if [lock_var]==0, set to 1
jnz spin ; retry if not acquiredmovzx / movsx — Extensión Cero/Signo
movzx extiende con ceros un valor menor a un registro mayor (carga sin signo). movsx extiende con signo (carga con signo). Use movsxd para cargar un valor con signo de 32 bits en un registro de 64 bits.
; movzx: zero-extend (unsigned)
movzx rax, byte [rsi] ; load byte, zero-extend to 64-bit
movzx eax, word [rdi] ; load word, zero-extend to 32-bit
; movsx: sign-extend (signed)
movsx rax, byte [rsi] ; sign-extend byte to 64-bit
movsx rax, dword [rdi] ; sign-extend 32-bit to 64-bit (movsxd)Aritmética y Lógica
add / sub — Aritmética Entera
add/sub establecen CF, OF, SF, ZF, PF y AF. Use jc/jo para detectar desbordamiento sin signo/con signo. Restar de rsp asigna espacio de pila — mantenga rsp alineado a 16 bytes antes de las llamadas según el ABI.
add rax, rbx ; rax = rax + rbx
add rax, 10 ; rax = rax + 10
sub rcx, rdx ; rcx = rcx - rdx
sub rsp, 32 ; allocate 32 bytes of stack (align!)
; 64-bit addition with carry check
add rax, rbx
jc .overflow ; jump if unsigned overflow (CF=1)imul / idiv — Multiplicar y Dividir
imul de dos operandos es la forma común — solo conserva los 64 bits bajos. Para multiplicación de ancho completo use mul/imul de un operando (resultado en rdx:rax). Antes de idiv, extienda el signo del dividendo con cqo; para div sin signo, ponga rdx a cero con xor rdx,rdx.
; Two-operand imul (most common): dst = dst * src
imul rax, rbx ; rax = rax * rbx (lower 64 bits)
imul rcx, 10 ; rcx = rcx * 10
; One-operand: rdx:rax = rax * src (full 128-bit)
mul rbx ; unsigned: rdx:rax = rax * rbx
imul rbx ; signed: rdx:rax = rax * rbx
; idiv: signed divide rdx:rax by src
; Must sign-extend rax into rdx:rax first!
cqo ; sign-extend rax -> rdx:rax
idiv rcx ; rax = rdx:rax / rcx, rdx = remainder
; Unsigned: use div (zero rdx first)
xor rdx, rdx
div rcx ; rax = rdx:rax / rcx, rdx = remainderinc / dec / neg
inc y dec NO actualizan el flag de acarreo (CF) — permite usarlos en cadenas de suma/resta multiprecisión. Sí actualizan ZF/SF/OF. neg calcula la negación en complemento a dos y pone CF=1 salvo que el operando sea 0.
inc rax ; rax++ (does NOT affect CF!)
dec rcx ; rcx-- (does NOT affect CF!)
neg rdx ; rdx = -rdx (two's complement negate)
; Common loop pattern
mov rcx, 10
.loop:
; ... loop body ...
dec rcx
jnz .loop ; repeat until rcx == 0and / or / xor / not / test
xor reg,reg es la forma idiomática de poner un registro a cero (codificación más corta que mov reg,0). test a,a equivale a and a,a pero descarta el resultado. and/or/xor limpian CF y OF.
and rax, 0xFF ; mask low byte (rax &= 0xFF)
or rcx, 0x10 ; set bit 4
xor rdx, rdx ; rdx = 0 (idiomatic zeroing)
xor rax, rax ; clear rax (shorter than mov rax,0)
not r8 ; bitwise NOT (one's complement)
test rax, rax ; set flags from rax & rax (checks zero/sign)
test rcx, 0x1 ; test if low bit set (odd/even check)shl / shr / sar — Desplazamientos
shl/shr son desplazamientos lógicos (relleno con ceros); sar preserva el bit de signo (aritmético). Multiplicar/dividir por potencias de dos mediante desplazamientos es mucho más rápido que imul/idiv. El contador se enmascara a 5 o 6 bits.
shl rax, 4 ; logical left shift (rax *= 16)
shr rax, 3 ; logical right shift (unsigned rax /= 8)
sar rax, 3 ; arithmetic right shift (signed rax /= 8)
; Rotate (carry not involved)
rol rax, 4 ; rotate left
ror rax, 4 ; rotate right
; Shift by CL (only low 5 bits used in 64-bit mode)
mov cl, 4
shl rax, cl ; shift left by 4Flujo de Control y Ramificación
cmp / test — Comparar
cmp establece flags como si restara sin almacenar. Para comparaciones con signo use je/jne/jl/jg/jle/jge; para sin signo jb/ja/jbe/jae. Mezclarlos es un bug clásico — jl comprueba SF!=OF mientras jb comprueba CF.
cmp rax, rbx ; compute rax - rbx, set flags (discard result)
cmp rax, 10 ; compare with immediate
; Signed comparisons
cmp rax, rbx
je .equal ; jump if rax == rbx (ZF=1)
jl .less ; jump if rax < rbx signed (SF!=OF)
jg .greater ; jump if rax > rbx signed (ZF=0 and SF==OF)
jle .le ; jump if rax <= rbx signed
jge .ge ; jump if rax >= rbx signed
; Unsigned comparisons (use 'below'/'above' mnemonics)
cmp rax, rbx
jb .below ; rax < rbx unsigned (CF=1)
ja .above ; rax > rbx unsigned (CF=0 and ZF=0)
jbe .be ; rax <= rbx unsigned
jae .ae ; rax >= rbx unsignedjmp — Salto Incondicional
jmp realiza un salto incondicional. Los saltos condicionales (jcc) prueban flags establecidos por cmp/test/aritmética. En modo 64 bits los saltos condicionales cercanos alcanzan ±2 GB. Los saltos indirectos permiten tablas de salto switch/case.
; Direct jump to a label
jmp .end
; Conditional jumps (short/near, 64-bit allows near)
.loop:
dec rcx
jnz .loop ; jump if ZF=0 (rcx != 0)
; Indirect jump through register/memory (jump table)
lea rax, [table]
mov rdi, [rax + rbx*8]
jmp rdi ; jump to address in rdi
; Jump table example (switch statement)
table: dq .case0, .case1, .case2, .case3Saltos Condicionales Comunes
Hay muchos sinónimos (je==jz, jb==jc==jnae). La distinción con signo (l/g) vs sin signo (b/a) es crítica: jl/jg prueban SF y OF; jb/ja prueban CF. Use formas con signo tras comparar enteros con signo, sin signo tras punteros/enteros sin signo.
; After cmp/test:
je / jz ; jump if equal / zero (ZF=1)
jne / jnz ; jump if not equal / nonzero (ZF=0)
; Signed
jl / jnge ; less (SF!=OF)
jge / jnl ; greater-or-equal (SF==OF)
jle / jng ; less-or-equal (ZF=1 or SF!=OF)
jg / jnle ; greater (ZF=0 and SF==OF)
; Unsigned
jb / jnae / jc ; below / carry (CF=1)
jae / jnb / jnc ; above-or-equal (CF=0)
jbe / jna ; below-or-equal (CF=1 or ZF=1)
ja / jnbe ; above (CF=0 and ZF=0)
; Special
js ; jump if sign (SF=1)
jns ; jump if not sign (SF=0)
jo ; jump if overflow (OF=1)
jno ; jump if no overflow (OF=0)Bucle con loop
loop decrementa rcx y salta si rcx!=0. Aunque es conveniente, es más lento en CPUs modernas que un par dec/jnz, por lo que los compiladores emiten dec/jnz. loope/loopz además requieren ZF=1; loopne/loopnz requieren ZF=0.
; loop: dec rcx then jump if rcx != 0
mov rcx, 5
.loop:
; ... body executes 5 times ...
loop .loop
; loope/loopz: loop while equal/zero
; loopne/loopnz: loop while not equal/not zero
; Modern compilers prefer dec + jnz (loop is slower on many CPUs)
mov rcx, 5
.loop:
; ...
dec rcx
jnz .loopcmov — Movimiento Condicional
cmovcc realiza un movimiento condicional — código sin ramas que evita penalizaciones de predicción errónea. El destino debe ser un registro. cmov siempre evalúa ambos operandos origen, evítelo cuando una ruta tenga efectos secundarios.
; cmovcc: move only if condition is true (branchless)
cmp rax, rbx
cmovl rax, rbx ; if rax < rbx (signed), rax = rbx -> rax = min(a,b)
; max(a, b)
cmp rax, rbx
cmovl rax, rbx ; rax = max(rax, rbx)? No: if rax<rbx, set rax=rbx -> max
; Branchless abs:
; abs(x): mask = x >> 63; result = (x ^ mask) - mask
mov rax, rdi
sar rdi, 63 ; all 1s if negative, 0 if positive
xor rax, rdi
sub rax, rdi ; rax = |original rdi|Pila, Funciones y Convención de Llamada
Prólogo y Epílogo de Función
El prólogo estándar guarda rbp, lo establece como puntero de marco y asigna locales restando de rsp. leave es un epílogo de 1 byte equivalente a mov rsp,rbp; pop rbp. El ABI System V AMD64 requiere rsp alineado a 16 bytes en el punto de llamada.
; System V AMD64 calling convention (Linux/macOS)
; Args: rdi, rsi, rdx, rcx, r8, r9 (then stack)
; Return: rax. Callee-saved: rbx, rbp, r12-r15
my_func:
push rbp
mov rbp, rsp ; standard prologue
sub rsp, 32 ; allocate locals (keep 16-byte aligned)
; ... function body ...
mov rsp, rbp ; or: leave
pop rbp ; standard epilogue
ret
; Compact form using leave
my_func2:
push rbp
mov rbp, rsp
sub rsp, 16
; ...
leave ; mov rsp,rbp; pop rbp
retcall / ret — Llamar y Retornar
call empuja la dirección de retorno (8 bytes en 64 bits) y salta al objetivo; ret la saca a rip. Para llamadas de cola use jmp para que el callee retorne directamente a su llamador — preserva la pila y habilita la optimización de llamadas de cola.
; call pushes return address, then jumps
call my_func
; ret pops return address into rip
my_func:
; ...
ret
; Call with arguments (System V AMD64)
mov rdi, 1 ; 1st arg
mov rsi, 2 ; 2nd arg
mov rdx, 3 ; 3rd arg
call add_three ; result in rax
; Tail call: jmp instead of call+ret
my_wrapper:
jmp target_func ; reuses our return addressFunciones Hoja y Zona Roja
El ABI System V AMD64 reserva una «zona roja» de 128 bytes bajo rsp que las funciones hoja (sin llamadas) pueden usar sin ajustar rsp. Windows x64 NO tiene zona roja. Las funciones hoja a menudo no necesitan prólogo.
; Leaf function (no calls) can use the red zone:
; 128 bytes below rsp that won't be clobbered by signals/interrupts
leaf_sqrt_sum:
; rdi, rsi = args, rax = result
mov rax, rdi
add rax, rsi
ret ; no prologue needed!
; Non-leaf functions MUST save rsp properly because
; a call would write into the red zone.
; Red zone is NOT honored on Windows x64.Variables Locales y Marco de Pila
Con puntero de marco (rbp), los locales se acceden en [rbp - offset] y los argumentos de pila en [rbp + offset]. Los compiladores a menudo omiten el puntero de marco y referencian los locales directamente desde rsp.
my_func:
push rbp
mov rbp, rsp
sub rsp, 32 ; 32 bytes for locals
; Local variables accessed via [rbp - offset]
mov qword [rbp-8], 10 ; local1
mov qword [rbp-16], 20 ; local2
; Read arguments (also via rbp once saved on stack,
; or directly from registers)
mov rax, [rbp-8]
add rax, [rbp-16]
leave
retPreservar Registros Callee-Saved
Los registros callee-saved (rbx, rbp, r12-r15) deben preservarse — push al entrar, pop en orden inverso al salir. Los caller-saved (rax, rcx, rdx, rsi, rdi, r8-r11) pueden usarse libremente, pero guárdelos si necesita sus valores entre llamadas.
; rbx, rbp, r12, r13, r14, r15 are callee-saved
; rax, rcx, rdx, rsi, rdi, r8-r11 are caller-saved
my_func:
push rbx ; we want to use rbx
push r12 ; and r12
mov rbx, rdi ; use them
mov r12, rsi
; ... do work, may call other functions ...
; (those calls will preserve rbx/r12 for us)
mov rax, rbx ; prepare return value
pop r12 ; restore in REVERSE order
pop rbx
retLlamadas al Sistema y Hello World
syscall — Llamada al Sistema Linux
En Linux x86-64, las syscalls usan la instrucción syscall con el número en rax y hasta 6 argumentos en rdi, rsi, rdx, r10, r8, r9 (¡r10, no rcx, para el 4º argumento!). Números comunes: write=1, read=0, exit=60, mmap=9. syscall destruye rcx y r11. macOS usa números distintos.
; Linux x86-64 syscall convention:
; rax = syscall number
; rdi, rsi, rdx, r10, r8, r9 = args (NOTE: r10 not rcx!)
; return value in rax; clobbers rcx and r11
; write(1, msg, 12)
mov rax, 1 ; syscall: write
mov rdi, 1 ; fd = stdout
lea rsi, [msg] ; buf
mov rdx, 12 ; count
syscall
; exit(0)
mov rax, 60 ; syscall: exit
xor rdi, rdi ; status = 0
syscall
section .data
msg: db "hello world", 10 ; 12 bytes with newlineHello World Completo (NASM, Linux)
Un hello world autónomo no enlaza con libc — llama al kernel directamente vía syscall. $ - msg calcula la longitud en tiempo de ensamblado. _start es el punto de entrada por defecto de ld. El programa termina con la syscall exit.
; nasm -f elf64 hello.asm && ld hello.o -o hello && ./hello
section .data
msg: db "Hello, World!", 10
msg_len: equ $ - msg ; length computed at assemble time
section .text
global _start
_start:
; write(1, msg, msg_len)
mov rax, 1 ; write
mov rdi, 1 ; stdout
mov rsi, msg
mov rdx, msg_len
syscall
; exit(0)
mov rax, 60
xor rdi, rdi
syscallLeer argc / argv de Línea de Comandos
Al entrar a _start (Linux, sin libc), el kernel coloca argc en [rsp], luego punteros argv, envp y NULL. Si enlaza con libc y usa main, el runtime de C los parsea — main recibe argc y argv en rdi/rsi.
; At _start the stack looks like:
; [rsp] = argc
; [rsp+8] = argv[0]
; [rsp+16] = argv[1]
; ...
; [rsp + 8*(argc+1)] = NULL
_start:
mov rdi, [rsp] ; argc
mov rsi, [rsp+8] ; argv[0] (program name)
mov rdx, [rsp+16] ; argv[1] (first user arg)
; Loop over argv
mov rbx, rsp
add rbx, 8 ; skip argc, point at argv[0]
.loop:
mov rax, [rbx]
test rax, rax
jz .done ; NULL terminator
; rax -> one argv string
add rbx, 8
jmp .loop
.done:
; exit
mov rax, 60
xor rdi, rdi
syscallLlamar Funciones de Librería C
Al llamar funciones C, alinee rsp a 16 bytes antes del call. Para funciones variádicas como printf, rax debe tener el número de argumentos vectoriales (XMM) — póngalo a 0 para llamadas solo enteras. Use main como entrada para que el runtime de C configure libc.
; Link with gcc: nasm -f elf64 demo.asm && gcc demo.o -o demo -no-pie
extern printf
section .data
fmt: db "sum = %d", 10, 0
section .text
global main
main:
push rbp
mov rbp, rsp
sub rsp, 16 ; keep stack 16-byte aligned
; printf("sum = %d\n", 42)
; Variadic: rdi=fmt, rsi=arg, rax=#vector regs (0)
lea rdi, [fmt]
mov rsi, 42
xor rax, rax ; 0 floating-point args
call printf
xor rax, rax ; return 0
leave
retFragmentos de Assembly relacionados
Copy-paste ready code for common tasks.
Hola Mundo mediante syscall de Linux
Un programa x86-64 independiente que imprime y sale usando solo llamadas al kernel.
Convención de llamada de funciones (System V AMD64)
Pasa argumentos en registros, preserva registros callee-saved y mantiene rsp alineado a 16 bytes.
Suma con bucle (1..N)
Suma enteros de 1..N con un bucle contado usando dec/jnz.
strlen — escanear hasta NUL
Calcula la longitud de una cadena C escaneando memoria hasta un byte cero.
memcpy — copiar con rep movsb
Usa la instrucción de cadena rep movsb para una copia de memoria compacta.
Manipulación de bits: popcount, ctz, abs
Usa instrucciones BMI/ABM para operaciones de bits sin ramificación.
Leer un archivo mediante syscalls
open/read/write/close de un archivo usando solo syscalls de Linux.
Factorial recursivo
Implementa factorial(n) recursivamente con un marco de pila correcto.
Was this helpful?