Registres et Déplacement de Données
Aperçu des Registres
x86-64 possède 16 registres généraux 64 bits. Écrire dans un sous-registre 32 bits (ex. eax) étend par zéro les 32 bits supérieurs ; écrire dans des sous-registres 16/8 bits laisse les bits supérieurs inchangés.
; 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 — Déplacer des Données
mov copie des données entre registres, mémoire et immédiats. Les déplacements mémoire-vers-mémoire sont interdits — passez par un registre. Spécifiez toujours la taille (byte/word/dword/qword) quand elle est ambiguë.
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 — Load Effective Address
lea calcule l'adresse d'un opérande mémoire sans y accéder. Souvent utilisé comme instruction arithmétique rapide (multiplication par 5/3/9 via échelle+index) et pour prendre l'adresse de variables de pile ou d'étiquettes.
; 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 — Opérations de Pile
push décrémente rsp de 8 puis stocke la valeur ; pop charge puis incrémente rsp. Dans l'ABI System V AMD64, les 6 premiers arguments entiers vont dans rdi, rsi, rdx, rcx, r8, r9 — push sert surtout à sauvegarder les registres 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 — Échange Atomique
xchg avec un opérande mémoire est toujours atomique (verrou implicite). xadd combine échange et addition. Avec lock cmpxchg (compare-and-swap), ils forment la base des primitives de synchronisation sans verrou.
; 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 — Extension Zéro/Signe
movzx étend par zéro une petite valeur dans un grand registre (chargement non signé). movsx étend avec le signe (chargement signé). Utilisez movsxd pour charger une valeur signée 32 bits dans un registre 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)Arithmétique et Logique
add / sub — Arithmétique Entière
add/sub positionnent CF, OF, SF, ZF, PF et AF. Utilisez jc/jo pour détecter les dépassements signé/non signé. Soustraire de rsp alloue de l'espace pile — gardez rsp aligné 16 octets avant les appels selon l'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 — Multiplier et Diviser
imul à deux opérandes est la forme courante — ne conserve que les 64 bits bas. Pour la multiplication pleine largeur, utilisez mul/imul à un opérande (résultat dans rdx:rax). Avant idiv, étendez le signe du dividende avec 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 et dec ne mettent PAS à jour le drapeau de retenue (CF) — ce qui permet de les utiliser dans des chaînes add/sub multi-précision. Ils mettent à jour ZF/SF/OF. neg calcule la négation en complément à deux et met CF=1 sauf si l'opérande est 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 est la façon idiomatique de mettre un registre à zéro (encodage plus court que mov reg,0). test a,a équivaut à and a,a mais ignore le résultat. and/or/xor effacent CF et 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 — Décalages
shl/shr sont des décalages logiques (remplissage zéro) ; sar préserve le bit de signe (arithmétique). Multiplier/diviser par des puissances de deux via décalages est bien plus rapide que imul/idiv. Le compteur est masqué à 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 4Flux de Contrôle et Branchements
cmp / test — Comparer
cmp positionne les drapeaux comme une soustraction sans stocker. Pour les comparaisons signées utilisez je/jne/jl/jg/jle/jge ; non signées jb/ja/jbe/jae. Les confondre est un bug classique — jl teste SF!=OF tandis que jb teste 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 — Saut Inconditionnel
jmp effectue un saut inconditionnel. Les sauts conditionnels (jcc) testent les drapeaux positionnés par cmp/test/arithmétique. En mode 64 bits, les sauts conditionnels proches atteignent ±2 Go. Les sauts indirects permettent les tables de saut 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, .case3Sauts Conditionnels Courants
Il existe de nombreux synonymes (je==jz, jb==jc==jnae). La distinction signé (l/g) vs non signé (b/a) est critique : jl/jg testent SF et OF ; jb/ja testent CF. Utilisez les formes signées après comparaison d'entiers signés.
; 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)Boucle avec loop
loop décrémente rcx et saute si rcx!=0. Bien que pratique, il est plus lent sur les CPU modernes qu'une paire dec/jnz, donc les compilateurs émettent dec/jnz. loope/loopz requièrent en plus ZF=1 ; loopne/loopnz requièrent 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 — Déplacement Conditionnel
cmovcc effectue un déplacement conditionnel — code sans branche évitant les pénalités de mauvaise prédiction. La destination doit être un registre. cmov évalue toujours les deux opérandes source, évitez-le quand un chemin a des effets de bord.
; 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|Pile, Fonctions et Convention d'Appel
Prologue et Épilogue de Fonction
Le prologue standard sauvegarde rbp, le définit comme pointeur de cadre et alloue des locaux en soustrayant de rsp. leave est une épilogue 1 octet équivalent à mov rsp,rbp; pop rbp. L'ABI System V AMD64 requiert rsp aligné 16 octets au point d'appel.
; 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 — Appeler et Retourner
call empile l'adresse de retour (8 octets en 64 bits) et saute à la cible ; ret la dépile dans rip. Pour les appels en queue, utilisez jmp pour que l'appelé retourne directement à votre appelant — cela préserve la pile et permet l'optimisation des appels en queue.
; 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 addressFonctions Feuilles et Zone Rouge
L'ABI System V AMD64 réserve une « zone rouge » de 128 octets sous rsp que les fonctions feuilles (sans appels) peuvent utiliser sans ajuster rsp. Windows x64 n'a PAS de zone rouge. Les fonctions feuilles n'ont souvent besoin d'aucun prologue.
; 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 et Cadre de Pile
Avec un pointeur de cadre (rbp), les locales sont accédées via [rbp - offset] et les arguments pile via [rbp + offset]. Les compilateurs omettent souvent le pointeur de cadre et référencent les locales directement depuis 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
retPréserver les Registres Callee-Saved
Les registres callee-saved (rbx, rbp, r12-r15) doivent être préservés — push à l'entrée, pop en ordre inverse à la sortie. Les caller-saved (rax, rcx, rdx, rsi, rdi, r8-r11) peuvent être librement modifiés, mais sauvegardez-les si besoin entre appels.
; 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
retAppels Système et Hello World
syscall — Appel Système Linux
Sur Linux x86-64, les syscalls utilisent l'instruction syscall avec le numéro dans rax et jusqu'à 6 arguments dans rdi, rsi, rdx, r10, r8, r9 (r10, pas rcx, pour le 4e argument). Numéros courants : write=1, read=0, exit=60, mmap=9. syscall détruit rcx et 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 Complet (NASM, Linux)
Un hello world autonome ne lie aucune libc — il appelle le noyau directement via syscall. $ - msg calcule la longueur à l'assemblage. _start est le point d'entrée par défaut de ld. Le programme se termine par 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
syscallLire argc / argv en Ligne de Commande
À l'entrée de _start (Linux, sans libc), le noyau place argc en [rsp], puis les pointeurs argv, envp et NULL. Si vous liez libc et utilisez main, le runtime C les analyse — main reçoit argc et argv dans 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
syscallAppeler des Fonctions de la Bibliothèque C
Pour appeler des fonctions C, alignez rsp à 16 octets avant le call. Pour les fonctions variadiques comme printf, rax doit contenir le nombre d'arguments vectoriels (XMM) — mettez-le à 0 pour les appels entiers uniquement. Utilisez main comme point d'entrée.
; 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 Assembly associés
Copy-paste ready code for common tasks.
Hello World via syscall Linux
Un programme x86-64 autonome qui affiche et quitte en n'utilisant que les appels système du noyau.
Convention d'appel de fonction (System V AMD64)
Passe les arguments dans des registres, préserve les registres callee-saved et garde rsp aligné sur 16 octets.
Somme en boucle (1..N)
Somme les entiers de 1 à N avec une boucle comptée utilisant dec/jnz.
strlen — balayer jusqu'à NUL
Calcule la longueur d'une chaîne C en balayant la mémoire jusqu'à un octet nul.
memcpy — copier avec rep movsb
Utilise l'instruction chaîne rep movsb pour une copie mémoire compacte.
Manipulation de bits : popcount, ctz, abs
Utilise les instructions BMI/ABM pour des opérations bit à bit sans branche.
Lire un fichier via syscalls
open/read/write/close d'un fichier en utilisant uniquement les syscalls Linux.
Factorielle récursive
Implémente factorial(n) récursivement avec une frame de pile correcte.
Was this helpful?