Comment exécutez-vous un programme tout seul sans un système d'exploitation en cours d'exécution? Pouvez-vous créer des programmes d'assemblage que l'ordinateur peut charger et exécuter au démarrage, par exemple, démarrer l'ordinateur à partir d'un lecteur flash et exécuter le programme qui se trouve sur le processeur?
assembly
x86
operating-system
bootloader
osdev
user2320609
la source
la source
Réponses:
Vous placez votre code binaire à un endroit où le processeur recherche après le redémarrage (par exemple l'adresse 0 sur ARM).
Réponse générale à la question: cela peut être fait. Il est souvent appelé "programmation bare metal". Pour lire à partir du lecteur flash, vous voulez savoir ce qu'est l'USB, et vous voulez avoir un pilote pour travailler avec cet USB. Le programme sur ce lecteur devrait également être dans un format particulier, sur un système de fichiers particulier ... C'est quelque chose que les chargeurs de démarrage font habituellement, mais votre programme pourrait inclure son propre chargeur de démarrage afin qu'il soit autonome, si le firmware ne veut que charger un petit bloc de code.
De nombreuses cartes ARM vous permettent de faire certaines de ces choses. Certains ont des chargeurs de démarrage pour vous aider avec la configuration de base.
Ici vous pouvez trouver un bon tutoriel sur la façon de faire un système d'exploitation de base sur un Raspberry Pi.
Edit: Cet article et l'ensemble du wiki.osdev.org répondront à la plupart de vos questions http://wiki.osdev.org/Introduction
De plus, si vous ne voulez pas expérimenter directement sur le matériel, vous pouvez l'exécuter comme une machine virtuelle en utilisant des hyperviseurs comme qemu. Découvrez comment exécuter "hello world" directement sur du matériel ARM virtualisé ici .
la source
Exemples exécutables
Créons et exécutons de minuscules programmes hello world bare metal qui s'exécutent sans système d'exploitation sur:
Nous les testerons également sur l'émulateur QEMU autant que possible, car cela est plus sûr et plus pratique pour le développement. Les tests QEMU ont été effectués sur un hôte Ubuntu 18.04 avec QEMU 2.11.1 préemballé.
Le code de tous les exemples x86 ci-dessous et plus est présent sur ce dépôt GitHub .
Comment exécuter les exemples sur du matériel réel x86
N'oubliez pas que l'exécution d'exemples sur du matériel réel peut être dangereuse, par exemple, vous pouvez effacer votre disque ou brique le matériel par erreur: ne faites cela que sur de vieilles machines qui ne contiennent pas de données critiques! Ou encore mieux, utilisez des devboards semi-jetables bon marché tels que le Raspberry Pi, voir l'exemple ARM ci-dessous.
Pour un ordinateur portable x86 typique, vous devez faire quelque chose comme:
Gravez l'image sur une clé USB (cela détruira vos données!):
branchez l'USB sur un ordinateur
allume ça
dites-lui de démarrer à partir de l'USB.
Cela signifie que le micrologiciel doit choisir USB avant le disque dur.
Si ce n'est pas le comportement par défaut de votre machine, continuez à appuyer sur Entrée, F12, ESC ou d'autres clés étranges après la mise sous tension jusqu'à ce que vous obteniez un menu de démarrage où vous pouvez choisir de démarrer à partir de l'USB.
Il est souvent possible de configurer l'ordre de recherche dans ces menus.
Par exemple, sur mon T430, je vois ce qui suit.
Après avoir allumé, c'est quand je dois appuyer sur Entrée pour entrer dans le menu de démarrage:
Ensuite, ici, je dois appuyer sur F12 pour sélectionner l'USB comme périphérique de démarrage:
De là, je peux sélectionner l'USB comme périphérique de démarrage comme ceci:
Alternativement, pour changer l'ordre de démarrage et choisir l'USB pour avoir une priorité plus élevée afin de ne pas avoir à le sélectionner manuellement à chaque fois, je frapperais F1 sur l'écran "Menu d'interruption de démarrage", puis naviguer vers:
Secteur de démarrage
Sur x86, la chose la plus simple et la plus basse que vous pouvez faire est de créer un secteur d'amorçage principal (MBR) , qui est un type de secteur d'amorçage , puis de l'installer sur un disque.
Ici, nous en créons un avec un seul
printf
appel:Résultat:
Notez que même sans rien faire, quelques caractères sont déjà imprimés à l'écran. Ceux-ci sont imprimés par le micrologiciel et servent à identifier le système.
Et sur le T430, nous obtenons simplement un écran vide avec un curseur clignotant:
main.img
contient les éléments suivants:\364
en octal ==0xf4
en hex: l'encodage d'unehlt
instruction, qui indique au CPU de cesser de fonctionner.Par conséquent, notre programme ne fera rien: seulement démarrer et arrêter.
Nous utilisons octal car les
\x
nombres hexadécimaux ne sont pas spécifiés par POSIX.Nous pourrions obtenir cet encodage facilement avec:
qui génère:
mais il est également documenté dans le manuel d'Intel.
%509s
produire 509 espaces. Nécessaire pour remplir le fichier jusqu'à l'octet 510.\125\252
en octal ==0x55
suivi de0xaa
.Ce sont 2 octets magiques requis qui doivent être les octets 511 et 512.
Le BIOS parcourt tous nos disques à la recherche de ceux amorçables, et il ne considère que ceux amorçables qui ont ces deux octets magiques.
S'il n'est pas présent, le matériel ne le traitera pas comme un disque amorçable.
Si vous n'êtes pas un
printf
maître, vous pouvez confirmer le contenu demain.img
avec:qui montre les attentes:
où
20
est un espace en ASCII.Le micrologiciel du BIOS lit ces 512 octets à partir du disque, les met en mémoire et définit le PC sur le premier octet pour commencer à les exécuter.
Secteur de démarrage Hello World
Maintenant que nous avons créé un programme minimal, passons à un monde bonjour.
La question évidente est: comment faire IO? Quelques options:
demandez au firmware, par exemple BIOS ou UEFI, de le faire pour nous
VGA: région de mémoire spéciale qui est imprimée à l'écran si elle est écrite. Peut être utilisé en mode protégé.
écrire un pilote et parler directement au matériel d'affichage. C'est la manière "correcte" de le faire: plus puissante, mais plus complexe.
port série . Il s'agit d'un protocole standardisé très simple qui envoie et reçoit des caractères d'un terminal hôte.
Sur les ordinateurs de bureau, cela ressemble à ceci:
Source .
Il n'est malheureusement pas exposé sur la plupart des ordinateurs portables modernes, mais c'est la voie à suivre pour les cartes de développement, voir les exemples ARM ci-dessous.
C'est vraiment dommage, car de telles interfaces sont vraiment utiles pour déboguer le noyau Linux par exemple .
utiliser les fonctionnalités de débogage des puces. ARM appelle leur semi-hébergeur par exemple. Sur le vrai matériel, cela nécessite un support matériel et logiciel supplémentaire, mais sur les émulateurs, cela peut être une option pratique gratuite. Exemple .
Ici, nous allons faire un exemple de BIOS car c'est plus simple sur x86. Mais notez que ce n'est pas la méthode la plus robuste.
main.S
GitHub en amont .
link.ld
Assembler et lier avec:
Résultat:
Et sur le T430:
Testé sur: Lenovo Thinkpad T430, UEFI BIOS 1.16. Disque généré sur un hôte Ubuntu 18.04.
Outre les instructions de montage standard de l'utilisateur, nous avons:
.code16
: indique à GAS de sortir du code 16 bitscli
: désactiver les interruptions logicielles. Ceux-ci pourraient faire redémarrer le processeur après lahlt
int $0x10
: effectue un appel BIOS. C'est ce qui imprime les caractères un par un.Les indicateurs de lien importants sont:
--oformat binary
: sortie du code d'assemblage binaire brut, ne l'enveloppez pas dans un fichier ELF comme c'est le cas pour les exécutables standard de l'espace utilisateur.Pour mieux comprendre la partie script de l'éditeur de liens, familiarisez-vous avec l'étape de relocalisation de la liaison: que font les éditeurs de liens?
Programmes de métal nu Cooler x86
Voici quelques configurations de métal nu plus complexes que j'ai réalisées:
Utilisez C au lieu de l'assemblage
Résumé: utilisez GRUB multiboot, qui résoudra beaucoup de problèmes ennuyeux auxquels vous n'avez jamais pensé. Voir la section ci-dessous.
La principale difficulté sur x86 est que le BIOS ne charge que 512 octets du disque vers la mémoire, et vous risquez de faire exploser ces 512 octets lorsque vous utilisez C!
Pour résoudre ce problème, nous pouvons utiliser un chargeur de démarrage en deux étapes . Cela fait d'autres appels BIOS, qui chargent plus d'octets du disque dans la mémoire. Voici un exemple d'assemblage minimal de l'étape 2 à partir de zéro en utilisant les appels BIOS int 0x13 :
Alternativement:
-kernel
option, qui charge un fichier ELF entier en mémoire. Voici un exemple ARM que j'ai créé avec cette méthode .kernel7.img
, tout comme le fait QEMU-kernel
.À des fins éducatives uniquement, voici un exemple de C minimal en une étape :
principal c
entrée.S
linker.ld
courir
Bibliothèque standard C
Cependant, les choses deviennent plus amusantes si vous souhaitez également utiliser la bibliothèque standard C, car nous n'avons pas le noyau Linux, qui implémente une grande partie des fonctionnalités de la bibliothèque standard C via POSIX .
Quelques possibilités, sans passer par un système d'exploitation complet comme Linux, incluent:
Écrivez votre propre. C'est juste un tas d'en-têtes et de fichiers C à la fin, non? Droite??
Newlib
Exemple détaillé sur: /electronics/223929/c-standard-libraries-on-bare-metal/223931
Newlib met en œuvre toutes les choses de spécifiques de non-OS ennuyeux pour vous, par exemple
memcmp
,memcpy
, etc.Ensuite, il vous fournit quelques talons pour implémenter les appels système dont vous avez besoin vous-même.
Par exemple, nous pouvons implémenter
exit()
sur ARM par semi-hébergement avec:comme indiqué dans cet exemple .
Par exemple, vous pouvez rediriger
printf
vers les systèmes UART ou ARM, ou implémenterexit()
avec semihosting .systèmes d'exploitation embarqués comme FreeRTOS et Zephyr .
De tels systèmes d'exploitation vous permettent généralement de désactiver la planification préventive, vous donnant ainsi un contrôle total sur l'exécution du programme.
Ils peuvent être vus comme une sorte de Newlib pré-implémenté.
Multiboot GNU GRUB
Les secteurs de démarrage sont simples, mais ils ne sont pas très pratiques:
C'est pour ces raisons que GNU GRUB a créé un format de fichier plus pratique appelé multiboot.
Exemple de fonctionnement minimal: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Je l'utilise également sur mon repo d'exemples GitHub pour pouvoir exécuter facilement tous les exemples sur du matériel réel sans graver l'USB un million de fois.
Résultat de l'UEMQ:
T430:
Si vous préparez votre système d'exploitation en tant que fichier multiboot, GRUB est alors en mesure de le trouver dans un système de fichiers standard.
C'est ce que font la plupart des distributions, en plaçant les images du système d'exploitation sous
/boot
.Les fichiers multiboot sont essentiellement un fichier ELF avec un en-tête spécial. Ils sont spécifiés par GRUB à: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Vous pouvez transformer un fichier multiboot en un disque amorçable avec
grub-mkrescue
.Firmware
En vérité, votre secteur de démarrage n'est pas le premier logiciel qui s'exécute sur le processeur du système.
Ce qui s'exécute en premier est le soi-disant firmware , qui est un logiciel:
Les firmwares bien connus incluent:
Le firmware fait des choses comme:
boucle sur chaque disque dur, USB, réseau, etc. jusqu'à ce que vous trouviez quelque chose d'amorçable.
Quand nous courons QEMU,
-hda
dit quemain.img
est un disque dur connecté au matériel, ethda
est le premier à être jugé, et il est utilisé.charger les 512 premiers octets à l'adresse de mémoire RAM
0x7c00
, y mettre le RIP du CPU et le laisser fonctionnerafficher des choses comme le menu de démarrage ou les appels d'impression du BIOS sur l'écran
Le micrologiciel offre des fonctionnalités de type OS dont dépendent la plupart des OS. Par exemple, un sous-ensemble Python a été porté pour fonctionner sur BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM
On peut affirmer que les firmwares sont indiscernables des OS, et que le firmware est la seule "vraie" programmation bare metal que l'on puisse faire.
Comme le dit ce développeur CoreOS :
État initial du BIOS
Comme beaucoup de choses dans le matériel, la normalisation est faible, et l'une des choses sur lesquelles vous ne devriez pas vous fier est l'état initial des registres lorsque votre code commence à s'exécuter après le BIOS.
Faites-vous donc une faveur et utilisez un code d'initialisation comme celui-ci: https://stackoverflow.com/a/32509555/895245
Les registres aiment
%ds
et%es
ont des effets secondaires importants, vous devez donc les mettre à zéro même si vous ne les utilisez pas explicitement.Notez que certains émulateurs sont plus agréables que le vrai matériel et vous donnent un bel état initial. Ensuite, lorsque vous exécutez sur du vrai matériel, tout se casse.
El Torito
Format pouvant être gravé sur CD: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
Il est également possible de produire une image hybride qui fonctionne sur ISO ou USB. Cela peut être fait avec
grub-mkrescue
( exemple ), et est également fait par le noyau Linux lors de l'make isoimage
utilisationisohybrid
.BRAS
Dans ARM, les idées générales sont les mêmes.
Il n'y a pas de firmware préinstallé semi-standardisé largement disponible comme le BIOS à utiliser pour l'IO, donc les deux types d'IO les plus simples que nous pouvons faire sont:
J'ai téléchargé:
quelques exemples simples QEMU C + Newlib et assemblage brut ici sur GitHub .
L' exemple prompt.c, par exemple, prend une entrée de votre terminal hôte et renvoie la sortie tout au long de l'UART simulé:
Voir aussi: Comment créer des programmes ARM bare metal et les exécuter sur QEMU?
une configuration de clignotant Raspberry Pi entièrement automatisée sur: https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker
Voir aussi: Comment exécuter un programme C sans OS sur le Raspberry Pi?
Pour "voir" les LED sur QEMU, vous devez compiler QEMU à partir de la source avec un indicateur de débogage: /raspberrypi/56373/is-it-possible-to-get-the-state-of- les-leds-et-gpios-dans-un-emulation-qemu-t
Ensuite, vous devriez essayer un monde bonjour UART. Vous pouvez commencer à partir de l'exemple de clignotant et remplacer le noyau par celui-ci: https://github.com/dwelch67/raspberrypi/tree/bce377230c2cdd8ff1e40919fdedbc2533ef5a00/uart01
Commencez par faire fonctionner l'UART avec Raspbian comme je l'ai expliqué sur: /raspberrypi/38/prepare-for-ssh-without-a-screen/54394#54394 Cela ressemblera à ceci:
Assurez-vous d'utiliser les bonnes broches, sinon vous pouvez graver votre convertisseur UART vers USB, je l'ai déjà fait deux fois en court-circuitant la masse et 5V ...
Enfin, connectez-vous à la série depuis l'hôte avec:
Pour le Raspberry Pi, nous utilisons une carte Micro SD au lieu d'une clé USB pour contenir notre exécutable, pour lequel vous avez normalement besoin d'un adaptateur pour vous connecter à votre ordinateur:
N'oubliez pas de déverrouiller l'adaptateur SD comme indiqué sur: /ubuntu/213889/microsd-card-is-set-to-read-only-state-how-can-i-write-data -on-it / 814585 # 814585
https://github.com/dwelch67/raspberrypi ressemble au tutoriel Raspberry Pi en métal nu le plus populaire disponible aujourd'hui.
Certaines différences par rapport à x86 incluent:
IO se fait en écrivant directement aux adresses magiques, il n'y a pas d' instructions
in
etout
.Ceci est appelé IO mappé en mémoire .
pour certains matériels réels, comme le Raspberry Pi, vous pouvez ajouter le micrologiciel (BIOS) vous-même à l'image disque.
C'est une bonne chose, car cela rend la mise à jour de ce firmware plus transparente.
Ressources
la source
Le système d'exploitation comme source d'inspiration
Le système d'exploitation est également un programme , nous pouvons donc également créer notre propre programme en créant à partir de zéro ou en modifiant (limitant ou ajoutant) les fonctionnalités de l'un des petits systèmes d'exploitation , puis en l' exécutant pendant le processus de démarrage (à l'aide d'une image ISO ) .
Par exemple, cette page peut être utilisée comme point de départ:
Comment écrire un système d'exploitation simple
Ici, l' ensemble du système d'exploitation s'intègre entièrement dans un secteur de démarrage de 512 octets ( MBR )!
Un tel système d'exploitation simple ou similaire peut être utilisé pour créer un cadre simple qui nous permettra:
Il existe cependant de nombreuses possibilités. Par exemple, pour voir un OS de langage d'assemblage x86 plus grand, nous pouvons explorer le système d'exploitation MykeOS , x86, qui est un outil d'apprentissage pour montrer le fonctionnement simple des OS en mode réel 16 bits, avec du code bien commenté et une documentation complète .
Boot Loader comme source d'inspiration
D'autres types de programmes courants qui s'exécutent sans le système d'exploitation sont également les chargeurs de démarrage . Nous pouvons créer un programme inspiré d'un tel concept par exemple en utilisant ce site:
Comment développer votre propre chargeur de démarrage
L'article ci-dessus présente également l' architecture de base d'un tel programme :
Comme nous pouvons le voir, cette architecture est très flexible et nous permet d'implémenter n'importe quel programme , pas nécessairement un chargeur de démarrage.
En particulier, il montre comment utiliser la technique du "code mixte" grâce à laquelle il est possible de combiner des constructions de haut niveau (en C ou C ++ ) avec des commandes de bas niveau (en Assembleur ). C'est une méthode très utile, mais nous devons nous rappeler que:
L'article montre également comment voir le programme créé en action et comment effectuer ses tests et déboguer.
Les applications UEFI comme source d'inspiration
Les exemples ci-dessus ont utilisé le fait de charger le secteur MBR sur le support de données. Cependant, nous pouvons aller plus loin dans les profondeurs en plaçant par exemple avec les applications UEFI :
Si nous voulons commencer à créer de tels programmes , nous pouvons, par exemple, commencer par ces sites Web:
Programmation pour EFI: Création d'un programme "Hello, World" / Programmation UEFI - Premiers pas
Explorer les problèmes de sécurité comme source d'inspiration
Il est bien connu qu'il existe tout un groupe de logiciels malveillants (qui sont des programmes) qui s'exécutent avant le démarrage du système d'exploitation .
Un grand nombre d'entre eux opèrent sur le secteur MBR ou les applications UEFI, tout comme les solutions ci-dessus, mais il y a aussi ceux qui utilisent un autre point d'entrée comme le Volume Boot Record (VBR) ou le BIOS :
ou peut-être un autre aussi.
Attaques avant le démarrage du système
Différentes façons de démarrer
Je pense également que dans ce contexte, il convient également de mentionner qu'il existe différentes formes de démarrage du système d'exploitation (ou du programme exécutable destiné à cela) . Il y a beaucoup, mais je voudrais faire attention de charger le code à partir du réseau en utilisant l' option de démarrage réseau ( PXE ), ce qui nous permet d'exécuter le programme sur l'ordinateur quel que soit son système d'exploitation et même indépendamment de tout support de stockage qui est directement connecté à l'ordinateur:
Qu'est-ce que le démarrage réseau (PXE) et comment pouvez-vous l'utiliser?
la source