O que exatamente acontece quando executo um arquivo no meu shell?

32

Então, achei que tinha um bom entendimento disso, mas apenas fiz um teste (em resposta a uma conversa em que discordava de alguém) e descobri que meu entendimento é defeituoso ...

Com o máximo de detalhes possível, o que exatamente acontece quando executo um arquivo no meu shell? O que quero dizer é que, se eu digitar: ./somefile some argumentsno meu shell e pressionar return (e somefileexistir no cwd e eu tiver lido + executar permissões ativadas somefile), o que acontecerá sob o capô?

Eu pensei que a resposta era:

  1. O shell faz um syscall to exec, passando o caminho parasomefile
  2. O kernel examina somefilee analisa o número mágico do arquivo para determinar se é um formato que o processador pode manipular
  3. Se o número mágico indicar que o arquivo está em um formato que o processador possa executar,
    1. um novo processo é criado (com uma entrada na tabela de processos)
    2. somefileé lido / mapeado para a memória. Uma pilha é criada e a execução salta para o ponto de entrada do código de somefile, ARGVinicializado em uma matriz dos parâmetros (a char**, ["some","arguments"])
  4. Se o número mágico for um shebang , exec()gera um novo processo como acima, mas o executável usado é o intérprete referenciado pelo shebang (por exemplo, /bin/bashou /bin/perl) e somefileé passado paraSTDIN
  5. Se o arquivo não tiver um número mágico válido, ocorrerá um erro como "arquivo inválido (número mágico incorreto): erro de formato Exec"

No entanto, alguém me disse que, se o arquivo é texto simples, o shell tenta executar os comandos (como se eu tivesse digitado bash somefile). Eu não acreditava nisso, mas apenas tentei e estava correto. Portanto, tenho claramente algumas idéias erradas sobre o que realmente acontece aqui e gostaria de entender a mecânica.

O que exatamente acontece quando executo um arquivo no meu shell? (com o máximo de detalhes possível ...)

Josh
fonte
Não há substituto perfeito para olhar o código-fonte para uma compreensão completa.
Curinga
1
@Wildcard é isso que eu estou fazendo agora, na verdade :-) Se eu puder, vou responder minha própria pergunta
Josh
1
source somefileé muito diferente de um novo processo que está sendo iniciado por ./somefile, no entanto.
thrig
@ thrig sim, eu concordo. Mas não achei que isso ./somefilefaria com que o bash execute os comandos somefilese o arquivo não tiver um número mágico. Eu pensei que seria apenas exibir um erro, e em vez disso, parece efetivamentesource somefile
Josh
Estou errado de novo, posso confirmar que, se somefilefor um arquivo de texto, um novo shell será gerado se tentar executá-lo. Um arquivo echo $$se comporta de maneira diferente se eu executar o código-fonte.
21413 Josh

Respostas:

31

A resposta definitiva para "como os programas são executados" no Linux é o par de artigos no LWN.net intitulado, surpreendentemente, Como os programas são executados e Como os programas são executados: binários ELF . O primeiro artigo aborda os scripts brevemente. (A rigor, a resposta definitiva está no código-fonte, mas esses artigos são mais fáceis de ler e fornecem links para o código-fonte.)

Um pouco de experimentação mostra que você praticamente acertou e que a execução de um arquivo contendo uma lista simples de comandos, sem um shebang, precisa ser manipulada pelo shell. A página de manual execve (2) contém código fonte para um programa de teste, execve; usaremos isso para ver o que acontece sem um shell. Primeiro, escreva um script de teste testscr1, contendo

#!/bin/sh

pstree

e outro testscr2, contendo apenas

pstree

Torne os dois executáveis ​​e verifique se os dois são executados a partir de um shell:

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

Agora tente novamente, usando execve(assumindo que você o criou no diretório atual):

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

testscr1ainda funciona, mas testscr2produz

execve: Exec format error

Isso mostra que o shell lida de maneira testscr2diferente. Porém, ele não processa o próprio script, ainda o usa /bin/sh; isso pode ser verificado canalizando testscr2para less:

./testscr2 | less -ppstree

No meu sistema, eu recebo

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

Como você pode ver, há o shell que eu estava usando, zshque foi iniciado less, e um segundo shell, simples sh( dashno meu sistema), para executar o script, que foi executado pstree. Em zsheste é tratado por zexecveem Src/exec.c: a casca usos execve(2)para tentar executar o comando, e se isso falhar, ele lê o arquivo para ver se ele tem um shebang, processá-lo em conformidade (qual o kernel também terá feito), e se isso falha ao tentar executar o arquivo sh, desde que não tenha lido nenhum byte zero no arquivo:

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

bashtem o mesmo comportamento, implementado execute_cmd.ccom um comentário útil (como apontado por taliezin ):

Execute um comando simples que, esperamos, seja definido em um arquivo de disco em algum lugar.

  1. fork ()
  2. conectar tubos
  3. procure o comando
  4. fazer redirecionamentos
  5. execve ()
  6. Se execvefalhar, veja se o arquivo tem o modo executável definido. Nesse caso, e não é um diretório, execute seu conteúdo como um script de shell.

POSIX define um conjunto de funções, conhecidos como as exec(3)funções , que o envoltório execve(2)e fornecer essa funcionalidade também; veja a resposta de muru para detalhes. No Linux, pelo menos essas funções são implementadas pela biblioteca C, não pelo kernel.

Stephen Kitt
fonte
Isso é fantástico e tem os detalhes que eu estava procurando, obrigado!
Josh
12

Em parte, isso depende da execfunção específica da família usada. execve, como Stephen Kitt mostrou em detalhes, apenas executa arquivos no formato binário correto ou scripts que começam com um shebang adequado.

No entanto , execlpe execvpvá um passo além: se o shebang não estiver correto, o arquivo será executado /bin/shno 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.)

Isso é um pouco suportado pelo POSIX (ênfase minha):

Uma fonte potencial de confusão observada pelos desenvolvedores padrão é sobre como o conteúdo de um arquivo de imagem de processo afeta o comportamento da família de funções exec. A seguir, é apresentada uma descrição das ações executadas:

  1. Se o arquivo de imagem do processo for um executável válido (em um formato executável e válido e com privilégios apropriados) para este sistema, o sistema executará o arquivo.

  2. Se o arquivo de imagem do processo tiver privilégios apropriados e estiver em um formato executável, mas não válido para este sistema (como um binário reconhecido para outra arquitetura), isso será um erro e o número do erro será definido como [EINVAL] (consulte mais tarde RATIONALE em [EINVAL]).

  3. Se o arquivo de imagem do processo tiver privilégios apropriados, mas não for reconhecido:

    1. Se for uma chamada para execlp () ou execvp (), eles chamarão um interpretador de comandos assumindo que o arquivo de imagem do processo seja um script de shell.

    2. Se não for uma chamada para execlp () ou execvp (), ocorrerá um erro e errno será definido como [ENOEXEC].

Isso não especifica como o interpretador de comandos é obtido, portanto, mas não especifica que um erro deve ser dado. Eu acho, portanto, que os desenvolvedores do Linux permitiram que esses arquivos fossem executados /bin/sh(ou isso já era uma prática comum e eles apenas seguiram o exemplo).

FWIW, a página de manualexec(3) do FreeBSD também menciona comportamentos semelhantes:

 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, no entanto, nenhum shell comum usa execlpou execvpdiretamente, presumivelmente para um controle mais preciso do ambiente. Todos eles implementam a mesma lógica usando execve.

muru
fonte
3
Eu também gostaria de acrescentar que, pelo menos no Linux, execl, execlp, execle, execv, execvpe execvpesão todos front-ends para o execvesyscall; os primeiros são fornecidos pela biblioteca C, o kernel apenas conhece execve(e execveatatualmente).
Stephen Kitt
@StephenKitt Isso explica por que eu não poderia encontrar uma página de manual para essas funções em de man7.org seção 2.
Muru
6

Isso pode ser uma adição à resposta de Stephen Kitt, como um comentário da bashfonte no arquivo execute_cmd.c:

Execute um comando simples que, esperamos, seja definido em um arquivo de disco em algum lugar.

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.  

Nesse caso, e não é um diretório, execute seu conteúdo como um script de shell.

taliezin
fonte
0

Ele é executado como um shell script, é não originado (por exemplo, variáveis definidas no arquivo executado não afetam fora). Provavelmente vestigial do passado enevoado, quando havia um formato de shell e um executável. Não é um executável, deve ser um script de shell.

vonbrand
fonte
2
Você entendeu mal a minha pergunta. O que acontece em detalhes? No mínimo, preciso entender o que verifica um shebang, isso exec()ou o shell? Quero significativamente mais internos
Josh