Existe-t-il un moyen simple de visualiser létape entre lassemblage du code en code machine?
Par exemple, si vous ouvrez un fichier binaire dans le bloc-notes, vous voyez une représentation textuelle du code machine. Je suppose que chaque octet (symbole) que vous voyez est le caractère ascii correspondant à sa valeur binaire?
Mais comment passer de lassemblage au binaire, que se passe-t-il dans les coulisses ??
Réponse
Consultez la documentation du jeu dinstructions, et vous trouverez des entrées comme celle-ci de un microcontrôleur pic pour chaque instruction:
La ligne « encoding » indique à quoi ressemble cette instruction en binaire. Dans ce cas, il commence toujours par 5 uns, puis un bit de don « t care » (qui peut être un ou zéro), puis les « k » représentent le littéral que vous ajoutez.
Le les premiers bits sont appelés un « opcode », sont uniques pour chaque instruction. Le CPU regarde essentiellement lopcode pour voir de quelle instruction il sagit, puis il sait décoder les « k » comme un nombre à ajouter.
Cest fastidieux, mais pas si difficile à encoder et à décoder. Jai eu un cours de premier cycle où nous devions le faire à la main dans les examens.
Pour créer un fichier exécutable complet, vous devez également faire des choses comme allouer de la mémoire, calculer les décalages de branche et le mettre dans un au format ELF , selon votre système dexploitation.
Réponse
Les opcodes dassemblage ont, pour la plupart, une correspondance biunivoque avec les instructions machine sous-jacentes. Il vous suffit donc didentifier chaque opcode dans le langage dassemblage, de le mapper à linstruction machine correspondante et décrire linstruction machine dans un fichier, avec ses paramètres correspondants (le cas échéant). Vous répétez ensuite le processus pour chaque opcode supplémentaire dans le fichier source.
Bien sûr, il faut plus que cela pour créer un fichier exécutable qui se chargera et fonctionnera correctement sur un système dexploitation, et la plupart des assembleurs décents le font ont des capacités supplémentaires au-delà du simple mappage des opcodes sur les instructions de la machine (comme les macros, par exemple).
Réponse
Le premier il vous faut quelque chose comme ce fichier . Il sagit de la base de données dinstructions pour les processeurs x86 utilisée par lassembleur NASM (que jai aidé à écrire, mais pas les parties qui traduisent réellement les instructions). Choisissons une ligne arbitraire dans la base de données:
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
Ce que cela signifie, cest que il décrit linstruction ADD
. Il existe plusieurs variantes de cette instruction, et la variante spécifique 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 de 8 bits (cest-à-dire une constante directement incluse dans linstruction). Voici un exemple dinstruction dassemblage qui utiliserait cette version:
add eax, 42
Maintenant, vous devez prendre votre saisie de texte et lanalyser en instructions et opérandes individuels. Pour linstruction ci-dessus, cela aboutirait probablement à une structure contenant linstruction, ADD
, et un tableau dopé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 dinstructions et trouvez la ligne qui correspond à la fois au nom de linstruction et aux types dopérandes. Si vous ne trouvez pas de correspondance, cest une erreur qui doit être présentée à lutilisateur (« combinaison illégale dopcode et dopérandes » ou similaire est le texte habituel).
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]
Ceci est un ensemble dinstructions qui décrivent comment générer linstruction de code machine qui « est requise:
- Le
mi
est une description des opérandes: un opérandemodr/m
(registre ou mémoire) (ce qui signifie que nous « devrons ajouter unmodr/m
octet à la fin de linstruction, sur laquelle nous reviendrons plus tard) et une instruction immédiate (qui sera utilisée dans la description de linstruction). - Le suivant est
hle
. Cela identifie la manière dont nous gérons le préfixe « lock ». Nous navons pas utilisé « lock », donc nous lignorons. - Ensuite est
o32
. Cela nous indique que si nous « réassemblons le code pour un 16- format de sortie bit, linstruction a besoin dun préfixe de remplacement de taille dopérande.Si nous produisions une sortie 16 bits, nous produirions le préfixe maintenant (0x66
), mais je suppose que nous ne sommes pas et continuerons. - Le suivant est
83
. Cest un octet littéral en hexadécimal. Nous le générons. -
Le suivant est
/0
. Ceci spécifie quelques bits supplémentaires dont nous aurons besoin dans loctet modr / m, et nous amène à le générer. Loctetmodr/m
est utilisé pour encoder des registres ou des références mémoire indirectes. Nous avons un seul opérande de ce type, 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
reg32
est daccord avec la taille requise de linstruction de la base de données originale (cest le cas). Le0
est le numéro du registre. Un octetmodr/m
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)
-
Comme nous travaillons avec un registre, le champ
mod
est0b11
. - Le champ
reg
est le numéro du registre que nous » utilisons,0b000
- Comme il ny a quun seul registre dans cette instruction, nous devons remplir le champ
rm
avec quelque chose. C’est à cela que servaient les données supplémentaires spécifiées dans/0
, nous les avons donc placées dans le champrm
,0b000
. - Loctet
modr/m
est donc0b11000000
ou0xC0
. Nous affichons ceci. - Le suivant est
ib,s
. Ceci spécifie un octet immédiat signé. Nous regardons les opérandes et notons que nous avons un valeur disponible. Nous le convertissons en octet signé et le produisons (42
=>0x2A
).
Linstruction assemblée complète est donc: 0x83 0xC0 0x2A
. Envoyez-la à votre module de sortie, avec une note quaucun des octets ne constitue une référence mémoire (le module de sortie peut avoir besoin de savoir sils le font).
Répétez pour chaque instruction. Gardez une trace des étiquettes afin que vous sachiez quoi insérer lorsquelles sont référencées. Ajoutez des fonctionnalités pour les macros et les directives qui sont transmises aux modules de sortie de votre fichier objet. Et cest essentiellement ainsi que fonctionne un assembleur.
Commentaires
Réponse
En pratique, un assembleur ne produit généralement pas directement des exécutables binaires , mais des fichier objet (à alimenter plus tard dans le linker ). Cependant, il existe des exceptions (vous pouvez utiliser certains assembleurs pour produire directement un exécutable binaire ; ils sont rares).
Tout dabord, notez que de nombreux assembleurs sont aujourdhui des logiciels libres . Alors téléchargez et compilez sur votre ordinateur les sources code de GNU en tant que (une partie de binutils ) et de nasm . Ensuite, étudiez leur code source. BTW, je recommande dutiliser Linux à cette fin (cest un système dexploitation très convivial pour les développeurs et les logiciels libres).
Le fichier objet produit par un assembleur contient notamment un segment de code et les instructions de relocalisation . Il est organisé dans un format de fichier bien documenté, qui dépend du système dexploitation. Sous Linux, ce format (utilisé pour les fichiers objets, les bibliothèques partagées, les vidages de mémoire et les exécutables) est ELF . Ce fichier objet est ensuite entré dans le linker (qui produit finalement un exécutable). Les déplacements sont spécifiés par l ABI (par exemple, x86-64 ABI ). Lisez le livre de Levine Linkers and Loaders pour en savoir plus.
Le segment de code dans un tel fichier objet contient code machine avec des trous (à remplir, à laide des informations de relocalisation, par léditeur de liens). Le code machine (relocalisable) généré par un assembleur est évidemment spécifique à un jeu dinstructions architecture .Les ISA x86 ou x86-64 (utilisés dans la plupart des processeurs dordinateurs portables ou de bureau) sont terriblement complexes dans leurs détails. Mais un sous-ensemble simplifié, appelé y86 ou y86-64, a été inventé à des fins denseignement. Lisez les diapositives dessus. Dautres réponses à cette question expliquent également un peu cela. Vous voudrez peut-être lire un bon livre sur l’architecture informatique .
La plupart des assembleurs travaillent dans deux passes , le second émettant une relocalisation ou corrigeant une partie de la sortie de la première passe. Ils utilisent désormais les techniques danalyse habituelles (alors lisez peut-être The Dragon Book ).
Comment un exécutable est lancé par le noyau du système dexploitation (par exemple, comment lappel système execve
fonctionne sous Linux ) est une question différente (et complexe). Il configure généralement un espace dadressage virtuel (dans le processus en faisant cela execve (2) …) puis réinitialisez létat interne du processus (y compris les registres user-mode ). Un éditeur de liens dynamique – tel que ld-linux.so (8) sous Linux peut être impliqué lors de lexécution. Lisez un bon livre, tel que Système dexploitation: trois éléments simples . Le wiki OSDEV donne également des informations utiles.
PS. Votre question est si vaste que vous devez lire plusieurs livres à ce sujet. Jai donné quelques références (très incomplètes). Vous devriez en trouver plus.
Commentaires
- Concernant les formats de fichier objet, pour un débutant I ‘ Je recommande de regarder le format RDOFF produit par NASM. Cela a été intentionnellement conçu pour être aussi simple que possible et fonctionne toujours dans une variété de situations. La source NASM comprend un éditeur de liens et un chargeur pour le format. (Divulgation complète – Jai conçu et écrit tous ces éléments)
$ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003
… ouais, vous ‘ avez tout à fait raison. 🙂