Skip to content
Learni
Voir tous les tutoriels
Programmation Bas Niveau

Comment maîtriser l'Assembly x86-64 en 2026

Read in English

Introduction

L'Assembly x86-64 reste l'arme ultime pour les développeurs experts en 2026, quand chaque cycle CPU compte. Que ce soit pour des kernels custom, des optimisations de hotspots dans des engines de jeu, ou des malwares défensifs, maîtriser l'assembleur vous donne un contrôle total sur le hardware. Contrairement aux langages haut niveau qui masquent les détails, l'Assembly expose les registres, les flags et les instructions SIMD pour des gains de performance de 10x ou plus.

Ce tutoriel expert, structuré des bases aux avancées, fournit du code 100% fonctionnel et copier-collable en NASM pour Linux x86-64. Nous couvrons les syscalls natives (sans libc), les boucles optimisées, les fonctions modulaires, et l'allocation mémoire dynamique via mmap. À la fin, vous saurez assembler, linker et déboguer comme un pro. Idéal pour les ingénieurs système ou les reverse engineers. Préparez votre émulateur QEMU ou une VM Ubuntu 64-bit ! (142 mots)

Prérequis

  • Système Linux x86-64 (Ubuntu 24.04+ ou équivalent)
  • NASM installé (sudo apt install nasm)
  • GNU Binutils (sudo apt install binutils)
  • GDB pour debug (sudo apt install gdb)
  • Connaissances avancées en C, registres x86 et appel système
  • Éditeur comme VS Code avec extension NASM

Installation des outils et Hello World

setup.sh
#!/bin/bash
sudo apt update
sudo apt install nasm binutils gdb

cat > hello.asm << 'EOF'
section .data
    msg db 'Hello, Assembly x86-64 !', 10
    len equ $ - msg

section .text
    global _start

_start:
    mov rax, 1          ; syscall write
    mov rdi, 1          ; stdout
    mov rsi, msg        ; pointeur message
    mov rdx, len        ; longueur
    syscall

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

nasm -f elf64 hello.asm -o hello.o
ld hello.o -o hello
./hello
echo $?
rm hello.o hello hello.asm

Ce script installe NASM et binutils, puis crée un programme Hello World complet utilisant la syscall write (rax=1) et exit (rax=60) sans libc pour minimiser la taille binaire (<1KB). Les registres RSI/RDX transportent les args. Exécutez-le tel quel ; il compile, linke et runne, affichant le message et status 0. Piège : Oublier global _start empêche ld de trouver l'entrée.

Comprendre les syscalls et sections NASM

Les syscalls x86-64 Linux passent par syscall avec args en registres : RAX=numéro, RDI/RSI/RDX/R10/R8/R9 pour params. Imaginez-les comme des portes hardware : pas de stack overhead comme en C. NASM structure le code en sections (.data pour constantes, .text pour exécutable). _start est l'entrée linker par défaut, remplaçant main().

Lecture clavier et écho avec boucle

echo.asm
section .bss
    buffer resb 256
    len resq 1

section .text
    global _start

_start:
    ; Lire input
    mov rax, 0          ; syscall read
    mov rdi, 0          ; stdin
    mov rsi, buffer     ; buffer
    mov rdx, 255        ; max len
    syscall
    mov [len], rax      ; stocker longueur réelle

    ; Écho
    mov rax, 1          ; write
    mov rdi, 1          ; stdout
    mov rsi, buffer     ; buffer
    mov rdx, [len]      ; len
    syscall

    ; Boucle pour uppercase (exemple simple)
    mov rcx, [len]
    mov rsi, buffer
.loop:
    cmp byte [rsi], 10  ; fin ligne?
    je .done
    cmp byte [rsi], 'a'
    jl .next
    cmp byte [rsi], 'z'
    jg .next
    add byte [rsi], 'A' - 'a'
.next:
    inc rsi
    dec rcx
    jnz .loop
.done:

    ; Ré-écho uppercase
    mov rax, 1
    mov rdi, 1
    mov rsi, buffer
    mov rdx, [len]
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall

Ce programme lit du stdin (syscall 0), stocke en .bss (zone non-initialisée), convertit en majuscules via boucle avec flags CF/ZF et jumps conditionnels (JE/JL/JG), puis ré-écrit. Utilise RCX/RSI pour compteur/pointeur comme un "itérateur hardware". Fonctionnel : tapez "hello" + Enter, output "HELLO". Piège : Ne pas null-terminer si strcat ; ici raw bytes.

Boucles, conditions et optimisation

  • Boucles : Utilisez RCX pour count-down (DEC/JNZ rapide), flags auto-mis à jour.
  • Conditions : Jcc (JE/JNE/JL/etc.) branchent sur ZF/SF/CF ; prédiction branch moderne >90% hit.
Analogie : Les flags sont comme des voyants LED du CPU, testés en 0 cycles.

Factorielle récursive et itérative

factorial.asm
section .data
    fmt db '%ld! = %ld', 10, 0

section .bss
    result resq 1

section .text
    global _start
    extern printf

; Fonction itérative
factorial_iter:
    mov rax, 1
    mov rcx, rdi
.loop:
    mul rcx
    dec rcx
    jnz .loop
    ret

; Appel (avec libc pour printf)
_start:
    mov rdi, 10         ; n=10
    call factorial_iter
    mov [result], rax

    mov rdi, fmt
    mov rsi, 10
    mov rdx, [result]
    xor rax, rax        ; float args=0
    call printf

    mov rax, 60
    xor rdi, rdi
    syscall

Première fonction : factorial_iter utilise MUL (RAX*RCX→RAX) en boucle descendante. Appel depuis _start avec libc pour printf (linkez avec nasm -f elf64 fact.asm && gcc -no-pie fact.o -o fact). Calcule 10! = 3,628,800. Récursivité évitée pour perf (stack overflow risk). Piège : MUL clobbre RDX ; utilisez toujours 64-bit.

Fonctions, calling convention et libc

Calling convention System V AMD64 : Args en RDI/RSI/RDX/RCX/R8/R9, callee sauve RBX/RBP/R12-15, caller RAX/RCX/RDX. Pour libc, liez avec gcc -nostdlib non, ici gcc pour simplicité. Fonctions = blocs réutilisables comme macros CPU.

Allocation dynamique avec mmap

mmap.asm
section .bss
    heap resq 1

section .text
    global _start

_start:
    ; mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
    mov rax, 9          ; mmap syscall
    xor rdi, rdi        ; addr=0
    mov rsi, 4096       ; length
    mov rdx, 3          ; PROT_READ|WRITE=1|2
    mov r10, 0x22       ; MAP_PRIVATE|ANONYMOUS=2|32
    mov r8, -1          ; fd=-1
    xor r9, r9          ; offset=0
    syscall
    mov [heap], rax     ; base pointeur

    ; Écrire 'ABCD' à heap
    mov rdi, rax
    mov word [rdi], 0x44434241  ; 'ABCD' little-endian

    ; Dump via write
    mov rax, 1
    mov rsi, [heap]
    mov rdx, 4
    mov rdi, 1
    syscall

    ; munmap
    mov rax, 11
    mov rdi, [heap]
    mov rsi, 4096
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall

Utilise mmap (syscall 9) pour allouer 4KB anonyme, écrit bytes directs (little-endian !), dump et munmap (11). Output 'ABCD'. Parfait pour heaps custom sans malloc. Piège : Alignement 4KB ; R10 au lieu de RCX pour 6e arg (convention syscall). Vérifiez /proc/sys/kernel/randomize_va_space=0 pour addr fixe en debug.

SIMD avec AVX pour vectorisation experte

avx.asm
section .data
    vec1 dq 1.0, 2.0, 3.0, 4.0
    vec2 dq 5.0, 6.0, 7.0, 8.0
    fmt db 'Résultat AVX: %f %f %f %f', 10, 0

section .bss
    result resq 4

section .text
    global _start
    extern printf

_start:
    ; Load doubles en YMM0/YMM1
    mov rsi, vec1
    movups ymm0, [rsi]
    mov rsi, vec2
    movups ymm1, [rsi]

    ; Addpd vectoriel
    vaddpd ymm2, ymm0, ymm1

    ; Store
    mov rsi, result
    vmovupd [rsi], ymm2

    ; Print (simplifié, assume fpu)
    mov rdi, fmt
    mov rsi, [result]
    mov rdx, [result+8]
    mov rcx, [result+16]
    mov r8, [result+24]
    xor rax, rax
    call printf

    vzeroupper
    mov rax, 60
    xor rdi, rdi
    syscall

Intro AVX (256-bit) : VMOVUPS charge doubles, VADDPD additionne 4 floats64 en parallèle (théorique 4x speedup). Stocke et print. Liez gcc -mavx2 avx.o -o avx. Nécessite CPU AVX (Intel SandyBridge+). Piège : VZEROUPPER avant ret pour éviter AVX-SSE transition penalty (30+ cycles !).

Bonnes pratiques

  • Minimisez branches : Utilisez CMOVcc au lieu de Jcc pour prédiction.
  • Registre allocation : Privilégiez RAX/RDI/RSI ; sauvez callee-saved.
  • Taille binaire : Évitez libc ; syscalls pures <2KB.
  • Debug : objdump -d prog et gdb prog avec si (step instr).
  • Perf : perf stat ./prog pour cycles/instr ; optimisez IPC >2.

Erreurs courantes à éviter

  • Oublier flags : JNZ sans TEST/CMP met ZF faux.
  • Endianness : MOV DWORD [mem], 1 → 01 00 00 00 (little).
  • 64-bit overflow : MUL sans check RDX haut.
  • Syscall args : RCX→R10 pour >5 args ; pas stack.

Pour aller plus loin

Comment maîtriser Assembly x86-64 en 2026 (Expert) | Learni