La première chose dont vous avez besoin est quelque chose comme ce fichier . Il s'agit de la base de données d'instructions pour les processeurs x86 utilisée par l'assembleur NASM (que j'ai aidé à écrire, mais pas les parties qui traduisent réellement les instructions). Permet de choisir une ligne arbitraire dans la base de données:
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
Cela signifie qu'il décrit l'instruction ADD
. Il existe plusieurs variantes de cette instruction, et celle qui est décrite ici est la variante qui prend soit un registre 32 bits soit une adresse mémoire et ajoute une valeur immédiate 8 bits (c'est-à-dire une constante directement incluse dans l'instruction). Un exemple d'assemblage qui utiliserait cette version est le suivant:
add eax, 42
Maintenant, vous devez prendre votre texte et l'analyser en instructions et opérandes individuels. Pour l'instruction ci-dessus, cela aboutirait probablement à une structure qui contient l'instruction ADD
, et un tableau d'opérandes (une référence au registre EAX
et à la valeur 42
). Une fois que vous avez cette structure, vous parcourez la base de données d'instructions et trouvez la ligne qui correspond à la fois au nom de l'instruction et aux types d'opérandes. Si vous ne trouvez pas de correspondance, c'est une erreur qui doit être présentée à l'utilisateur ("combinaison illégale d'opcode et d'opérandes" ou similaire est le texte habituel).
Une fois que nous avons obtenu la ligne de la base de données, nous regardons la troisième colonne, qui pour cette instruction est:
[mi: hle o32 83 /0 ib,s]
Il s'agit d'un ensemble d'instructions qui décrivent comment générer l'instruction de code machine requise:
- Le
mi
est une description des opérandes: un opérande modr/m
(registre ou mémoire) (ce qui signifie que nous devrons ajouter un modr/m
octet à la fin de l'instruction, que nous reviendrons plus tard) et un une instruction immédiate (qui être utilisé dans la description de l'instruction).
- Le suivant est
hle
. Ceci identifie la façon dont nous gérons le préfixe "lock". Nous n'avons pas utilisé "lock", nous l'ignorons donc.
- Le suivant est
o32
. Cela nous indique que si nous assemblons du code pour un format de sortie 16 bits, l'instruction a besoin d'un préfixe de remplacement de taille d'opérande. Si nous produisions une sortie 16 bits, nous produirions le préfixe now ( 0x66
), mais je suppose que nous ne le sommes pas et continuons.
- Le suivant est
83
. Il s'agit d'un octet littéral en hexadécimal. Nous le sortons.
Le suivant est /0
. Cela spécifie quelques bits supplémentaires dont nous aurons besoin dans le sous-élément modr / m, et nous amène à le générer. L' modr/m
octet est utilisé pour coder des registres ou des références de mémoire indirectes. Nous avons un seul tel opérande, un registre. Le registre a un numéro, qui est spécifié dans un autre fichier de données :
eax REG_EAX reg32 0
Nous vérifions que cela reg32
correspond à la taille requise de l'instruction de la base de données d'origine (c'est le cas). C'est 0
le numéro du registre. Un modr/m
octet est une structure de données spécifiée par le processeur, qui ressemble à ceci:
(most significant bit)
2 bits mod - 00 => indirect, e.g. [eax]
01 => indirect plus byte offset
10 => indirect plus word offset
11 => register
3 bits reg - identifies register
3 bits rm - identifies second register or additional data
(least significant bit)
Parce que nous travaillons avec un registre, le mod
champ est 0b11
.
- Le
reg
champ est le numéro du registre que nous utilisons,0b000
- Parce qu'il n'y a qu'un seul registre dans cette instruction, nous devons remplir le
rm
champ avec quelque chose. C'est ce que les données supplémentaires spécifiées dans /0
était pour, nous avons donc mis que dans le rm
domaine, 0b000
.
- L'
modr/m
octet est donc 0b11000000
ou 0xC0
. Nous sortons cela.
- Le suivant est
ib,s
. Ceci spécifie un octet immédiat signé. Nous regardons les opérandes et notons que nous avons une valeur immédiate disponible. Nous le convertissons en octet signé et le sortons ( 42
=> 0x2A
).
L'instruction assemblé complète est donc: 0x83 0xC0 0x2A
. Envoyez-le à votre module de sortie, avec une note qu'aucun des octets ne constitue des références de mémoire (le module de sortie peut avoir besoin de savoir s'ils le font).
Répétez pour chaque instruction. Gardez une trace des étiquettes pour savoir quoi insérer lorsqu'elles sont référencées. Ajoutez des fonctionnalités pour les macros et les directives qui sont transmises à vos modules de sortie de fichier objet. Et c'est essentiellement comment fonctionne un assembleur.