Comme Harold souligné: il n'y a pas de raison.
Peut-être que les auteurs ont trouvé l'utilisation d'un mouvement vers l'avant plus attrayant qu'un mouvement inverse ou peut-être qu'ils ont juste pris le premier opcode. J'ai jeté un coup d'œil sur le code source de la NASM et j'ai découvert que l'encodage est fait essentiellement avec une grande table de recherche, donc c'est vraiment une question de goût. L'utilisation de l'autre opcode (8AC3) aurait simplifié le code (je suppose) si l'analyse n'avait pas utilisé une table de recherche: les instructions comme addps
sont asymétriques et en utilisant 8A /r
pour mov al, bl
le code pourrait être réutilisé pour calculer l'octet ModR/M pour addps
et des instructions similaires aussi. addps xmm0, xmm3
utilise le même octet ModR/M (C3) que mov al, bl
lorsque le 8A /r
est utilisé.
Notez que les registres A
(B
) et xmm0
(xmm0
) sont codés avec les mêmes numéros.
Il est toujours amusant, cependant, de comprendre pourquoi il existe deux codages.
Comme Mark Hopkins (re)discovered, the earlier x86 instructions encoding make a lot more sense in octal.
Un octet en octal a trois chiffres que j'appellerai G P F (Group, oPeration, Flags).
G est le groupe octal, les instructions dans le même groupe ont tendance à effectuer des tâches similaires (par exemple, arithmétique vs mouvements).
Cependant, ce n'est pas une division stricte.
P est l'opération; par exemple, dans le groupe arithmétique, une opération est la soustraction et une autre est l'addition.
F est un bitet utilisé pour contrôler le comportement de l'opération.Chaque groupe et chaque opération utilise le chiffre F à sa guise, il peut même ne pas être un bit défini (par exemple G = 2, P = 7 est mov r16, imm16
et F est utilisé pour sélectionner r16
).
Pour les instructions mov
qui se déplacent à partir de la mémoire/registre dans un registre ou dans l'autre sens autour de la G est égal à 2 et p est égal à 1. Le
F est un champ de 3 bits avec sémantique:
2 1 0 bit
+---+---+---+
| s | d | b |
+---+---+---+
s = 1 if moving to/from a segment register
0 if moving to/from a gp register
d = 1 if moving mem -> reg
0 if moving mem <- reg
b = 1 if moving a WORD
0 if moving a BYTE
Nous pouvons commencer à former des opcodes, mais nous manquons toujours un moyen de sélectionner les opérandes.
G=2, P=1, F={s=0, d=0, b=0} 210 (88) mov r/m8, r8
G=2, P=1, F={s=0, d=0, b=1} 211 (89) mov r/m16, r16
G=2, P=1, F={s=0, d=1, b=0} 212 (8A) mov r8, r/m8
G=2, P=1, F={s=0, d=1, b=1} 213 (8B) mov r16, r/m16
G=2, P=1, F={s=1, d=0, b=0} 214 (8C) mov r/m16, Sreg
G=2, P=1, F={s=1, d=0, b=1} 215 (8D) Not a move, segment registers are 16-bit
G=2, P=1, F={s=1, d=1, b=0} 216 (8E) mov Sreg, r/m16
G=2, P=1, F={s=1, d=1, b=1} 217 (8F) Not a move, segment registers are 16-bit
Après l'opcode doit venir l'octet ModR/M, il permet de sélectionner le mode d'adressage et le registre.
Le ModR/M octet peut être considéré, en octal, comme trois champs: X R M.
X et M sont combinés ensemble pour former le mode d'adressage.
R sélectionne le registre (par exemple 0 = A, 3 = B). L'un des modes d'adressage (X = 3, M = any) permet d'adresser les registres (via M) et non la mémoire.
Par exemple, X = 3, R = 0, M = 3 (C3) définit le registre B comme l'opérande «mémoire» et le registre A comme opérande de registre.
Lorsque X = 3, R = 3, M = 0 (D8) définit le registre A comme l'opérande "mémoire" et le registre B comme opérande de registre.
Ici, nous pouvons voir où se situe l'ambiguïté: l'octet ModR/M nous permet de coder un registre source et un registre de destination. Pendant ce temps, l'opcode permet de coder un déplacement de la source à la destination ou de la destination à la source - cela nous donne la liberté de choisir quel registre est quoi.
Par exemple, supposons que nous voulons déplacer B dans A.
Si nous nous installons sur A comme opérande de registre (source) et B comme opérande de mémoire (destination), puis l'octet ModR/M est X = 3, R = 0, M = 3 (C3). Pour passer de B à A, comme dans votre exemple, en utilisant les 8 bits inférieurs seulement, nous codons le mouvement comme suit: G = 2, P = 1, F = {s = 0, d = 1, b = 0} (8A) parce que nous déplaçons mem-> reg (B-> A). Ainsi, l'instruction finale est 8AC3. Si nous choisissons A comme opérande de mémoire (destination) et B comme opérande de registre (source), l'octet ModR/M est X = 3, R = 3, M = 0 (D8).
Le mouvement est G = 2, P = 1, F = {s = 0, d = 0, b = 0} (88) parce que nous déplaçons reg-> mem (B-> A).
L'instruction finale est 88D8.
Si nous voulons déplacer tout le registre 16 bits (nous négligeons préfixes taille d'opérandes ici) que nous venons de définir le bit b de F:
G = 2, P = 1, F = {s = 0 , d = 1, b = 1} pour le premier cas, conduisant à 8BC3.
G = 2, P = 1, F = {s = 0, d = 0, b = 1} pour le second cas, conduisant à 89D8.
Vous pouvez vérifier cela avec ndisasm
00000000 8AC3 mov al,bl
00000002 88D8 mov al,bl
00000004 8BC3 mov ax,bx
00000006 89D8 mov ax,bx
Aucune raison particulière, il suffit de choisir quelque chose. – harold