Executando strace
no bash
shell que é instruído a executar mkdir
forneceu esta saída que mostra muitas estatísticas antes de executar o mkdir
binário real:
BASH$> strace -f sh -c "bash -c \"mkdir /tmp\" 2>&1 | nl | grep -e "execve\|stat\|access"
[.....]
2766 [pid 17371] stat(".", {st_mode=S_IFDIR|0750, st_size=17262, ...}) = 0
2767 [pid 17371] stat("/usr/local/sbin/mkdir", 0x7ffd87aad0a0) = -1 ENOENT 2767 (No such file or directory)
2768 [pid 17371] stat("/usr/local/bin/mkdir", 0x7ffd87aad0a0) = -1 ENOENT (No such file or directory)
2769 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2770 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2771 [pid 17371] access("/usr/bin/mkdir", X_OK) = 0
2772 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2773 [pid 17371] access("/usr/bin/mkdir", R_OK) = 0
2774 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2775 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2776 [pid 17371] access("/usr/bin/mkdir", X_OK) = 0
2777 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2778 [pid 17371] access("/usr/bin/mkdir", R_OK) = 0
2779 [pid 17371] execve("/usr/bin/mkdir", ["mkdir", "/tmp"], 0x557ec7e15920 /* 5 vars */) = 0
Minha pergunta é: É normal (e se sim por qual motivo) é /usr/bin/mkdir
stat()
ed muito? As linhas de saída são numeradas e, em particular, eu me pergunto que sentido a linha 2776
fará uma vez que já 2771
tenha sido executada. Também fiquei com a impressão de que o bash poderia ter salvo todas as chamadas do sistema desde 2770
a final execve
, pois stat
deveria ter fornecido as informações de uma só vez? o que estou perdendo?
Desde então, procurei uma explicação e verifiquei como um shell alternativo dash
se comportaria, ele também mostra algumas coisas stat()
:
DASH$> strace -f sh -c "dash -c \"mkdir /tmp\" 2>&1 | nl | grep -e "execve\|stat\|access"
[....]
2792 [pid 17372] stat("/usr/local/sbin/mkdir", 0x7ffc66010b50) = -1 ENOENT (No such file or directory)
2793 [pid 17372] stat("/usr/local/bin/mkdir", 0x7ffc66010b50) = -1 ENOENT (No such file or directory)
2794 [pid 17372] stat("/usr/sbin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2795 [pid 17372] execve("/usr/sbin/mkdir", ["mkdir", "/run"], 0x55d8d3453bb8 /* 6 vars */) = 0
Estou ciente de que as linhas 2792
, 2793
, semelhantes às linhas 2767
, 2768 are because of searching the executable in the various directories in the current
PATH`.
Se isso for descontado, então dash
só faz uma única estatística e bash
faz 10. Novamente levantando a questão: isso é normal?
ATUALIZAÇÃO:
Havia mais geteuid()
, getguid()
e getuid()
misturado getgid()
nas estatísticas do bash
BASH$>strace -f sh -c "bash -c \"mkdir /tmp\"" 2>&1 | grep -e "execve\|stat\|access\|geteuid\|getegid\|getuid\|getgid"
[....]
[pid 24534] stat("/usr/local/bin/mkdir", 0x7fffda480f30) = -1 ENOENT (No such file or directory)
[pid 24534] stat("/usr/local/sbin/mkdir", 0x7fffda480f30) = -1 ENOENT (No such file or directory)
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", X_OK) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", R_OK) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", X_OK) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", R_OK) = 0
[pid 24534] execve("/usr/bin/mkdir", ["mkdir", "/tmp"], 0x55adcd4dc040 /* 55 vars */) = 0
então talvez isso possa dar uma pista do que está "acontecendo aqui" com o bash? Está fazendo alguma verificação para evitar setuid
explorações?
**ATUALIZAÇÃO 2: ** O combo geteuid()
, getguid()
, getuid()
e getgid()
access parece ser a marca registrada do uso glibc
da int eaccess(const char *pathname, int mode);
função de biblioteca do . Cada uso de eaccess
incorre no uso de all geteuid
, getguid
, e getuid
, já que o bash executa a função findcmd.c , que por sua vez executa eaccess duas vezes assim.getgid
access
file_status
#if defined (HAVE_EACCESS)
/* Use eaccess(2) if we have it to take things like ACLs and other
file access mechanisms into account. eaccess uses the effective
user and group IDs, not the real ones. We could use sh_eaccess,
but we don't want any special treatment for /dev/fd. */
if (eaccess (name, X_OK) == 0)
r |= FS_EXECABLE;
if (eaccess (name, R_OK) == 0)
r |= FS_READABLE;
onde cada eaccess pode ser vinculado a 4 syscalls.
Você deve olhar para o loop em
findcmd.c:find_user_command_in_path()
.stat()
é chamado (defile_status()
) duas vezes para cada elemento no caminho: uma vez viafind_in_path_element()
na linha 640 e uma vez viais_directory()
na linha 645 .Como você mencionou, é também em
file_status()
queeaccess()
é chamado.Embora isso possa ser otimizado, lembre-se de que não é grande coisa, porque o caminho é então criptografado, e toda essa pesquisa e estatísticas acontecem apenas na primeira vez que um comando é usado.
Olhando para o código-fonte do bash , a resposta é:
Sim , as ligações são normais, são devido a vários fatores, incluindo
bash
executa uma funçãofile_status
que inclui uma chamada parastat
e na maioria das configurações do GNU/Linux duas chamadas separadaseaccess
para fromglibc
glibc
'seaccess
é executado novamentestat
e, em seguida, todo o grupo feliz degeteuid
,getegid
,getuid
egetgid
finalmenteaccess
(como pode não ter gravado nenhuma informação útil de seu inicialstat
e talvez glibc simplesmente não sinta vontade de salvar em syscalls (switches de contexto não importam!).bash
quer ter certeza de que o arquivo encontrado pode iterar pelos diretórios emPATH
que é realmente executável e legível para o usuário que tenta fazê-lo. (um teste)bash
executa umahash
tabela para reduzir o caminho de pesquisa em invocações sucessivas (o segundo teste comfile_status
)Tudo isso gera as inúmeras chamadas de sistema, sim, supérfluas. 6/11 syscalls para cada cantate no PATH e outras 6/11 syscalls uma vez que o comando foi encontrado anteriormente na tabela de hash e, portanto, é verificado se ainda é válido.
a
file_status
função na aparência do bashfindcmd.c
no caso da minha compilação para minha caixa linux assim (ou seja, os ifdefs avaliados)que será executado uma vez
stat()
inicialmente eeaccess()
duas vezes que por sua vez (sendo que aglibc
função é executada:stat
geteuid
getguid
getuid
getgid
access
)e, portanto, é responsável por esta parte da saída original do bash: