GNU/Linux >> Tutoriels Linux >  >> Linux

Linux – Pourquoi le vrai et le faux sont-ils si grands ?

Après avoir découvert que plusieurs commandes courantes (telles que read ) sont en fait des commandes intégrées de Bash (et lors de leur exécution à l'invite, j'exécute en fait un script shell à deux lignes qui transmet simplement à la fonction intégrée), je cherchais à voir si la même chose est vraie pour true et 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 que j'ai trouvé le plus surprenant, c'est leur taille. Je m'attendais à ce qu'ils ne fassent que quelques octets chacun, comme true est simplement exit 0 et false est exit 1 .

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

Cependant, j'ai découvert à ma grande surprise que les deux fichiers pesaient plus de 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$

Ma question est donc :pourquoi sont-ils si gros ? Qu'y a-t-il dans l'exécutable autre que le code de retour ?

PS :j'utilise RHEL 7.4

Réponse acceptée :

Dans le passé, /bin/true et /bin/false dans le shell étaient 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, du moins en bash , le true et false les commandes sont implémentées en tant que commandes intégrées du shell. Ainsi, aucun fichier binaire exécutable n'est appelé par défaut, à la fois lors de l'utilisation du false et true directives dans le bash ligne de commande et scripts shell internes.

Depuis le bash source, 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
    };

Aussi par @meuh commentaires :

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

On peut donc dire avec un haut degré de certitude que le true et false les fichiers exécutables existent principalement pour être appelés depuis d'autres programmes .

Désormais, la réponse portera sur le /bin/true binaire de coreutils paquet dans Debian 9/64 bits. (/usr/bin/true exécutant RedHat. RedHat et Debian utilisent à la fois les coreutils package, a analysé la version compilée de ce dernier en l'ayant plus sous la main).

Comme on peut le voir dans le fichier source false.c , /bin/false est compilé avec (presque) le même code source que /bin/true , renvoyant juste EXIT_FAILURE (1) à la place, donc cette réponse peut être appliquée pour les deux binaires.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Comme cela 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 question directe à la réponse why are true and false so large? peut-être, car il n'y a plus de raisons aussi pressantes de se soucier de leurs meilleures performances. Ils ne sont pas essentiels pour bash performances, n'étant plus utilisé 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 limité pour le serveur/bureau typique, et ils ne prennent même plus la peine d'utiliser le même binaire pour false et true , car il est juste déployé deux fois dans les distributions utilisant coreutils .

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

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

Le true l'utilitaire a en effet obtenu plus de code brut au fil des ans, notamment le support standard de --version et --help .

Cependant, ce n'est pas la (seule) justification principale de sa taille, mais plutôt, tout en étant lié dynamiquement (à l'aide de bibliothèques partagées), d'avoir également une partie d'une bibliothèque générique couramment utilisée par coreutils binaires liés en tant que bibliothèque statique. La métada pour construire un elf Le fichier exécutable représente également une partie importante du binaire, étant un fichier relativement petit selon les normes actuelles.

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

En relation :Utiliser _roff pour souligner des mots ?

Comme le dit @Maks, 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 contient la source complète des coreutils bibliothèques)

Nous pouvons voir les différents blocs de construction du /bin/true binaire ici (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

Parmi ceux-ci :

  • le texte (généralement du code) fait environ 24 Ko
  • les données (variables initialisées, principalement des chaînes) font environ 1 Ko
  • bss (données non initialisées) 0,5 Ko

Sur les 24 Ko, environ 1 Ko sert à réparer les 58 fonctions externes.

Cela laisse encore environ 23 Ko pour le reste du code. Nous montrerons ci-dessous que le fichier principal réel - le code main() + usage() est d'environ 1 Ko compilé, et expliquerons à quoi servent les 22 Ko restants.

Explorer plus loin le binaire avec readelf -S true , nous pouvons voir que tandis que le binaire est de 26159 octets, le code compilé réel est de 13017 octets, et le reste est un assortiment de données/code d'initialisation.

Cependant, true.c n'est pas toute l'histoire et 13 Ko semblent à peu près excessifs s'il ne s'agissait que de ce fichier ; nous pouvons voir les fonctions appelées dans main() qui ne sont pas listés dans les fonctions externes vues dans le elf avec objdump -T true; fonctions présentes sur :

  • https://github.com/coreutils/gnulib/blob/master/lib/progname.c
  • https://github.com/coreutils/gnulib/blob/master/lib/closeout.c
  • https://github.com/coreutils/gnulib/blob/master/lib/version-etc.c

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

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

Donc mon premier soupçon était en partie correct, alors que la bibliothèque utilise des bibliothèques dynamiques, le /bin/true le binaire est gros *parce qu'il en a certains bibliothèques statiques incluses avec * (mais ce n'est pas la seule cause).

Compiler du code C n'est généralement pas ça inefficace pour avoir un tel espace non comptabilisé, d'où mon soupçon initial que quelque chose n'allait pas.

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

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

Le main()+usage()+version()+strings+vars réellement compilé n'utilise qu'environ 3 Ko à 3,5 Ko.

C'est en effet ironique, ces petits et humbles utilitaires sont devenus de plus grande taille 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 à 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

Linux
  1. Linux - Qu'est-ce que la mémoire élevée et la mémoire faible sous Linux ?

  2. Quels caractères sont interdits dans les noms de répertoires Windows et Linux ?

  3. Pourquoi les Makefiles sous Linux sont-ils si utiles ?

  4. Pourquoi les bibliothèques partagées sous Linux sont-elles exécutables ?

  5. Qu'est-ce que la mémoire haute et la mémoire basse sous Linux ?

7 raisons pour lesquelles j'utilise Manjaro Linux et vous devriez aussi

Que sont les journaux Linux et où les trouver

Pourquoi Linux est-il si mauvais et Windows 11 meilleur à tous points de vue ?

Pourquoi certains Emoji N&B et d'autres sont-ils trop gros ?

Comment et pourquoi utiliser Linux pour installer Telnet

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