Comment passer du code dassemblage au code machine (génération de code)

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:

exemple dinstruction addlw

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érande modr/m (registre ou mémoire) (ce qui signifie que nous « devrons ajouter un modr/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. Loctet modr/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). Le 0 est le numéro du registre. Un octet modr/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 est 0b11.

  • 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 champ rm, 0b000.
  • Loctet modr/m est donc 0b11000000 ou 0xC0. 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

  • Merci. Excellente explication mais ne devrait pas ‘ être  » 0x83 0xC0 0x2A  » plutôt que  » 0x83 0xB0 0x2A  » car 0b11000000 = 0xC0
  • @Kamran – $ 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. 🙂

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)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *