Que se passe-t-il exactement lorsque j'exécute un fichier dans mon shell?

32

Donc, je pensais que j'avais une bonne compréhension de cela, mais j'ai juste fait un test (en réponse à une conversation où je n'étais pas d'accord avec quelqu'un) et j'ai trouvé que ma compréhension était imparfaite ...

De manière aussi détaillée que possible, que se passe-t-il exactement lorsque j'exécute un fichier dans mon shell? Ce que je veux dire, c’est que si je tape: ./somefile some argumentsdans mon shell et appuie sur Entrée (et somefileexiste dans cwd, et j’ai lu + exécuté les autorisations sur somefile), que se passe-t-il sous le capot?

Je pensais que la réponse était:

  1. Le shell fait un appel système en execpassant le chemin àsomefile
  2. Le noyau examine somefileet examine le nombre magique du fichier pour déterminer s'il s'agit d'un format que le processeur peut gérer.
  3. Si le nombre magique indique que le fichier est dans un format que le processeur peut exécuter, alors
    1. un nouveau processus est créé (avec une entrée dans la table de processus)
    2. somefileest lu / mappé à la mémoire. Une pile est créée et l'exécution saute au point d'entrée du code de somefile, avec ARGVinitialisé à un tableau de paramètres (a char**, ["some","arguments"])
  4. Si le numéro de magie est un tralala puis exec()génère un nouveau procédé comme ci - dessus, mais l'exécutable utilisé est l'interpréteur référencé par le tralala (par exemple , /bin/bashou /bin/perl) et somefileest passé àSTDIN
  5. Si le fichier n'a pas de numéro magique valide, une erreur du type "fichier invalide (mauvais numéro magique): erreur de format Exec" se produit

Cependant, quelqu'un m'a dit que si le fichier est en texte brut, le shell tente d'exécuter les commandes (comme si j'avais tapé bash somefile). Je n'y croyais pas, mais je l'ai juste essayé et c'était correct. J'ai donc clairement des idées fausses sur ce qui se passe réellement ici et j'aimerais comprendre les mécanismes.

Que se passe-t-il exactement lorsque j'exécute un fichier dans mon shell? (autant de détails est raisonnable ...)

Josh
la source
1
Il n'y a pas de substitut parfait à l'examen du code source pour une compréhension approfondie.
Wildcard
1
@Wildcard c'est ce que je fais en ce moment, en fait :-) Si je peux, je répondrai à ma propre question
Josh
1
source somefileest très différent d’un nouveau processus déclenché par ./somefile, cependant.
Thrig
@thrig oui, je suis d'accord. Mais je ne pensais pas que ./somefilecela entraînerait l’exécution des commandes par bash somefilesi le fichier n’avait pas de numéro magique. Je pensais que cela afficherait simplement une erreur, et à la place, cela semble être efficacesource somefile
Josh
Je me trompe encore, je peux confirmer que s'il somefiles'agit d'un fichier texte, un nouveau shell est créé si j'essaie de l'exécuter. Un fichier echo $$se comporte différemment si je l'exécute par rapport à son source.
Josh

Réponses:

32

La réponse définitive à "comment les programmes sont exécutés" sur Linux est la paire d'articles sur LWN.net intitulée, assez étonnamment, Comment les programmes sont exécutés et Comment les programmes sont exécutés: les fichiers binaires ELF . Le premier article traite brièvement des scripts. (À proprement parler, la réponse définitive se trouve dans le code source, mais ces articles sont plus faciles à lire et fournissent des liens vers le code source.)

Un peu d’expérimentation montre que vous avez très bien compris et que l’exécution d’un fichier contenant une simple liste de commandes, sans shebang, doit être gérée par le shell. La page de manuel execve (2) contient le code source d’un programme de test, execve; nous l'utiliserons pour voir ce qui se passe sans shell. Commencez par écrire un test testscr1, contenant

#!/bin/sh

pstree

et un autre, testscr2ne contenant que

pstree

Rendez-les exécutables et vérifiez qu’ils s’exécutent tous les deux à partir d’un shell:

chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less

Maintenant, essayez à nouveau, en utilisant execve(en supposant que vous l’aviez construit dans le répertoire courant):

./execve ./testscr1
./execve ./testscr2

testscr1fonctionne toujours, mais testscr2produit

execve: Exec format error

Cela montre que le shell gère testscr2différemment. Il ne traite pas le script lui-même, il l'utilise toujours /bin/shpour le faire. ceci peut être vérifié en testscr2passant à less:

./testscr2 | less -ppstree

Sur mon système, je reçois

    |-gnome-terminal--+-4*[zsh]
    |                 |-zsh-+-less
    |                 |     `-sh---pstree

Comme vous pouvez le constater, il y a le shell que j'utilisais zsh, qui a démarré less, et un deuxième shell, en clair sh( dashsur mon système), permettant d'exécuter le script, qui s'est exécuté pstree. Dans zshc'est gérée par zexecvedans Src/exec.c: l'utilisation shell execve(2)pour tenter d'exécuter la commande, et si cela échoue, il lit le fichier pour voir si elle a un tralala, la traiter en conséquence (que le noyau aura également fait), et si cela échoue, il essaie d'exécuter le fichier avec sh, tant qu'il n'a pas lu d'octet zéro dans le fichier:

        for (t0 = 0; t0 != ct; t0++)
            if (!execvebuf[t0])
                break;
        if (t0 == ct) {
            argv[-1] = "sh";
            winch_unblock();
            execve("/bin/sh", argv - 1, newenvp);
        }

basha le même comportement, implémenté execute_cmd.cavec un commentaire utile (comme l'a souligné taliezin ):

Exécutez une commande simple qui, espérons-le, est définie quelque part dans un fichier de disque.

  1. fork ()
  2. connecter des tuyaux
  3. rechercher la commande
  4. faire des redirections
  5. execve ()
  6. En cas d' execveéchec, vérifiez si le mode exécutable est défini dans le fichier. Si c'est le cas et qu'il ne s'agit pas d'un répertoire, exécutez son contenu en tant que script shell.

POSIX définit un ensemble de fonctions, connu sous les exec(3)fonctions , qui enveloppe execve(2)et fournir cette fonctionnalité aussi; voir la réponse de muru pour plus de détails. Sous Linux au moins, ces fonctions sont implémentées par la bibliothèque C, pas par le noyau.

Stephen Kitt
la source
C'est fantastique et a le détail que je cherchais, merci!
Josh
12

Cela dépend en partie de la execfonction familiale utilisée.execve, comme Stephen Kitt l’ a montré en détail, n’exécute que les fichiers au format binaire approprié ou les scripts commençant par un shebang approprié.

Cependant , execlpet execvpallez un peu plus loin: si le shebang n’est pas correct, le fichier est exécuté /bin/shsous Linux. De man 3 exec:

Special semantics for execlp() and execvp()
   The execlp(), execvp(), and execvpe() functions duplicate the actions
   of the shell in searching for an executable file if the specified
   filename does not contain a slash (/) character.
   …

   If the header of a file isn't recognized (the attempted execve(2)
   failed with the error ENOEXEC), these functions will execute the
   shell (/bin/sh) with the path of the file as its first argument.  (If
   this attempt fails, no further searching is done.)

Ceci est quelque peu soutenu par POSIX (c'est moi qui souligne):

Une des sources potentielles de confusion relevées par les développeurs standard concerne l'impact du contenu d'un fichier image de processus sur le comportement de la famille de fonctions exec. Ce qui suit est une description des actions entreprises:

  1. Si le fichier image de processus est un fichier exécutable valide (dans un format qui est exécutable et valide et qui dispose des privilèges appropriés) pour ce système, celui-ci est exécuté.

  2. Si le fichier image de processus a les privilèges appropriés et est dans un format exécutable mais non valide pour ce système (comme un binaire reconnu pour une autre architecture), il s’agit d’une erreur et errno contient le code [EINVAL] (voir plus bas RATIONALE). sur [EINVAL]).

  3. Si le fichier image de processus dispose des privilèges appropriés mais n'est pas reconnu autrement:

    1. S'il s'agit d'un appel à execlp () ou execvp (), ils invoquent un interpréteur de commande en supposant que le fichier d'image de processus est un script shell.

    2. S'il ne s'agit pas d'un appel à execlp () ou execvp (), une erreur se produit et errno contient le code d'erreur [ENOEXEC].

Ceci ne spécifie pas comment l’interpréteur de commande est obtenu, donc, mais ne spécifie pas qu’une erreur doit être donnée. Je suppose donc que les développeurs Linux ont autorisé l’utilisation de tels fichiers /bin/sh(ou c’était déjà une pratique courante et ils ont juste emboîté le pas).

FWIW, la page deexec(3) manuel FreeBSD pour mentionne également un comportement similaire:

 Some of these functions have special semantics.

 The functions execlp(), execvp(), and execvP() will duplicate the actions
 of the shell in searching for an executable file if the specified file
 name does not contain a slash ``/'' character. 
 …
 If the header of a file is not recognized (the attempted execve()
 returned ENOEXEC), these functions will execute the shell with the path
 of the file as its first argument.  (If this attempt fails, no further
 searching is done.)

AFAICT, cependant, aucun shell commun n’utilise execlpou execvpdirectement, vraisemblablement pour un contrôle plus fin de l’environnement. Ils mettent tous en œuvre la même logique en utilisant execve.

muru
la source
3
Je voudrais aussi ajouter que , au moins sur Linux, execl, execlp, execle, execv, execvpet execvpesont tous les frontaux à la execvesyscall; les premiers sont fournis par la bibliothèque C, le noyau n’en connaît que execve(et de execveatnos jours).
Stephen Kitt
@StephenKitt Cela explique pourquoi je n'ai pas trouvé de page de manuel pour ces fonctions dans la section 2 de man7.org.
muru
6

Cela pourrait être un ajout à la réponse de Stephen Kitt, en tant que commentaire de la bashsource dans le fichier execute_cmd.c:

Exécutez une commande simple qui, espérons-le, est définie quelque part dans un fichier de disque.

1. fork ()
2. connect pipes
3. look up the command
4. do redirections
5. execve ()
6. If the execve failed, see if the file has executable mode set.  

Si tel est le cas et qu'il ne s'agit pas d'un répertoire, exécutez son contenu en tant que script shell.

taliézine
la source
0

Il est exécuté en tant que script shell, il n'est pas généré (par exemple, les variables définies dans le fichier exécuté ne concernent pas l'extérieur). Probablement vestigiale du passé brumeux, quand il y avait un shell et un format exécutable. Ce n'est pas un exécutable, ce doit être un script shell.

vonbrand
la source
2
Vous avez mal compris ma question. Qu'est-ce qui se passe en détail? Au minimum, je dois comprendre ce qui vérifie un shebang, est-ce exec()ou le shell? Je veux beaucoup plus d'internes
Josh