Programmation Assembleur/x86/Copie entre registres et mémoire
Programmation Assembleur/x86 |
Modifier ce modèle |
Syntaxes des assembleurs
[modifier | modifier le wikicode]Il existe deux variantes principales et opposées de syntaxe pour les instructions en assembleur :
- la syntaxe Intel où l'opérande destination est placé avant l'opérande source.
- Exemple :
mov ax, bx ; AX <- BX transfert des 16 bits de BX vers AX.
- la syntaxe AT&T où l'opérande source est placé avant l'opérande destination.
- Exemple :
mov %bx, %ax ; BX -> AX transfert des 16 bits de BX vers AX.
L'interprétation peut donc être ambiguë si on ne sait pas quelle syntaxe est employée. Les assembleurs sous DOS et Windows utilisent la syntaxe Intel, qui semble plus naturelle pour les opérations arithmétiques et conserve l'ordre des expressions arithmétiques :
add eax, 7 ; EAX <- EAX+7
Les assembleurs sous Linux utilisent la syntaxe AT&T, qui conserve l'ordre des mots pour expliquer les expressions arithmétiques :
add $7, %eax ; Ajouter 7 à EAX
La distinction entre les deux syntaxes est donc importante à faire :
- Dans la syntaxe Intel où l'opérande destination est placé avant l'opérande source, le nom des registres et les valeurs littérales n'ont pas de préfixe.
- Dans la syntaxe AT&T où l'opérande source est placé avant l'opérande destination, le nom des registres est préfixé par le caractère pourcent et les valeurs littérales sont préfixé par le caractère dollar.
syntaxe Intel (DOS, Windows, ...) | syntaxe AT&T (Linux, ...) |
---|---|
add eax, 7
mov dword ptr [rbp-0x4],edi
imul ecx, [rdi + rax*4 + 20], 12345
|
add $7, %eax
mov %edi,-0x4(%rbp)
imul $12345, 20(%rdi, %rax, 4), %ecx
|
Transfert unitaire
[modifier | modifier le wikicode]L'instruction la plus courante est MOV (abréviation de l'anglais move : déplacer) qui permet de transférer le contenu d'un emplacement mémoire vers un registre et inversement, ou entre deux registres de même taille.
Le nombre de bits transféré est défini par la taille du registre source ou cible du transfert. Exemple (syntaxe destination, source) :
mov ax, bx ; transfert des 16 bits de BX vers AX.
Les assembleurs peuvent employer des suffixes pour le nom de l'instruction indiquant ce nombre d'octets :
- movb (Move Byte) : 8 bits,
- movw (Move Word) : 16 bits,
- movl (Move Long) : 32 bits.
Exemple (syntaxe source, destination) :
.data
valeur:
.long 2
.text
.global _start
_start:
movl $6, %eax
# %eax = 6
movw %ax, valeur
# valeur = 6
movl $0, %ebx
# %ebx = 0
movb %al, %bl
# %ebx = 6
movl valeur, %ebx
# %ebx = 6
movl $valeur, %esi
# %esi = l’address de valeur
xorl %ebx, %ebx
# %ebx = 0
movw valeur(, %ebx, 1), %bx
# %ebx = 6
# Linux sys_exit
mov $1, %eax
xorl %ebx, %ebx
int $0x80
Transfert de séquence
[modifier | modifier le wikicode]Une famille d'instructions gère les séquences d'octets en mémoire :
- les instructions
MOVS
transfèrent les octets d'une zone mémoire à une autre (Move Sequence), - les instructions
CMPS
comparent le contenu de deux zones mémoire (Compare Sequence), - les instructions
LODS
transfère les octets d'une zone mémoire vers le registre EAX (Load Sequence), - les instructions
STOS
transfère le contenu du registre EAX vers une zone mémoire (Store Sequence), - les instructions
SCAS
comparent le contenu du registre EAX à celui d'une zone mémoire (Scan Sequence).
Quantité traitée
[modifier | modifier le wikicode]Le nom de ces opérations se termine par un préfixe indiquant la quantité traitée à chaque appel :
B
(Byte) pour un octet de 8 bits :MOVSB
,CMPSB
,LODSB
,STOSB
,SCASB
;W
(Word) pour un mot de 16 bits :MOVSW
,CMPSW
,LODSW
,STOSW
,SCASW
;D
(Double word) pour un double mot (32 bits) :MOVSD
,CMPSD
,LODSD
,STOSD
,SCASD
.
Registres utilisés et opérations
[modifier | modifier le wikicode]Ces opérations utilisent les registres dédiés pour adresser la mémoire :
- DS:SI / DS:ESI pointant la zone mémoire source (instructions
MOVS*
,CMPS*
,LODS*
), - ES:DI / ES:EDI pointant la zone mémoire destination (instructions
MOVS*
,CMPS*
,STOS*
,SCAS*
).
Ces registres sont incrémentés ou décrémentés après chaque traitement par ces opérations, selon la valeur de l'indicateur de direction (DF) :
- s'il est positionné à 0, les registres (E)SI et (E)DI sont incrémentés de 1, 2 ou 4 selon la quantité traitée à chaque appel,
- S'il est positionné à 1, les registres (E)SI et (E)DI sont décrémentés de 1, 2 ou 4 selon la quantité traitée à chaque appel.
Mnémotechnique : DF = Decrement Flag
Cet indicateur est généralement positionné juste avant les instructions de traitement en utilisant l'instruction CLD
(CLear Direction) ou STD
(SeT Direction).
Le tableau ci-dessous résume les opérations effectuées par les instructions et les registres utilisés :
Instructions | Source | Destination | Opération |
---|---|---|---|
MOVS* | [DS:(E)SI] | [ES:(E)DI] | Transfert de source vers destination (Move). |
LODS* | [DS:(E)SI] | (E)AX | Transfert de source vers le registre EAX (Load). |
STOS* | (E)AX | [ES:(E)DI] | Transfert du registre EAX vers destination (Store). |
CMPS* | [DS:(E)SI] | [ES:(E)DI] | Met à jour les indicateurs après comparaison entre source et destination. |
SCAS* | (E)AX | [ES:(E)DI] | Met à jour les indicateurs après comparaison entre le registre EAX et destination. |
Répétition et boucle
[modifier | modifier le wikicode]Ces instructions effectuent le traitement d'une quantité unitaire de données, mais deviennent efficaces et puissantes en combinaison d'instructions répétant l'opération en boucle. Pour répéter ces opérations, les instructions de boucle et répétition utilisent le registre CX (Counter) en le décrémentant et répétant l'opération tant que le compteur n'atteint pas zéro. Le registre CX est donc initialisé avant la boucle ou répétition avec le nombre d'octets, de mots ou de double-mots à traiter.
; Code non montré :
; ... initialiser les registres DS, SI pour la source
; ... initialiser les registres ES, DI pour la destination,
mov cx, 0x1FF ; transférer 511 mots de 16 bits
cld ; incrémenter les pointeurs après chaque itération
rep ; répéter CX fois...
movsw ; ...le transfert d'un mot de 16 bits
- L'instruction
REPZ
répète l'instruction de séquence qui suit tant que le registre CX n'est pas à zéro et que le flag Zero (ZF) vaut 0 (c'est à dire jusqu'à ZF=1 : REPeat until Zero). - L'instruction
REPNZ
répète l'instruction de séquence qui suit tant que le registre CX n'est pas à zéro et que le flag Zero (ZF) vaut 1 (c'est à dire jusqu'à ZF=0 : REPeat until Not Zero).
Le flag Zero (ZF) est pris en compte seulement si l'opération qui suit est CMPS*
ou SCAS*
.
Il est ignoré pour les opérations de transfert MOVS*
, LODS*
ou STOS*
.
Cela signifie que pour ces 3 opérations REPZ
et REPNZ
sont identiques, et les assembleurs ajoutent donc un alias REP
qui en général est compilé en une instruction REPZ
.
Les préfixes REPZ
et REPNZ
ne répète que l'instruction de séquence qui suit.
Pour répéter une série d'instructions, notamment pour LODS*
où l'assignation répétée dans le registre AX n'a pas d'intérêt réel, il faut utiliser les instructions LOOP*
.
Les instructions LOOP*
ont 3 variantes dont le comportement ne varient que par rapport au flag zéro (ZF) :
- L'instruction
LOOPZ
décrémente le registre (E)CX, puis s'il n'est pas à zéro et que le flag zéro (ZF) vaut 1 (LOOP while CX > 0 and Zero) saute à l'adresse indiquée. - L'instruction
LOOPNZ
décrémente le registre (E)CX, puis s'il n'est pas à zéro et que le flag zéro (ZF) vaut 0 (LOOP while CX > 0 and Not Zero) saute à l'adresse indiquée. - L'instruction
LOOP
décrémente le registre (E)CX, puis s'il n'est pas à zéro (LOOP while CX > 0) saute à l'adresse indiquée.
; Code non montré :
; ... initialiser les registres DS, SI pour la source
mov cx, 200 ; lire 200 octets
cld ; incrémenter les pointeurs après chaque itération
:iteration
lodsb ; lecture d'un octet de DS:(E)SI dans AL
call printchar ; appel à une sous-routine pour afficher le caractère de code AL
cmp al, 0 ; compare AL à 00 qui marque la fin de chaîne de caractère
loopnz iteration ; décrémente CX, et boucle si ZF=0 (AL différent de 00)
Enfin, il existe une instruction JCXZ
qui ne modifie pas le registre (E)CX mais saute à l'adresse indiquée si le registre (E)CX vaut 0.
Dans le cas d'un compteur d'itération, elle permet donc de sortir d'une boucle infinie.
Préfixes de changement de segment
[modifier | modifier le wikicode]Les préfixes de changement de segment n'influence que le registre de segment DS utilisé pour la source.
Cela signifie qu'ils n'influencent que les instructions MOVS*
, CMPS*
, LODS*
.
; Code non montré :
; ... initialiser les registres ES, SI pour la source (même registre ES que la destination)
; ... initialiser les registres ES, DI pour la destination,
mov cx, 0x1FF ; transférer 511 mots de 16 bits
cld ; incrémenter les pointeurs après chaque itération
es: ; utiliser le segment ES au lieu de DS
rep ; répéter CX fois...
movsw ; ...le transfert d'un mot de 16 bits
Autres transferts
[modifier | modifier le wikicode]Le transfert entre certains registres pour lesquels il n'existe pas d'instruction peut s'effectuer en utilisant la pile, comme par exemple pour le transfert entre le registre AX et celui des indicateurs (FLAGS) :
; Transfert de FLAGS vers AX
pushf ; met la valeur du registre FLAGS dans le pile,
pop ax ; puis la retire de la pile pour la mettre dans le registre AX.
L'instruction LAHF
(Load AH from Flags) permet de transférer la valeur des 8 bits de poids faible du registre FLAGS vers le registre AH (bits 15 à 8 du registre AX / EAX).
L'instruction SAHF
(Save AH to Flags) effectue le transfert inverse.
L'instruction XLAT
transfère l'octet pointé par DS:(BX+AL) dans le registre AL.