Was genau passiert, wenn ich eine Datei in meiner Shell ausführe?

32

Also dachte ich, ich hätte ein gutes Verständnis dafür, führte aber nur einen Test durch (als Reaktion auf ein Gespräch, bei dem ich mit jemandem nicht einverstanden war) und stellte fest, dass mein Verständnis fehlerhaft ist ...

So detailliert wie möglich , was genau passiert , wenn ich eine Datei in meinem Shell ausführen? Was ich meine ist, wenn ich Folgendes eintippe : ./somefile some argumentsin meine Shell und somefiledie Eingabetaste drücke (und in der CWD vorhanden ist und ich über Lese- und Ausführungsberechtigungen verfüge somefile), was passiert dann unter der Haube?

Ich dachte die Antwort wäre:

  1. Die Shell ruft syscall an execund leitet den Pfad ansomefile
  2. Der Kernel überprüft somefileund überprüft die magische Nummer der Datei, um festzustellen, ob es sich um ein Format handelt, das der Prozessor verarbeiten kann
  3. Wenn die magische Zahl angibt, dass die Datei ein Format hat, das der Prozessor ausführen kann, dann
    1. ein neuer Prozess wird angelegt (mit einem Eintrag in der Prozesstabelle)
    2. somefilewird gelesen / in den Speicher abgebildet. Ein Stack wird erstellt und die Ausführung springt zum Einstiegspunkt des Codes von somefile, wobei ARGVein Array der Parameter (a char**, ["some","arguments"]) initialisiert wird.
  4. Wenn die magische Zahl ein Shebang ist, wird exec()ein neuer Prozess wie oben beschrieben erzeugt. Als ausführbare Datei wird jedoch der Interpreter verwendet, auf den der Shebang (z. B. /bin/bashoder /bin/perl) verweist, und somefilean den er weitergeleitet wirdSTDIN
  5. Wenn die Datei keine gültige magische Nummer hat, tritt ein Fehler wie "Ungültige Datei (falsche magische Nummer): Exec-Formatfehler" auf

Allerdings hat mir jemand gesagt, dass die Shell versucht, die Befehle auszuführen, wenn es sich bei der Datei um reinen Text handelt (als hätte ich etwas eingegeben) bash somefile ). Ich habe das nicht geglaubt, aber ich habe es einfach versucht, und es war richtig. Ich habe also offensichtlich einige Missverständnisse darüber, was hier tatsächlich passiert, und möchte die Mechanik verstehen.

Was genau passiert, wenn ich eine Datei in meiner Shell ausführe? (Soweit Detail zumutbar ist ...)

Josh
quelle
Es gibt keinen perfekten Ersatz dafür, den Quellcode für ein umfassendes Verständnis zu betrachten.
Wildcard
1
@Wildcard das ist, was ich gerade tue, eigentlich :-) Wenn ich kann, werde ich meine eigene Frage beantworten
Josh
1
source somefileunterscheidet sich jedoch stark von einem neuen Prozess, der von einem anderen gespalten wird ./somefile.
Thrig
@thrig ja da stimme ich zu. Aber ich hätte nicht gedacht, dass ./somefiledies dazu führen würde, dass Bash die Befehle ausführt, somefilewenn die Datei keine magische Nummer hätte. Ich dachte, es würde nur einen Fehler anzeigen, und stattdessen scheint es effektivsource somefile
Josh
Ich liege wieder falsch. Ich kann bestätigen, dass, wenn somefilees sich um eine Textdatei handelt, eine neue Shell erstellt wird, wenn ich versuche, sie auszuführen. Eine Datei echo $$verhält sich anders, wenn ich sie im Vergleich zur Quelle ausführe.
Josh

Antworten:

31

Die endgültige Antwort auf die Frage, wie Programme unter Linux ausgeführt werden, ist das Artikelpaar auf LWN.net mit dem überraschenden Titel Wie Programme ausgeführt werden und Wie Programme ausgeführt werden: ELF-Binärdateien . Der erste Artikel befasst sich kurz mit Skripten. (Genau genommen befindet sich die endgültige Antwort im Quellcode, diese Artikel sind jedoch einfacher zu lesen und enthalten Links zum Quellcode.)

Ein kleines Experimentieren zeigt, dass Sie so ziemlich alles richtig gemacht haben und dass die Ausführung einer Datei, die eine einfache Liste von Befehlen enthält, von der Shell erledigt werden muss. Die Manpage execve (2) enthält den Quellcode für ein Testprogramm. Wir werden das nutzen, um zu sehen, was ohne Shell passiert. Schreiben Sie zunächst ein Testskript testscr1mit

#!/bin/sh

pstree

und ein anderer testscr2, der nur enthält

pstree

Machen Sie beide ausführbar und stellen Sie sicher, dass beide von einer Shell ausgeführt werden:

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

Versuchen Sie es jetzt erneut mit execve Sie es (vorausgesetzt, Sie haben es im aktuellen Verzeichnis erstellt):

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

testscr1 läuft noch, aber testscr2 produziert

execve: Exec format error

Dies zeigt, dass die Shell testscr2unterschiedlich behandelt. Das Skript selbst wird jedoch nicht verarbeitet /bin/sh. dies kann durch Rohrleitungen überprüft werden , testscr2um less:

./testscr2 | less -ppstree

Auf meinem System bekomme ich

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

Wie Sie sehen, wurde die von mir verwendete Shell zshgestartet lessund eine zweite Shell sh( dashauf meinem System), um das Skript auszuführen, das ausgeführt wurde pstree. In zshdieser wird durch behandelt zexecvein Src/exec.c: Die Shell benutzt , execve(2)um zu versuchen , den Befehl auszuführen, und wenn das nicht funktioniert, liest es die Datei zu sehen , ob es eine shebang hat, ist es entsprechend der Verarbeitung (die die Kernel auch getan hat), und wenn die schlägt fehl, es wird versucht, die Datei mit auszuführen sh, solange kein Null-Byte aus der Datei gelesen wurde:

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

bashhat das gleiche Verhalten, implementiert execute_cmd.cmit einem hilfreichen Kommentar (wie von taliezin hervorgehoben ):

Führen Sie einen einfachen Befehl aus, der hoffentlich irgendwo in einer Festplattendatei definiert ist.

  1. fork ()
  2. Rohre anschließen
  3. Schlagen Sie den Befehl nach
  4. Weiterleitungen durchführen
  5. execve ()
  6. Wenn dies execvefehlschlägt, prüfen Sie, ob für die Datei der ausführbare Modus festgelegt wurde. Wenn dies der Fall ist und es sich nicht um ein Verzeichnis handelt, führen Sie dessen Inhalt als Shell-Skript aus.

POSIX definiert einen Satz von Funktionen, wie bekannt die exec(3)Funktionen , die Wickel execve(2)und auch diese Funktionalität bereitzustellen; siehe muru ‚s Antwort für Details. Zumindest unter Linux werden diese Funktionen von der C-Bibliothek implementiert, nicht vom Kernel.

Stephen Kitt
quelle
Das ist fantastisch und hat das Detail, das ich gesucht habe, danke!
Josh
12

Dies hängt zum Teil von der jeweiligen execFamilienfunktion ab, die verwendet wird. execveWie Stephen Kitt ausführlich gezeigt hat, werden nur Dateien im richtigen Binärformat oder Skripten ausgeführt, die mit einem richtigen Shebang beginnen.

Allerdings , execlpund execvpnoch einen Schritt weiter gehen: Wenn die shebang nicht korrekt sind, wird die Datei mit ausgeführt /bin/shunter Linux. Von 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.)

Dies wird in gewisser Weise von POSIX (Schwerpunkt Mine) unterstützt:

Eine mögliche Quelle der Verwirrung, die die Standardentwickler bemerken, ist, wie sich der Inhalt einer Prozessabbilddatei auf das Verhalten der exec-Funktionsfamilie auswirkt. Das Folgende ist eine Beschreibung der ergriffenen Maßnahmen:

  1. Wenn die Prozessabbilddatei eine gültige ausführbare Datei (in einem ausführbaren und gültigen Format und mit den entsprechenden Berechtigungen) für dieses System ist, führt das System die Datei aus.

  2. Wenn die Prozessabbilddatei über die entsprechenden Berechtigungen verfügt und ein Format aufweist, das ausführbar ist, aber für dieses System nicht gültig ist (z. B. eine erkannte Binärdatei für eine andere Architektur), ist dies ein Fehler und errno wird auf [EINVAL] gesetzt (siehe später RATIONALE) auf [EINVAL]).

  3. Wenn die Prozessabbilddatei über entsprechende Berechtigungen verfügt, jedoch nicht anderweitig erkannt wird:

    1. Wenn dies ein Aufruf von execlp () oder execvp () ist, rufen sie einen Befehlsinterpreter auf, wobei angenommen wird, dass es sich bei der Prozessabbilddatei um ein Shell-Skript handelt.

    2. Wenn dies kein Aufruf von execlp () oder execvp () ist, tritt ein Fehler auf und errno wird auf [ENOEXEC] gesetzt.

Dies gibt nicht an, wie der Befehlsinterpreter abgerufen wird, gibt jedoch nicht an, dass ein Fehler angegeben werden muss. Ich vermute daher, dass die Linux-Entwickler solche Dateien laufen ließen/bin/sh (oder dies war bereits eine gängige Praxis und sie folgten einfach dem Beispiel).

FWIW, die FreeBSD-Manpage,exec(3) erwähnt auch ein ähnliches Verhalten:

 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 verwendet jedoch keine gemeinsame Shell execlpoder execvpdirekt, vermutlich zur feineren Kontrolle über die Umgebung. Sie alle implementieren dieselbe Logik mit execve.

muru
quelle
3
Ich würde auch , dass zumindest unter Linux hinzufügen, execl, execlp, execle, execv, execvpund execvpesind alle Frontends an die execvesyscall; Erstere werden von der C-Bibliothek bereitgestellt, über die der Kernel nur Bescheid weiß execve(und execveatheutzutage).
Stephen Kitt
@StephenKitt Das erklärt, warum ich in Abschnitt 2 von
man7.org
6

Dies könnte eine Ergänzung zur Antwort von Stephen Kitt sein, als Kommentar aus der bashQuelle in der Datei execute_cmd.c:

Führen Sie einen einfachen Befehl aus, der hoffentlich irgendwo in einer Festplattendatei definiert ist.

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.  

Wenn dies der Fall ist und es sich nicht um ein Verzeichnis handelt, führen Sie dessen Inhalt als Shell-Skript aus.

Taliezin
quelle
0

Es wird als Shell-Skript ausgeführt, es wird nicht bezogen (z. B. wirken sich in der ausgeführten Datei gesetzte Variablen nicht auf die Umgebung aus). Vermutlich ein Überbleibsel aus der nebligen Vergangenheit, als es eine Shell und ein ausführbares Format gab. Keine ausführbare Datei, es muss ein Shell-Skript sein.

vonbrand
quelle
2
Sie haben meine Frage falsch verstanden. Was passiert im Detail? Zumindest muss ich verstehen, was nach einem Schebang sucht, ist das exec()oder die Muschel? Ich möchte deutlich mehr Interna
Josh