Skip to content

Niveau 3 · Chapitre 3.1

Ce qu’est une instruction

La plus petite chose qu’on puisse demander à un CPU : comment s’écrit une instruction en assembleur — mnémonique, opérandes, tailles — ce qu’elle modifie, les règles que doivent respecter ses opérandes, la syntaxe Intel face à AT&T, et les octets derrière le texte.

Un CPU n’exécute pas des programmes au sens où on l’entend. Il exécute des instructions : de minuscules opérations fixes comme « copier cette valeur », « additionner ces deux nombres », « sauter à cette adresse ». Un programme en est une longue liste en mémoire, et le CPU les exécute l’une après l’autre, des milliards de fois par seconde.

Le langage d’assemblage est la forme lisible de ces instructions : une ligne de texte par instruction. C’est ce qu’affiche un désassembleur quand on ouvre un binaire, et ce que produit un compilateur à partir du C juste avant d’en faire du code machine.

Anatomie d’une instruction

loop:   add eax, ecx        ; eax = eax + ecx
PartieIciRôle
étiquette (facultative)loop:un nom pour l’adresse de cette instruction, que les sauts peuvent viser
mnémoniqueaddl’opération
opérandeseax, ecxce sur quoi elle opère
commentaire; eax = eax + ecxignoré par l’assembleur

Dans la syntaxe Intel utilisée sur ce site, le premier opérande est la destination. add eax, ecx signifie eax ← eax + ecx. La plupart des instructions x86 prennent deux opérandes, et la destination est aussi l’une des entrées : l’ancienne valeur de eax est remplacée par le résultat.

Trois sortes d’opérandes

Chaque opérande est l’une de ces trois choses :

  • un registre, une case de stockage nommée à l’intérieur du CPU : eax, rbx, r8… Le chapitre sur les registres les présente.
  • une valeur immédiate, une constante écrite dans l’instruction elle-même : 5, 0xff, -1.
  • un opérande mémoire, écrit entre crochets : la valeur située à une adresse. [rbp-4] signifie « la mémoire à l’adresse rbp moins 4 ». Les différentes façons de calculer cette adresse — base, index, facteur d’échelle, déplacement — sont les modes d’adressage du niveau ISA.
mov eax, 5                      ; immédiat → registre
mov ecx, eax                    ; registre → registre
mov DWORD PTR [rbp-4], ecx      ; registre → mémoire
mov edx, DWORD PTR [rbp-4]      ; mémoire → registre

DWORD PTR donne la taille d’un accès mémoire : BYTE (8 bits), WORD (16), DWORD (32) ou QWORD (64). Avec un opérande registre, le nom du registre suffit à indiquer la taille — eax fait 32 bits — et le mot-clé est facultatif. Avec seulement un opérande mémoire et une valeur immédiate, il est obligatoire : mov [rbp-4], 5 ne dit pas s’il faut écrire 1, 2, 4 ou 8 octets, et l’assembleur le refuse pour taille d’opérande ambiguë.

Ce que modifie une instruction

Chaque instruction lit un peu d’état et en écrit un peu. Les effets possibles sont peu nombreux :

  1. les registres — le registre de destination reçoit une nouvelle valeur ;
  2. les drapeaux — les instructions arithmétiques et logiques notent des faits sur leur résultat : nul, négatif, débordement… (le chapitre sur les drapeaux) ;
  3. la mémoire — un rangement écrit des octets à une adresse ;
  4. rip, le pointeur d’instruction — toujours, car il passe à l’instruction suivante, ou à la cible d’un saut.

Avancez pas à pas dans ce programme et observez chaque sorte de changement :

Live · Quatre instructions, quatre sortes d’effets
program— ▸ is the next instruction
  1. mov ecx, 7 ; un registre
  2. mov eax, 5
  3. add eax, ecx ; un registre et les drapeaux
  4. mov DWORD PTR [rbp-4], eax ; la mémoire
step 0
Loading emulator…
The instructions the compiler generated, and the registers, flags and stack they change.

mov ne fait que copier : il ne touche jamais aux drapeaux. add écrit eax = 12 et met à jour les drapeaux. Le dernier mov écrit 12 sur la pile, en [rbp-4]. Et rip avance à chaque pas.

Les règles

Le CPU n’implémente que certaines combinaisons d’opérandes, et l’assembleur refuse les autres. Voici celles que vous rencontrerez :

InterditPourquoiÀ la place
mov DWORD PTR [rbp-4], DWORD PTR [rbp-8]au plus un opérande mémoire explicite par instructionpasser par un registre
mov 5, eaxune valeur immédiate ne peut pas être une destinationmov eax, 5
mov eax, bxles deux opérandes doivent avoir la même taillemovzx eax, bx (extension par zéro)
mov [rbp-4], 5taille inconnuemov DWORD PTR [rbp-4], 5
add rax, 0x100000000les immédiats font au plus 32 bits (étendus avec le signe), sauf dans movle charger d’abord dans un registre

Ces cinq lignes sont refusées par un vrai assembleur : invalid operand for instruction ou ambiguous operand size. (Les instructions de chaîne comme movsb font exception : elles copient de mémoire à mémoire, avec les deux adresses implicites dans rsi et rdi.) La règle d’un seul opérande mémoire explique pourquoi le code compilé est plein de chargements dans des registres : le CPU calcule sur les registres, et la mémoire sert surtout à les remplir et à y ranger les résultats.

Deux façons d’écrire la même chose : Intel et AT&T

La même instruction machine peut s’écrire dans deux syntaxes d’assembleur. La syntaxe Intel est celle des manuels d’Intel, de NASM, MASM, IDA, Ghidra et de ce site. La syntaxe AT&T est la syntaxe traditionnelle des outils GNU : c’est ce qu’affichent par défaut objdump et gdb sous Linux.

IntelAT&T
ordre des opérandesdestination, sourcesource, destination
registreseax%eax
immédiats5$5
tailled’après les opérandes, ou DWORD PTRsuffixe du mnémonique : b, w, l, q
mémoire[rbx+rcx*8+16]16(%rbx,%rcx,8)

Les trois mêmes instructions, désassemblées dans les deux syntaxes :

Intel                                     AT&T
add  eax, ecx                             addl  %ecx, %eax
mov  eax, 0x5                             movl  $0x5, %eax
mov  rax, qword ptr [rbx + 8*rcx + 0x10]  movq  0x10(%rbx,%rcx,8), %rax

Pour obtenir la syntaxe Intel avec les outils GNU : objdump -d -M intel, ou set disassembly-flavor intel dans gdb. Une fois qu’on sait que l’ordre est inversé, lire l’une ou l’autre n’est qu’une question d’habitude.

Derrière le texte : des octets

Le CPU ne voit jamais le texte. L’assembleur transforme chaque ligne en code machine, quelques octets qui encodent l’opération et ses opérandes :

01 c8                              add    eax, ecx
b8 05 00 00 00                     mov    eax, 0x5
48 8b 44 cb 10                     mov    rax, qword ptr [rbx + 8*rcx + 0x10]
c7 45 fc 0a 00 00 00               mov    dword ptr [rbp - 0x4], 0xa
c3                                 ret
48 b8 88 77 66 55 44 33 22 11      movabs rax, 0x1122334455667788

Les instructions x86 font de 1 à 15 octets. ret tient en un octet, et un mov avec une constante de 64 bits en demande dix. Les constantes apparaissent en petit-boutiste dans les octets : 05 00 00 00 vaut 5. La disposition des bits — préfixes, opcode, ModRM, déplacement, immédiat — relève de l’architecture du jeu d’instructions, et le chapitre du niveau ISA sur les formats d’instructions la décortique. À ce niveau-ci, une ligne d’assembleur est une instruction, et cela suffit.

Combien d’instructions existe-t-il ?

Le x86-64 compte environ un millier de mnémoniques quand on inclut toutes les instructions SIMD et système, et bien plus de variantes de chacune. Mais le code réel en utilise un petit noyau. En comptant les instructions des versions x86-64 de quatre programmes ordinaires — ssh, vim, zip et curl, environ 764 000 instructions au total :

RangMnémoniquePart
1mov31,7 %
2call7,8 %
3lea7,7 %
4cmp6,6 %
5je5,4 %
6test5,3 %
7pop4,4 %
8push4,4 %
9xor4,1 %
10jmp4,0 %

Ces quatre programmes utilisent 284 mnémoniques différents, mais les 10 premiers couvrent 81 % de leurs instructions et les 20 premiers 93 %. Près d’un tiers de tout, c’est mov : l’essentiel du travail d’un programme consiste à déplacer des données entre la mémoire et les registres. Apprendre quelques dizaines d’instructions suffit pour lire l’essentiel d’un désassemblage. Le chapitre suivant en fait le tour.

À retenir

  • Une instruction est une opération élémentaire. L’assembleur l’écrit sous forme d’un mnémonique suivi d’opérandes, la destination en premier en syntaxe Intel.
  • Les opérandes sont des registres, des immédiats ou de la mémoire ([...]), avec une taille : byte, word, dword, qword.
  • Une instruction peut modifier des registres, les drapeaux, la mémoire et rip — rien d’autre.
  • Les règles : au plus un opérande mémoire explicite, des tailles identiques, pas de destination immédiate, et des immédiats limités à 32 bits hors de mov.
  • La syntaxe AT&T inverse les opérandes et ajoute %, $ et des suffixes de taille.
  • Chaque ligne s’assemble en 1 à 15 octets de code machine, et quelques dizaines de mnémoniques forment la quasi-totalité du code réel.

Dans ce niveau

  1. 3.1Ce qu’est une instruction
  2. 3.2Tour d’horizon du jeu d’instructions
  3. 3.3Les registres
  4. 3.4Drapeaux, cmp et sauts conditionnels
  5. 3.5Conventions d’appel (System V, cdecl)
  6. 3.6Assembleur, éditeur de liens et chargeur
  7. 3.7Directives, sections et macros