Opcode (ou code opération) désigne l'élément d'une instruction machine qui indique à l'unité centrale quelle opération élémentaire exécuter. On peut le comparer à un verbe dans une phrase : il dit « faire ceci », alors que les opérandes (paramètres de l'instruction) correspondent aux compléments ou sujets (adresses, registres, constantes...). Dans le code machine, chaque instruction contient en général un opcode suivi éventuellement d'un ou plusieurs opérandes.
Représentation et format
Pour des raisons matérielles, les opcodes sont stockés et traités sous forme binaire. Pour faciliter la lecture et l'édition, on les exprime souvent en hexadécimal : par exemple le motif binaire 10100101 peut s'écrire A5 en hexadécimal. Les opcodes modernes occupent au minimum un octet (deux caractères hexadécimaux), mais la taille peut varier selon l'architecture : certains jeux d'instructions utilisent des encodages à longueur fixe (par exemple 32 bits), d'autres des encodages à longueur variable (un octet à plusieurs octets).
Structure d'une instruction
Une instruction machine peut être composée de plusieurs champs :
- Champ opcode : identifie l'opération (addition, saut, chargement, etc.).
- Opérandes : registres, adresses mémoire ou valeurs immédiates utilisées par l'opération.
- Bits de mode / préfixes : modifient l'interprétation de l'instruction (taille d'opérande, segment, accès privilégié, etc.).
Selon l'architecture, l'opcode peut occuper tout ou partie du premier octet, et les autres bits servir à coder des registres ou des modes d'adressage. Les modes d'adressage (immédiat, registre, mémoire, relatif, indirect...) déterminent comment retrouver ou interpréter les opérandes.
Exemples concrets
- Sur l'architecture x86, l'instruction NOP (ne fait rien) est codée par l'opcode 0x90. Le jeu d'instructions x86 utilise un encodage variable et des préfixes qui rendent certains opcodes multi-octets.
- Sur des architectures RISC comme MIPS ou ARM (en mode ARM), les instructions ont souvent un encodage à longueur fixe (par exemple 32 bits), ce qui simplifie le décodage matériel.
- Dans les machines virtuelles (par exemple le bytecode Java ou le Common Intermediate Language .NET), on trouve aussi des opcodes — mais ils sont destinés à une machine virtuelle et non directement au processeur matériel, ce qui facilite la portabilité.
RISC vs CISC
Deux approches de conception d'ISA (Instruction Set Architecture) influencent le nombre et la complexité des opcodes :
- RISC (Reduced Instruction Set Computer) : ensemble d'instructions réduit, opérations simples et souvent de taille d'encodage fixe. Avantage : décodage et exécution rapides, pipeline efficace.
- CISC (Complex Instruction Set Computer) : beaucoup d'instructions diverses, certaines très complexes. Avantage : pouvoir exprimer directement des opérations complexes, parfois au coût d'un décodage matériel ou d'un microcode plus lourd.
Programmation, assembleur et portabilité
Les programmeurs écrivent rarement des opcodes purs (suites binaires). Ils utilisent un assembleur qui traduit des mnémoniques lisibles (par exemple MOV, ADD, JMP) en opcodes binaires correspondants. Cette traduction est spécifique à l'architecture : un opcode produit par un assembleur pour une machine A ne fonctionnera pas forcément sur une machine B si leurs ISA diffèrent.
Pour plus de portabilité, on écrit dans des langages de haut niveau. Ces langages sont compilés (ou interprétés) en code machine approprié pour la cible. Les machines virtuelles (Java, .NET) utilisent un niveau intermédiaire (bytecode/IL) dont les opcodes sont indépendants du processeur et sont ensuite traduits/compilés pour la plateforme d'exécution.
Décodage, microcode et compatibilité
Sur certaines architectures CISC, un microcode traduit des opcodes complexes en opérations internes plus simples. Les opcodes et leur signification sont définis par l'ISA et dépendent du matériel : un même nom d'instruction peut avoir des encodages différents selon le processeur, et de nouvelles extensions ajoutent des opcodes supplémentaires (par exemple SSE/AVX sur x86).
Outils et usages pratiques
- Assembleurs : convertissent mnémoniques en opcodes.
- Désassembleurs : lisent des binaires et affichent les mnémoniques correspondants (utile pour l'analyse, le debugging et la rétro-ingénierie).
- Émulateurs : simulent un jeu d'instructions et exécutent les opcodes d'une architecture sur une autre.
Sécurité, erreurs et maintenance
Une instruction contenant un opcode invalide provoque généralement une exception (« illegal instruction »). Les opcodes et leur encodage sont donc des points d'attention pour la sécurité : des séquences d'opcodes malformées peuvent être exploitées par des attaquants (exploits), ou être utilisées par des protections (obfuscation, anti-désassemblage). La maintenance binaire (patching) peut consister à modifier directement des opcodes dans un exécutable.
Conclusion
Un opcode est l'élément fondamental qui permet à un processeur d'exécuter une action. Sa forme (binaire/hexadécimale), sa taille et son comportement dépendent de l'architecture matérielle. Comprendre les opcodes, leur encodage et leur interaction avec les opérandes et les modes d'adressage est essentiel pour l'assembleur, la conception de processeurs, l'émulation, le reverse engineering et certaines questions de sécurité informatique.