¿Por qué son tan grandes lo verdadero y lo falso?


80

Después de descubrir que varios comandos comunes (como read) son realmente Bash builtins (y cuando los ejecuto en el indicador, en realidad estoy ejecutando un script de shell de dos líneas que simplemente reenvía al builtin), estaba buscando para ver si el mismo es cierto para truey false.

Bueno, definitivamente son binarios.

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$

Sin embargo, lo que más me sorprendió fue su tamaño. Esperaba que solo fueran unos pocos bytes cada uno, ya trueque básicamente es justo exit 0y falsees exit 1.

sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$

Sin embargo, para mi sorpresa, encontré que ambos archivos tienen un tamaño superior a 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$

Entonces mi pregunta es: ¿Por qué son tan grandes? ¿Qué hay en el ejecutable que no sea el código de retorno?

PD: estoy usando RHEL 7.4


99
Debe utilizar command -V trueno which. Saldrá: true is a shell builtinpara bash.
meuh

32
truey false están incorporados en cada shell moderno, pero los sistemas también incluyen versiones de programas externos porque es parte del sistema estándar para que los programas que invocan comandos directamente (sin pasar por el shell) puedan usarlos. whichignora los builtins y solo busca comandos externos, por lo que solo le mostró los externos. Intenta type -a truey en su type -a falselugar.
mtraceur el

74
Es irónico que escriba una pregunta tan larga para decir "¿Por qué son truey false29kb cada uno? ¿Qué hay en el ejecutable que no sea el código de retorno?"
David Richerby el

77
Algunas versiones anteriores de Unix solo tenían un archivo vacío para true ya que era un programa sh válido que devolvería el código de salida 0. Realmente desearía poder encontrar un artículo que leí hace años sobre la historia de la verdadera utilidad desde un archivo vacío hasta la monstruosidad que es hoy, pero todo lo que pude encontrar es esto: trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
Philip

99
Obligatorio: la implementación más pequeña de false: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah

Respuestas:


117

En el pasado, /bin/truey /bin/falseen el shell, en realidad eran guiones.

Por ejemplo, en un PDP / 11 Unix System 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
$  

Hoy en día, al menos en bash, los comandos truey falsese implementan como comandos integrados de shell. Por lo tanto, no se invocan archivos binarios ejecutables de forma predeterminada, tanto cuando se usan las directivas falsey trueen la bashlínea de comandos como dentro de los scripts de shell.

De la bashfuente 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
    };

También según los comentarios de @meuh:

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

Así que se puede decir con un alto grado de certeza el truey falsearchivos ejecutables existen principalmente para ser llamado desde otros programas .

De ahora en adelante, la respuesta se centrará en el /bin/truebinario del coreutilspaquete en Debian 9/64 bits. ( /usr/bin/trueejecutando RedHat. RedHat y Debian usan el coreutilspaquete, analizan la versión compilada de este último y lo tienen más a mano).

Como se puede ver en el archivo fuente false.c, /bin/falsese compila con (casi) el mismo código fuente que /bin/true, simplemente devolviendo EXIT_FAILURE (1), por lo que esta respuesta se puede aplicar a ambos binarios.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Como también puede ser confirmado por ambos ejecutables que tienen el mismo tamaño:

$ 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

Por desgracia, la pregunta directa a la respuesta why are true and false so large?podría ser, porque ya no hay razones tan apremiantes para preocuparse por su alto rendimiento. No son esenciales para el bashrendimiento, ya no son utilizados por bash(secuencias de comandos).

Comentarios similares se aplican a su tamaño, 26 KB para el tipo de hardware que tenemos hoy en día es insignificante. El espacio no está en la prima para la típica de un servidor / escritorio más, y que ni siquiera se molestan más a utilizar el mismo binario para falsey true, a medida que se despliega solo dos veces en el uso de distribuciones coreutils.

Sin embargo, centrándonos en el verdadero espíritu de la pregunta, ¿por qué algo que debería ser tan simple y pequeño, se vuelve tan grande?

La distribución real de las secciones de /bin/truees como se muestra en estos gráficos; el código principal + datos asciende a aproximadamente 3 KB de un binario de 26 KB, lo que equivale al 12% del tamaño de /bin/true.

La trueutilidad obtuvo más código crujiente a lo largo de los años, especialmente el soporte estándar para --versiony --help.

Sin embargo, esa no es la (única) justificación principal para que sea tan grande, sino que, mientras está vinculado dinámicamente (usando bibliotecas compartidas), también tiene parte de una biblioteca genérica comúnmente utilizada por coreutilsbinarios vinculados como una biblioteca estática. La metada para construir un elfarchivo ejecutable también representa una parte significativa del binario, ya que es un archivo relativamente pequeño para los estándares actuales.

El resto de la respuesta es para explicar cómo llegamos a construir los siguientes cuadros que detallan la composición del /bin/truearchivo binario ejecutable y cómo llegamos a esa conclusión.

bintrue bintrue2

Como dice @Maks, el binario se compiló desde C; Según mi comentario también, también se confirma que es de coreutils. Estamos apuntando directamente al autor (es) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , en lugar del gnu git como @Maks (mismas fuentes, repositorios diferentes - este repositorio fue seleccionado ya que tiene la fuente completa de las coreutilsbibliotecas)

Podemos ver los diversos bloques de construcción del /bin/truebinario aquí (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

De aquellos:

  • el texto (generalmente el código) es de alrededor de 24 KB
  • los datos (variables inicializadas, principalmente cadenas) son de alrededor de 1 KB
  • bss (datos no inicializados) 0.5 KB

De los 24 KB, alrededor de 1 KB es para arreglar las 58 funciones externas.

Eso todavía deja alrededor de 23 KB para el resto del código. A continuación, mostraremos a continuación que el archivo principal real - el código main () + use () está compilado alrededor de 1 KB, y explicaremos para qué se usan los otros 22 KB.

Al profundizar más en el binario readelf -S true, podemos ver que, si bien el binario tiene 26159 bytes, el código compilado real es 13017 bytes, y el resto es una variedad de datos / código de inicialización.

Sin embargo, true.cno es toda la historia y 13 KB parece bastante excesivo si solo fuera ese archivo; podemos ver funciones llamadas main()que no están listadas en las funciones externas vistas en el duende con objdump -T true; funciones que están presentes en:

Esas funciones adicionales no vinculadas externamente en main()son:

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

Entonces, mi primera sospecha fue en parte correcta, mientras que la biblioteca está usando bibliotecas dinámicas, el /bin/truebinario es grande * porque tiene algunas bibliotecas estáticas incluidas * (pero esa no es la única causa).

Compilar el código C no suele ser tan ineficiente para no tener en cuenta ese espacio, de ahí mi sospecha inicial de que algo andaba mal.

El espacio extra, casi el 90% del tamaño del binario, es de hecho bibliotecas / metadatos extra.

Al usar Hopper para desensamblar / descompilar el binario para comprender dónde están las funciones, se puede ver que el código binario compilado de la función true.c / use () es en realidad 833 bytes, y de la función true.c / main () es 225 bytes, que es aproximadamente un poco menos de 1 KB. La lógica para las funciones de versión, que está enterrada en las bibliotecas estáticas, es de alrededor de 1 KB.

El principal () + uso () + versión () + cadenas + vars compilados reales solo están usando alrededor de 3 KB a 3.5 KB.

De hecho, es irónico, tales utilidades pequeñas y humildes se han vuelto más grandes por las razones explicadas anteriormente.

pregunta relacionada: Comprender qué está haciendo un binario de Linux

true.c main () con la función infractora invoca:

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);
}

El tamaño decimal de las diversas secciones del binario:

$ 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

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

Salida de objdump -T true(funciones externas vinculadas dinámicamente en tiempo de ejecución)

$ 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

55
Habiendo hecho algo de programación recientemente con un microcontrolador de 64kB + 2kB, 28kB no parece tan pequeño ...
Barleyman

1
@Barleyman tiene OpenWRT, yocto, uClinux, uclib, busybox, microcoreutils y otras soluciones para ese tipo de entornos. Editó la publicación con su preocupación.
Rui F Ribeiro

44
@Barleyman: Si estaba optimizando el tamaño del ejecutable binario, puede implementar trueo falsecon un ejecutable ELF x86 de 45 bytes, empaquetando el código ejecutable (4 instrucciones x86) dentro del encabezado del programa ELF (¡sin soporte para ninguna opción de línea de comandos!) . Un tutorial de Whirlwind sobre la creación de ejecutables ELF realmente para Teensy para Linux . (O un poco más grande si desea evitar depender de los detalles de implementación del cargador Linux ELF: P)
Peter Cordes

3
No, realmente no. Yocto, por ejemplo, se puede agrupar en menos de un megabyte, que es mucho más de 64 kB. En este tipo de dispositivo, puede usar RTOS de algún tipo con procesos rudimentarios / gestión de memoria, pero incluso esos pueden llegar a ser demasiado pesados. Escribí un simple sistema cooperativo de subprocesos múltiples y utilicé la protección de memoria integrada para proteger el código de ser sobrescrito. En total, el firmware consume unos 55kB en este momento, por lo que no hay demasiado espacio para gastos generales adicionales. Esas
enormes

2
@PeterCordes seguro, pero necesita un par de magnitudes de más recursos antes de que Linux sea viable. Por lo que vale, C ++ tampoco funciona realmente en ese entorno. Bueno, no las bibliotecas estándar de todos modos. Iostream está disponible a unos 200kB, etc.
Barleyman el

34

La implementación probablemente proviene de GNU coreutils. Estos binarios se compilan a partir de C; no se ha hecho ningún esfuerzo particular para hacerlos más pequeños de lo que son por defecto.

Podrías intentar compilar la implementación trivial de trueti mismo, y notarás que ya tiene unos KB de tamaño. Por ejemplo, en mi sistema:

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

Por supuesto, tus binarios son aún más grandes. Eso es porque también admiten argumentos de línea de comandos. Intenta correr /usr/bin/true --helpo /usr/bin/true --version.

Además de los datos de la cadena, el binario incluye lógica para analizar banderas de línea de comando, etc. Al parecer, esto agrega hasta 20 KB de código.

Como referencia, puede encontrar el código fuente aquí: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c


2
Para su información, me estaba quejando de estas implementaciones de coreutils en su rastreador de errores, pero no hay posibilidad de solucionarlo lists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html
rudimeier

77
No es la lógica de los argumentos, C no es tan ineficiente ... son las bibliotecas en línea / tareas de mantenimiento. Echa un vistazo a mi respuesta para los detalles sangrientos.
Rui F Ribeiro

8
Esto es engañoso porque sugiere que el código compilado de la máquina (desde C o de otro modo) es lo que ocupa una gran cantidad de espacio: la sobrecarga de tamaño real tiene más que ver con cantidades masivas de biblioteca estándar / caldera de tiempo de ejecución estándar que el compilador incorpora en línea para interoperar con la biblioteca C (glibc, a menos que haya escuchado que su sistema usa algo más, probablemente) y, en menor medida, encabezados / metadatos ELF (muchos de los cuales no son estrictamente necesarios, pero se consideran lo suficientemente valiosos) para incluir en las compilaciones predeterminadas).
mtraceur el

2
Las cadenas main () + use () + reales en ambas funciones son de alrededor de 2 KB, no de 20 KB.
Rui F Ribeiro

2
@JdeBP lógica para --versión / versión funciones 1KB, --usage / - help 833 bytes, main () 225 bytes y toda la información estática del binario es 1KB
Rui F Ribeiro

25

Reducirlos a la funcionalidad principal y escribir en ensamblador produce binarios mucho más pequeños.

Los binarios originales verdadero / falso se escriben en C, que por su naturaleza extrae varias referencias de biblioteca + símbolo. Si ejecuta readelf -a /bin/trueesto es bastante notable.

352 bytes para un ejecutable estático ELF eliminado (con espacio para guardar un par de bytes al optimizar el asm para el tamaño del 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
$

O, con un enfoque un poco desagradable / ingenioso (felicitaciones a stalkr ), cree sus propios encabezados ELF, reduciéndolo a 132 127 bytes. Estamos entrando en el territorio de Code Golf aquí.

$ 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
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
terdon

2
También vea este excelente artículo: muppetlabs.com/~breadbox/software/tiny/teensy.html
mic_e

3
Está utilizando la int 0x80ABI de 32 bits en un ejecutable de 64 bits, lo cual es inusual pero compatible . Usar syscallno te ahorraría nada. Se ebxignoran los bytes altos de , por lo que podría usar 2 bytes mov bl,1. O por supuesto xor ebx,ebxpara cero . Linux ensu registros enteros a cero, por lo que podrían simplemente inc eaxpara obtener 1 = __NR_exit (i386 ABI).
Peter Cordes

1
Actualicé el código en su ejemplo de golf para usar el ABI de 64 bits y lo reduje a 127 bytes true. (No veo una manera fácil de manejar menos de 128 bytes para false, sin embargo, que no sean el uso de 32 bits ABI o aprovechando el hecho de que Linux pone a cero registros en el inicio del proceso, por lo que mov al,252(2 bytes) funciona. push imm8/ pop rdiHaría También funciona en lugar de leaestablecer edi=1, pero todavía no podemos superar el ABI de 32 bits donde podríamos mov bl,1sin un prefijo 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

Bastante grande en mi Ubuntu 16.04 también. exactamente del mismo tamaño? ¿Qué los hace tan grandes?

strings $(which true)

(extracto:)

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, hay ayuda para lo verdadero y lo falso, así que probémoslo:

true --help 
true --version
#

Nada. Ah, había esta otra línea:

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

Entonces, en mi sistema, es / bin / true, no / 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.

Por lo tanto, hay ayuda, hay información de versión, vinculante a una biblioteca para la internacionalización. Esto explica gran parte del tamaño, y el shell utiliza su comando optimizado de todos modos y la mayoría de las veces.


Incluyendo bibliotecas estáticas, y la mitad del tamaño del binario para elf metada. Mira mi respuesta.
Rui F Ribeiro
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.