Perché il vero e il falso sono così grandi?


80

Dopo aver scoperto che diversi comandi comuni (come read) sono in realtà builtin di Bash (e quando li eseguo al prompt sto effettivamente eseguendo uno script shell a due righe che inoltra solo al builtin), stavo cercando di vedere se lo stesso è vero per truee false.

Bene, sono sicuramente binari.

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$

Tuttavia, ciò che ho trovato più sorprendente è stata la loro dimensione. Mi aspettavo che fossero solo pochi byte ciascuno, come trueè sostanzialmente giusto exit 0ed falseè exit 1.

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

Tuttavia, ho scoperto con sorpresa che entrambi i file hanno dimensioni superiori 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$

Quindi la mia domanda è: perché sono così grandi? Cosa c'è nell'eseguibile diverso dal codice di ritorno?

PS: sto usando RHEL 7.4


9
Si dovrebbe usare command -V truenon which. Produrrà: true is a shell builtinper bash.
Meuh

32
truee false sono integrati in ogni shell moderna, ma i sistemi includono anche versioni di programmi esterni perché fanno parte del sistema standard in modo che i programmi che invocano direttamente i comandi (bypassando la shell) possano usarli. whichignora i builtin e cerca solo i comandi esterni, motivo per cui ti mostra solo quelli esterni. Prova type -a truee type -a falseinvece.
mtraceur,

74
È ironico che tu scriva una domanda così lunga da dire "Perché sono truee false29kb ciascuno? Cosa c'è nell'eseguibile oltre al codice di ritorno?"
David Richerby,

7
Alcune prime versioni di unix avevano appena un file vuoto per true poiché si trattava di un programma sh valido che restituiva il codice di uscita 0. Vorrei davvero poter trovare un articolo che ho letto anni fa sulla storia dell'utilità vera da un file vuoto a la mostruosità che è oggi, ma tutto ciò che ho potuto trovare è questo: trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
Philip

9
Obbligatorio: la più piccola implementazione di false: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah

Risposte:


117

In passato, /bin/truee /bin/falsenella shell c'erano effettivamente degli script.

Ad esempio, in un sistema Unix PDP / 11 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
$  

Al giorno d'oggi, almeno in bash, i comandi truee falsesono implementati come comandi integrati della shell. Pertanto, per impostazione predefinita, nessun file binario eseguibile viene richiamato, sia quando si usano le direttive falsee truenella bashriga di comando sia all'interno degli script della shell.

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

Anche per i commenti di @meuh:

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

Quindi si può dire con un alto grado di certezza trueche falsei file eseguibili esistono principalmente per essere richiamati da altri programmi .

D'ora in poi, la risposta si concentrerà sul /bin/truebinario dal coreutilspacchetto in Debian 9/64 bit. ( /usr/bin/trueeseguendo RedHat. RedHat e Debian usano entrambi il coreutilspacchetto, hanno analizzato la versione compilata di quest'ultimo avendo più a portata di mano).

Come si può vedere nel file sorgente false.c, /bin/falseviene compilato con (quasi) lo stesso codice sorgente /bin/true, restituendo invece EXIT_FAILURE (1), quindi questa risposta può essere applicata per entrambi i binari.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Come può anche essere confermato da entrambi gli eseguibili con le stesse dimensioni:

$ 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

Purtroppo, la domanda diretta alla risposta why are true and false so large?potrebbe essere, perché non ci sono più motivi così urgenti per preoccuparsi della loro prestazione massima. Non sono essenziali per le bashprestazioni, non vengono più utilizzati da bash(scripting).

Commenti simili si applicano alle loro dimensioni, 26 KB per il tipo di hardware che abbiamo oggi è insignificante. Lo spazio non è più premium per il tipico server / desktop e non si preoccupano più nemmeno di utilizzare lo stesso binario per falsee true, poiché viene distribuito due volte nelle distribuzioni utilizzando coreutils.

Concentrandosi, tuttavia, nel vero spirito della domanda, perché qualcosa che dovrebbe essere così semplice e piccolo, diventa così grande?

La vera distribuzione delle sezioni di /bin/trueè come mostrano questi grafici; il codice principale + i dati ammontano a circa 3 KB su un binario di 26 KB, che corrisponde al 12% della dimensione di /bin/true.

L' trueutilità ha effettivamente ottenuto più codice cruft nel corso degli anni, in particolare il supporto standard per --versione --help.

Tuttavia, non è la (sola) principale giustificazione per essere così grande, ma piuttosto, pur essendo collegato dinamicamente (usando librerie condivise), avendo anche parte di una libreria generica comunemente usata dai coreutilsbinari collegati come libreria statica. La metada per la creazione di un elffile eseguibile costituisce anche una parte significativa del file binario, essendo un file relativamente piccolo per gli standard odierni.

Il resto della risposta è per spiegare come siamo riusciti a costruire i seguenti grafici che descrivono in dettaglio la composizione del /bin/truefile binario eseguibile e come siamo arrivati ​​a quella conclusione.

bintrue bintrue2

Come dice @Maks, il binario è stato compilato da C; come per il mio commento, è anche confermato che proviene da coreutils. Puntiamo direttamente agli autori git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , invece che gnu git come @Maks (stesse fonti, repository diversi - questo repository è stato selezionato in quanto ha la fonte completa delle coreutilslibrerie)

/bin/trueQui possiamo vedere i vari blocchi del binario (Debian 9 - 64 bit da 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

Di quelli:

  • il testo (di solito il codice) è di circa 24 KB
  • i dati (variabili inizializzate, principalmente stringhe) sono di circa 1 KB
  • bss (dati non inizializzati) 0,5 KB

Dei 24 KB, circa 1 KB serve per riparare le 58 funzioni esterne.

Rimangono circa 23 KB per il resto del codice. Mostreremo sotto che il file principale effettivo - codice main () + use () è compilato intorno a 1 KB e spiegheremo a cosa servono gli altri 22 KB.

Esplorando ulteriormente il binario con readelf -S true, possiamo vedere che mentre il binario è 26159 byte, il codice compilato effettivo è 13017 byte e il resto è un codice di dati / inizializzazione assortito.

Tuttavia, true.cnon è l'intera storia e 13KB sembrano praticamente eccessivi se fosse solo quel file; possiamo vedere funzioni chiamate main()che non sono elencate nelle funzioni esterne viste nell'elfo con objdump -T true; funzioni presenti in:

Queste funzioni extra non collegate esternamente main()sono:

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

Quindi il mio primo sospetto era in parte corretto, mentre la libreria utilizza librerie dinamiche, il /bin/truebinario è grande * perché include alcune librerie statiche * (ma questa non è l'unica causa).

La compilazione del codice C di solito non è così inefficiente da non tenere conto di tale spazio, quindi il mio sospetto iniziale era sbagliato.

Lo spazio extra, quasi il 90% delle dimensioni del file binario, è in effetti librerie / metadati degli elfi extra.

Mentre si utilizza Hopper per disassemblare / decompilare il file binario per capire dove si trovano le funzioni, si può vedere che il codice binario compilato della funzione true.c / use () è in realtà 833 byte e della funzione true.c / main () è 225 byte, che è circa leggermente inferiore a 1 KB. La logica per le funzioni di versione, che è sepolta nelle librerie statiche, è di circa 1 KB.

L'effettiva compilata main () + use () + version () + stringhe + vars utilizza solo da 3 KB a 3,5 KB.

È davvero ironico, tali piccole e umili utility sono diventate più grandi per le ragioni spiegate sopra.

domanda correlata: capire cosa sta facendo un binario Linux

true.c main () con la funzione offensiva chiama:

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

La dimensione decimale delle varie sezioni 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

Uscita di 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)

Output di objdump -T true(funzioni esterne collegate dinamicamente in fase di esecuzione)

$ 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
Avendo recentemente programmato con un microcontrollore da 64kB + 2kB, 28kB non sembrano poi così piccoli ..
Barleyman,

1
@Barleyman hai OpenWRT, yocto, uClinux, uclib, busybox, microcoreutils e altre soluzioni per quel tipo di ambienti. Modificato il post con la tua preoccupazione.
Rui F Ribeiro,

4
@Barleyman: se stavi ottimizzando le dimensioni eseguibili binarie, puoi implementare trueo falsecon un eseguibile ELF x86 a 45 byte, comprimendo il codice eseguibile (4 istruzioni x86) all'interno dell'intestazione del programma ELF (senza supporto per le opzioni della riga di comando!) . Un tutorial di Whirlwind sulla creazione di eseguibili ELF davvero Teensy per Linux . (O leggermente più grande se si desidera evitare a seconda dei dettagli di implementazione del caricatore ELF Linux: P)
Peter Cordes,

3
Non proprio no. Ad esempio, Yocto può essere stipato in meno di un megabyte che è heap e limiti superiori a 64 kB. In questo tipo di dispositivo è possibile utilizzare RTOS di qualche tipo con una gestione rudimentale di processo / memoria, ma anche quelli possono facilmente diventare troppo pesanti. Ho scritto un semplice sistema di multithreading cooperativo e ho usato la protezione della memoria integrata per proteggere il codice dalla sovrascrittura. Tutto sommato, il firmware consuma circa 55 KB in questo momento, quindi non c'è troppo spazio lì per un sovraccarico aggiuntivo. Quei
giganteschi 2kB

2
@PeterCordes è certo, ma hai bisogno di un paio di magnitudini di più risorse prima che Linux diventi praticabile. Per quello che vale, C ++ non funziona nemmeno in quell'ambiente. Bene, non comunque le librerie standard. Iostream è a circa 200kB ecc.
Barleyman,

34

L'implementazione probabilmente proviene dai coreutils GNU. Questi binari sono compilati da C; nessuno sforzo particolare è stato fatto per renderli più piccoli di quanto non siano di default.

Potresti provare a compilare la banale implementazione di truete stesso e noterai che ha già una dimensione di pochi KB. Ad esempio, sul mio sistema:

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

Certo, i tuoi binari sono ancora più grandi. Questo perché supportano anche argomenti da riga di comando. Prova a correre /usr/bin/true --helpo /usr/bin/true --version.

Oltre ai dati delle stringhe, il binario include la logica per analizzare i flag della riga di comando, ecc. Ciò a quanto pare aggiunge fino a circa 20 KB di codice.

Per riferimento, puoi trovare il codice sorgente qui: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c


2
FYI ero lamentarsi per queste coreutils implementazioni sul loro bug tracker, ma nessuna possibilità di farlo riparare lists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html
rudimeier

7
Non è la logica per gli argomenti, C non è così inefficiente ... sono le librerie in linea / i compiti di mantenimento. Dai un'occhiata alla mia risposta per i dettagli cruenti.
Rui F Ribeiro,

8
Questo è fuorviante perché suggerisce che il codice macchina compilato (da C o altro) è ciò che occupa l'enorme quantità di spazio: il sovraccarico delle dimensioni effettive ha più a che fare con enormi quantità di libreria C / caldaia standard runtime che viene incorporata dal compilatore in per interagire con la libreria C (glibc, a meno che tu non abbia sentito che il tuo sistema utilizza qualcos'altro, probabilmente) e, in misura minore, intestazioni / metadati ELF (molti dei quali non sono strettamente necessari, ma considerati abbastanza utili da includere nelle build predefinite).
mtraceur,

2
Le stringhe main () + use () + effettive su entrambe le funzioni sono circa 2 KB, non 20 KB.
Rui F Ribeiro,

2
Logica @JdeBP per le funzioni --version / version 1KB, --usage / - help 833 byte, main () 225 byte e tutti i dati statici del binario sono 1KB
Rui F Ribeiro,

25

Spogliandoli fino alla funzionalità principale e scrivendo nell'assemblatore si ottengono binari molto più piccoli.

I binari originali vero / falso sono scritti in C, che per sua natura estrae riferimenti a varie librerie + simboli. Se esegui readelf -a /bin/truequesto è abbastanza evidente.

352 byte per un eseguibile statico ELF rimosso (con spazio per salvare un paio di byte ottimizzando l'asm per la dimensione del codice).

$ 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
$

Oppure, con un approccio un po 'brutto / geniale (complimenti a stalkr ), crea le tue intestazioni ELF, portandolo a 132 127 byte. Stiamo entrando nel territorio di Code Golf qui.

$ 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
I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
terdon

2
Vedi anche questo eccellente articolo
mic_e

3
Stai utilizzando l' int 0x80ABI a 32 bit in un eseguibile a 64 bit, il che è insolito ma supportato . L'uso syscallnon ti risparmierebbe nulla. I byte alti di ebxvengono ignorati, quindi è possibile utilizzare 2 byte mov bl,1. O ovviamente xor ebx,ebxper zero . Linux INITs registri interi a zero, in modo da potrebbero solo inc eaxper ottenere 1 = __NR_exit (i386 ABI).
Peter Cordes,

1
Ho aggiornato il codice sul tuo esempio golfed per utilizzare l'ABI a 64 bit e lo ho ridotto a 127 byte per true. (Non vedere un modo semplice per gestire meno di 128 byte per false, anche se, oltre che utilizzare 32 bit ABI o approfittando del fatto che Linux azzera registri all'avvio processo, quindi mov al,252(2 byte) funziona. push imm8/ pop rdiSarebbe funziona anche al posto leadell'impostazione edi=1, ma non possiamo ancora battere l'ABI a 32 bit dove potremmo fare mov bl,1senza un prefisso 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

Abbastanza grande anche sul mio Ubuntu 16.04. esattamente le stesse dimensioni? Cosa li rende così grandi?

strings $(which true)

(estratto:)

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, c'è aiuto per vero e falso, quindi proviamo:

true --help 
true --version
#

Niente. Ah, c'era questa altra linea:

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

Quindi sul mio sistema, è / bin / true, non / 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.

Quindi c'è aiuto, ci sono informazioni sulla versione, vincolanti per una libreria per l'internazionalizzazione. Questo spiega gran parte delle dimensioni e la shell utilizza comunque il suo comando ottimizzato e il più delle volte.


Comprese le librerie statiche e metà delle dimensioni del file binario per metada elfo. Vedi la mia risposta
Rui F Ribeiro,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.