Pourquoi le vrai et le faux sont-ils si gros?

80

Après avoir découvert que plusieurs commandes courantes (telles que read) étaient en fait des commandes intégrées à Bash (et lorsque je les ai exécutées à l’invite, j’exécute en fait un script shell à deux lignes qui ne fait que transmettre à la commande intégrée), j’ai cherché à savoir si le même est vrai pour trueet false.

Eh bien, ce sont définitivement des binaires.

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$

Cependant, ce qui m'a le plus surpris, c'est leur taille. Je m'attendais à ce qu'ils ne soient que quelques octets chacun, ce qui trueest fondamentalement juste exit 0et qui l' falseest exit 1.

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

Cependant, à ma grande surprise, les deux fichiers ont une taille supérieure à 28 Ko.

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$

Donc ma question est: pourquoi sont-ils si gros? Que contient l'exécutable à part le code de retour?

PS: J'utilise RHEL 7.4

Kidburla
la source
9
Vous devriez utiliser command -V truepas which. Il va sortir: true is a shell builtinpour bash.
meuh
32
trueet false sont intégrés à chaque shell moderne, mais le système en inclut également des versions de programme externes car il fait partie du système standard de sorte que les programmes appelant directement des commandes (en contournant le shell) puissent les utiliser. whichignore les commandes intégrées et ne recherche que les commandes externes. C'est pourquoi il ne vous a montré que les commandes externes. Essayez type -a trueet à la type -a falseplace.
mtraceur
74
Il est ironique que vous écriviez une question aussi longue et vous demandent: "Pourquoi y at- trueil false29 ko chacun? Que contient l'exécutable à part le code de retour?"
David Richerby
7
Certaines premières versions d'Unix avaient juste un fichier vide pour true puisqu'il s'agissait d'un programme sh valide renvoyant le code de sortie 0. J'aimerais vraiment pouvoir trouver un article que j'ai lu il y a des années sur l'historique du véritable utilitaire à partir d'un fichier vide. la monstruosité qui règne
Philip
9
Obligatoire - la plus petite implémentation de false: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah le

Réponses:

117

Dans le passé /bin/trueet /bin/falsedans le shell, il y avait en fait des scripts.

Par exemple, dans 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
$  

De nos jours, au moins dans bash, les commandes trueet falsesont implémentées en tant que commandes intégrées au shell. Ainsi, aucun fichier binaire exécutable n'est appelé par défaut, que ce soit lors de l'utilisation des directives falseet truedans la bashligne de commande ou à l'intérieur de scripts shell.

De la bashsource builtins/mkbuiltins.c:

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

Aussi par commentaires @meuh:

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

Donc , on peut dire avec un degré élevé de certitude les trueet les falsefichiers exécutables existent principalement pour être appelé d'autres programmes .

À partir de maintenant, la réponse portera sur le /bin/truefichier binaire du coreutilspaquet dans Debian 9/64 bits. ( /usr/bin/truesous RedHat. RedHat et Debian utilisent le coreutilspaquet, analysent la version compilée de ce dernier et l’ont plus à portée de main).

Comme on peut le voir dans le fichier source false.c, il /bin/falseest compilé avec (presque) le même code source que /bin/true, mais renvoie simplement EXIT_FAILURE (1) à la place, de sorte que cette réponse peut être appliquée aux deux fichiers binaires.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Comme il peut également être confirmé par les deux exécutables ayant la même taille:

$ 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

Hélas, la réponse directe à cette question why are true and false so large?pourrait être la suivante: il n’ya plus de raisons aussi pressantes de se soucier de leurs meilleures performances. Ils ne sont pas essentiels à la bashperformance, ils ne sont plus utilisés par bash(script).

Des commentaires similaires s'appliquent à leur taille, 26 Ko pour le type de matériel que nous avons aujourd'hui est insignifiant. L'espace n'est plus rare pour le serveur / poste de travail typique, et ils ne se gênent même plus pour utiliser le même binaire pour falseet true, comme il vient d'être déployé deux fois dans les distributions coreutils.

En se concentrant cependant dans l'esprit de la question, pourquoi quelque chose qui devrait être si simple et si petit devient-il si grand?

La distribution réelle des sections de /bin/trueest comme le montre ces graphiques; le code principal + les données représentent environ 3 Ko sur un fichier binaire de 26 Ko, ce qui représente 12% de la taille de /bin/true.

L' trueutilitaire a en effet obtenu plus de code cruel au fil des ans, notamment le support standard pour --versionet --help.

Cependant, il ne s’agit pas de la (seule) principale justification de sa taille, mais bien d’une liaison dynamique (à l’aide de bibliothèques partagées), d’une partie d’une bibliothèque générique couramment utilisée par les coreutilsbinaires liés en tant que bibliothèque statique. La méta pour construire un elffichier exécutable représente également une partie importante du binaire, c’est un fichier relativement petit par rapport aux normes actuelles.

Le reste de la réponse consiste à expliquer comment nous avons construit les graphiques suivants détaillant la composition du /bin/truefichier binaire exécutable et comment nous en sommes arrivés à cette conclusion.

bintrue bintrue2

Comme @Maks le dit, le binaire a été compilé à partir de C; selon mon commentaire également, il est également confirmé qu'il provient de coreutils. Nous pointons directement vers le ou les auteurs git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , au lieu du gnu git comme @Maks (mêmes sources, différents référentiels - ce référentiel a été sélectionné car il possède la source complète des coreutilsbibliothèques)

Nous pouvons voir les différents blocs de construction du /bin/truebinaire ici (Debian 9 - 64 bits depuis 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 celles:

  • le texte (généralement du code) est d'environ 24 Ko
  • les données (variables initialisées, principalement des chaînes) sont d'environ 1 Ko
  • bss (données non initialisées) 0.5Ko

Sur les 24 Ko, environ 1 Ko est destiné à la correction des 58 fonctions externes.

Cela laisse encore environ 23 Ko environ pour le reste du code. Nous allons montrer ci-dessous que le fichier principal actuel - principal () + code d'utilisation () est d'environ 1Ko compilé, et expliquer ce à quoi servent les autres 22Ko.

En approfondissant le binaire avec readelf -S true, nous pouvons voir que, si le binaire est de 26159 octets, le code compilé réel est de 13017 octets et le reste est un code de données / initialisation assorti.

Cependant, true.cl’histoire n’est pas complète et 13 Ko semble plutôt excessif s’il ne s’agissait que de ce fichier; on peut voir des fonctions appelées dedans main()qui ne sont pas listées dans les fonctions externes vues dans l'elfe avec objdump -T true; fonctions présentes à:

Les fonctions supplémentaires non liées en externe main()sont:

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

Donc , mon premier soupçon était en partie correcte, tandis que la bibliothèque utilise les bibliothèques dynamiques, le /bin/truebinaire est grand * car il a quelques bibliothèques statiques inclus avec elle * (mais ce n'est pas la seule cause).

Compiler du code C n’est généralement pas aussi inefficace pour avoir un tel espace perdu, d’où mon soupçon initial, quelque chose clochait.

L'espace supplémentaire, près de 90% de la taille du binaire, correspond en effet à des bibliothèques supplémentaires / métadonnées elf.

Tout en utilisant Hopper pour désassembler / décompiler le binaire pour comprendre où se trouvent les fonctions, on peut voir que le code binaire compilé de la fonction true.c / usage () est en réalité de 833 octets, et que la fonction true.c / main () est de 225 octets, ce qui correspond à peu près à 1 Ko. La logique des fonctions de version, qui est enfouie dans les bibliothèques statiques, est d'environ 1 Ko.

La compilation actuelle main () + usage () + version () + chaînes + vars ne consomme que 3 Ko à 3,5 Ko.

Il est en effet ironique de constater que ces petits et modestes services publics sont devenus plus grands pour les raisons expliquées ci-dessus.

question connexe: Comprendre ce que fait un binaire Linux

true.c main () avec les appels de fonction incriminés:

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 taille décimale des différentes sections du binaire:

$ 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

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

Sortie de objdump -T true(fonctions externes liées dynamiquement au moment de l'exécution)

$ 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
Rui F Ribeiro
la source
5
Après avoir récemment programmé avec un microcontrôleur de 64 Ko + 2 Ko, 28 Ko ne semble pas si petit que ça ...
Barleyman
1
@Barleyman, vous avez OpenWRT, yocto, uClinux, uclib, busybox, microcoreutils et d’autres solutions pour ce type d’environnements. Edité le post avec votre préoccupation.
Rui Ribeiro F
4
@Barleyman: Si vous optimisiez la taille de l'exécutable binaire, vous pouvez implémenter trueou falseavec un exécutable ELF x86 de 45 octets, en compressant le code exécutable (4 instructions x86) dans l'en-tête du programme ELF (sans prise en charge des options de ligne de commande!). . Didacticiel Whirlwind sur la création d’exécutables ELF vraiment Teensy pour Linux . (Ou légèrement plus grand si vous voulez éviter de dépendre des détails de l'implémentation du chargeur ELF sous Linux: P)
Peter Cordes
3
Non, pas vraiment. Le Yocto, par exemple, peut contenir moins d’un mégaoctet dont la capacité est supérieure à 64 Ko. Dans ce type d’appareil, vous pouvez utiliser une sorte de RTOS avec une gestion de processus / mémoire rudimentaire, mais même ceux-ci peuvent facilement devenir trop lourds. J'ai écrit un système multithreading coopératif simple et utilisé la protection de mémoire intégrée pour protéger le code contre l'écrasement. Tout compte fait, le micrologiciel consomme environ 55 Ko à l'heure actuelle, donc pas trop de place pour des frais généraux supplémentaires. Ces ginormous 2kB chercher des tables ..
Barleyman
2
@ PeterCordes, bien sûr, mais vous avez besoin de plusieurs ressources supplémentaires pour que Linux devienne viable. Pour ce que ça vaut, C ++ ne fonctionne pas vraiment dans cet environnement non plus. Eh bien, pas les bibliothèques standard de toute façon. Iostream est à environ 200 Ko, etc.
Barleyman,
34

L'implémentation vient probablement de GNU coreutils. Ces binaires sont compilés à partir de C; aucun effort particulier n'a été fait pour les rendre plus petites qu'elles ne le sont par défaut.

Vous pouvez essayer de compiler l'implémentation triviale de truevous-même, et vous remarquerez que sa taille est déjà réduite à quelques Ko. Par exemple, sur mon système:

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

Bien sûr, vos fichiers binaires sont encore plus gros. C'est parce qu'ils supportent aussi les arguments en ligne de commande. Essayez de courir /usr/bin/true --helpou /usr/bin/true --version.

En plus des données de chaîne, le binaire inclut une logique pour analyser les indicateurs de ligne de commande, etc. Cela ajoute apparemment environ 20 Ko de code.

Pour référence, vous pouvez trouver le code source ici: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c

Maks Verver
la source
2
FYI, je me plaignais de ces implémentations de coreutils sur leur traqueur de bogues, mais aucune chance de le réparer. Lists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html
rudimeier
7
Ce n’est pas la logique des arguments, C n’est pas si inefficace ... ce sont des bibliothèques en ligne / tâches ménagères. Regardez ma réponse pour les détails sanglants.
Rui F Ribeiro
8
Ceci est trompeur car cela suggère que le code machine compilé (de C ou autre) est ce qui prend énormément d’espace - la surcharge de taille réelle a plus à voir avec des quantités énormes d’exploitations standard de bibliothèque / runtime C standard qui sont alignées par le compilateur pour interagir avec la bibliothèque C (glibc, à moins que vous n'ayez entendu dire que votre système utilise autre chose, probablement), et, dans une moindre mesure, les en-têtes / métadonnées ELF (dont beaucoup ne sont pas strictement nécessaires, mais jugés suffisants inclure dans les versions par défaut).
mtraceur
2
Les chaînes main () + usage () + réelles des deux fonctions tournent autour de 2 Ko, et non de 20 Ko.
Rui F Ribeiro
2
Logique @JdeBP pour les fonctions --version / version 1KB, --usage / - help 833 octets, main () 225 octets et l'ensemble des données statiques du fichier binaire est égal à 1KB
Rui F Ribeiro
25

En les réduisant à la fonctionnalité principale et en écrivant en assembleur, on obtient des binaires beaucoup plus petits.

Les binaires vrais / faux originaux sont écrits en C, ce qui, de par sa nature, attire diverses références de bibliothèque + symbole. Si vous exécutez readelf -a /bin/truecela est assez visible.

352 octets pour un exécutable statique ELF dépouillé (avec suffisamment d'espace pour économiser quelques octets en optimisant l'asm pour la taille du code).

$ 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, avec une approche un peu méchante / ingénieuse (félicitations à stalkr ), créez vos propres en-têtes ELF, en les réduisant à 132 127 octets. Nous entrons dans le territoire de Code Golf ici.

$ 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
$
steve
la source
2
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
terdon
2
Voir aussi cet excellent article: muppetlabs.com/~breadbox/software/tiny/teensy.html
mic_e
3
Vous utilisez int 0x80ABI 32 bits dans un exécutable 64 bits, ce qui est inhabituel mais pris en charge . L'utilisation syscallne vous sauverait rien. Les octets hauts de ebxsont ignorés, vous pouvez donc utiliser 2 octets mov bl,1. Ou bien sur xor ebx,ebxpour zéro . Linux écrit son nombre entier à zéro, vous pouvez donc simplement inc eaxobtenir 1 = __NR_exit (ABI i386).
Peter Cordes
1
J'ai mis à jour le code de votre exemple golfé pour utiliser ABI 64 bits, et lui attribuer une valeur de 127 octets true. (Je ne vois pas un moyen facile de gérer moins de 128 octets pour false, cependant, d' autres que l' utilisation du 32 bits ABI ou en profitant du fait que Linux zéros registres au démarrage du processus, donc mov al,252(2 octets) fonctionne. push imm8/ pop rdiSerait fonctionne également au lieu de leapour le réglage edi=1, mais nous ne pouvons toujours pas battre le ABI 32 bits où nous pourrions mov bl,1sans préfixe 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

Assez gros sur mon Ubuntu 16.04 aussi. exactement la même taille? Qu'est-ce qui les rend si gros?

strings $(which true)

(extrait:)

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, il y a de l'aide pour le vrai et le faux, alors essayons-le:

true --help 
true --version
#

Rien. Ah, il y avait cette autre ligne:

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

Donc sur mon système, c'est / bin / true, pas / 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.

Donc, il y a de l'aide, il y a des informations de version, liant à une bibliothèque pour l'internationalisation. Cela explique en grande partie la taille, et le shell utilise sa commande optimisée de toute façon et la plupart du temps.

Utilisateur inconnu
la source
Y compris les bibliothèques statiques, et la moitié de la taille du binaire pour Metada elf. Voir ma réponse.
Rui F Ribeiro