Depois de descobrir que vários comandos comuns (como read
) são realmente integrados ao Bash (e ao executá-los no prompt, na verdade, estou executando um script de shell de duas linhas que apenas encaminha para o integrado), eu estava olhando para ver se o mesmo é verdade para true
e false
.
Bem, eles são definitivamente binários.
sh-4.2$ which true
/usr/bin/true
sh-4.2$ which false
/usr/bin/false
sh-4.2$ file /usr/bin/true
/usr/bin/true: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=2697339d3c19235
06e10af65aa3120b12295277e, stripped
sh-4.2$ file /usr/bin/false
/usr/bin/false: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=b160fa513fcc13
537d7293f05e40444fe5843640, stripped
sh-4.2$
No entanto, o que eu achei mais surpreendente foi o tamanho. Eu esperava que eles fossem apenas alguns bytes cada, como true
é basicamente justo exit 0
e false
é exit 1
.
sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$
No entanto, descobri, para minha surpresa, que os dois arquivos têm mais de 28 KB.
sh-4.2$ stat /usr/bin/true
File: '/usr/bin/true'
Size: 28920 Blocks: 64 IO Block: 4096 regular file
Device: fd2ch/64812d Inode: 530320 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2018-01-25 19:46:32.703463708 +0000
Modify: 2016-06-30 09:44:27.000000000 +0100
Change: 2017-12-22 09:43:17.447563336 +0000
Birth: -
sh-4.2$ stat /usr/bin/false
File: '/usr/bin/false'
Size: 28920 Blocks: 64 IO Block: 4096 regular file
Device: fd2ch/64812d Inode: 530697 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2018-01-25 20:06:27.210764704 +0000
Modify: 2016-06-30 09:44:27.000000000 +0100
Change: 2017-12-22 09:43:18.148561245 +0000
Birth: -
sh-4.2$
Então, minha pergunta é: por que eles são tão grandes? O que há no executável além do código de retorno?
PS: estou usando o RHEL 7.4
linux
reverse-engineering
Kidburla
fonte
fonte
command -V true
não deve usarwhich
. Ele produzirá:true is a shell builtin
para bash.true
efalse
são integrados em todos os shell modernos, mas os sistemas também incluem versões externas de programas porque fazem parte do sistema padrão, para que os programas que invocam comandos diretamente (ignorando o shell) possam usá-los.which
ignora os componentes internos e procura apenas comandos externos, e é por isso que ele apenas mostrou os externos. Tentetype -a true
e emtype -a false
vez disso.true
efalse
29kb cada? O que há no executável além do código de retorno?"false
: muppetlabs.com/~breadbox/software/tiny/teensy.htmlRespostas:
No passado,
/bin/true
e/bin/false
no shell eram realmente scripts.Por exemplo, em um sistema PDP / 11 Unix 7:
Atualmente, pelo menos em
bash
, os comandostrue
efalse
são implementados como comandos internos do shell. Portanto, nenhum arquivo binário executável é chamado por padrão, ao usar as diretivasfalse
etrue
nabash
linha de comando e dentro dos scripts de shell.A partir da
bash
fontebuiltins/mkbuiltins.c
:Também por @meuh comentários:
Portanto, pode-se dizer com um alto grau de certeza que os arquivos executáveis
true
efalse
existem principalmente para serem chamados de outros programas .A partir de agora, a resposta se concentrará no
/bin/true
binário docoreutils
pacote no Debian 9/64 bits. (/usr/bin/true
executando o RedHat. O RedHat e o Debian usam ambos oscoreutils
pacotes, analisaram a versão compilada do último, tendo-o em mãos).Como pode ser visto no arquivo de origem
false.c
,/bin/false
é compilado com (quase) o mesmo código de origem/bin/true
, retornando EXIT_FAILURE (1), portanto, essa resposta pode ser aplicada aos dois binários.Como também pode ser confirmado pelos dois executáveis com o mesmo tamanho:
Infelizmente, a pergunta direta à resposta
why are true and false so large?
poderia ser, porque não há mais motivos tão urgentes para se preocupar com o desempenho máximo deles. Eles não são essenciais para obash
desempenho, não estão mais sendo usados porbash
(scripts).Comentários semelhantes se aplicam ao seu tamanho, 26 KB para o tipo de hardware que temos hoje em dia é insignificante. O espaço não é mais premium para o servidor / área de trabalho típico, e eles nem se preocupam mais em usar o mesmo binário
false
etrue
, como são implementados apenas duas vezes em distribuições usandocoreutils
.Concentrando-se, no entanto, no verdadeiro espírito da pergunta, por que algo que deveria ser tão simples e pequeno, fica tão grande?
A distribuição real das seções de
/bin/true
é como esses gráficos mostram; o código principal + dados equivale a aproximadamente 3 KB de um binário de 26 KB, o que equivale a 12% do tamanho de/bin/true
.De
true
fato, o utilitário obteve mais códigos de cruft ao longo dos anos, principalmente o suporte padrão para--version
e--help
.No entanto, essa não é a (única) justificativa principal por ser tão grande, mas por estar dinamicamente vinculada (usando bibliotecas compartilhadas), tendo também parte de uma biblioteca genérica comumente usada por
coreutils
binários vinculados como uma biblioteca estática. A meta para a construção de umelf
arquivo executável também representa uma parte significativa do binário, sendo um arquivo relativamente pequeno para os padrões atuais.O restante da resposta é para explicar como construímos os gráficos a seguir detalhando a composição do
/bin/true
arquivo binário executável e como chegamos a essa conclusão.Como o @Maks diz, o binário foi compilado a partir de C; conforme meu comentário também, também é confirmado que é do coreutils. Estamos apontando diretamente para o (s) autor (es) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , em vez do gnu git como @Maks (mesmas fontes, diferentes repositórios - este repositório foi selecionado por ter a fonte completa das
coreutils
bibliotecas)Podemos ver os vários blocos de construção do
/bin/true
binário aqui (Debian 9 - 64 bits decoreutils
):Daqueles:
Dos 24 KB, cerca de 1 KB é para a fixação das 58 funções externas.
Isso ainda deixa cerca de 23 KB para o restante do código. Mostraremos abaixo que o código principal atual do arquivo - main () + use () é de aproximadamente 1 KB compilado e explicaremos para que os outros 22 KB são usados.
Pesquisando mais além no binário
readelf -S true
, podemos ver que, embora o binário tenha 26159 bytes, o código compilado real é 13017 bytes e o restante é um código de dados / inicialização variado.No entanto, essa
true.c
não é a história toda e os 13 KB parecem excessivos se fosse apenas esse arquivo; podemos ver as funções chamadasmain()
que não estão listadas nas funções externas vistas no elfo comobjdump -T true
; funções presentes em:Essas funções extras não vinculadas externamente
main()
são:Portanto, minha primeira suspeita foi parcialmente correta, enquanto a biblioteca está usando bibliotecas dinâmicas, o
/bin/true
binário é grande * porque possui algumas bibliotecas estáticas incluídas * (mas essa não é a única causa).Compilar o código C não costuma ser tão ineficiente para ter esse espaço inexplicado, portanto, minha suspeita inicial de que algo estava errado.
O espaço extra, quase 90% do tamanho do binário, é na verdade metadados extras de bibliotecas / elf.
Ao usar o Hopper para desmontar / descompilar o binário para entender onde estão as funções, pode ser visto o código binário compilado da função true.c / use () na verdade é de 833 bytes e a função true.c / main () é 225 bytes, que é aproximadamente um pouco menor que 1 KB. A lógica para as funções da versão, oculta nas bibliotecas estáticas, é de cerca de 1 KB.
O atual compilado main () + use () + version () + strings + vars estão usando apenas entre 3 KB e 3.5 KB.
É realmente irônico, tais utilidades pequenas e humildes aumentaram de tamanho pelas razões explicadas acima.
questão relacionada: Entendendo o que um binário Linux está fazendo
true.c
main () com as chamadas de função incorretas:O tamanho decimal das várias seções do binário:
Saída de
readelf -S true
Saída de
objdump -T true
(funções externas vinculadas dinamicamente no tempo de execução)fonte
true
oufalse
com um executável x86 ELF de 45 bytes, embalando o código executável (4 instruções x86) dentro do cabeçalho do programa ELF (sem suporte para opções de linha de comando!) . Um tutorial turbilhão sobre a criação de executáveis ELF realmente adolescentes para Linux . (Ou um pouco maior se você quer evitar dependendo Linux ELF detalhes de implementação loader: P)A implementação provavelmente vem dos GNU coreutils. Esses binários são compilados a partir de C; nenhum esforço específico foi feito para torná-los menores do que são por padrão.
Você pode tentar compilar a implementação trivial de
true
si mesmo e perceberá que ele já tem poucos KB de tamanho. Por exemplo, no meu sistema:Obviamente, seus binários são ainda maiores. Isso porque eles também suportam argumentos de linha de comando. Tente correr
/usr/bin/true --help
ou/usr/bin/true --version
.Além dos dados da string, o binário inclui lógica para analisar sinalizadores de linha de comando, etc. Isso adiciona aproximadamente 20 KB de código, aparentemente.
Para referência, você pode encontrar o código fonte aqui: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
fonte
Reduzi-los à funcionalidade principal e escrever no assembler produz binários muito menores.
Os binários verdadeiro / falso originais são escritos em C, que por sua natureza atrai várias referências de símbolos de biblioteca +. Se você executar
readelf -a /bin/true
isso é bastante perceptível.352 bytes para um executável estático ELF despojado (com espaço para salvar alguns bytes otimizando o asm para o tamanho do código).
Ou, com um pouco de uma abordagem desagradável / engenhosa (parabéns ao stalkr ), crie seus próprios cabeçalhos ELF, reduzindo-os para
132127 bytes. Estamos entrando no território do Code Golf aqui.fonte
int 0x80
ABI de 32 bits em um executável de 64 bits, o que é incomum, mas suportado . Usarsyscall
não salvaria nada. Os bytes altos deebx
são ignorados, para que você possa usar 2 bytesmov bl,1
. Ou, é claro,xor ebx,ebx
para zero . O número inteiro de inits do Linux é registrado como zero, então você podeinc eax
obter apenas 1 = __NR_exit (i386 ABI).true
. (Eu não vejo uma maneira fácil de gerenciar menos de 128 bytes parafalse
, no entanto, que não usando o 32-bit ABI ou tirar vantagem do fato de que o Linux zeros registros na inicialização do processo, de modomov al,252
(2 bytes) funciona.push imm8
/pop rdi
Faria também trabalhar em vez delea
para a definiçãoedi=1
, mas ainda não pode bater o 32-bit ABI onde podíamosmov bl,1
sem um prefixo REX.Muito grande no meu Ubuntu 16.04 também. exatamente o mesmo tamanho? O que os torna tão grandes?
(excerto:)
Ah, existe ajuda para verdadeiro e falso, então vamos tentar:
Nada. Ah, havia essa outra linha:
Portanto, no meu sistema, é / bin / true, não / usr / bin / true
Portanto, há ajuda, há informações de versão, vinculando uma biblioteca para internacionalização. Isso explica grande parte do tamanho, e o shell usa seu comando otimizado de qualquer maneira e na maioria das vezes.
fonte