Por que o verdadeiro e o falso são tão grandes?


80

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 truee 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 0e 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


9
Você command -V truenão deve usar which. Ele produzirá: true is a shell builtinpara bash.
meuh

32
truee false 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. whichignora os componentes internos e procura apenas comandos externos, e é por isso que ele apenas mostrou os externos. Tente type -a truee em type -a falsevez disso.
Mtraceur

74
É irônico que você escreva uma pergunta tão longa para dizer "Por que truee false29kb cada? O que há no executável além do código de retorno?"
precisa saber é o seguinte

7
Algumas versões anteriores do unix tinham apenas um arquivo vazio para true, pois esse era um programa sh válido que retornaria o código de saída 0. Eu realmente gostaria de encontrar um artigo que li anos atrás sobre a história do verdadeiro utilitário, de um arquivo vazio para a monstruosidade que é hoje, mas tudo que eu poderia encontrar é esta: trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
Philip

9
Obrigatória - a menor implementação de false: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah

Respostas:


117

No passado, /bin/truee /bin/falseno shell eram realmente scripts.

Por exemplo, em um sistema PDP / 11 Unix 7:

$ ls -la /bin/true /bin/false
-rwxr-xr-x 1 bin         7 Jun  8  1979 /bin/false
-rwxr-xr-x 1 bin         0 Jun  8  1979 /bin/true
$
$ cat /bin/false
exit 1
$
$ cat /bin/true
$  

Atualmente, pelo menos em bash, os comandos truee falsesão implementados como comandos internos do shell. Portanto, nenhum arquivo binário executável é chamado por padrão, ao usar as diretivas falsee truena bashlinha de comando e dentro dos scripts de shell.

A partir da bashfonte builtins/mkbuiltins.c:

char * posix_builtins [] =
    {
      "alias", "bg", "cd", "command", "** false **", "fc", "fg", "getopts", "jobs",
      "kill", "newgrp", "pwd", "read", "** true **", "umask", "unalias", "wait",
      (char *) NULL
    };

Também por @meuh comentários:

$ command -V true false
true is a shell builtin
false is a shell builtin

Portanto, pode-se dizer com um alto grau de certeza que os arquivos executáveis truee falseexistem principalmente para serem chamados de outros programas .

A partir de agora, a resposta se concentrará no /bin/truebinário do coreutilspacote no Debian 9/64 bits. ( /usr/bin/trueexecutando o RedHat. O RedHat e o Debian usam ambos os coreutilspacotes, 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.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Como também pode ser confirmado pelos dois executáveis ​​com o mesmo tamanho:

$ ls -l /bin/true /bin/false
-rwxr-xr-x 1 root root 31464 Feb 22  2017 /bin/false
-rwxr-xr-x 1 root root 31464 Feb 22  2017 /bin/true

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 o bashdesempenho, não estão mais sendo usados ​​por bash(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 falsee true, como são implementados apenas duas vezes em distribuições usando coreutils.

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 truefato, o utilitário obteve mais códigos de cruft ao longo dos anos, principalmente o suporte padrão para --versione --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 coreutilsbinários vinculados como uma biblioteca estática. A meta para a construção de um elfarquivo 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/truearquivo binário executável e como chegamos a essa conclusão.

bintrue bintrue2

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 coreutilsbibliotecas)

Podemos ver os vários blocos de construção do /bin/truebinário aqui (Debian 9 - 64 bits de coreutils):

$ file /bin/true
/bin/true: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9ae82394864538fa7b23b7f87b259ea2a20889c4, stripped

$ size /bin/true
    text       data     bss     dec     hex filename
   24583       1160     416   26159    662f true

Daqueles:

  • o texto (geralmente código) é de cerca de 24 KB
  • dados (variáveis ​​inicializadas, principalmente cadeias) são de cerca de 1 KB
  • bss (dados não inicializados) 0,5 KB

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.cnão é a história toda e os 13 KB parecem excessivos se fosse apenas esse arquivo; podemos ver as funções chamadas main()que não estão listadas nas funções externas vistas no elfo com objdump -T true; funções presentes em:

Essas funções extras não vinculadas externamente main()são:

  • set_program_name ()
  • close_stdout ()
  • version_etc ()

Portanto, minha primeira suspeita foi parcialmente correta, enquanto a biblioteca está usando bibliotecas dinâmicas, o /bin/truebiná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:

int
main (int argc, char **argv)
{
  /* Recognize --help or --version only if it's the only command-line
     argument.  */
  if (argc == 2)
    {
      initialize_main (&argc, &argv);
      set_program_name (argv[0]);           <-----------
      setlocale (LC_ALL, "");
      bindtextdomain (PACKAGE, LOCALEDIR);
      textdomain (PACKAGE);

      atexit (close_stdout);             <-----

      if (STREQ (argv[1], "--help"))
        usage (EXIT_STATUS);

      if (STREQ (argv[1], "--version"))
        version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version,  AUTHORS,  <------
                     (char *) NULL);
    }

  exit (EXIT_STATUS);
}

O tamanho decimal das várias seções do binário:

$ size -A -t true 
true  :
section               size      addr
.interp                 28       568
.note.ABI-tag           32       596
.note.gnu.build-id      36       628
.gnu.hash               60       664
.dynsym               1416       728
.dynstr                676      2144
.gnu.version           118      2820
.gnu.version_r          96      2944
.rela.dyn              624      3040
.rela.plt             1104      3664
.init                   23      4768
.plt                   752      4800
.plt.got                 8      5552
.text                13017      5568
.fini                    9     18588
.rodata               3104     18624
.eh_frame_hdr          572     21728
.eh_frame             2908     22304
.init_array              8   2125160
.fini_array              8   2125168
.jcr                     8   2125176
.data.rel.ro            88   2125184
.dynamic               480   2125272
.got                    48   2125752
.got.plt               392   2125824
.data                  128   2126240
.bss                   416   2126368
.gnu_debuglink          52         0
Total                26211

Saída de readelf -S true

$ readelf -S true
There are 30 section headers, starting at offset 0x7368:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000000254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000000274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000000298  00000298
       000000000000003c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000000002d8  000002d8
       0000000000000588  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000000860  00000860
       00000000000002a4  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000000b04  00000b04
       0000000000000076  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000000b80  00000b80
       0000000000000060  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000000be0  00000be0
       0000000000000270  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000000e50  00000e50
       0000000000000450  0000000000000018  AI       5    25     8
  [11] .init             PROGBITS         00000000000012a0  000012a0
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000000012c0  000012c0
       00000000000002f0  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000000015b0  000015b0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000000015c0  000015c0
       00000000000032d9  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         000000000000489c  0000489c
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000000048c0  000048c0
       0000000000000c20  0000000000000000   A       0     0     32
  [17] .eh_frame_hdr     PROGBITS         00000000000054e0  000054e0
       000000000000023c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000005720  00005720
       0000000000000b5c  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000206d68  00006d68
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000206d70  00006d70
       0000000000000008  0000000000000008  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000206d78  00006d78
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .data.rel.ro      PROGBITS         0000000000206d80  00006d80
       0000000000000058  0000000000000000  WA       0     0     32
  [23] .dynamic          DYNAMIC          0000000000206dd8  00006dd8
       00000000000001e0  0000000000000010  WA       6     0     8
  [24] .got              PROGBITS         0000000000206fb8  00006fb8
       0000000000000030  0000000000000008  WA       0     0     8
  [25] .got.plt          PROGBITS         0000000000207000  00007000
       0000000000000188  0000000000000008  WA       0     0     8
  [26] .data             PROGBITS         00000000002071a0  000071a0
       0000000000000080  0000000000000000  WA       0     0     32
  [27] .bss              NOBITS           0000000000207220  00007220
       00000000000001a0  0000000000000000  WA       0     0     32
  [28] .gnu_debuglink    PROGBITS         0000000000000000  00007220
       0000000000000034  0000000000000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  00007254
       000000000000010f  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Saída de objdump -T true(funções externas vinculadas dinamicamente no tempo de execução)

$ objdump -T true

true:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __uflow
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 getenv
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 free
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 abort
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __errno_location
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 strncmp
0000000000000000  w   D  *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 _exit
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __fpending
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 textdomain
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fclose
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 bindtextdomain
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 dcgettext
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __ctype_get_mb_cur_max
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 strlen
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.4   __stack_chk_fail
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 mbrtowc
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 strrchr
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 lseek
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 memset
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fscanf
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 close
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __libc_start_main
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 memcmp
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fputs_unlocked
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 calloc
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 strcmp
0000000000000000  w   D  *UND*  0000000000000000              __gmon_start__
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.14  memcpy
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fileno
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 malloc
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fflush
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 nl_langinfo
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 ungetc
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __freading
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 realloc
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fdopen
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 setlocale
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.3.4 __printf_chk
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 error
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 open
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fseeko
0000000000000000  w   D  *UND*  0000000000000000              _Jv_RegisterClasses
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 __cxa_atexit
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 exit
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 fwrite
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.3.4 __fprintf_chk
0000000000000000  w   D  *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 mbsinit
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 iswprint
0000000000000000  w   DF *UND*  0000000000000000  GLIBC_2.2.5 __cxa_finalize
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.3   __ctype_b_loc
0000000000207228 g    DO .bss   0000000000000008  GLIBC_2.2.5 stdout
0000000000207220 g    DO .bss   0000000000000008  GLIBC_2.2.5 __progname
0000000000207230  w   DO .bss   0000000000000008  GLIBC_2.2.5 program_invocation_name
0000000000207230 g    DO .bss   0000000000000008  GLIBC_2.2.5 __progname_full
0000000000207220  w   DO .bss   0000000000000008  GLIBC_2.2.5 program_invocation_short_name
0000000000207240 g    DO .bss   0000000000000008  GLIBC_2.2.5 stderr

5
Tendo feito alguma programação recentemente com um microcontrolador 64kB + 2kB, 28kb não parece tão pequeno ..
Barleyman

1
@ Barleyman, você tem OpenWRT, yocto, uClinux, uclib, busybox, microcoreutils e outras soluções para esse tipo de ambiente. Editou a postagem com sua preocupação.
Rui F Ribeiro

4
@ Barleyman: Se você estava otimizando o tamanho do executável binário, pode implementar trueou falsecom 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)
Peter Cordes

3
Não, realmente não. O Yocto, por exemplo, pode ser amontoado em menos de um megabyte, que é superior a 64kB. Nesse tipo de dispositivo, você pode usar algum tipo de RTOS com gerenciamento rudimentar de processo / memória, mas mesmo esses podem facilmente se tornar pesados ​​demais. Escrevi um sistema cooperativo de multithreading simples e usei a proteção de memória incorporada para proteger o código contra a substituição. No total, o firmware consome 55kB agora, portanto, não há muito espaço para sobrecarga adicional. Esses 2kB ginormous olhar para cima tabelas ..
Barleyman

2
@ PeterCordes, com certeza, mas você precisa de algumas magnitudes de mais recursos antes que o Linux se torne viável. Pelo que vale, o C ++ também não funciona nesse ambiente. Bem, não as bibliotecas padrão de qualquer maneira. O Iostream está em torno de 200kB etc.
Barleyman

34

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 truesi mesmo e perceberá que ele já tem poucos KB de tamanho. Por exemplo, no meu sistema:

$ echo 'int main() { return 0; }' | gcc -xc - -o true
$ wc -c true
8136 true

Obviamente, seus binários são ainda maiores. Isso porque eles também suportam argumentos de linha de comando. Tente correr /usr/bin/true --helpou /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


2
Para sua informação, eu estava reclamando sobre essas implementações do coreutils no rastreador de erros, mas não há chance de consertá-lo lists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html
rudimeier

7
Não é a lógica dos argumentos, C não é tão ineficiente ... é a tarefa das bibliotecas / tarefas domésticas em linha. Dê uma olhada na minha resposta para os detalhes sangrentos.
Rui F Ribeiro

8
Isso é enganoso porque sugere que o código de máquina compilado (a partir de C ou de outra forma) é o que ocupa uma enorme quantidade de espaço - a sobrecarga de tamanho real tem mais a ver com grandes quantidades de clichê padrão da biblioteca C / tempo de execução que é incorporado pelo compilador em para interoperar com a biblioteca C (glibc, a menos que você tenha ouvido falar que seu sistema provavelmente usa outra coisa, provavelmente) e, em menor grau, cabeçalhos / metadados ELF (muitos dos quais não são estritamente necessários, mas são considerados suficientemente úteis) para incluir nas construções padrão).
Mtraceur

2
O real main () + use () + seqüências de caracteres em ambas as funções são em torno de 2 KB, não 20 KB.
Rui F Ribeiro

2
@JdeBP lógica para --version / versão funtions 1 KB, --usage / - ajudar 833 bytes, main () 225 bytes e inteiros os dados estáticos do binário é de 1 KB
Rui F Ribeiro

25

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/trueisso é 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).

$ more true.asm false.asm
::::::::::::::
true.asm
::::::::::::::
global _start
_start:
 mov ebx,0
 mov eax,1     ; SYS_exit from asm/unistd_32.h
 int 0x80      ; The 32-bit ABI is supported in 64-bit code, in kernels compiled with IA-32 emulation
::::::::::::::
false.asm
::::::::::::::
global _start
_start:
 mov ebx,1
 mov eax,1
 int 0x80
$ nasm -f elf64 true.asm && ld -s -o true true.o     # -s means strip
$ nasm -f elf64 false.asm && ld -s -o false false.o
$ ll true false
-rwxrwxr-x. 1 steve steve 352 Jan 25 16:03 false
-rwxrwxr-x. 1 steve steve 352 Jan 25 16:03 true
$ ./true ; echo $?
0
$ ./false ; echo $?
1
$

Ou, com um pouco de uma abordagem desagradável / engenhosa (parabéns ao stalkr ), crie seus próprios cabeçalhos ELF, reduzindo-os para 132 127 bytes. Estamos entrando no território do Code Golf aqui.

$ cat true2.asm
BITS 64
  org 0x400000   ; _start is at 0x400080 as usual, but the ELF headers come first

ehdr:           ; Elf64_Ehdr
  db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
  times 8 db 0
  dw  2         ; e_type
  dw  0x3e      ; e_machine
  dd  1         ; e_version
  dq  _start    ; e_entry
  dq  phdr - $$ ; e_phoff
  dq  0         ; e_shoff
  dd  0         ; e_flags
  dw  ehdrsize  ; e_ehsize
  dw  phdrsize  ; e_phentsize
  dw  1         ; e_phnum
  dw  0         ; e_shentsize
  dw  0         ; e_shnum
  dw  0         ; e_shstrndx
  ehdrsize  equ  $ - ehdr

phdr:           ; Elf64_Phdr
  dd  1         ; p_type
  dd  5         ; p_flags
  dq  0         ; p_offset
  dq  $$        ; p_vaddr
  dq  $$        ; p_paddr
  dq  filesize  ; p_filesz
  dq  filesize  ; p_memsz
  dq  0x1000    ; p_align
  phdrsize  equ  $ - phdr

_start:
  xor  edi,edi         ; int status = 0
      ; or  mov dil,1  for false: high bytes are ignored.
  lea  eax, [rdi+60]   ; rax = 60 = SYS_exit, using a 3-byte instruction: base+disp8 addressing mode
  syscall              ; native 64-bit system call, works without CONFIG_IA32_EMULATION

; less-golfed version:
;      mov  edi, 1    ; for false
;      mov  eax,252   ; SYS_exit_group from asm/unistd_64.h
;      syscall

filesize  equ  $ - $$      ; used earlier in some ELF header fields

$ nasm -f bin -o true2 true2.asm
$ ll true2
-rw-r--r-- 1 peter peter 127 Jan 28 20:08 true2
$ chmod +x true2 ; ./true2 ; echo $?
0
$

2
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
terdon

2
Ver também este excelente write-up: muppetlabs.com/~breadbox/software/tiny/teensy.html
mic_e

3
Você está usando a int 0x80ABI de 32 bits em um executável de 64 bits, o que é incomum, mas suportado . Usar syscallnão salvaria nada. Os bytes altos de ebxsão ignorados, para que você possa usar 2 bytes mov bl,1. Ou, é claro, xor ebx,ebxpara zero . O número inteiro de inits do Linux é registrado como zero, então você podeinc eax obter apenas 1 = __NR_exit (i386 ABI).
Peter Cordes

1
Atualizei o código no seu exemplo de golfe para usar a ABI de 64 bits e reduza para 127 bytes true. (Eu não vejo uma maneira fácil de gerenciar menos de 128 bytes para false, 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 modo mov al,252(2 bytes) funciona. push imm8/ pop rdiFaria também trabalhar em vez de leapara a definição edi=1, mas ainda não pode bater o 32-bit ABI onde podíamos mov bl,1sem um prefixo REX.
Peter Cordes

2
l $(which true false)
-rwxr-xr-x 1 root root 27280 Mär  2  2017 /bin/false
-rwxr-xr-x 1 root root 27280 Mär  2  2017 /bin/true

Muito grande no meu Ubuntu 16.04 também. exatamente o mesmo tamanho? O que os torna tão grandes?

strings $(which true)

(excerto:)

Usage: %s [ignored command line arguments]
  or:  %s OPTION
Exit with a status code indicating success.
      --help     display this help and exit
      --version  output version information and exit
NOTE: your shell may have its own version of %s, which usually supersedes
the version described here.  Please refer to your shell's documentation
for details about the options it supports.
http://www.gnu.org/software/coreutils/
Report %s translation bugs to <http://translationproject.org/team/>
Full documentation at: <%s%s>
or available locally via: info '(coreutils) %s%s'

Ah, existe ajuda para verdadeiro e falso, então vamos tentar:

true --help 
true --version
#

Nada. Ah, havia essa outra linha:

NOTE: your shell may have its own version of %s, which usually supersedes
    the version described here.

Portanto, no meu sistema, é / bin / true, não / usr / bin / true

/bin/true --version
true (GNU coreutils) 8.25
Copyright © 2016 Free Software Foundation, Inc.
Lizenz GPLv3+: GNU GPL Version 3 oder höher <http://gnu.org/licenses/gpl.html>
Dies ist freie Software: Sie können sie ändern und weitergeben.
Es gibt keinerlei Garantien, soweit wie es das Gesetz erlaubt.

Geschrieben von Jim Meyering.

LANG=C /bin/true --version
true (GNU coreutils) 8.25
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Jim Meyering.

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.


Incluindo bibliotecas estáticas e metade do tamanho do binário para elf metada. Veja minha resposta.
Rui F Ribeiro
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.