Registradores e Movimento de Dados
Visão de Registradores
x86-64 possui 16 registradores de propósito geral de 64 bits. Escrever em um subregistrador de 32 bits (ex. eax) estende com zeros os 32 bits superiores; escrever em subregistradores de 16/8 bits deixa os bits superiores inalterados.
; 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 Dados
mov copia dados entre registradores, memória e imediatos. Movimentos memória-para-memória não são permitidos — use um registrador intermediário. Especifique sempre o tamanho (byte/word/dword/qword) quando ambíguo.
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 — Carregar Endereço Efetivo
lea calcula o endereço de um operando de memória sem acess á-lo. Frequentemente usado como instrução aritmética rápida (multiplicação por 5/3/9 via escala+índice) e para obter endereços de variáveis de pilha.
; 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 — Operações de Pilha
push decrementa rsp em 8 e armazena o valor; pop carrega e incrementa rsp. No ABI System V AMD64, os primeiros 6 argumentos inteiros vão em rdi, rsi, rdx, rcx, r8, r9 — push serve para salvar registradores 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 — Troca Atômica
xchg com operando de memória é sempre atômico (bloqueio implícito). xadd combina troca e adição. Junto com lock cmpxchg, formam a base de primitivas de sincronização sem bloqueio.
; 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 — Extensão de Zero/Sinal
movzx estende com zeros um valor menor em um registrador maior (carga sem sinal). movsx estende com sinal (carga com sinal). Use movsxd para carregar um valor com sinal de 32 bits em um registrador 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 e Lógica
add / sub — Aritmética Inteira
add/sub definem CF, OF, SF, ZF, PF e AF. Use jc/jo para detectar overflow sem sinal/com sinal. Subtrair de rsp aloca espaço de pilha — mantenha rsp alinhado a 16 bytes antes de chamadas conforme o 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 e Dividir
imul de dois operandos é a forma comum — mantém apenas os 64 bits baixos. Para multiplicação de largura total use mul/imul de um operando (resultado em rdx:rax). Antes de idiv, estenda o sinal do dividendo com cqo.
; 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 e dec NÃO atualizam o flag de carry (CF) — permite usá-los em cadeias add/sub multiprecisão. Atualizam ZF/SF/OF. neg calcula negação em complemento de dois e põe CF=1 salvo se o operando for 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 é a forma idiomática de zerar um registrador (codificação mais curta que mov reg,0). test a,a equivale a and a,a mas descarta o resultado. and/or/xor limpam CF e 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 — Deslocamentos
shl/shr são deslocamentos lógicos (preenchimento com zero); sar preserva o bit de sinal (aritmético). Multiplicar/dividir por potências de dois via deslocamentos é muito mais rápido que imul/idiv. A contagem é mascarada para 5 ou 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 4Fluxo de Controle e Ramificação
cmp / test — Comparar
cmp define flags como uma subtração sem armazenar. Para comparações com sinal use je/jne/jl/jg/jle/jge; sem sinal jb/ja/jbe/jae. Misturá-los é um bug clássico — jl testa SF!=OF enquanto jb testa 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 um salto incondicional. Saltos condicionais (jcc) testam flags definidos por cmp/test/aritmética. No modo 64 bits, saltos condicionais próximos alcançam ±2 GB. Saltos indiretos permitem tabelas 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 Condicionais Comuns
Há muitos sinônimos (je==jz, jb==jc==jnae). A distinção com sinal (l/g) vs sem sinal (b/a) é crítica: jl/jg testam SF e OF; jb/ja testam CF. Use formas com sinal após comparar inteiros com sinal.
; 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)Loop com loop
loop decrementa rcx e salta se rcx!=0. Embora conveniente, é mais lento em CPUs modernas que um par dec/jnz, então compiladores emitem dec/jnz. loope/loopz também requerem ZF=1; loopne/loopnz requerem 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 — Movimento Condicional
cmovcc realiza um movimento condicional — código sem ramificação que evita penalidades de previsão errada. O destino deve ser um registrador. cmov sempre avalia ambos os operandos de origem, evite-o com efeitos colaterais.
; 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|Pilha, Funções e Convenção de Chamada
Prólogo e Epílogo de Função
O prólogo padrão salva rbp, define-o como ponteiro de frame e aloca locais subtraindo de rsp. leave é um epílogo de 1 byte equivalente a mov rsp,rbp; pop rbp. O ABI System V AMD64 requer rsp alinhado a 16 bytes no ponto de chamada.
; 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 — Chamar e Retornar
call empurra o endereço de retorno (8 bytes em 64 bits) e salta para o alvo; ret o desempilha em rip. Para chamadas de cauda use jmp para que o chamado retorne diretamente ao seu chamador — preserva a pilha e habilita otimização de chamada de cauda.
; 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 addressFunções Folha e Zona Vermelha
O ABI System V AMD64 reserva uma «zona vermelha» de 128 bytes sob rsp que funções folha (sem chamadas) podem usar sem ajustar rsp. Windows x64 NÃO tem zona vermelha. Funções folha muitas vezes não precisam de 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.Variáveis Locais e Frame de Pilha
Com ponteiro de frame (rbp), locais são acessados via [rbp - offset] e argumentos de pilha via [rbp + offset]. Compiladores frequentemente omitem o ponteiro de frame e referenciam locais diretamente de 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 Registradores Callee-Saved
Registradores callee-saved (rbx, rbp, r12-r15) devem ser preservados — push na entrada, pop em ordem inversa na saída. Caller-saved (rax, rcx, rdx, rsi, rdi, r8-r11) podem ser livremente modificados, mas salve-os se necessário entre chamadas.
; 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
retChamadas de Sistema e Hello World
syscall — Chamada de Sistema Linux
No Linux x86-64, syscalls usam a instrução syscall com o número em rax e até 6 argumentos em rdi, rsi, rdx, r10, r8, r9 (r10, não rcx, para o 4º argumento!). Números comuns: write=1, read=0, exit=60, mmap=9. syscall destrói rcx e r11.
; 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)
Um hello world autônomo não liga nenhuma libc — chama o kernel diretamente via syscall. $ - msg calcula o comprimento em tempo de montagem. _start é o ponto de entrada padrão do ld. O programa termina com a 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
syscallLer argc / argv da Linha de Comando
Na entrada de _start (Linux, sem libc), o kernel coloca argc em [rsp], depois ponteiros argv, envp e NULL. Se ligar libc e usar main, o runtime C os analisa — main recebe argc e argv em 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
syscallChamar Funções da Biblioteca C
Ao chamar funções C, alinhe rsp a 16 bytes antes do call. Para funções variádicas como printf, rax deve conter o número de argumentos vetoriais (XMM) — ponha em 0 para chamadas só de inteiros. Use main como ponto de entrada.
; 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
retSnippets de Assembly relacionados
Copy-paste ready code for common tasks.
Hello World via syscall do Linux
Um programa x86-64 autônomo que imprime e sai usando apenas syscalls do kernel.
Convenção de chamada de função (System V AMD64)
Passa argumentos em registradores, preserva callee-saved e mantém rsp alinhado a 16 bytes.
Soma em laço (1..N)
Soma inteiros de 1..N com um laço contado usando dec/jnz.
strlen — varrer até NUL
Calcula o comprimento de uma string C varrendo a memória até um byte zero.
memcpy — copiar com rep movsb
Usa a instrução de string rep movsb para uma cópia de memória compacta.
Manipulação de bits: popcount, ctz, abs
Usa instruções BMI/ABM para operações de bits sem desvio.
Ler um arquivo via syscalls
open/read/write/close de um arquivo usando apenas syscalls do Linux.
Fatorial recursivo
Implementa factorial(n) recursivamente com um stack frame correto.
Was this helpful?