Co dokładnie dzieje się, gdy wykonuję plik w mojej powłoce?

32

Pomyślałem więc, że dobrze to rozumiem, ale właśnie przeprowadziłem test (w odpowiedzi na rozmowę, w której nie zgadzałem się z kimś) i stwierdziłem, że moje rozumienie jest błędne ...

Jak bardzo szczegółowo, co dokładnie dzieje się, gdy wykonuję plik w mojej powłoce? Mam na myśli to, że jeśli napiszę : ./somefile some argumentsw mojej powłoce i naciśnie klawisz Return (i somefileistnieje w cwd, a mam uprawnienia do odczytu + wykonywania somefile), to co się stanie pod maską?

Myślałem, że odpowiedź brzmi:

  1. Powłoka wykonuje polecenie systemowe exec, przekazując ścieżkę dosomefile
  2. Jądro sprawdza somefilei sprawdza magiczną liczbę pliku, aby ustalić, czy jest to format obsługiwany przez procesor
  3. Jeśli magiczna liczba wskazuje, że plik ma format, który procesor może wykonać, to
    1. tworzony jest nowy proces (z wpisem w tabeli procesów)
    2. somefilejest odczytywany / mapowany do pamięci. Tworzony jest stos, a wykonanie przeskakuje do punktu wejścia kodu somefile, z ARGVzainicjalizowanym do tablicy parametrów (a char**, ["some","arguments"])
  4. Jeśli magiczna liczba to shebang, to exec()spawnuje nowy proces jak wyżej, ale użyty plik wykonywalny to interpreter, do którego odwołuje się shebang (np. /bin/bashLub /bin/perl) i somefilejest przekazywany doSTDIN
  5. Jeśli plik nie ma prawidłowej magicznej liczby, pojawia się błąd typu „nieprawidłowy plik (zła magiczna liczba): błąd formatu Exec”

Jednak ktoś powiedział mi, że jeśli plik jest zwykłym tekstem, wówczas powłoka próbuje wykonać polecenia (tak jakbym napisał bash somefile). Nie wierzyłem w to, ale po prostu spróbowałem i było to poprawne. Wyraźnie mam więc pewne nieporozumienia na temat tego, co się tu właściwie dzieje i chciałbym zrozumieć mechanikę.

Co dokładnie dzieje się, gdy wykonuję plik w mojej powłoce? (w najdrobniejszych szczegółach jest rozsądne ...)

Josh
źródło
Nie ma idealnego substytutu dla spojrzenia na kod źródłowy dla pełnej głębi zrozumienia.
Wildcard,
1
@Wildcard to właśnie robię teraz :-) Jeśli mogę, odpowiem na własne pytanie
Josh
1
source somefileróżni się jednak bardzo od nowego procesu, który został opracowany ./somefile.
thrig
@ thrig tak, zgadzam się. Ale nie sądziłem, ./somefileże spowoduje to, że bash wykona polecenia, somefilejeśli plik nie będzie miał magicznej liczby. Myślałem, że po prostu wyświetli błąd, a zamiast tego wydaje się być skutecznysource somefile
Josh
Znowu się mylę, mogę potwierdzić, że jeśli somefilejest to plik tekstowy, to jeśli spróbuję go uruchomić, pojawi się nowa powłoka. Plik echo $$zachowuje się inaczej, jeśli wykonam go w stosunku do źródła.
Josh

Odpowiedzi:

31

Ostateczną odpowiedzią na „jak uruchamiają się programy” w Linuksie jest para artykułów na LWN.net zatytułowanych, co zaskakujące, jak uruchamiać programy i jak uruchamiać programy: pliki binarne ELF . Pierwszy artykuł krótko opisuje skrypty. (Ściśle mówiąc, ostateczna odpowiedź znajduje się w kodzie źródłowym, ale artykuły te są łatwiejsze do odczytania i zawierają linki do kodu źródłowego).

Trochę eksperymentów pokazuje, że właściwie wszystko dobrze zrozumiałeś i że wykonanie pliku zawierającego prostą listę poleceń bez shebang musi być obsługiwane przez powłokę. Strona podręcznika execve (2) zawiera kod źródłowy programu testowego, execve; użyjemy tego, aby zobaczyć, co się stanie bez powłoki. Najpierw napisz testcript testscr1, zawierający

#!/bin/sh

pstree

i kolejny testscr2, zawierający tylko

pstree

Ustaw je jako pliki wykonywalne i sprawdź, czy oba działają z poziomu powłoki:

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

Teraz spróbuj ponownie, używając execve(zakładając, że wbudowałeś go w bieżący katalog):

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

testscr1nadal działa, ale testscr2produkuje

execve: Exec format error

To pokazuje, że powłoka działa testscr2inaczej. Jednak nie przetwarza samego skryptu, nadal używa /bin/shdo tego celu; można to zweryfikować przesyłając testscr2do less:

./testscr2 | less -ppstree

W moim systemie mam

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

Jak widać, istnieje powłoka zsh, której używałem, która się rozpoczęła less, i druga powłoka, zwykła sh( dashw moim systemie), do uruchomienia skryptu, który został uruchomiony pstree. W zshten jest obsługiwany przez zexecvew Src/exec.c: skorupy zastosowań execve(2)spróbować uruchomić komendę, a jeśli to się nie powiedzie, to odczytuje plik, aby zobaczyć, czy posiada ono shebang, przetwarza je odpowiednio (co jądro będzie również zrobić), a jeśli to kończy się niepowodzeniem, próbuje uruchomić plik sh, o ile nie odczytał z pliku żadnego bajtu zerowego:

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

bashma to samo zachowanie, zaimplementowane execute_cmd.cz pomocnym komentarzem (jak zauważył taliezin ):

Wykonaj proste polecenie, które, mam nadzieję, jest gdzieś zdefiniowane w pliku dyskowym.

  1. fork ()
  2. połączyć rury
  3. wyszukaj polecenie
  4. dokonać przekierowań
  5. execve ()
  6. Jeśli się execvenie powiedzie, sprawdź, czy plik ma ustawiony tryb wykonywalny. Jeśli tak, to nie jest katalog, a następnie uruchom jego zawartość jako skrypt powłoki.

POSIX definiuje zestaw funkcji, zwany na exec(3)funkcje , które owijane execve(2)i zapewnić tę funkcję też; szczegóły patrz odpowiedź muru . W Linuksie przynajmniej te funkcje są implementowane przez bibliotekę C, a nie przez jądro.

Stephen Kitt
źródło
To jest fantastyczne i ma szczegóły, których szukałem, dziękuję!
Josh
12

Częściowo zależy to od konkretnej execużytej funkcji rodziny. execve, jak szczegółowo pokazał Stephen Kitt , uruchamia tylko pliki w poprawnym formacie binarnym lub skrypty, które zaczynają się od właściwego shebang.

Jednakże , execlpi execvppójść o krok dalej: jeżeli shebang nie była prawidłowa, plik zostanie wykonany z /bin/shLinux. Od 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.)

Jest to nieco wspierane przez POSIX (moje podkreślenie):

Jednym z potencjalnych źródeł nieporozumień zauważonych przez standardowych programistów jest to, w jaki sposób zawartość pliku obrazu procesu wpływa na zachowanie rodziny funkcji exec. Poniżej znajduje się opis podjętych działań:

  1. Jeśli plik obrazu procesu jest poprawnym plikiem wykonywalnym (w formacie, który jest wykonywalny i prawidłowy i ma odpowiednie uprawnienia) dla tego systemu, wówczas system wykonuje ten plik.

  2. Jeśli plik obrazu procesu ma odpowiednie uprawnienia i jest w formacie wykonywalnym, ale niepoprawnym dla tego systemu (takim jak rozpoznany plik binarny dla innej architektury), to jest to błąd, a errno jest ustawione na [EINVAL] (patrz później RATIONALE w [EINVAL]).

  3. Jeśli plik obrazu procesu ma odpowiednie uprawnienia, ale nie jest rozpoznawany w inny sposób:

    1. Jeśli jest to wywołanie execlp () lub execvp (), wówczas wywołują interpreter poleceń, zakładając, że plik obrazu procesu jest skryptem powłoki.

    2. Jeśli nie jest to wywołanie execlp () lub execvp (), wówczas wystąpi błąd i errno jest ustawione na [ENOEXEC].

Nie określa to sposobu uzyskania interpretera poleceń, więc nie określa, że ​​należy podać błąd. Sądzę więc, że deweloperzy Linuksa zezwalali na uruchamianie takich plików /bin/sh(lub była to już powszechna praktyka i po prostu poszły w ich ślady).

FWIW, strona podręczna FreeBSDexec(3) również wspomina o podobnym zachowaniu:

 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 nie ma jednak powszechnych zastosowań powłoki execlplub execvpbezpośrednio, prawdopodobnie w celu uzyskania lepszej kontroli nad środowiskiem. Wszystkie wykorzystują tę samą logikę execve.

muru
źródło
3
Chciałbym również dodać, że co najmniej na Linuksie execl, execlp, execle, execv, execvpi execvpesą przednie końce do execvesyscall; te pierwsze są dostarczane przez bibliotekę C, jądro zna tylko execve(i execveatobecnie).
Stephen Kitt,
@StephenKitt To wyjaśnia, dlaczego nie mogłem znaleźć strony dla tych funkcji w sekcji 2
man7.org
6

Może to być dodatek do odpowiedzi Stephena Kitta jako komentarz ze bashźródła w pliku execute_cmd.c:

Wykonaj proste polecenie, które, mam nadzieję, jest gdzieś zdefiniowane w pliku dyskowym.

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.  

Jeśli tak, to nie jest katalog, a następnie uruchom jego zawartość jako skrypt powłoki.

taliezin
źródło
0

To pobiera wykonywane jako skrypt powłoki, to nie pochodzą (na przykład, zmienne określone w zawartej pliku nie wpływają na zewnątrz). Prawdopodobnie ślad po mglistej przeszłości, kiedy istniała jedna powłoka i jeden format wykonywalny. To nie jest plik wykonywalny, musi to być skrypt powłoki.

vonbrand
źródło
2
Źle zrozumiałeś moje pytanie. Co dzieje się szczegółowo? Muszę przynajmniej zrozumieć, co sprawdza, czy to shebang, exec()czy to jest skorupa? Chcę znacznie więcej wewnętrznych
Josh