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

30

Então, eu pensei que eu tinha uma boa compreensão disso, mas apenas fiz um teste (em resposta a uma conversa em que eu discordei de alguém) e descobri que minha compreensão é falha ...

No máximo de detalhes possível o que exatamente acontece quando eu executo um arquivo no meu shell? O que quero dizer é que, se eu digitar: ./somefile some arguments no meu shell e pressionar return (e somefile existe no cwd, e eu li + executar permissões em somefile ), então o que acontece sob o capô?

Eu pensei que a resposta foi:

  1. O shell faz um syscall para exec , passando o caminho para somefile
  2. O kernel examina somefile e 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 pode 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 , com ARGV inicializado para uma matriz dos parâmetros (a char** , ["some","arguments"] )
  4. Se o número mágico for um shebang , então exec() gerará um novo processo como acima, mas o O executável usado é o interpretador referido pelo shebang (por exemplo, /bin/bash ou /bin/perl ) e somefile é passado para STDIN
  5. Se o arquivo não tiver um número mágico válido, um erro como "arquivo inválido (número mágico inválido): erro no formato Exec" ocorre

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

O que exatamente acontece quando eu executo um arquivo no meu shell? (com tantos detalhes é razoável ...)

    
por Josh 09.03.2016 / 22:53

4 respostas

28

A resposta definitiva para "como os programas são executados" no Linux é o par de artigos sobre LWN.net intitulado, surpreendentemente, Como os programas são executados e Como os programas são executados: ELF binários . O primeiro artigo aborda scripts brevemente. (Estritamente falando, 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.)

Uma pequena 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 o código-fonte de um programa de teste, execve; vamos usar isso para ver o que acontece sem um shell. Primeiro, escreva um testcript, testscr1 , contendo

#!/bin/sh

pstree

e outro, testscr2 , contendo apenas

pstree

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

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

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

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

testscr1 ainda é executado, mas testscr2 produz

execve: Exec format error

Isso mostra que o shell manipula testscr2 de maneira diferente. Ele não processa o script em si, mas ainda usa /bin/sh para fazer isso; isso pode ser verificado pela canalização de testscr2 para less :

./testscr2 | less -ppstree

No meu sistema, recebo

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

Como você pode ver, há o shell que eu estava usando, zsh , que iniciou less e um segundo shell, sh ( dash no meu sistema), para executar o script, que foi executado %código%. Em pstree , isso é tratado por zsh em zexecve : o shell usa Src/exec.c para tentar executar o comando, e se isso falhar, ele lê o arquivo para ver se ele tem um shebang, processando-o de acordo (o que o kernel também terá feito), e se falha ele tenta executar o arquivo com execve(2) , contanto que ele não tenha lido nenhum byte zero do 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);
        }

sh tem o mesmo comportamento, implementado em bash com um comentário útil (como apontado por taliezin ):

Execute a simple command that is hopefully defined in a disk file somewhere.

  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. If so, and it isn't a directory, then execute its contents as a shell script.

O POSIX define um conjunto de funções, conhecidas como as funções execute_cmd.c , que envolvem exec(3) e forneça essa funcionalidade também; veja resposta do muru para detalhes. No Linux, pelo menos, essas funções são implementadas pela biblioteca C, não pelo kernel.

    
por 09.03.2016 / 23:57
12

Em parte, isso depende da função familiar exec específica usada. execve , como Stephen Kitt mostrou em detalhes, só executa arquivos no formato binário correto ou scripts que começam com um bom shebang.

No entanto, , execlp e execvp vão um passo além: se o shebang não estiver correto, o arquivo é executado com /bin/sh no 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 por POSIX (grifo meu):

One potential source of confusion noted by the standard developers is over how the contents of a process image file affect the behavior of the exec family of functions. The following is a description of the actions taken:

  1. If the process image file is a valid executable (in a format that is executable and valid and having appropriate privileges) for this system, then the system executes the file.

  2. If the process image file has appropriate privileges and is in a format that is executable but not valid for this system (such as a recognized binary for another architecture), then this is an error and errno is set to [EINVAL] (see later RATIONALE on [EINVAL]).

  3. If the process image file has appropriate privileges but is not otherwise recognized:

    1. If this is a call to execlp() or execvp(), then they invoke a command interpreter assuming that the process image file is a shell script.

    2. If this is not a call to execlp() or execvp(), then an error occurs and errno is set to [ENOEXEC].

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

FWIW, o A página de manual do FreeBSD para exec(3) também menciona um comportamento similar:

 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 execlp ou execvp diretamente, presumivelmente para um melhor controle sobre o ambiente. Todos eles implementam a mesma lógica usando execve .

    
por 10.03.2016 / 02:37
6

Esta poderia ser uma adição à resposta de Stephen Kitt, como um comentário de bash source no arquivo execute_cmd.c :

Execute a simple command that is hopefully defined in a disk file somewhere.

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.  

If so, and it isn't a directory, then execute its contents as a shell script.

    
por 10.03.2016 / 00:25
0

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

    
por 09.03.2016 / 23:17