web-dev-qa-db-fra.com

Qu'est-ce que C et Assembler compilent réellement?

J'ai donc découvert que les programmes C (++) ne compilaient pas en binaire (je me suis peut-être trompé, dans ce cas, je suis désolé: D), mais à une gamme de choses (table des symboles , trucs liés aux os, ...) mais ...

  • L'assembleur "compile-t-il" en binaire pur? Cela signifie qu'aucune ressource supplémentaire ne se trouve à part des ressources telles que des chaînes prédéfinies, etc.

  • Si C compile quelque chose d'autre que du simple binaire, comment ce petit chargeur de démarrage d'assembleur peut-il simplement copier les instructions du disque dur dans la mémoire et les exécuter? Je veux dire si le noyau du système d'exploitation, qui est probablement écrit en C, est compilé en quelque chose de différent du simple binaire - comment le chargeur de démarrage le gère-t-il?

edit: Je sais que l’assembleur ne "compile" pas car il ne contient que le jeu d’instructions de votre machine - je n’ai pas trouvé le bon mot pour lequel l’assembleur "s’assemble". Si vous en avez un, laissez-le ici comme commentaire et je le changerai.

44
lamas

C est généralement compilé en assembleur, simplement parce que cela simplifie la vie du pauvre compilateur.

Le code d'assemblage assemble toujours (pas "compile") à le code objet relogeable . Vous pouvez considérer cela comme un code machine binaire et des données binaires, mais avec beaucoup de décoration et de métadonnées. Les parties clés sont:

  • Le code et les données apparaissent dans les "sections" nommées.

  • Les fichiers objet déplaçables peuvent inclure les définitions de labels , qui font référence à des emplacements dans les sections.

  • Les fichiers objet pouvant être déplacés peuvent inclure des "trous" qui doivent être remplis avec les valeurs des étiquettes définies ailleurs. Le nom officiel pour un tel trou est une entrée de relocalisation .

Par exemple, si vous compilez et assemblez (mais ne liez pas) ce programme

int main () { printf("Hello, world\n"); }

vous êtes susceptible de vous retrouver avec un fichier objet déplaçable avec

  • Une section text contenant le code machine pour main

  • Une définition d'étiquette pour main qui pointe au début de la section de texte

  • Une section rodata (données en lecture seule) contenant les octets du littéral de chaîne "Hello, world\n"

  • Une entrée de relocalisation qui dépend de printf et qui pointe vers un "trou" dans une instruction d'appel au milieu d'une section de texte.

Si vous êtes sur un système Unix, un fichier objet déplaçable est généralement appelé fichier .o, comme dans hello.o, et vous pouvez explorer les définitions d'étiquettes et les utiliser avec un outil simple appelé nm. Vous pouvez ainsi obtenir des informations plus détaillées outil compliqué appelé objdump.

J'enseigne un cours sur ces sujets et les étudiants écrivent un assembleur et un éditeur de liens, ce qui prend quelques semaines, mais quand ils l'ont fait, la plupart d'entre eux maîtrisent assez bien le code objet déplaçable. Ce n'est pas une chose si facile.

40
Norman Ramsey

Prenons un programme en C.

Lorsque vous exécutez 'gcc' ou 'cl' sur le programme c, il passera par les étapes suivantes:

  1. Pré-traitement par le pré-processeur (#include, #ifdef, analyse trigraphique, encodage des traductions, gestion des commentaires, macros ...)
  2. Analyse lexicale (production de jetons et d'erreurs lexicales).
  3. Analyse syntaxique (production d'un arbre d'analyse et d'erreurs syntaxiques).
  4. Analyse sémantique (production d'une table de symboles, informations de cadrage et erreurs de cadrage/de frappe).
  5. Sortie dans Assembly (ou un autre format intermédiaire)
  6. Optimisation de l'assemblage (comme ci-dessus). Probablement en cordes ASM encore.
  7. Assemblage de l'Assemblée dans un format d'objet binaire.
  8. Liaison de l’Assemblée avec les bibliothèques statiques nécessaires, ainsi que son déplacement au besoin.
  9. Sortie de l'exécutable final au format elf ou coff.

En pratique, certaines de ces étapes peuvent être effectuées en même temps, mais c'est l'ordre logique.

Notez qu'il existe un 'conteneur' de format elf ou coff autour du binaire exécutable réel.

Vous constaterez qu'un livre sur les compilateurs (je recommande le Dragon book, le livre d'introduction standard sur le terrain) aura all les informations dont vous avez besoin et plus encore.

Comme Marco l'a commenté, la liaison et le chargement constituent une vaste zone et le livre Dragon s'arrête plus ou moins à la sortie du fichier binaire exécutable. Passer de là à fonctionner sur un système d’exploitation est un processus complexe, que Levine in Linkers and Loaders couvre.

J'ai wiki'd cette réponse pour permettre aux gens Tweak toute erreur/ajouter des informations. 

35
Paul Nathan

La traduction de C++ en un exécutable binaire comporte différentes phases. La spécification de langue n'énonce pas explicitement les phases de traduction. Cependant, je vais décrire les phases de traduction courantes. 

Source C++ To Assembly ou Itermediate Language

Certains compilateurs traduisent en réalité le code C++ dans un langage d'assemblage ou un langage intermédiaire. Ce n'est pas une phase obligatoire, mais utile pour le débogage et les optimisations. 

Code d'assemblage à objet

La prochaine étape commune consiste à traduire le langage d'assemblage en un code objet. Le code objet contient le code Assembly avec des adresses relatives et des références ouvertes à des sous-routines externes (méthodes ou fonctions). En général, le traducteur insère autant d'informations que possible dans un fichier objet. Tout le reste est non résolu .

Lier des codes d'objet

La phase de liaison combine un ou plusieurs codes d'objet, résout les références et élimine les sous-routines en double. La sortie finale est un exécutable fichier. Ce fichier contient des informations sur le système d’exploitation et les adresses relative .

Execution Binary Fichiers

Le système d'exploitation charge le fichier exécutable, généralement à partir d'un disque dur, et le place en mémoire. Le système d'exploitation peut convertir les adresses relatives en emplacements physiques. Le système d'exploitation peut également préparer des ressources (telles que des DLL et des widgets d'interface graphique) requises par l'exécutable (qui peuvent être spécifiées dans le fichier Executable).

Compiler directement en binaire Certains compilateurs, tels que ceux utilisés dans Embedded Systems, permettent de compiler directement à partir de C++ en un code binaire exécutable. Ce code aura des adresses physiques au lieu d'adresses relatives et ne nécessitera pas de système d'exploitation à charger.

Avantages

L'un des avantages de ces phases est que les programmes C++ peuvent être décomposés, compilés individuellement et liés ultérieurement. Ils peuvent même être liés à des éléments d'autres développeurs (bibliothèques a.k.a.). Cela permet aux développeurs de ne compiler que des éléments en développement et de les lier à des éléments déjà validés. En général, la traduction de C++ en objet est la partie du processus qui prend du temps. En outre, une personne ne veut pas attendre que toutes les phases soient terminées en cas d'erreur dans le code source.

Gardez l'esprit ouvert et attendez-vous toujours du Troisième alternative (Option) .

18
Thomas Matthews

Pour répondre à vos questions, veuillez noter que ceci est subjectif car il existe différents processeurs, différentes plates-formes, différents assembleurs et compilateurs C, dans ce cas, je parlerai de la plate-forme Intel x86.

  1. Les assembleurs ne compilent pas en binaire pur, ils sont du code machine brut, défini avec des segments, tels que data, text et bss to name, mais quelques-uns sont appelés du code objet. L'éditeur de liens intervient et ajuste les segments pour le rendre exécutable, c'est-à-dire prêt à être exécuté. Incidemment, la sortie par défaut lorsque vous compilez avec gcc est 'a.out', ce qui est un raccourci pour Assembler Output.
  2. Les chargeurs de démarrage ont une directive spéciale définie, à l'époque de DOS, il était courant de trouver une directive telle que .Org 100h, qui définit le code d'assembleur comme étant de l'ancienne variété .COM avant que .EXE ne prenne en popularité. De plus, vous n’aviez pas besoin d’un assembleur pour produire un fichier .COM, en utilisant l’ancien debug.exe fourni avec MSDOS, fonctionnait bien pour les petits programmes simples, les fichiers .COM n’ayant pas besoin d’un éditeur de liens, ils étaient prêts à l'emploi. exécuter le format binaire. Voici une simple session utilisant DEBUG.
 1: * a 0100 
 2: * mov AH, 07 
 3: * int 21 
 4: * cmp AL, 00 
 5 : * jnz 010c 
 6: * mov AH, 07 
 7: * int 21 
 8: * mov AH, 4C 
 9: * int 21 
 10: * 
 11: * r CX 
 12: * 10 
 13: * n respond.com 
 14: * w 
 15: * q 

Cela produit un programme .COM prêt à fonctionner, appelé «respond.com», qui attend une frappe et ne l'envoie pas à l'écran. Remarquez, au début, l'utilisation de 'a 100h' qui indique que le pointeur d'instruction commence à 100h, caractéristique d'un fichier .COM. Cet ancien script était principalement utilisé dans les fichiers de commandes en attente d'une réponse et non en écho. Le script original peut être trouvé ici .

Encore une fois, dans le cas des chargeurs de démarrage, ils sont convertis au format binaire. Il existait un programme qui venait avec DOS, appelé EXE2BIN . C'était le travail de convertir le code d'objet brut en un format pouvant être copié sur un disque amorçable pour amorcer. N'oubliez pas qu'aucun éditeur de liens n'est exécuté sur le code assemblé, car il est destiné à l'environnement d'exécution et configure le code pour le rendre exécutable et exécutable.

Le BIOS lors de l’amorçage s'attend à ce que le code soit au segment: offset, 0x7c00, si ma mémoire est correcte, le code (après avoir été EXE2BIN), commencera à s'exécuter, puis le chargeur de démarrage se replacera plus bas dans la mémoire et continuera à charger par émettre int 0x13 pour lire sur le disque, allumer la porte A20, activer le DMA, basculer sur le mode protégé car le BIOS est en mode 16 bits, les données lues sur le disque sont chargées en mémoire, puis le chargeur d'amorçage émet un saut lointain dans le code de données (susceptible d'être écrit en C). C'est en gros comment le système démarre. 

Ok, le paragraphe précédent semble abstrait et simple, il se peut que j’ai oublié quelque chose, mais c’est comme ça en quelques mots.

J'espère que cela vous aidera, Cordialement, Tom.

3
t0mm13b

Il y a deux choses que vous pouvez mélanger ici. Généralement, il y a deux sujets:

Ce dernier peut se compiler avec le premier en cours de montage. Certains formats intermédiaires ne sont pas assemblés, mais exécutés par une machine virtuelle. Dans le cas de C++, il peut être compilé dans CIL, qui est assemblé dans un assemblage .NET, ce qui crée une certaine confusion.

Mais en général, C et C++ sont généralement compilés en binaire, ou en d'autres termes, dans un format de fichier exécutable.

1
Kornel Kisielewicz

Vous avez beaucoup de réponses à lire, mais je pense pouvoir garder ceci succinct.

"Code binaire" fait référence aux bits qui alimentent les circuits du microprocesseur. Le microprocesseur charge chaque instruction de la mémoire en séquence, en faisant tout ce qu'ils disent. Différentes familles de processeurs ont différents formats pour les instructions: x86, ARM, PowerPC, etc. Vous dirigez le processeur vers l’instruction souhaitée en lui donnant l’adresse de celle-ci en mémoire, puis il progresse joyeusement dans le reste du programme.

Lorsque vous voulez charger un programme dans le processeur, vous devez d'abord rendre le code binaire accessible en mémoire afin qu'il ait une adresse en premier lieu. Le compilateur C génère un fichier dans le système de fichiers, qui doit être chargé dans un nouvel espace d'adressage virtuel. Par conséquent, en plus du code binaire, ce fichier doit inclure les informations qu'il possède - code binaire et ce à quoi son espace d'adressage devrait ressembler.

Un chargeur de démarrage a des exigences différentes, son format de fichier peut donc être différent. Mais l'idée est la même: le code binaire est toujours une charge utile dans un format de fichier plus volumineux, qui inclut au minimum un contrôle de cohérence afin de s'assurer qu'il est écrit dans le bon jeu d'instructions.

Les compilateurs et les assembleurs C sont généralement configurés pour produire des fichiers de bibliothèque statiques. Pour les applications intégrées, il est plus probable que vous trouviez un compilateur qui produit quelque chose comme une image de mémoire brute avec des instructions commençant à l'adresse zéro. Sinon, vous pouvez écrire un éditeur de liens qui convertit la sortie du compilateur C en tout ce que vous voulez.

1
Potatoswatter

Ils compilent un fichier dans un format spécifique (COFF pour Windows, etc.), composé d’en-têtes et de segments, dont certains ont des codes op "binaires". Les assembleurs et les compilateurs (tels que C) créent le même type de sortie. Certains formats, tels que les anciens fichiers * .COM, ne comportaient aucun en-tête, mais comportaient encore certaines hypothèses (telles que l'emplacement de chargement ou la taille de la mémoire).

Sur les machines Windows, le boostrapper du système d'exploitation se trouve dans un secteur de disque chargé par le BIOS, où ces deux éléments sont "clairs". Une fois que le système d'exploitation a chargé son chargeur, il peut lire les fichiers contenant des en-têtes et des segments.

Est ce que ça aide?

1
Steven Sudit

Pour répondre à la partie de la question relative à l’Assemblée, l’Assemblée ne compile pas en binaire si je comprends bien. Assemblée === binaire. Cela traduit directement. Chaque opération d'assemblage a une chaîne binaire qui lui correspond directement. Chaque opération a un code binaire et chaque variable de registre a une adresse binaire.

C’est-à-dire, sauf si Assembler! = Assembly et j’ai mal compris votre question.

1
Daniel Bingham

Si je comprends bien, un chipset (processeur, etc.) aura un ensemble de registres pour stocker des données et comprendra un ensemble d'instructions pour manipuler ces registres. Les instructions consisteront par exemple en "stocke cette valeur dans ce registre", "déplace cette valeur" ou "compare ces deux valeurs". Ces instructions sont souvent exprimées sous forme de codes alphabétiques abrégés (langage d'assemblage ou assembleur) pouvant être associés à des nombres compris par le chipset - ces chiffres sont présentés à la puce en binaire (code machine).

Ces codes sont le niveau le plus bas auquel le logiciel parvient. Aller plus loin que cela entre dans l’architecture de la puce, et c’est quelque chose dans lequel je n’ai pas été impliqué.

0
Laizer

Vous trouverez ci-dessus une foule de réponses, mais j'ai pensé ajouter ces ressources qui vous donneront une idée de ce qui se passe. Fondamentalement, sous Windows et Linux, quelqu'un a essayé de créer le plus petit exécutable possible. sous Linux, ELF, Windows, PE.

Les deux passent en revue ce qui est supprimé et pourquoi et vous utilisez des assembleurs pour construire des fichiers ELF sans utiliser les options -felf comme qui le font à votre place.

J'espère que cela pourra aider.

Éditer - vous pouvez aussi jeter un coup d’œil à l’Assemblée pour un chargeur de démarrage tel que celui de truecrypt http://www.truecrypt.org ou "stage1" de grub (le bit qui est écrit dans le MDR).

0
user257111

Les fichiers exécutables (format PE sous Windows) ne peuvent pas être utilisés pour démarrer l'ordinateur car le chargeur PE n'est pas en mémoire. 

L'amorçage fonctionne de la manière suivante: l'enregistrement de démarrage principal sur le disque contient un blob de quelques centaines d'octets de code. Le BIOS de l'ordinateur (dans ROM sur la carte mère) charge ce blob en mémoire et définit le pointeur d'instruction du processeur au début de ce code de démarrage. 

Le code de démarrage charge ensuite un chargeur "de deuxième étape" sous Windows appelé NTLDR (sans extension) à partir du répertoire racine. C'est le code machine brut qui, comme le chargeur MBR, est chargé en mémoire froide et exécuté. 

NTLDR a la capacité complète de charger des fichiers PE, y compris des DLL et des pilotes.

0
Tyler Durden