Après avoir découvert que plusieurs commandes courantes (telles que read
) sont en fait des commandes internes Bash (et lorsque je les exécute à l'invite, j'exécute en fait un script Shell à deux lignes qui transmet simplement à la commande intégrée), j'étais cherchant à 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 représentent que quelques octets chacun, car true
est fondamentalement juste 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 trouvé à ma grande surprise que les deux fichiers mesurent 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$
Alors ma question est: pourquoi sont-ils si gros? Que contient l'exécutable autre que le code retour?
PS: j'utilise RHEL 7.4
Dans le passé, /bin/true
Et /bin/false
Dans le Shell étaient en fait des scripts.
Par exemple, dans un système 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
$
De nos jours, au moins dans bash
, les commandes true
et false
sont implémentées en tant que commandes intégrées à Shell. Ainsi, aucun fichier binaire exécutable n'est appelé par défaut, à la fois lors de l'utilisation des directives false
et true
dans la ligne de commande bash
et dans les scripts Shell.
À partir de la source bash
, builtins/mkbuiltins.c
:
char * posix_builtins [] = { "alias", "bg", "cd", "command", "** false **", "fc", "fg", " getopts "," jobs ", " kill "," newgrp "," pwd "," read "," ** true ** "," umask "," unalias "," wait ", (car *) NULL };
Aussi par @meuh commentaires:
$ command -V true false
true is a Shell builtin
false is a Shell builtin
On peut donc dire avec une grande certitude que les fichiers exécutables true
et false
existent principalement pour être appelés à partir d'autres programmes .
Désormais, la réponse se concentrera sur le binaire /bin/true
Du paquetage coreutils
dans Debian 9/64 bits. (/usr/bin/true
Exécutant RedHat. RedHat et Debian utilisent tous les deux le paquetage coreutils
, ont analysé la version compilée de ce dernier en ayant plus à portée de 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 simplement EXIT_FAILURE (1) à la place, donc ceci 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?
Pourrait être, car il n'y a plus de raisons si pressantes de se soucier de leurs meilleures performances. Ils ne sont pas essentiels aux performances de bash
, n'étant plus utilisés par bash
(script).
Des commentaires similaires s'appliquent à leur taille, 26 Ko pour le type de matériel que nous avons de nos jours 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 simplement déployé deux fois dans les distributions utilisant coreutils
.
En se concentrant, cependant, dans le véritable esprit de la question, pourquoi quelque chose qui devrait être si simple et si petit, devient si grand?
La répartition réelle des sections de /bin/true
Est comme le montrent ces graphiques; le code principal + les données s'élèvent à environ 3 Ko sur un binaire de 26 Ko, ce qui représente 12% de la taille de /bin/true
.
L'utilitaire true
a en effet obtenu plus de code cru au fil des ans, notamment le support standard pour --version
Et --help
.
Cependant, ce n'est pas la (seule) principale justification pour qu'il soit si grand, mais plutôt, tout en étant lié dynamiquement (à l'aide de bibliothèques partagées), ayant également une partie d'une bibliothèque générique couramment utilisée par les binaires coreutils
liés comme une bibliothèque statique. La métada pour la construction d'un fichier exécutable elf
représente également une partie importante du binaire, étant un fichier relativement petit par rapport aux normes actuelles.
Le reste de la réponse est d'expliquer comment nous avons pu construire les graphiques suivants détaillant la composition du fichier binaire exécutable /bin/true
Et comment nous sommes arrivés à cette conclusion.
Comme le dit @Maks, le binaire a été compilé à partir de C; selon mon commentaire également, il est également confirmé qu'il s'agit de coreutils. Nous pointons directement vers le (s) auteur (s) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , au lieu du gnu git comme @Maks (idem sources, différents référentiels - ce référentiel a été sélectionné car il contient la source complète des bibliothèques coreutils
)
Nous pouvons voir les différents blocs de construction du binaire /bin/true
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
De celles:
Sur les 24 Ko, environ 1 Ko est destiné à la fixation des 58 fonctions externes.
Cela laisse encore environ 23 Ko pour le reste du code. Nous montrerons ci-dessous que le code principal du fichier principal - main () + usage () est d'environ 1 Ko compilé, et expliquerons à quoi servent les 22 autres Ko.
En approfondissant le binaire avec readelf -S true
, Nous pouvons voir que bien que le binaire soit de 26159 octets, le code compilé réel est de 13017 octets, et le reste est un code de données/d'initialisation assorti.
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 des fonctions appelées dans main()
qui ne sont pas listées dans les fonctions externes vues dans l'elfe avec objdump -T true
; fonctions présentes à:
Ces fonctions supplémentaires non liées en externe dans main()
sont:
Donc mon premier soupçon était en partie correct, alors que la bibliothèque utilise des bibliothèques dynamiques, le binaire /bin/true
Est grand * car il a un peu statique bibliothèques incluses avec elle * (mais ce n'est pas la seule cause).
Compiler du code C n'est généralement pas si inefficace pour avoir un tel espace inexpliqué, d'où mon soupçon initial, quelque chose n'allait pas.
L'espace supplémentaire, près de 90% de la taille du binaire, est en effet des métadonnées extra bibliothèques/elf.
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 fait de 833 octets, et de la fonction true.c/main () 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.
Les principaux fichiers compilés main () + usage () + version () + chaînes + vars n'utilisent qu'environ 3 Ko à 3,5 Ko.
Il est en effet ironique que ces petits et humbles utilitaires soient devenus plus grands pour les raisons expliquées ci-dessus.
question connexe: Comprendre ce que fait un binaire Linux
true.c
Main () avec la fonction incriminée appelle:
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
L'implémentation provient probablement de GNU coreutils. Ces binaires sont compilés à partir de C; aucun effort particulier n'a été fait pour les rendre plus petits qu'ils ne le sont par défaut.
Vous pouvez essayer de compiler vous-même l'implémentation triviale de true
, et vous remarquerez que sa taille est déjà de 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 binaires sont encore plus gros. En effet, ils prennent également en charge les arguments de ligne de commande. Essayez d'exécuter /usr/bin/true --help
ou /usr/bin/true --version
.
En plus des données de chaîne, le binaire inclut une logique pour analyser les drapeaux de ligne de commande, etc. Cela représente apparemment jusqu'à 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
Les réduire à la fonctionnalité de base et écrire dans l'assembleur donne des binaires beaucoup plus petits.
Les vrais binaires vrais/faux sont écrits en C, qui par sa nature tire dans diverses références bibliothèque + symboles. Si vous exécutez readelf -a /bin/true
c'est assez perceptible.
352 octets pour un exécutable statique ELF dépouillé (avec de la place 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 un peu d'une approche méchante/ingénieuse (bravo à stalkr ), créez vos propres en-têtes ELF, en le réduisant à 132 127 octets. Nous entrons Code Golf territoire 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
$
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:
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.
Il y a donc de l'aide, il y a des informations de version, liées à une bibliothèque pour l'internationalisation. Cela explique une grande partie de la taille, et le Shell utilise sa commande optimisée de toute façon et la plupart du temps.
La réponse acceptée par Rui F Ribeiro donne beaucoup de bonnes informations, mais il en manque et j'ai l'impression que cela laisse au lecteur une impression trompeuse que la taille du code est petite par rapport à "ELF overhead" et similaire.
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 grand * car il contient certaines bibliothèques statiques incluses * (mais ce n'est pas la seule cause).
La liaison statique est à la granularité du fichier objet (ou encore plus fine avec --gc-sections
), il n'est donc pas logique de parler d'une bibliothèque statique "liée"; la seule partie liée est ce qui est utilisé, et la taille du code ici est du code qui est réellement (gratuitement) utilisé par la version coreutils de true
. Cela n'a pas de sens de le compter séparément de ce qui apparaît dans true.c
.
L'espace supplémentaire, près de 90% de la taille du binaire, est en effet des métadonnées extra bibliothèques/elf.
Les métadonnées ELF sont à peu près entièrement des tables nécessaires à la liaison dynamique, et sont loin d'être à 90% de la taille. Ce sont les lignes de size -A
sortie pertinente:
section size addr .interp 28 568 .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 .plt 752 4800 .plt.got 8 5552 .dynamic 480 2125272 .got 48 2125752 .got.plt 392 2125824
pour un total d'environ 5,5k, soit une moyenne d'environ 100 octets par symbole dynamique (pas tout à fait juste parce que certains ne sont pas par symbole, mais c'est un chiffre quelque peu significatif).
Un grand contributeur à la taille que la réponse de Rui n'a pas couvert est:
.eh_frame_hdr 572 21728 .eh_frame 2908 22304
Ce 3.5k est des tables de déroulement DWARF pour la gestion des exceptions C++, la trace introspective, etc. Elles sont complètement inutiles pour true
, mais incluses par la politique GCC dans toutes les sorties sauf si vous les omettez explicitement avec une option très verbeuse -fno-asynchronous-unwind-tables
. Les motivations derrière cela sont expliquées dans ne réponse de moi sur Stack Overflow .
Je décrirais donc la ventilation finale comme suit:
En particulier, il est notable que si la quantité de code nécessaire des bibliothèques liées dynamiquement était suffisamment petite, la liaison statique pourrait être plus petite que la liaison dynamique.